5
注意LifecycleProcessor
接口继承了Lifcycle
接口。同时,增加了2个方法,用于处理容器的refreshed
和closed
事件。
startup
和shutdown
方法调用次序非常重要。若两个对象有依赖关系,依赖方会在依赖启动之后启动,会在依赖停止之前停止。然而,有时依赖并不直接。也许你仅知道某些类型对象优先于另外一种类型启动。此场景中,SmartLifecycle
接口也许是个好主意,该接口有个方法getPhase()
,此方法是其父接口Phased
中的方法:
public interface Phased {
int getPhase();
}
public interface SmartLifecycle extends Lifecycle, Phased {
boolean isAutoStartup();
void stop(Runnable callback);
}
启动时,最低层次的phase
最先启动,停止时,该次序逆序执行。因此,若对象实现了SmartLifecycle
接口,它的getPhase()
方法返回Integer.MIN_VALUE
,那么该对象最先启动,最后停止。若是返回了Integer.MAX_VALUE
,那么该方法最后启动最先停止(因为该对象依赖其他bean才能运行)。关于phase
的值,常规的并未实现SmartLifecycle
接口的Lifecycle
对象,其值默认为0。因此,负phase
值表示要在常规Lifecycle
对象之前启动(在常规Lifecycyle
对象之后停止),使用 正值则恰恰相反。
如你所见,SmartLifecycle
中stop()
方法有一个回调参数。所有的实现在关闭处理完成后会调用回调的run()
方法。TODO 。它相当于开启了异步关闭功能,和LifecycleProcessor
接口默认实现DefaultLifecycleProcessor
类的异步,该类会为每个phase
的回调等待超时。每个phase
默认的超时是30秒。可以重写该类默认的实例,该类在容器内默认bean名称是lifecycleProcessor
。如果你仅想修改超时,这么写就足够了。
<bean id="lifecycleProcessor" class="org.springframework.context.support.DefaultLifecycleProcessor">
<!-- timeout value in milliseconds -->
<property name="timeoutPerShutdownPhase" value="10000"/>
</bean>
As mentioned, the LifecycleProcessor interface defines callback methods for the refreshing and closing of the context as well. The latter will simply drive the shutdown process as if stop() had been called explicitly, but it will happen when the context is closing. The refresh callback on the other hand enables another feature of SmartLifecycle beans. When the context is refreshed (after all objects have been instantiated and initialized), that callback will be invoked, and at that point the default lifecycle processor will check the boolean value returned by each SmartLifecycle object’s isAutoStartup() method. If "true", then that object will be started at that point rather than waiting for an explicit invocation of the context’s or its own start() method (unlike the context refresh, the context start does not happen automatically for a standard context implementation). The "phase" value as well as any "depends-on" relationships will determine the startup order in the same way as described above.
TODO 书接前文,LifecycleProcessor
接口也定义了容器的refreshing
和closing
事件。后者会驱动shutdown
处理,就像是明确的调用了stop()
方法,但是它是发生在容器关闭期间。refresh
回调开启了SmartLifecycle
bean的另一个功能 。当上下文环境刷新时(在所有的对象实例化和初始化之后),则会调用refresh回调,同时,默认的lifecycle processor
检查每个SmartLifecycle
对象的isAutoStartup()
方法返回的布尔值。若为true
,对象则会在那时启动,而不是等待容器显示调用之后或者是他自己的start()
方法调用之后(这和容器刷新不同,标准的容器实现启动不会自动发生)。phase
值和depends-on
关系一样,都使用了相同的方法决定了的启动次序。
<h5 id='beans-factory-shutdown'>非web应用中安全的关闭Spring IoC容器</h5>
注意
本章适用于非web应用。基于Spring web的应用的
ApplicationContext
实现类,已经提供了支持,用于在应用关闭时安全的关闭Spring IoC容器。
在一个非web应用的环境中使用Spring IoC容器;比如,在一个富客户端桌面的环境中;得在JVM中注册一个shutdown
钩子。这么做是为了安全的关闭,在关闭时保证所单例bean的相关的destroy
方法会被调用,这样就可以释放所有的资源。当然了,你必须得正确的配置和实现销毁回调。
要注册shutdown钩子,得调用registerShutdownHood()
方法,该方法在AbstractApplicationContext
类中。
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public final class Boot {
public static void main(final String[] args) throws Exception {
AbstractApplicationContext ctx = new ClassPathXmlApplicationContext(
new String []{"beans.xml"});
// add a shutdown hook for the above context...
ctx.registerShutdownHook();
// app runs here...
// main method exits, hook is called prior to the app shutting down...
}
}
<h4 id='beans-factory-aware'>ApplicationContextAware and BeanNameAware</h4>
org.springframework.context.ApplicationContextAware
接口实现类的实例将会持有ApplicationContext
的引用:
public interface ApplicationContextAware {
void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
}
因此可以编程式的使用ApplicationContext
手动的创建bean,通过ApplicationContext
接口或者是该接口的子类,比如ConfigurableApplicationContext
,该类还增加了方法。用途之一是编程式的检索bean,有时非常有用。然而,大多数情况下,要避免编程式检索bean,这样的话你的代码就会和Spring耦合,这不是IoC的风格,Ioc的风格是协作类作为bean的属性。ApplicationContext
类的其他方法提供了文件资源的访问接口、发布应用事件、访问MessageSource
消息资源。这些附加的功能请参看Section 5.15, “Additional Capabilities of the ApplicationContext”
自Spring2.5起,可以使用自动装配获取ApplicationContext
引用。传统的constructor
和byType
自动装配模式(详情参看 Section 5.4.5, “Autowiring collaborators”能为构造参数或者setter
方法提供一个ApplicationContext
类的依赖注入。为了更加灵活,还增加了自动注入的注解功能,它能自动注入属性和自动注入多参数方法。使用注解,ApplicationContext
可以自动注入到ApplicationContext
类型的属性、构造参数、方法参数。详情参看Section 5.9.2, “@Autowired”.
org.springframework.beans.factory.BeanNameAware
接口的实现类,若是由ApplicationContext
创建了该类的实例,该实例将会持有相关的对象定义的引用。
public interface BeanNameAware {
void setBeanName(string name) throws BeansException;
}
The callback is invoked after population of normal bean properties but before an initialization callback such as InitializingBean afterPropertiesSet or a custom init-method.
TODO这个回调在设置属性之后调用,但是在initialization
回调之前,比如InitializingBean
的afterPropertiesSet
或者 自定义的init-method
<h4 id='aware-list'>Other Aware interfaces</h4>
Besides ApplicationContextAware and BeanNameAware discussed above, Spring offers a range of Aware interfaces that allow beans to indicate to the container that they require a certain infrastructure dependency. The most important Aware interfaces are summarized below - as a general rule, the name is a good indication of the dependency type:
名称 | 注入依赖 | 详情 |
---|---|---|
ApplicationContextAware | ApplicationContext | Section 5.6.2, “ApplicationContextAware and BeanNameAware” |
ApplicationEventPublisherAware | 发布事件 | Section 5.15, “Additional Capabilities of the ApplicationContext” |
BeanClassLoaderAware | 加载bean的类加载器 | Section 5.3.2, “Instantiating beans” |
BeanFactoryAware | 声明BeanFactory | Section 5.6.2, “ApplicationContextAware and BeanNameAware” |
BeanNameAware | 生命bean 的名字 | Section 5.6.2, “ApplicationContextAware and BeanNameAware” |
BootstrapContextAware | Resource adapter BootstrapContext the container runs in. Typically available only in JCA aware ApplicationContexts | Chapter 26, JCA CCI |
LoadTimeWeaverAware | Defined weaver for processing class definition at load time | Section 9.8.4, “Load-time weaving with AspectJ in the Spring Framework” |
MessageSourceAware | Configured strategy for resolving messages (with support for parametrization and internationalization) | Section 5.15, “Additional Capabilities of the ApplicationContext” |
NotificationPublisherAware | Spring JMX notification publisher | Section 25.7, “Notifications” |
PortletConfigAware | Current PortletConfig the container runs in. Valid only in a web-aware Spring ApplicationContext | Chapter 20, Portlet MVC Framework |
PortletContextAware | Current PortletContext the container runs in. Valid only in a web-aware Spring ApplicationContext | Chapter 20, Portlet MVC Framework |
ResourceLoaderAware | Configured loader for low-level access to resources | Chapter 6, Resources |
ServletConfigAware | Current ServletConfig the container runs in. Valid only in a web-aware Spring ApplicationContext | Chapter 17, Web MVC framework |
除上面讨论过的ApplicationContextAware
和BeanNameAware
,Spring提供了一些了Aware
接口,这些接口可以提供容器中相关的基础(SpringAPI)依赖。最重要的Aware
接口参看下面的摘要,命名相当规范,看名字就能知道依赖类型:
Table 5.4. Aware interfaces
名称 | 注入依赖 | 详情 |
---|---|---|
ApplicationContextAware | ApplicationContext | Section 5.6.2, “ApplicationContextAware and BeanNameAware” |
ApplicationEventPublisherAware | 发布事件 | Section 5.15, “Additional Capabilities of the ApplicationContext” |
BeanClassLoaderAware | 加载bean的类加载器 | Section 5.3.2, “Instantiating beans” |
BeanFactoryAware | 声明BeanFactory | Section 5.6.2, “ApplicationContextAware and BeanNameAware” |
BeanNameAware | 生命bean 的名字 | Section 5.6.2, “ApplicationContextAware and BeanNameAware” |
BootstrapContextAware | Resource adapter BootstrapContext the container runs in. Typically available only in JCA aware ApplicationContexts | Chapter 26, JCA CCI |
LoadTimeWeaverAware | Defined weaver for processing class definition at load time | Section 9.8.4, “Load-time weaving with AspectJ in the Spring Framework” |
MessageSourceAware | Configured strategy for resolving messages (with support for parametrization and internationalization) | Section 5.15, “Additional Capabilities of the ApplicationContext” |
NotificationPublisherAware | Spring JMX notification publisher | Section 25.7, “Notifications” |
PortletConfigAware | Current PortletConfig the container runs in. Valid only in a web-aware Spring ApplicationContext | Chapter 20, Portlet MVC Framework |
PortletContextAware | Current PortletContext the container runs in. Valid only in a web-aware Spring ApplicationContext | Chapter 20, Portlet MVC Framework |
ResourceLoaderAware | Configured loader for low-level access to resources | Chapter 6, Resources |
ServletConfigAware | Current ServletConfig the container runs in. Valid only in a web-aware Spring ApplicationContext | Chapter 17, Web MVC framework |
注意,这些接口的用法使代码与Spring API耦合,这不符合IoC风格。同样,除非有需求的基础bean才使用编程式访问容器。
<h3 id='beans-child-bean-definitions'>Spring Bean的继承</h3>
Spring bean定义包含各种配置信息,包括构造参数,属性值,容器特定信息例如初始化方法、静态工厂方法等等。Spring子bean定义继承父bean定义配置。子bean能覆盖值,若有需要还能增加其他配置。使用继承能少打好多字。这是模板的一种形式,讲究的就是效率。
编程式的方式使用ApplicationContext
场景,子bean的定义代表ChildBeanDefinition
类。大多数用户不需要使用如此底层的SpringAPI,通常是使用类似ClassPathXmlApplicationContext
的bean声明。若用XML配置,通过parent
属性表示子bean定义,指定父bean的标识作为parent
属性值。
<bean id="inheritedTestBean" abstract="true"
class="org.springframework.beans.TestBean">
<property name="name" value="parent"/>
<property name="age" value="1"/>
</bean>
<bean id="inheritsWithDifferentClass"
class="org.springframework.beans.DerivedTestBean"
parent="inheritedTestBean" init-method="initialize">
<property name="name" value="override"/>
<!-- the age property value of 1 will be inherited from parent -->
</bean>
若子bean中未指定class
属性,则子bean集成父bean的class
属性,子bean可以重写覆盖此属性。若要覆盖重写class
属性,子bean的class类型必须兼容父bean的class,也就是,子bean必须能接收父bean的属性值。
其他的属性也是通常取自子bean的配置:depends on, autowire mode, dependency check, singleton, lazy init.
前面样例中,使用abstract
属性指定了父bean为抽象定义。如父bean中未指定class,则必须指定父bean为抽象bean。看代码:
<bean id="inheritedTestBeanWithoutClass" abstract="true">
<property name="name" value="parent"/>
<property name="age" value="1"/>
</bean>
<bean id="inheritsWithClass" class="org.springframework.beans.DerivedTestBean"
parent="inheritedTestBeanWithoutClass" init-method="initialize">
<property name="name" value="override"/>
<!-- age will inherit the value of 1 from the parent bean definition-->
</bean>
上述的父bean不能实例化,因为她不完整,是抽象的bean,作为子bean的纯模板时,它是非常有用的。试试通过属性引用或者使用getBean()
方法调用该bean,会抛错。容器内部的preInstantiateSingletons()
方法会忽略抽象bean。
ApplicationContext
类默认会预先实例化所有的单例bean。因此,如果有做模板用的父bean,父bean定义中指定了classs
属性,则必须指定abstract
为true
,这是非常重要的,否则容器会预先实例化该bean。
<h3 id='beans-factory-extension'>容器扩展点</h3>
通常开发者无需自己实现APplicationContext
,而是使用插件扩展Spring IoC容器,插件是某些指定的集成接口的实现。下面记账讲解这些集成接口。
<h4 id='beans-factory-extension-bp'>使用BeanPostProcessor自定义bean</h4>
BeanPostProcessor
接口定义了实例化逻辑、依赖逻辑等回调方法,即可以自定义也可以覆盖容器默认方法。若果要在Spring容器完成实例化、配置、初始化bean之后执行自定义逻辑,则以插件方式实现BeanPostProcessor
。
可以配置多个BeanPostProcessor
实例,可以设置BeanPostProcessors
的order
属性来控制其执行次序。让BeanPostProcessor
实现Ordered
接口,就能设置次属性。如果使用自定义BeanPostProcessor
,也得考虑实现Ordered
接口。更多的细节,参阅BeanPostProcessor
和Ordered
接口的javadocs。也可以查阅programmatic registration of BeanPostProcessors译注,SPring参考手册中这个链接确实没有
NOTE
BeanPostProcessors
操作bean的实例;也就是,Spring IoC容器实例化bean的实例时BeanPostProcessors
开始运行。
BeanPostProcessors
在各自容器内有效。当使用容器继承时,BeanPostProcessors
缺不会继承。如果在某容器内定义了BeanPostProcessor
,近在本容器中生效。或句话说,一个容器中的bean不会使用另一个容器内的BeanPostProcessor
处理,继承的容器也不行。要改变bean定义(也就是,bean定义的蓝图,译注蓝图应该是指各种配置元数据,比如xml、注解等),你得使用
BeanFactorPostProcessor
,详情参看in Section 5.8.2, “Customizing configuration metadata with a BeanFactoryPostProcessor”
org.springframework.beans.factory.config.BeanPostProcessor
接口有2个回调方法组成。当这样的类在容器内注册为post-processor
,容器创建所有bean,在容器初始化方法(比如InitializingBean
的afterProperieSet()
方法和其他所有的声明的init
方法)和所有bean 初始化回调之前,运行post-processor
回调。
ApplicationContext
自动探测在配置元数据中定义的BeanPostProcessor
。ApplicationContext
注册这些bean为post-processors
,这样就可以在bean创建之前调用。Bean的post-processors
可以像其他bean那样部署到容器里。
注意,在configuration
类中,使用@Bean
工厂方法声明BeanPostProcessor
,该工厂方法的返回类型必须是该实现类或者至少得是org.springframework.beans.factory.config.BeanPostProcessor
接口,清楚的标识出post-processor
。否则,ApplicationContext
不会开启根据类型自动探测。因为BeanPostProcessor
需要尽早的实例化,这样在容器中即可用于其他bean的初始化,因此这种尽早的类型探测至关重要。
注意
编程式注册BeanPostProcessor
尽管推荐的BeanPostProcessor
的注册方式是通过ApplicationContext
的自动探测机制,但是也可以使用ConfigurableBeanFactory
类调用其addBeanPostProcessor
实现编程式的注册。编程式注册是非常有用的,比如用于在注册之前实现等价的逻辑,再比如跨容器复制post processors
。注意使用编程式注册BeanPostProcessors
并不会遵守Ordered
接口的次序。注册的顺序就是执行的次序。此外还得记得,编程式的注册BeanPostProcessors
会在自动探测注册的BeanPostProcessors
之前处理,无论自动探测注册的BeanPostProcessors
指定了多么优先的次序。
注意
BeanPostProcessor和AOP的自动代理
Classes that implement the BeanPostProcessor interface are special and are treated differently by the container. All BeanPostProcessors and beans that they reference directly are instantiated on startup, as part of the special startup phase of the ApplicationContext. Next, all BeanPostProcessors are registered in a sorted fashion and applied to all further beans in the container. Because AOP auto-proxying is implemented as a BeanPostProcessor itself, neither BeanPostProcessors nor the beans they reference directly are eligible for auto-proxying, and thus do not have aspects woven into them.
容器会特殊对待BeanPostProcessor
接口。所有的BeanPostProcessors
及引用了BeanPostProcessors
的bean会在启动时实例化,作为ApplicationContext
特殊的启动阶段。接下来,所有的BeanPostProcessors
都会按照次序注册到容器中,在其他bean使用BeanPostProcessors
处理时也会使用此顺序。因为AOP的auto-proxying自动代理是BeanPostProcessor
的默认实现,它既不引用BeanPostProcessors
也不引用其他bean,不会发生auto-proxying自动代理,因此不会有切面织入。TODO对于
BeanPostProcessor
类型的bean,会看到这样一条日志:"Bean foo is not eligible for getting processed by all BeanPostProcessor interfaces (for example: not eligible for auto-proxying)"
注意,如果有bean通过自动注入或者@Resource
(可能会导致自动注入)注入到BeanPostProcessor
,在使用类型匹配检索依赖bean时Spring也许会访问到不期望的bean,导致生成不合适的auto-proxying自动代理或者其他post-processing
。举个栗子,如果使用@Resouce
依赖注解,而且field/setter
上注解的名字和bean中声明名字不一致时,Spring将会使用类型匹配访问其他bean。
下面 示例中讲解了在ApplicationContext
中如何撰写、注册、使用BeanPostProcessors
栗子:Hello World,BeanPostProcessor风格
第一个示例,讲解基础用法。栗子展示了一个自定义BeanPostProcessor
实现,功能是在容器创建bean时,调用每一个bean的toString()
方法并输出到控制台。
上干活,fuck goods
package scripting;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.BeansException;
public class InstantiationTracingBeanPostProcessor implements BeanPostProcessor {
// simply return the instantiated bean as-is
public Object postProcessBeforeInitialization(Object bean,
String beanName) throws BeansException {
return bean; // we could potentially return any object reference here...
}
public Object postProcessAfterInitialization(Object bean,
String beanName) throws BeansException {
System.out.println("Bean '" + beanName + "' created : " + bean.toString());
return bean;
}
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:lang="http://www.springframework.org/schema/lang"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/lang
http://www.springframework.org/schema/lang/spring-lang.xsd">
<lang:groovy id="messenger"
script-source="classpath:org/springframework/scripting/groovy/Messenger.groovy">
<lang:property name="message" value="Fiona Apple Is Just So Dreamy."/>
</lang:groovy>
<!--
when the above bean (messenger) is instantiated, this custom
BeanPostProcessor implementation will output the fact to the system console
-->
<bean class="scripting.InstantiationTracingBeanPostProcessor"/>
</beans>
注意InstantiationTracingBeanPostProcessor
是如何定义的。它甚至没有名字,因为它能像其他bean那样依赖注入。(上面的配置中,使用Groovy script
创建了个bean。Spring动态语言支持的详细讲解参看Chapter 29, Dynamic language support
下面的java应用使用上面的配置和代码执行,
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.scripting.Messenger;
public final class Boot {
public static void main(final String[] args) throws Exception {
ApplicationContext ctx = new ClassPathXmlApplicationContext("scripting/beans.xml");
Messenger messenger = (Messenger) ctx.getBean("messenger");
System.out.println(messenger);
}
}
将会输出:
Bean messenger created : org.springframework.scripting.groovy.GroovyMessenger@272961
org.springframework.scripting.groovy.GroovyMessenger@272961
Example: The RequiredAnnotationBeanPostProcessor
对于扩展Spring IoC容器,使用回调函数或者注解联结一个自定义BeanPostProcessor
实现类是常用的手段。例如Spring的RequiredAnnotationBeanPostProcessor
,是个BeanPostProcessor
实现类,spring内置,作用是确保Spring bean定义上的带注解的JavaBean属性确实被注入了值。
<h4 id='beans-factory-extension-factory-postprocessors'>使用BeanFactoryPostProcessor自定义配置元数据</h4>
接下来的扩展点讲一讲org.springframework.beans.factory.config.BeanFactoryPostProcessor
。此接口的语法和BeanPostProcessor
类似,有一个主要的不同之处:BeanFactoryPostProcessor
操作bean的配置元数据;也就是,Spring IoC容器允许BeanFactoryPostProcessor
读取配置元数据并且在容器实例化bean之前可能修改配置。
可以配置多个BeanFactoryPostProcessors
,通过设置order
属性控制它们的执行次序。BeanFactoryPostProcessor
若是实现了Ordered
接口,则可设置该属性。若是自定义BeanFactorPostProcessor
,同时得考虑实现Ordered
接口。详情参阅BeanFactoryPostProcessor
的javadocs。
NOTE
如果要改变bean实例(根据配置元数据创建的对象),那么就需要使用BeanPostProcessor
(上一章描述的in Section 5.8.1, “Customizing beans using a BeanPostProcessor”)。当使用BeanFactoryPostProcessor
处理实例时(使用BeanFactory.getBean()方法),如此早的处理bean实例,违反了标准的容器生命周期。通过bean post processing
也许会引起负面影响。
BeanFactoryPostProcessors
的作用域也是在各自的容器内。如果使用容器继承,这一点也是应该注意的。如果在某容器内定义了BeanFactoryPostProcessor
,则仅应用于本容器。某容器内的bean定义,不会使用另一个容器的BeanFactoryPostProcessors
处理,容器之间有继承关系也不行。
为了让配置元数据的改变应用,声明在ApplicationContext
内的bean工厂post-processor
都是自动执行。Spring包含一系列的预先定义的bean工厂post-processors
,比如PropertyOverrideConfigurer
和PropertyPlaceholderConfigurer
。也可以使用自定义BeanFactoryPostProcessor
,比如注册一个自定义属性编辑器。
ApplicationContext
自动探测BeanFactoryPostProcessor
接口的实现类。容器使用这些bean作为bean工厂post-processors
。可以像其他bean那样将post-processor
部署在容器内。
NOTE
若使用BeanPostProcessors
,通常不会给BeanFactoryPostProcessors
配置延迟初始化。如果没有其他bean引用BeanFactoryPostProcessor
,则post-processor
根本不会实例化。因此设置延迟初始化将会被忽略,BeanFactoryPostProcessor
将会及时实例化,甚至在<beans/>
元素设置了default-lazy-init
属性为true
也不行。
<h5 id='beans-factory-placeholderconfigurer'>Example: the Class name substitution PropertyPlaceholderConfigurer</h5>
可以使用PropertyPlaceholderConfigurer
将bean的属性值使用标准的Java Properties格式定义在一个单独的文件中。这样可以将应用的自定义环境配置属性隔离出来,比如数据库URLs和密码,这样就降低了修改容器内XML配置或者Java 代码的的复杂性和风险。
考虑下面的XML配置片段,使用了placeholder
值定义了DataSource
。样例展示了一个外部的Properties
文件的属性配置。运行时,PropertyPlaceholderConfigurer
会应用到配置元数据中,替换指定格式的placeholders
,格式为${property-name}
,这样的格式与Ant/log4j/JSP EL
风格相同。
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations" value="classpath:com/foo/jdbc.properties"/>
</bean>
<bean id="dataSource" destroy-method="close"
class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
在标准java Properties格式文件中实际的值:
jdbc.driverClassName=org.hsqldb.jdbcDriver
jdbc.url=jdbc:hsqldb:hsql://production:9002
jdbc.username=sa
jdbc.password=root
因此,字串${jdbc.username}
在运行时赋值为sa
,其他的${key}
都会被替换为文件中与key
对应的值。PropertyPlaceholderConfigurer
检查bean定义中大多数的placeholders
占位符,placeholder
的前缀和后缀都是自定义的。
使用Spring2.5引入的上下文命名空间,就可以用一个专用配置元素配置属性placeholders
占位符。可以指定多个locations
,多个locations
使用,
逗号分割。
<context:property-placeholder location="classpath:com/foo/jdbc.properties"/>
PropertyPlaceholderConfigurer
不仅仅检索指定的Properties
文件。默认情况,若是在指定的Properties
配置文件中找不到指定的属性property
,也会检查Java 的系统属性System properties
。通过设置systemPropertiesMode
属性的值,定义默认查找行为,该属性值有几个取值:
- never:不检查系统属性
- fallback:如果未在指定文件中解析出属性值,则检查系统属性。此项为默认行为。
- override:先检查系统属性。系统属性会覆盖其他配置文件中的属性。
PropertyPlaceholderConfigurer
更多详情参看javadocs
TIP
可以使用PropertyPlaceholderConfigurer
替换类名,有时,某些类在运行时才能确定,那么这将非常有用。
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<value>classpath:com/foo/strategy.properties</value>
</property>
<property name="properties">
<value>custom.strategy.class=com.foo.DefaultStrategy</value>
</property>
</bean>
<bean id="serviceStrategy" class="${custom.strategy.class}"/>
若类在运行时期间不能解析为合法类,
ApplicationContext
创建非延迟初始化bean的preInstantiateSingletons()
期间抛错误,
<h5 id='beans-factory-overrideconfigurer'>Example: the PropertyOverrideConfigurer</h5>
PropertyOverrideConfigurer
,是另一个ben工厂的post-processor
,类似于PropertyPlaceholderConfigurer
,但是有不同之处,bean源定义可以设置默认值或者根本不设置值。若一个overriding Properties
文件不包含某个bean属性,就使用默认的上下文定义。
注意bean定义并不知道它会被重写,所以使用了重写配置在XML配置中并不直观。如果有多个PropertyOverrideConfigurer
实例为相同的bean属性配置了不同的值,最后一个实例配置生效。
Properties文件配置格式如下
beanName.property=value
举例:
dataSource.driverClassName=com.mysql.jdbc.Driver
dataSource.url=jdbc:mysql:mydb
上述文件中的配置,将会赋值给在容器中的定义的bean的相应属性 ,bean的名字是datasource
,有driver
属性和url
属性
Compound property names are also supported, as long as every component of the path except the final property being overridden is already non-null (presumably initialized by the constructors). In this example…
同样支持复合属性,属性路径可以要多长有多长,但是属性不能为null(),看样例:
foo.fred.bob.sammy=123
bean foo
有属性fred
,fred
有属性bob
,bob
有属性sammy
,sammy
赋值为123
Note
指定重写值都是字面值;不会解析为bean引用。就算是指定的值,在XML的bean定义中bean的名字,也不会解析为该引用,而是解析为字面值。
使用Spring 2.5中引入的上下文命名空间,可以为配置属性指定专用配置元素
<context:property-override location="classpath:override.properties"/>
<h4 id='beans-factory-extension-factorybean'>使用FactoryBean自定义实例化逻辑</h4>
对象实现org.springframework.beans.factory.FactoryBean
接口,则成为它本身的工厂。
FactoryBean
接口是Spring IoC容器实例化逻辑的扩展点。假如初始化代码非常复杂,此时使用java编码比使用XML配置更容易表达。这种场景中,就可以自定义FactoryBean
,在类中撰写复杂的初始化程序,并将其作为插件加入到容器中。
FactoryBean
接口有3个方法:
- Object getObject():返回本工厂创建的对象实例。此实例也许是共享的,依赖于该工厂返回的是单例或者是原型。
- boolean isSingleton():如果
FactoryBean
返回的是单例,该方法返回值为true
,否则为false
- Class getObjectType():返回对象类型。对象类型是
getObject()
方法返回的对象的类型,如果不知道的类型则返回null。
FactoryBean
概念和接口在Spring框架中大量使用。Spring内置的有超过50个实现。
当使用ApplicationContext
的getBean()
方法获取FactoryBean
实例本身而不是它所产生的bean,则要使用&
符号+id。比如,现有FactoryBean
,它有id,在容器上调用getBean("myBean")
将返回FactoryBean
所产生的bean,调用getBean("&myBean")
将返回FactoryBean
它本身的实例。
<h3 id='beans-annotation-config'>基于注解的把配置元数据</h3>
注解比XML好么?
注解比XML好么,简单的说得看情况。详细的说,各有优缺点。因为定义的方式,注解在声明处提供了大量的
上下文信息,所以注解配置要更简洁。然而,XML擅长在不接触源码或者无需反编译的情况下组装组件。
虽然有这样的争议:注解类不再是`POJO`,并且配置更加分散难以控制,
但是还是有人更喜欢在源码上使用注解配置。
无论选择哪一样,Spring都能很好的支持,甚至混合也行。值得指出的是,
使用`[JavaConfig](#beans-java)`选项,Spring能在不接触目标组件源码的情况下
无侵入的使用注解,这可以通过IDE完成 [Spring Tool Suite](https://spring.io/tools/sts)
对于XML配置,还有另外一个选择,基于注解的配置,它是依赖于字节码元数据,替代XML组装组件。码农码畜可以使用注解替代XML描述bean的组装,开发者将配置撰写到组件类上,使用注解标注相关的类、方法、域上。就像前面提到的 in the section called “Example: The RequiredAnnotationBeanPostProcessor”,使用BeanPostProcessor
联结注解是常见的扩展Spring IoC容器的手段。举个栗子,Spring2.0引入的通过@Required
注解强制检查必须属性值。Spring 2.5采用了类似的手法使用注解处理依赖注入。本质上,@Autowired
注解提供了相同的能力,在这一章有详解Section 5.4.5, “Autowiring collaborators”,但是@Autowired
提供了更细粒度的控制和更强的能力。Spirng 2.5也增加了对JSR-250注解的支持,比如@PostConstruct
,@PreDestory
。Srping3.0增加支持了JSR-330(JAVA依赖注入)注解,这些注解在javax.inject
包内,例如@Inject
和@Named
。详情参看那些注解的相关章节。
注意
注解注入在XML注入之前执行,因此同时使用这两种方式注入时,XML配置会覆盖注解配置。
同样的Spring风格,就像特别的bean定义那样注册他们,但是也能像下面这样隐式注册(注意包含context namespace上下文命名空间)
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config/>
</beans>
(隐式注册的post-processors
包括AutowiredAnnotationBeanPostProcessor
,CommonAnnotationBeanPostProcessor
,PersistenceAnnotationBeanPostProcessor
,还有前面提到的RequiredAnnotationBeanPostProcessor
)
注意
<context:annotation-config/>
仅会检索它所在的应用context上下文中bean上的注解。也就是,如果在WebApplicationContext
中为DispatcherServlet
设置<context:annotation-config/>
,它仅会检查controllers
中@Autowired
的bean,并不会检查service
。详情参看Section 17.2, “The DispatcherServlet”
<h4 id='beans-required-annotation'>@Required</h4>
@Required
注解应用于bean的setter方法,像这样:
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Required
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}
这个注解意思是受到影响的bean属性在配置时必须赋值,在bean定义中明确指定其属性值或者通过自动注入。若该属性未指定值,容器会抛异常。这导致及时明确的失败,避免NullPointerExceptions
或者晚一些时候才发现。仍然推荐,你在编码过程中使用断言,举个栗子,在init
方法,做了这些强制的必须引用的检查,但是属性值甚至不再容器范围内。
<h4 id='beans-autowired-annotation'>@Autowired</h4>
如你所料,@Autowired
注解也是应用在"传统的"setter方法上:
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Autowired
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}
注意
注意
在下面的样例中,使用JSR 330的@Inject
注解可以替代@autowired
注解。详情参看这里
也可以将注解用于带一个或多个参数的其他方法上,看样例:
public class MovieRecommender {
private MovieCatalog movieCatalog;
private CustomerPreferenceDao customerPreferenceDao;
@Autowired
public void prepare(MovieCatalog movieCatalog,
CustomerPreferenceDao customerPreferenceDao) {
this.movieCatalog = movieCatalog;
this.customerPreferenceDao = customerPreferenceDao;
}
// ...
}
@Autowired
也可以应用于构造函数上或者属性上:
public class MovieRecommender {
@Autowired
private MovieCatalog movieCatalog;
private CustomerPreferenceDao customerPreferenceDao;
@Autowired
public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
this.customerPreferenceDao = customerPreferenceDao;
}
// ...
}