1 依赖注入
在IoC模式中,被注入对象又是通过哪些方式来通知IoC Service Provider为其提供适当服务的呢?----依赖注入方式
- 构造方法注入
- setter方法注入
- 接口注入
1.1 构造方法注入
构造方法注入,就是被注入对象可以通过在其构造方法中声明依赖对象的参数列表, 让外部(通常是IoC容器)知道它需要哪些依赖对象。
public FXNewsProvider(IFXNewsListener newsListner, IFXNewsPersister newsPersister) {
this.newsListener = newsListner;
this.newPersistener = newsPersister;
}
对象被构造完成后,即进入就绪状态,可以马上使用。
1.2 setter方法注入
通过setter方法将相应的依赖对象设置到被注入对象中
public class FXNewsProvider {
private IFXNewsListener newsListener;
private IFXNewsPersister newPersistener;
public IFXNewsListener getNewsListener() {
return newsListener;
}
public void setNewsListener(IFXNewsListener newsListener) {
this.newsListener = newsListener;
}
public IFXNewsPersister getNewPersistener() {
return newPersistener;
}
public void setNewPersistener(IFXNewsPersister newPersistener) {
this.newPersistener = newPersistener;
}
}
1.3 接口注入
被注入对象必须实现某个接口。这个接口提供一个方法,用来为其注入依赖对象。
实现接口。
1.4 三种注入方式的比较
- 接口注入:最不提倡,带有侵入性。
- 构造方法注入:优点是构造完成后,就进入就绪,可以马上使用。缺点是依赖对象多,构造方法参数长。通过反射构造对象时,对相同类型的参数处理比较困难,维护和使用上也比较麻烦。而且在Java中,构造方法无法被继承,无法设置默认值。对于非必须的依赖处理,可能需要引入多个构造方法,而参数数量的变动可能造成维护上的不便
- setter方法注入。因为方法可以命名,所以setter方法注入在描述性上要比构造方法注入好一些。另外,setter方法可以被继承,允许设置默认值,而且有良好的IDE支持。缺点当然就是对象无法在构造完成后马上进入就绪状态
2 IoC Service Provider:IOC容器的抽象
2.1 IoC Service Provider 的职责
业务对象的构建管理
- 业务对象的构建管理:IoC Service Provider需要将对象的构建逻辑从客户端对象那里剥离出来,以免这部分逻辑污染业务对象的实现。
业务对象间的依赖绑定
- 业务对象间的依赖绑定:IoC Service Provider通过结合之前构建和管理的所有业务对象,以及各个业务对象间可以识别的依赖关系,将这些对象所依赖的对象注入绑定,从而保证每个业务对象在使用的时候,可以处于就绪状态。
2.2 对象间的依赖关系怎么管理:对象注册与依赖绑定方式
- 直接编码
- 配置文件
- 元数据方式
1. 直接编码
通过程序编码的方式将被注入对象和依赖对象注册到容器中,并明确它们相互之间的依赖注入关系。
IoContainer container = ...;
container.register(FXNewsProvider.class,new FXNewsProvider());
container.register(IFXNewsListener.class,new DowJonesNewsListener());
...
FXNewsProvider newsProvider = (FXNewsProvider)container.get(FXNewsProvider.class);
newProvider.getAndPersistNews();
接口注入
IoContainer container = ...;
container.register(FXNewsProvider.class,new FXNewsProvider());
container.register(IFXNewsListener.class,new DowJonesNewsListener());
...
container.bind(IFXNewsListenerCallable.class, container.get(IFXNewsListener.class));
...
FXNewsProvider newsProvider = (FXNewsProvider)container.get(FXNewsProvider.class);
newProvider.getAndPersistNews();
container.bind(IFXNewsListenerCallable.class, container.get(IFXNewsListener.class));
2. 配置文件:外部配置文件
<bean id="newsProvider" class="..FXNewsProvider">
<property name="newsListener">
<ref bean="djNewsListener"/>
</property>
<property name="newPersistener">
<ref bean="djNewsPersister"/>
</property>
</bean>
<bean id="djNewsListener"
class="..impl.DowJonesNewsListener">
</bean>
<bean id="djNewsPersister"
class="..impl.DowJonesNewsPersister">
</bean>
读取配置文件并使用
container.readConfigurationFiles(...);
FXNewsProvider newsProvider = (FXNewsProvider)container.getBean("newsProvider");
newsProvider.getAndPersistNews();
从容器中获取bean:container.getBean(“newsProvider”);
3. 元数据方式:注解方式
在类中使用元数据信息来标注各个对象之间的依赖关系,然后由Guice框架根据这些注解所提供的信息将这些对象组装后,交给客户端对象使用。
使用Guice的 相应注解标注后的FXNewsProvider定义。
public class FXNewsProvider {
private IFXNewsListener newsListener;
private IFXNewsPersister newPersistener;
@Inject
public FXNewsProvider(IFXNewsListener listener, IFXNewsPersister persister) {
this.newsListener = listener;
this.newPersistener = persister;
}
...
}
通过@Inject,我们指明需要IoC Service Provider通过构造方法注入方式,为FXNewsProvider注入 其所依赖的对象。
余下的依赖相关信息,在Guice中是由相应的Module来提供的,使用的Module实现。
public class NewsBindingModule extends AbstractModule {
@Override
protected void configure() {
bind(IFXNewsListener.class)
.to(DowJonesNewsListener.class).in(Scopes.SINGLETON);
bind(IFXNewsPersister.class)
.to(DowJonesNewsPersister.class).in(Scopes.SINGLETON);
}
}
通过Module指定进一步的依赖注入相关信息之后,我们就可以直接从Guice那里取得最终已经注入完毕,并直接可用的对象了
Injector injector = Guice.createInjector(new NewsBindingModule());
FXNewsProvider newsProvider = injector.getInstance(FXNewsProvider.class);
newsProvider.getAndPersistNews();
3 IOC 容器
Spring的IoC容器和IoC Service Provider之间的关系
Spring提供了两种容器类型:BeanFactory和ApplicationContext。
-
BeanFactory。基础类型IoC容器,提供完整的IoC服务支持。如果没有特殊指定,默认采用延迟初始化策略(lazy-load)。对于资源有限,并且功能要求不是很严格的场景,BeanFactory是比较合适的 IoC容器选择。用到才加载。
-
ApplicationContext。ApplicationContext在BeanFactory的基础上构建,是相对比较高级的容器实现,除了拥有BeanFactory的所有支持,ApplicationContext还提供了其他高级特性,比如事件发布、国际化信息支持等,这些会在后面详述。ApplicationContext所管理的对象,在该类型容器启动之后,默认全部初始化并绑定完成。所以,相对于BeanFactory来说,ApplicationContext要求更多的系统资源,同时,因为在启动时就完成所有初始化,容 器启动时间较之BeanFactory也会长一些。在那些系统资源充足,并且要求更多功能的场景中, ApplicationContext类型的容器是比较合适的选择。
总结:BeanFactory:懒加载,ApplicationContext:启动即加载,拥有BeanFactory特性和其他高级特性。
BeanFactory和ApplicationContext继承关系
IOC容器的实现
Spring的IoC容器所起的作用,以某种方式加载Configuration Metadata(通常也就是XML格式的配置信息),然后根据这些信息绑定整个系统的对象,最终组装成 一个可用的基于轻量级容器的应用系统。
Spring的IoC容器实现以上功能的过程,基本上可以按照类似的流程划分为两个阶段,即容器启动阶段和Bean实例化阶段。
容器启动阶段
- 通过某种途径加载Configuration MetaData
- 容器需要依赖某些工具类(BeanDefinitionReader)对加载的Configuration MetaData进行解析和分析,并将分析后的信息编组为相应的BeanDefinition
- 把这些保存了bean定义必要信息的BeanDefinition,注册到相应的BeanDefinitionRegistry
- 这样容器启动工作就完成了
该阶段所做的工作可以认为是准备性的,重点更加侧重于对象管理信息的收集。当然, 一些验证性或者辅助性的工作也可以在这个阶段完成。
Bean实例化阶段
经过第一阶段,现在所有的bean定义信息都通过BeanDefinition的方式注册到了BeanDefinitionRegistry中。当某个请求方通过容器的getBean方法明确地请求某个对象,或者因依赖关系容器 需要隐式地调用getBean方法时,就会触发第二阶段的活动。
- 检查所请求的对象之前是否已经初始化。如果没有,则会根据注册的 BeanDefinition所提供的信息实例化被请求对象,并为其注入依赖。
- 如果该对象实现了某些回调接口,也会根据回调接口的要求来装配它。
- 当该对象装配完毕之后,容器会立即将其返回请求方使用。
BeanFactoryPostProcessor干预容器的启动
Spring提供了一种叫做BeanFactoryPostProcessor的容器扩展机制。该机制允许我们在容器实 例化相应对象之前,对注册到容器的BeanDefinition所保存的信息做相应的修改。这就相当于在容器实现的第一阶段最后加入一道工序,让我们对最终的BeanDefinition做一些额外的操作,比如修改其中bean定义的某些属性,为bean定义增加其他信息等。
如果要自定义实现BeanFactoryPostProcessor
- 通常我们需要实现org.springframework. beans.factory.config.BeanFactoryPostProcessor接口。
- 因为一个容器可能拥有多个BeanFactoryPostProcessor,这个时候可能需要实现类同时实现Spring的org.springframework.core.Ordered接口,以保证各个BeanFactoryPostProcessor可以按照预先设定的顺序执行(如果顺序紧要的话)。
因为Spring已经提供了几个现成的BeanFactoryPostProcessor实现类,所以,大多时候,我们很少自己去实现某个BeanFactoryPostProcessor。
org.springframework.beans. factory.config.PropertyPlaceholderConfigurer和org.springframework.beans.factory. config.PropertyOverrideConfigurer是两个比较常用的BeanFactoryPostProcessor。
为了处理配置文件中的数据类型与真正的业务对象所定义的数据类型转换,Spring还允许我们通过 org.springframework.beans.factory.config.CustomEditorConfigurer来注册自定义的PropertyEditor以补助容器中默认的PropertyEditor。可以参考BeanFactoryPostProcessor的Javadoc 来了解更多其实现子类的情况。
通过两种方式来应用 BeanFactoryPostProcessor,分别针对基本的 IoC 容 器 BeanFactory和较为先进的容器ApplicationContext。
手动装配BeanFactory使用的BeanFactoryPostProcessor
// 声明将被后处理的BeanFactory实例
ConfigurableListableBeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("..."));
// 声明要使用的BeanFactoryPostProcessor
PropertyPlaceholderConfigurer propertyPostProcessor = new PropertyPlaceholderConfigurer();
propertyPostProcessor.setLocation(new ClassPathResource("..."));
// 执行后处理操作
propertyPostProcessor.postProcessBeanFactory(beanFactory);
如果拥有多个BeanFactoryPostProcessor,我们可以添加更多类似的代码来应用所有的这些 BeanFactoryPostProcessor。
对于ApplicationContext来说,情况看起来要好得多。因为ApplicationContext会自动识别配 置文件中的BeanFactoryPostProcessor并应用它,所以,相对于BeanFactory,在ApplicationContext 中加载并应用BeanFactoryPostProcessor,仅需要在XML配置文件中将这些BeanFactoryPostProcessor简单配置一下即可。
通过ApplicationContext使用BeanFactoryPostProcessor
<beans>
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>conf/jdbc.properties</value>
<value>conf/mail.properties</value>
</list>
</property>
</bean>
...
</beans>
下面让我们看一下Spring提供的这几个BeanFactoryPostProcessor实现都可以完成什么功能。
PropertyPlaceholderConfigurer
PropertyPlaceholderConfigurer允许我们在XML配置文件中使用占位符(PlaceHolder), 并将这些占位符所代表的资源单独配置到简单的properties文件中来加载。
使用了占位符的数据源配置
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<property name="url">
<value>${jdbc.url}</value>
</property>
<property name="driverClassName">
<value>${jdbc.driver}</value>
</property>
<property name="username">
<value>${jdbc.username}</value>
</property>
<property name="password">
<value>${jdbc.password}</value>
</property>
<property name="testOnBorrow">
<value>true</value>
</property>
<property name="testOnReturn">
<value>true</value>
</property>
<property name="testWhileIdle">
<value>true</value>
</property>
<property name="minEvictableIdleTimeMillis">
<value>180000</value>
</property>
<property name="timeBetweenEvictionRunsMillis">
<value>360000</value>
</property>
<property name="validationQuery">
<value>SELECT 1</value>
</property>
<property name="maxActive">
<value>100</value>
</property>
</bean>
-
当BeanFactory在第一阶段加载完成所有配置信息时,BeanFactory中保存的对象的属性信息还只是以占位符的形式存在,如 j d b c . u r l 、 {jdbc.url}、 jdbc.url、{jdbc.driver}。
当 PropertyPlaceholderConfigurer作为BeanFactoryPostProcessor被应用时,它会使用properties 配置文件中的配置信息来替换相应BeanDefinition中占位符所表示的属性值。
-
当进入容器实现的第二阶段实例化bean时,bean定义中的属性值就是最终替换完成的了。
PropertyPlaceholderConfigurer不单会从其配置的properties文件中加载配置项,同时还会检 查Java的System类中的Properties,可以通过setSystemPropertiesMode()或者setSystemPropertiesModeName()来控制是否加载或者覆盖System相应Properties的行为。
PropertyPlaceholderConfigurer提供了SYSTEM_PROPERTIES_MODE_FALLBACK、SYSTEM_PROPERTIES_MODE_NEVER和SYSTEM_ PROPERTIES_MODE_OVERRIDE三种模式。默认采用的是SYSTEM_PROPERTIES_ MODE_FALLBACK,即如果properties文件中找不到相应配置项,则到System的Properties中查找,我们还可以选择不检查System 的Properties或者覆盖它。更多信息请参照PropertyPlaceholderConfigurer的Javadoc文档。
PropertyOverrideConfigurer
可以通过PropertyOverrideConfigurer对容器中配置的任何你想处理的bean定义的property信 息进行覆盖替换。
如果要对容器中的某些bean定义的property信息进行覆盖,我们需要按照如下规则提供一个 PropertyOverrideConfigurer使用的配置文件:
beanName.propertyName=value
properties文件中的键是以XML中配置的bean定义的beanName为标志开始的(通常就 是id指定的值),后面跟着相应被覆盖的property的名称。
针对dataSource定义给出的PropertyOverrideConfigurer的propeties文件配置信息:
# pool-adjustment.properties
dataSource.minEvictableIdleTimeMillis=1000
dataSource.maxActive=50
将PropertyOverrideConfigurer加载到容器之后,dataSource原来定义的默认值就会被pool-adjustment.properties文件中的信息所覆盖:
<bean class="org.springframework.beans.factory.config.PropertyOverrideConfigurer">
<property name="location" value="pool-adjustment.properties"/>
</bean>
当容器中配置的多个PropertyOverrideConfigurer对同一个bean定义的同一个property值进行处理的时候,最后一个将会生效。
配置在properties文件中的信息通常都以明文表示,PropertyOverrideConfigurer的父类 PropertyResourceConfigurer提供了一个protected类型的方法convertPropertyValue,允许子类覆盖这个方法对相应的配置项进行转换,如对加密后的字符串解密之后再覆盖到相应的bean定义中。 当然,既然PropertyPlaceholderConfigurer也同样继承了PropertyResourceConfigurer,我们也可以针对PropertyPlaceholderConfigurer应用类似的功能。
CustomEditorConfigurer
CustomEditorConfigurer是另一种类型的BeanFactoryPostProcessor实现,它只是辅助性地将后期会用到的信息注册到容器,对BeanDefinition没有做任何变动。 我们知道,不管对象是什么类型,也不管这些对象所声明的依赖对象是什么类型,通常都是通过 XML(或者properties甚至其他媒介)文件格式来配置这些对象类型。
但XML所记载的,都是String类型,即容器从XML格式的文件中读取的都是字符串形式,最终应用程序却是由各种类型的对象所构成。
要想完成这种由字符串到具体对象的转换(不管这个转换工作最终由谁来做),都需要这种转换规则 相关的信息,而CustomEditorConfigurer就是帮助我们传达类似信息的。
Spring内部通过JavaBean的PropertyEditor来帮助进行String类型到其他类型的转换工作。只要为每种对象类型提供一个 PropertyEditor ,就可以根据该对象类型取得与其相对应的 PropertyEditor来做具体的类型转换。
以下是这些Spring提供的部分PropertyEditor的简要说明。
- StringArrayPropertyEditor。该PropertyEditor会将符合CSV格式的字符串转换成**String[]**数组的形式,默认是以逗号(,)分隔的字符串,但可以指定自定义的字符串分隔符。ByteArrayPropertyEditor、CharArrayPropertyEditor等都属于类似功能的PropertyEditor,参照Javadoc可以取得相应的详细信息。
- ClassEditor。根据String类型的class名称,直接将其转换成相应的Class对象,相当于通过**Class.forName(String)**完成的功效。可以通过String[]数组的形式传入需转换的值,以达到与提供的ClassArrayEditor同样的目的。
- FileEditor。Spring提供的对应java.io.File类型的PropertyEditor。同属于对资源进行定位的PropertyEditor还有InputStreamEditor、URLEditor等。
- LocaleEditor。针对java.util.Locale类型的PropertyEditor,格式可以参照LocaleEditor和Locale的Javadoc说明。
- PatternEditor。针对Java SE 1.4之后才引入的java.util.regex.Pattern的PropertyEditor,格式可以参照java.util.regex.Pattern类的Javadoc。
以上这些PropertyEditor,容器通常会默认加载使用,所以,即使我们不告诉容器应该如何对这些类型进行转换,容器同样可以正确地完成工作。但当我们需要指定的类型没有包含在以上所提到的PropertyEditor之列的时候,就需要给出针对这种类型的PropertyEditor实现,并通过CustomEditorConfigurer告知容器,以便容器在适当的时机使用到适当的PropertyEditor。
-
自定义PropertyEditor
- 直接让PropertyEditor实现类去实现java.beans.PropertyEditor接口
- 直接继承java.beans.PropertyEditorSupport类以避免实现java.beans.PropertyEditor接口的所有方法。
public class DatePropertyEditor extends PropertyEditorSupport { private String datePattern; @Override public void setAsText(String text) throws IllegalArgumentException { DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern(getDatePattern()); Date dateValue = dateTimeFormatter.parseDateTime(text).toDate(); setValue(dateValue); } public String getDatePattern() { return datePattern; } public void setDatePattern(String datePattern) { this.datePattern = datePattern; } }
bean的生命周期
bean实例化阶段的实现逻辑了。
容器启动之后,并不会马上就实例化相应的bean定义。容器现在仅仅拥有所有对象的 BeanDefinition来保存实例化阶段将要用的必要信息。
只有当请求方通过BeanFactory的getBean()方法来请求某个对象实例的时候,才有可能触发Bean实例化阶段的活动。
BeanFactory的getBean法可以被客户端对象显式调用,也可以在容器内部隐式地被调用。
隐式调用有如下两种情况。
- 对于BeanFactory来说,对象实例化默认采用延迟初始化。通常情况下,当对象A被请求而需要第一次实例化的时候,如果它所依赖的对象B之前同样没有被实例化,那么容器会先实例化对象A所依赖的对象。这时容器内部就会首先实例化对象B,以及对象 A依赖的其他还没有实例化的对象。这种情况是容器内部调用getBean(),对于本次请求的请求方是隐式的。
- ApplicationContext启动之后会实例化所有的bean定义,这个特性在本书中已经多次提到。但ApplicationContext在实现的过程中依然遵循Spring容器实现流程的两个阶段,只不过它会在启动阶段的活动完成之后,紧接着调用注册到该容器的所有bean定义的实例化方法getBean()。这就是为什么当你得到ApplicationContext类型的容器引用时,容器内所有对象已经被全部实例化完成。不信你查一下类org.AbstractApplicationContext的**refresh()**方法。
只有当对应某个bean定义的getBean()方法第一次被调用时,不管是显式的还是隐式的,Bean实例化阶段的活动才会被触发。
第二次被调用则会直接返回容器缓存的第一次实例化完的对象实例(prototype类型bean除外)。当getBean()方法内部发现该bean定义之前还没有被实例化之后,会通过createBean()方法来进行具体的对象实例化。
可以在org.springframework.beans.factory.support.AbstractBeanFactory类的代码中查看到getBean()方法的完整实现逻辑,可以在其子类org.springframework.beans. factory.support.AbstractAutowireCapableBeanFactory的代码中一窥**createBean()**方法的全貌。
第一步:Bean的实例化:返回BeanWrapper实例
容器在内部实现的时候,采用“策略模式(Strategy Pattern)”来决定采用何种方式初始化bean实例。
可以通过反射或者CGLIB动态字节码生成来初始化相应的bean实例或者动态生成其子类。
- org.springframework.beans.factory.support.InstantiationStrategy定义是实例化策略的抽象接口,其直接子类SimpleInstantiationStrategy实现了简单的对象实例化功能,可以通过反射来实例化对象实例,但不支持方法注入方式的对象实例化。
- CglibSubclassingInstantiationStrategy继承了SimpleInstantiationStrategy的以反射方式实例化对象的功能,并且通过CGLIB的动态字节码生成功能,该策略实现类可以动态生成某个类的子类,进而满足了方法注入所需的对象实例化需求。默认情况下,容器内部采用的是CglibSubclassingInstantiationStrategy。
- 容器只要根据相应bean定义的BeanDefintion取得实例化信息,结合CglibSubclassingInstantiationStrategy以及不同的bean定义类型,就可以返回实例化完成的对象实例。以BeanWrapper对构造完成的对象实例进行包裹,返回相应的BeanWrapper实例。
至此,第一步结束。
BeanWrapper接口通常在Spring框架内部使用,它有一个实现类org.springframework.beans.BeanWrapperImpl。其作用是对某个bean进行“包裹”,然后对这个“包裹”的bean进行操作,比如设置或者获取bean的相应属性值。
第一步结束后返回BeanWrapper实例,是为了第二步“设置对象属性”。
BeanWrapper定义继承了org.springframework.beans.PropertyAccessor接口,可以以统一的方式对对象属性进行访问;BeanWrapper定义同时又直接或者间接继承了PropertyEditorRegistry和TypeConverter接口。当把各种PropertyEditor注册给容器时,BeanWrapper用到这些PropertyEditor。
在第一步构造完成对象之后
- Spring会根据对象实例构造一个BeanWrapperImpl实例,
- 将之前CustomEditorConfigurer注册的PropertyEditor复制一份给BeanWrapperImpl实例(这就是BeanWrapper同时又是PropertyEditorRegistry的原因)。
这样,当BeanWrapper转换类型、设置对象属性值时,就可以使用BeanWrapper对bean实例操作。
Object provider = Class.forName("package.name.FXNewsProvider").newInstance();
Object listener = Class.forName("package.name.DowJonesNewsListener").newInstance();
Object persister = Class.forName("package.name.DowJonesNewsPersister").newInstance();
BeanWrapper newsProvider = new BeanWrapperImpl(provider);
newsProvider.setPropertyValue("newsListener", listener);
newsProvider.setPropertyValue("newPersistener", persister);
assertTrue(newsProvider.getWrappedInstance() instanceof FXNewsProvider);
assertSame(provider, newsProvider.getWrappedInstance());
assertSame(listener, newsProvider.getPropertyValue("newsListener"));
assertSame(persister, newsProvider.getPropertyValue("newPersistener"));
第二步:Aware接口
当对象实例化完成并且相关属性以及依赖设置完成之后,Spring容器会检查当前对象实例是否实现了一系列的以Aware命名结尾的接口定义。
如果是,则将这些Aware接口定义中规定的依赖注入给当前对象实例。
BeanFactory的Aware接口。
- org.springframework.beans.factory.BeanNameAware。如果Spring容器检测到当前对象实例实现了该接口,会将该对象实例的bean定义对应的beanName设置到当前对象实例。
- org.springframework.beans.factory.BeanClassLoaderAware。如果容器检测到当前对象实例实现了该接口,会将对应加载当前bean的Classloader注入当前对象实例。默认会使用加载org.springframework.util.ClassUtils类的Classloader。
- org.springframework.beans.factory.BeanFactoryAware。在介绍方法注入的时候,我们提到过使用该接口以便每次获取prototype类型bean的不同实例。如果对象声明实现了BeanFactoryAware接口,BeanFactory容器会将自身设置到当前对象实例。这样,当前对象实例就拥有了一个BeanFactory容器的引用,并且可以对这个容器内允许访问的对象按照需要进行访问。
对于ApplicationContext类型的容器,也存在几个Aware相关接口。不过在检测这些接口并设置相关依赖的实现机理上,与以上几个接口处理方式有所不同,使用的是下面将要说到的BeanPostProcessor方式。不过,设置Aware接口这一步与BeanPostProcessor是相邻的,把这几个接口放到这里一起提及,也没什么不可以的。
对于ApplicationContext类型容器,容器在这一步还会检查以下几个Aware接口并根据接口定义设置相关依赖。
- org.springframework.context.ResourceLoaderAware 。 ApplicationContext 实现了Spring的ResourceLoader接口(后面会提及详细信息)。当容器检测到当前对象实例实现了ResourceLoaderAware接口之后,会将当前ApplicationContext自身设置到对象实例,这样当前对象实例就拥有了其所在ApplicationContext容器的一个引用。
- org.springframework.context.ApplicationEventPublisherAware。ApplicationContext作为一个容器,同时还实现了ApplicationEventPublisher接口,这样,它就可以作为ApplicationEventPublisher来使用。所以,当前ApplicationContext器如果检测到当前实例化的对象实例声明了ApplicationEventPublisherAware接口,则会将自身注入当前对象。
- org.springframework.context.MessageSourceAware。ApplicationContext通过MessageSource接口提供国际化的信息支持,即I18n(Internationalization)。它自身就实现了MessageSource接口,所以当检测到当前对象实例实现MessageSourceAware接口,则会将自身注入当前对象实例。
- org.springframework.context.ApplicationContextAware。 如果ApplicationContext容器检测到当前对象实现了ApplicationContextAware接口,则会将自身注入当前对象实例。
第三步:BeanPostProcessor
BeanPostProcessor的概念容易与BeanFactoryPostProcessor的概念混淆。
但只要记住BeanPostProcessor是存在于对象实例化阶段,而BeanFactoryPostProcessor则是存在于容器启动阶段,这两个概念就比较容易区分了。
与BeanFactoryPostProcessor通常会处理容器内所有符合条件的BeanDefinition类似,BeanPostProcessor会处理容器内所有符合条件的实例化后的对象实例。该接口声明了两个方法,分别在两个不同的时机执行,见如下代码定义:
public interface BeanPostProcessor {
@Nullable
default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
@Nullable
default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
}
postProcessAfterInitialization()则是对应BeanPostProcessor后置处理那一步将会执行的方法。BeanPostProcessor的两个方法中都传入了原来的对象实例的引用,这为我们扩展容器的对象实例化过程中的行为提供了极大的便利,我们几乎可以对传入的对象实例执行任何的操作。
通常比较常见的使用BeanPostProcessor的场景,是处理标记接口实现类,或者为当前对象提供代理实现。
ApplicationContext对应的那些Aware接口实际上就是通过BeanPostProcessor的方式进行处理的。
当ApplicationContext中每个象的实例化过程走到BeanPostProcessor前置处理这一步时,ApplicationContext容器会检测到之前注册到容器ApplicationContextAwareProcessor这个BeanPostProcessor的实现类,然后就会调用其postProcessBeforeInitialization()方法,查并设置Aware相关依赖。
ApplicationContextAwareProcessor的postProcessBeforeInitialization()代码很简单明了
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof ResourceLoaderAware) {
((ResourceLoaderAware) bean).setResourceLoader(this.applicationContext);
}
if (bean instanceof ApplicationEventPublisherAware) {
((ApplicationEventPublisherAware) bean).setApplicationEventPublisher
(this.applicationContext);
}
if (bean instanceof MessageSourceAware) {
((MessageSourceAware) bean).setMessageSource(this.applicationContext);
}
if (bean instanceof ApplicationContextAware) {
((ApplicationContextAware) bean).setApplicationContext(this.applicationContext);
}
return bean;
}
除了检查标记接口以便应用自定义逻辑,还可以通过BeanPostProcessor对当前对象实例做更多的处理。比如替换当前对象实例或者字节码增强当前对象实例等。Spring的AOP则更多地使用BeanPostProcessor来为对象生成相应的代理对象,如org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator。我们将在Spring AOP部分详细介绍该类和AOP相关概念。
BeanPostProcessor是容器提供的对象实例化阶段的强有力的扩展点。为了进一步演示它的强大威力,我们有必要实现一个自定义的BeanPostProcessor
自定义BeanPostProcessor
假设系统中所有的IFXNewsListener实现类需要从某个位置取得相应的服务器连接密码,而且系 统中保存的密码是加密的,那么在IFXNewsListener发送这个密码给新闻服务器进行连接验证的时候,首先需要对系统中取得的密码进行解密,然后才能发送。我们将采用BeanPostProcessor技术, 对所有的IFXNewsListener的实现类进行统一的解密操作。
(1) 标注需要进行解密的实现类 为了能够识别那些需要对服务器连接密码进行解密的IFXNewsListener实现,我们声明了接口 PasswordDecodable,并要求相关IFXNewsListener实现类实现该接口。PasswordDecodable接口声 明以及相关的IFXNewsListener实现类定义
public interface PasswordDecodable {
String getEncodedPassword();
void setDecodedPassword(String password);
}
public class DowJonesNewsListener implements IFXNewsListener, PasswordDecodable {
private String password;
public String[] getAvailableNewsIds() {
// 省略
}
public FXNewsBean getNewsByPK(String newsId) {
// 省略
}
public void postProcessIfNecessary(String newsId) {
// 省略
}
public String getEncodedPassword() {
return this.password;
}
public void setDecodedPassword(String password) {
this.password = password;
}
}
(2) 实现相应的BeanPostProcessor对符合条件的Bean实例进行处理
我们通过PasswordDecodable接口声明来区分将要处理的对象实例①,当检查到当前对象实例实 现了该接口之后,就会从当前对象实例取得加密后的密码,并对其解密。然后将解密后的密码设置回 当前对象实例。之后,返回的对象实例所持有的就是解密后的密码
public class PasswordDecodePostProcessor implements BeanPostProcessor {
public Object postProcessAfterInitialization(Object object, String beanName) throws BeansException {
return object;
}
public Object postProcessBeforeInitialization(Object object, String beanName) throws BeansException {
if (object instanceof PasswordDecodable) {
String encodedPassword = ((PasswordDecodable) object).getEncodedPassword();
String decodedPassword = decodePassword(encodedPassword);
((PasswordDecodable) object).setDecodedPassword(decodedPassword);
}
return object;
}
private String decodePassword(String encodedPassword) {
// 实现解码逻辑
return encodedPassword;
}
}
(3) 将自定义的BeanPostProcessor注册到容器
只有将自定义的BeanPostProcessor实现类告知容器,容器才会在合适的时机应用它。所以,我 们需要将PasswordDecodePostProcessor注册到容器。 5 对于BeanFactory类型的容器来说,我们需要通过手工编码的方式将相应的BeanPostProcessor 注册到容器,也就是调用ConfigurableBeanFactory的addBeanPostProcessor()方法
ConfigurableBeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource(...));
beanFactory.addBeanPostProcessor(new PasswordDecodePostProcessor());
...
// getBean();
对于ApplicationContext容器来说,事情则方便得多,直接将相应的BeanPostProcessor实现 类通过通常的XML配置文件配置一下即可。ApplicationContext容器会自动识别并加载注册到容器 的BeanPostProcessor,如下配置内容将我们的PasswordDecodePostProcessor注册到容器:
<beans>
<bean id="passwordDecodePostProcessor" class="package.name.PasswordDecodePostProcessor">
<!--如果需要,注入必要的依赖-->
</bean>
...
</beans>
合理利用BeanPostProcessor这种Spring的容器扩展机制,将可以构造强大而灵活的应用系统。
有一种特殊类型的BeanPostProcessor我们没有提到,它的执行时机与通常的BeanPostProcessor不同。 org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor 接口可以在对象的实例化过程中导致某种类似于电路“短路”的效果。实际上,并非所有注册到Spring容器内的bean定义都是按照生命周期那样流程实例化的。在所有的步骤之前,也就是实例 化bean对象步骤之前,容器会首先检查容器中是否注册有InstantiationAwareBeanPostProcessor类型的BeanPostProcessor。如果有,首先使用相应的InstantiationAwareBeanPostProcessor来构造对象实例。
构造成功后直接返回构造完成的对象实例,而不会按照“正规的流程”继续执行。这就是它可能造成“短路”的原因。
不过,通常情况下都是Spring容器内部使用这种特殊类型的BeanPostProcessor做一些动态对 象代理等工作,我们使用普通的BeanPostProcessor实现就可以。
第四步:InitializingBean和init-method
org.springframework.beans.factory.InitializingBean是容器内部广泛使用的一个对象生命周期标识接口,其定义如下:
public interface InitializingBean {
void afterPropertiesSet() throws Exception;
}
在对象实例化过程调用过“BeanPostProcessor的前置处理” 之后,会接着检测当前对象是否实现了InitializingBean接口。如果是,则会调用其**afterPropertiesSet()**方法进一步调整对象实例的状态。比如,在有些情况下,某个业务对象实例化完成后,还不能处于可以使用状态。这个时候就可以让该业务对象实现该接口,并在方法afterPropertiesSet() 中完成对该业务对象的后续处理。
虽然该接口在Spring容器内部广泛使用,但如果真的让我们的业务对象实现这个接口,则显得 Spring容器比较具有侵入性。所以,Spring还提供了另一种方式来指定自定义的对象初始化操作,那就 是在XML配置的时候,使用的init-method属性。
通过init-method,系统中业务对象的自定义初始化操作可以以任何方式命名,而不再受制于 InitializingBean的afterPropertiesSet()。如果系统开发过程中规定:所有业务对象的自定义初始化操作都必须以init()命名,为了省去挨个的设置init-method这样的烦琐,我们还可以通过最顶层的的default-init-method统一指定这一init()方法名。
一般,我们是在集成第三方库,或者其他特殊的情况下,才会需要使用该特性。
第五步:DisposableBean与destroy-method
当所有的一切,该设置的设置,该注入的注入,该调用的调用完成之后,容器将检查singleton类 型的bean实例,看其是否实现了org.springframework.beans.factory.DisposableBean接口。或者其对应的bean定义是否通过的destroy-method属性指定了自定义的对象销毁方法。如果是, 就会为该实例注册一个用于对象销毁的回调(Callback),以便在这些singleton类型的对象实例销毁之前,执行销毁逻辑。
与InitializingBean和init-method用于对象的自定义初始化相对应,DisposableBean和 destroy-method为对象提供了执行自定义销毁逻辑的机会。
最常见到的该功能的使用场景就是在Spring容器中注册数据库连接池,在系统退出后,连接池应 该关闭,以释放相应资源。
通常情况下使用destroy-method处理资源释放的数据源注册配置。
使用了自定义销毁方法的数据源配置定义
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="url">
<value>${jdbc.url}</value>
</property>
<property name="driverClassName">
<value>${jdbc.driver}</value>
</property>
<property name="username">
<value>${jdbc.username}</value>
</property>
<property name="password">
<value>${jdbc.password}</value>
</property>
...
</bean>
不过,这些自定义的对象销毁逻辑,在对象实例初始化完成并注册了相关的回调方法之后,并不 会马上执行。回调方法注册后,返回的对象实例即处于使用状态,只有该对象实例不再被使用的时候, 才会执行相关的自定义销毁逻辑,此时通常也就是Spring容器关闭的时候。但Spring容器在关闭之前, 不会聪明到自动调用这些回调方法。所以,需要我们告知容器,在哪个时间点来执行对象的自定义销毁方法。
对于BeanFactory容器来说。我们需要在独立应用程序的主程序退出之前,或者其他被认为是合 适的情况下(依照应用场景而定),如代码清单4-57所示,调用ConfigurableBeanFactory提供的 destroySingletons()方法销毁容器中管理的所有singleton类型的对象实例。
使用ConfigurableBeanFactory的destroySingletons()方法触发销毁对象行为
public class ApplicationLauncher {
public static void main(String[] args) {
BasicConfigurator.configure();
BeanFactory container = new XmlBeanFactory(new ClassPathResource("..."));
BusinessObject bean = (BusinessObject) container.getBean("...");
bean.doSth();
((ConfigurableListableBeanFactory) container).destroySingletons();
// 应用程序退出,容器关闭
}
}
如果不能在合适的时机调用destroySingletons(),那么所有实现了DisposableBean接口的对象实例或者声明了destroy-method的bean定义对应的对象实例,它们的自定义对象销毁逻辑就形同 虚设,因为根本就不会被执行!
对于ApplicationContext容器来说。道理是一样的。但AbstractApplicationContext为我们 提供了**registerShutdownHook()**方法,该方法底层使用标准的Runtime类的addShutdownHook()方 式来调用相应bean对象的销毁逻辑,从而保证在Java虚拟机退出之前,这些singtleton类型的bean对象实例的自定义销毁逻辑会被执行。当然AbstractApplicationContext注册的shutdownHook不只是 调用对象实例的自定义销毁逻辑,也包括ApplicationContext相关的事件发布等。
使用registerShutdownHook()方法注册并触发对象销毁逻辑回调行为
public class ApplicationLauncher {
public static void main(String[] args) {
BasicConfigurator.configure();
BeanFactory container = new ClassPathXmlApplicationContext("...");
((AbstractApplicationContext) container).registerShutdownHook();
BusinessObject bean = (BusinessObject) container.getBean("...");
bean.doSth();
// 应用程序退出,容器关闭
}
}
同样的道理,在Spring 2.0引入了自定义scope之后,使用自定义scope的相关对象实例的销毁逻辑, 也应该在合适的时机被调用执行。不过,所有这些规则不包含prototype类型的bean实例,因为prototype 对象实例在容器实例化并返回给请求方之后,容器就不再管理这种类型对象实例的生命周期了。至此,bean走完了它在容器中“光荣”的一生。