Spring Beans
Spring Bean 的概念
由IoC容器管理的那些组成你应用程序的对象叫Bean, Bean就是由Spring容器初始化、装配及管理的对象,除此之外,bean就与应用程序中的其他对象没有什么区别了。那IoC怎样确定如何实例化Bean、管理Bean之间的依赖关系以及管理Bean呢?这就需要配置元数据,在Spring中由BeanDefinition代表
|
|
helloworld.xml
放在 resources
目录
|
|
|
|
一、准备配置文件:就像前边Hello World配置文件一样,在配置文件中声明Bean定义也就是为Bean配置元数据。
二、由IoC容器进行解析元数据: IoC容器的Bean Reader读取并解析配置文件,根据定义生成BeanDefinition配置元数据对象,IoC容器根据BeanDefinition进行实例化、配置及组装Bean。
**三、实例化IoC容器:**由客户端实例化容器,获取需要的Bean。
在Spring Ioc容器的代表就是org.springframework.beans包中的BeanFactory接口,BeanFactory接口提供了IoC容器最基本功能;而org.springframework.context包下的ApplicationContext接口扩展了BeanFactory,还提供了与Spring AOP集成、国际化处理、事件传播及提供不同层次的context实现 (如针对web应用的WebApplicationContext)。简单说, BeanFactory提供了IoC容器最基本功能,而 ApplicationContext 则增加了更多支持企业级功能支持。ApplicationContext完全继承BeanFactory,因而BeanFactory所具有的语义也适用于ApplicationContext。
除了测试程序的代码外,也就是程序入口,所有代码都没有出现Spring任何组件,而且所有我们写的代码没有实现框架拥有的接口,因而能非常容易的替换掉Spring,是不是非入侵。
客户端代码完全面向接口编程,无需知道实现类,可以通过修改配置文件来更换接口实现,客户端代码不需要任何修改。是不是低耦合。
如果在开发初期没有真正的实现,我们可以模拟一个实现来测试,不耦合代码,是不是很方便测试。
Bean之间几乎没有依赖关系,是不是很容易重用。
Bean的配置
Spring IoC容器目的就是管理Bean,这些Bean将根据配置文件中的Bean定义进行创建,而Bean定义在容器内部由BeanDefinition对象表示,该定义主要包含以下信息:
●全限定类名(FQN):用于定义Bean的实现类;
●Bean行为定义:这些定义了Bean在容器中的行为;包括作用域(单例、原型创建)、是否惰性初始化及生命周期等;
●Bean创建方式定义:说明是通过构造器还是工厂方法创建Bean;
●Bean之间关系定义:即对其他bean的引用,也就是依赖关系定义,这些引用bean也可以称之为同事bean 或依赖bean,也就是依赖注入。
Bean定义只有“全限定类名”在当使用构造器或静态工厂方法进行实例化bean时是必须的,其他都是可选的定义。难道Spring只能通过配置方式来创建Bean吗?回答当然不是,某些SingletonBeanRegistry接口实现类实现也允许将那些非BeanFactory创建的、已有的用户对象注册到容器中,这些对象必须是共享的,比如使用DefaultListableBeanFactory 的registerSingleton() 方法。不过建议采用元数据定义。
Bean的命名
每个Bean可以有一个或多个id(或称之为标识符或名字),在这里我们把第一个id称为“标识符”,其余id叫做“别名”;这些id在IoC容器中必须唯一。
不指定id,只配置必须的全限定类名,由IoC容器为其生成一个标识,客户端必须通过接口“T getBean(Class requiredType)”获取Bean;
指定id,必须在Ioc容器中唯一;
|
|
指定name,这样name就是“标识符”,必须在Ioc容器中唯一;
指定id和name,id就是标识符,而name就是别名,必须在Ioc容器中唯一;
指定多个name,多个name用“,”、“;”、“ ”分割,第一个被用作标识符,其他的(alias1、alias2、alias3)是别名,所有标识符也必须在Ioc容器中唯一;
|
|
使用 <alias>
标签指定别名,别名也必须在IoC容器中唯一
使用基于XML的配置元数据时,在XML中id是一个真正的XML id属性,因此当其他的定义来引用这个id时就体现出id的好处了,可以利用XML解析器来验证引用的这个id是否存在,从而更早的发现是否引用了一个不存在的bean,而使用name,则可能要在真正使用bean时才能发现引用一个不存在的bean。
Spring Bean 生命周期
Spring 只帮我们管理单例模式 Bean 的完整生命周期,对于 prototype 的 bean ,Spring 在创建好交给使用者之后则不会再管理后续的生命周期。
- Spring容器 从XML 文件中读取bean的定义,并实例化bean。
- Spring根据bean的定义填充所有的属性。
- 如果bean实现了BeanNameAware 接口,Spring 传递bean 的ID 到 setBeanName方法。
- 如果Bean 实现了 BeanFactoryAware 接口, Spring传递beanfactory 给setBeanFactory 方法。
- 如果有任何与bean相关联的BeanPostProcessors,Spring会在postProcesserBeforeInitialization()方法内调用它们。
- 如果bean实现IntializingBean了,调用它的afterPropertySet方法,如果bean声明了初始化方法,调用此初始化方法。
- 如果有BeanPostProcessors 和bean 关联,这些bean的postProcessAfterInitialization() 方法将被调用。
- 如果bean实现了 DisposableBean,它将调用destroy()方法。
生命周期介入
注解方式
|
|
InitializingBean, DisposableBean 接口
|
|
自定义初始化和销毁方法
也可以自定义方法用于在初始化、销毁阶段调用:
public class SpringLifeCycle{
private final static Logger LOGGER = LoggerFactory.getLogger(SpringLifeCycle.class);
public void start(){
LOGGER.info("SpringLifeCycle start");
}
public void destroy(){
LOGGER.info("SpringLifeCycle destroy");
}
}
@Configuration
public class LifeCycleConfig {
@Bean(initMethod = "start", destroyMethod = "destroy")
public SpringLifeCycle create(){
SpringLifeCycle springLifeCycle = new SpringLifeCycle() ;
return springLifeCycle ;
}
}
以上是在 SpringBoot 中可以这样配置,如果是原始的基于 XML 也是可以使用:
|
|
*实现 Aware 接口
*Aware
接口可以用于在初始化 bean 时获得 Spring 中的一些对象,如获取 Spring 上下文
等。
|
|
BeanPostProcessor 增强处理器
实现 BeanPostProcessor 接口,Spring 中所有 bean 在做初始化时都会调用该接口中的两个方法,可以用于对一些特殊的 bean 进行处理:
|
|
有两个重要的bean 生命周期方法,第一个是setup , 它是在容器加载bean的时候被调用。第二个方法是 teardown 它是在容器卸载类的时候被调用。
bean 标签有两个重要的属性(init-method和destroy-method)。用它们你可以自己定制初始化和注销方法。它们也有相应的注解(@PostConstruct和@PreDestroy)。
instantiate bean对象实例化
②populate properties 封装属性
③如果Bean实现BeanNameAware 执行 setBeanName
④如果Bean实现BeanFactoryAware 或者 ApplicationContextAware 设置工厂 setBeanFactory 或者上下文对象 setApplicationContext
⑤如果存在类实现 BeanPostProcessor(后处理Bean) ,执行postProcessBeforeInitialization,BeanPostProcessor接口提供钩子函数,用来动态扩展修改Bean。(程序自动调用后处理Bean)
|
|
注意:这个前处理Bean和后处理Bean会对所有的Bean进行拦截。
⑥如果Bean实现InitializingBean 执行 afterPropertiesSet
⑦调用 <bean init-method="init">
指定初始化方法 init
⑧如果存在类实现 BeanPostProcessor(处理Bean) ,执行postProcessAfterInitialization
⑨执行业务处理
⑩如果Bean实现 DisposableBean 执行 destroy
⑪调用 <bean destroy-method="customerDestroy">
指定销毁方法 customerDestroy
实例化。Spring通过new关键字将一个Bean进行实例化,JavaBean都有默认的构造函数,因此不需要提供构造参数。
填入属性。Spring根据xml文件中的配置通过调用Bean中的setXXX方法填入对应的属性。
事件通知。Spring依次检查Bean是否实现了BeanNameAware、BeanFactoryAware、ApplicationContextAware、BeanPostProcessor、InitializingBean接口,如果有的话,依次调用这些接口。
使用。应用程序可以正常使用这个Bean了。
销毁。如果Bean实现了DisposableBean接口,就调用其destroy方法。
|
|
- destroy-method 只对 scope=“singleton” 有效
- 销毁方法,必须关闭ApplicationContext对象(手动调用),才会被调用
|
|
加载过程
1.容器寻找Bean的定义信息并且将其实例化。
2.如果允许提前暴露工厂,则提前暴露这个bean的工厂,这个工厂主要是返回该未完全处理的bean.主要是用于避免单例属性循环依赖问题.
3.受用依赖注入,Spring按照Bean定义信息配置Bean的所有属性。
4.如果Bean实现了BeanNameAware接口,工厂调用Bean的**setBeanName()**方法传递Bean的ID。
5.如果Bean实现了BeanFactoryAware接口,工厂调用**setBeanFactory()**方法传入工厂自身。
6.如果BeanPostProcessor和Bean关联,那么它们的**postProcessBeforeInitialzation()**方法将被调用。
7.如果Bean指定了init-method方法,它将被调用。
8.如果有BeanPostProcessor和Bean关联,那么它们的postProcessAfterInitialization()方法将被调用
9.最后如果配置了destroy-method方法则注册DisposableBean.
到这个时候,Bean已经可以被应用系统使用了,并且将被保留在Bean Factory中知道它不再需要。 有两种方法可以把它从Bean Factory中删除掉。
1.如果Bean实现了DisposableBean接口,destory()方法被调用。
2.如果指定了订制的销毁方法,就调用这个方法。
Bean 的作用域
- singleton : bean在每个Spring ioc 容器中只有一个实例。
- prototype:一个bean的定义可以有多个实例。
- request:每次http请求都会创建一个bean,该作用域仅在基于web的Spring ApplicationContext情形下有效。
- session:在一个HTTP Session中,一个bean定义对应一个实例。该作用域仅在基于web的Spring ApplicationContext情形下有效。
- global-session:在一个全局的HTTP Session中,一个bean定义对应一个实例。该作用域仅在基于web的Spring ApplicationContext情形下有效。
缺省的Spring bean 的作用域是Singleton.
Spring框架中的单例bean不是线程安全的。
Bean 的自动装配
Spring 容器能够自动装配相互合作的bean,这意味着容器不需要和配置,能通过Bean工厂自动处理bean之间的协作。
- no:默认的方式是不进行自动装配,通过显式设置ref 属性来进行装配。
- **byName:**通过参数名 自动装配,Spring容器在配置文件中发现bean的autowire属性被设置成byname,之后容器试图匹配、装配和该bean的属性具有相同名字的bean。
- **byType::**通过参数类型自动装配,Spring容器在配置文件中发现bean的autowire属性被设置成byType,之后容器试图匹配、装配和该bean的属性具有相同类型的bean。如果有多个bean符合条件,则抛出错误。
- constructor:这个方式类似于byType, 但是要提供给构造器参数,如果没有确定的带参数的构造器参数类型,将会抛出异常。
- **autodetect:**首先尝试使用constructor来自动装配,如果无法工作,则使用byType方式。
自动装配的局限性是:
- 重写: 你仍需用 和 配置来定义依赖,意味着总要重写自动装配。
- 基本数据类型:你不能自动装配简单的属性,如基本数据类型,String字符串,和类。
- **模糊特性:**自动装配不如显式装配精确,如果有可能,建议使用显式装配。
Spring 的内部 bean
当一个bean仅被用作另一个bean的属性时,它能被声明为一个内部bean,为了定义inner bean,在Spring 的 基于XML的 配置元数据中,可以在 或 元素内使用 元素,内部bean通常是匿名的,它们的Scope一般是prototype。