Part III. Core Technologies--Chapter 4, The IoC container(4.5~4.8)

来源:互联网 发布:激光雷达slam算法 编辑:程序博客网 时间:2024/04/28 23:46

4.5 Bean范围

    当创建 bean 时,也就创建了对通过 bean 定义创建的类真正实例的配方。bean 定义的 配方这个概念是很重要的,因为它就意味着,你可以通过配方来创建一个类的多个对象实例。 

    你不仅可以控制从特定 bean 定义创建出的对象的各个依赖和要配置到对象中的值,也 可以控制对象的范围。这个方法非常强大并且很灵活,你可以选择通过配置创建的对象的范 围,而不必去处理 Java 类级别对象的范围。 Bean 可以被定义部署成一种范围:开箱, Spring Framework 支持五种范围,里面的三种仅仅通过感知 web 的 ApplicationContext 可用。 下列的范围支持开箱。你也可以创建自定义范围。

表 4.3. Bean 范围 线程范围 beans

范围描述

singleton

(默认) Scopes a single bean definition to a single object instance per Spring IoC container.为每一个Spring IoC容器中的Bean定义生成一个独立对象的实例。

prototype

每个Bean定义有任意多个对象实例。

request

以一个独立的HTTP请求为生命周期的Bean定义;也就是说,每一个 HTTP 请求有一个 bean 的独立的实例而不是一个独立的 bean。仅仅在可感知 Web 的 Spring ApplicationContext 中可用。

session

定义一个生命周期为一个 HTTP Session 的独立的 bean。仅仅在可感知 Web 的 Spring ApplicationContext 中可用。

global session

定义一个生命周期为一个全局的 HTTP Session 的独立的 bean。典型地 是仅仅使用 portlet 上下文时可用。仅仅在可感知 Web 的 Spring ApplicationContext 中可用。 线程范围的 bean


 注意

在 Spring 3.0 当中,线程范围是可用的,但是它默认是不注册的。要获取更多信息,请 参考 SimpleThreadScope 的 JavaDoc 文档。对于如何注册这个范围或其它自定义范围的做法, 请参考 4.5.5.2 节,“使用自定义范围”。

4.5.1 单例范围

    仅仅共享被管理的 bean 的一个实例,所有使用一个或多个 id 来匹配 bean 的结果对 bean 请求,由 Spring 容器返回指定 bean 的实例。 

    另外一种方式,当你定义一个范围是单例的 bean 后,Spring 的 IoC 容器通过 bean 的定 义创建了这个对象的一个实例。这个独立的实例存储在单例 bean 的缓存中,所有对这个命 名 bean 的后续的请求和引用都返回缓存的对象。


图 4.2. 

    Spring 中对单例 bean 的概念和四人帮(Gang of Four,GoF)设计模式书中定义的单例 模式是不同的。 GoF 的单例硬编码了对象的范围,也就是特定的类仅仅能有一个实例被每一 个 ClassLoader 来创建。 Spring 中单例 bean 的范围,是对每一个容器和 bean 来说的。这 就意味着在独立的 Spring 容器中,对一个特定的类创建了一个 bean,那么 Spring 容器通过 bean 的定义仅仅创建这个类的一个实例。在 Spring 中,单例范围是默认的范围。要在 XML 中定义单例的 bean,可以按照如下示例来编写:

<bean id="accountService" class="com.foo.DefaultAccountService"/><!-- the following is equivalent, though redundant (singleton scope is the default) --><bean id="accountService" class="com.foo.DefaultAccountService" scope="singleton"/>

4.5.2 prototype范围

    非单例,原型范围的 bean 就是当每次对指定 bean 进行请求时,一个新的 bean 的实例 就会创建。也就是说, bean 被注入到其它 bean 或是在容器中通过 getBean()方法来请求时就 会创建新的 bean 实例。作为一项规则,对所有有状态的 bean 使用原型范围,而对于无状 态的 bean 使用单例范围。

    下图讲述了 Spring 的原型范围。数据访问对象(DAO)通常不是配置成原型的,因为典 型的 DAO 不会持有任何对话状态;仅仅是对作者简单对单例图的重用。


图4.3. 

    下面的示例在 XML 中定义了原型范围的 bean:

<bean id="accountService" class="com.foo.DefaultAccountService" scope="prototype"/>

    和其它范围相比,Spring 不管理原型 bean 的完整生命周期;容器实例化,配置并装配 原型对象,并把他给客户端,对于原型 bean 的实例来说,就没有进一步的跟踪记录了。因 此,尽管初始化生命周期回调方法可以对所有对象调用而不管范围,那么在原型范围中,配 置销毁生命周期回调是不能被调用的。客户端代码必须清理原型范围的对象并且释放原型 bean 持有的系统资源。要让 Spring 容器来释放原型范围 bean 持有的资源,可以使用自定义bean 后处理器,它持有需要被清理的 bean 的引用。 

    在某些方面,关于原型范围 bean 的 Spring 容器的角色就是对 Java new 操作符的替代。 所有之后生命周期的管理必须由客户端来处理。(关于 Spring 容器中 bean 的生命周期的详细内容,可以参考 4.6.1 节,“生命周期回调”)

4.5.3 单例Bean和原型Bean依赖

    当你使用单例范围的 bean 和其依赖是原型范围的 bean 时,要当心依赖实在实例化时 被解析的。因此,如果依赖注入了原型范围的 bean 到单例范围的 bean 中,新的原型 bean 就被实例化并且依赖注入到单例 bean 中。原型实例是唯一的实例并不断提供给单例范围的 bean。 

    假设你想单例范围的 bean 在运行时可以重复获得新的原型范围 bean 的实例。那么就 不能将原型范围的 bean 注入到单例 bean 中,因为这个注入仅仅发生一次,就是在 Spring 容器实例化单例 bean 并解析和注入它的依赖时。如果你在运行时需要原型 bean 新的实例 而不是仅仅一次,请参考 4.4.6 节, Section 4.4.6, “Method injection”

4.5.4 Request, session, and global session 范围

    requestsession, 和global session 范围仅仅当你使用感知web的Spring ApplicationContext实现类可用(比如XmlWebApplicationContext )。如果你在常规的Spring IoC容器中使用如ClassPathXmlApplicationContext这些范围,那么就会因为一个未知的Bean的范围而得到IllegalStateException。

初始化web 配置

    要支持request、session和global session级别(Web 范围的Bean)范围的Bean,一些细微的初始化配置需要在定义Bean之前做。(这些初始化的配置对标准的范围,单例和原型是不需要的)

    如果来完成这些初始化设置是基于你使用的特定Servlet容器的。

    如果使用Spring的Web MVC来访问这些范围的Bean,实际上,是由Spring的DispatcherServlet或DispatcherPortlet来处理请求的,那就没有特殊的设置了:DispatcherServlet和DispatcherPortlet已经可以访问所有相关的状态。

    如果你使用支持Servlet 2.5规范以上的web容器,在Spring的DispatcherServlet(比如,当使用JSF或Struts时)之外来处理请求,那么就需要添加org.springframework.web.context.request.RequestContextListener。对于Servlet 3.0 +,这可以通过WebApplicationInitializer 接口编程完成。年长的容器中,添加以下声明您的web应用程序的web.xml文件:

<web-app>    ...    <listener>        <listener-class>            org.springframework.web.context.request.RequestContextListener        </listener-class>    </listener>    ...</web-app>

    另外,如果在你的listener设置有问题,考虑提供RequestContextFilterg。该过滤器mapping取决于周围的web应用程序的配置,所以你必须适当的改变它。

<web-app>    ...    <filter>        <filter-name>requestContextFilter</filter-name>        <filter-class>org.springframework.web.filter.RequestContextFilter</filter-class>    </filter>    <filter-mapping>        <filter-name>requestContextFilter</filter-name>        <url-pattern>/*</url-pattern>    </filter-mapping>    ...</web-app>

DispatcherServletRequestContextListener 和RequestContextFilter 所有都可以做相同的事情,即绑定HTTP请求对象到服务于请求的Thread中,这使得Bean在请求和会话范围内对后续的调用链可用。

Request范围

考虑一下下面的Bean定义:

<bean id="loginAction" class="com.foo.LoginAction" scope="request"/>

    Spring容器通过使用LoginAction Bean定义来为每个HTTP请求创建一个LoginAction Bean的新的实例。也就是说,LoginActionBean是在HTTP请求级别范围。你可以改变创建的实例内部的状态,怎么改都可以,因为从相同的LoginAction Bean定义中创建的其他实例在状态上不会看到这些改变;他们对单独的请求都是独立的。当请求完成处理,Bean在请求范围内被抛弃了。

Session 范围

考虑一下下面的Bean定义:

<bean id="userPreferences" class="com.foo.UserPreferences" scope="session"/>

    Spring容器通过使用UserPreferences Bean的定义来为每个HTTP会话的生命周期创建UserPreferences Bean的新的实例。换句话说,UserPreferences Bean在HTTP session级别的范围内是有效地。正如request-scoped的Bean,你可以改变创建的实例内部状态,怎么改都可以,要知道其他HTTP session实例也是使用从相同UserPreferences Bean的定义中创建的实例,他们不会看到状态的改变,因为他们对于单独的HTTP session都是独立的。当HTTP session最终被丢弃时,那么和这个HTTP session相关的Bean也就被丢弃了。


    

Global session 范围

考虑一下下面的Bean定义: 

<bean id="userPreferences" class="com.foo.UserPreferences" scope="globalSession"/>

    global session范围和标准的HTTP Session范围类似,但仅能应用于基于portlet上下文的web应用程序。portlet规范定义了全局的session概念,该会话在所有portlet之间所共享来构建一个单独的portlet web应用程序。在global session范围内定义的Bean的范围(约束)就会在全局portlet session的生命周期内。

    如果你编写了一个标准的基于Servlet的web应用,并且定义了一个或者多个有global session范围的Bean,那么就会使用标准的HTTP session范围,也不会抛出什么错误。

各种范围的Bean作为依赖

    Spring IoC容器不仅仅管理对象(Bean)的实例,而且也装配协作者(依赖)。如果你想注入(比如)一个HTTP请求范围的Bean到另一个Bean中,那么就必须在有范围Bean中注入一个AOP代理。也就是说,你需要注入一个代理对象来公开相同的公共接口作为有范围的对象,这个对象也可以从相关的范围(比如,HTTP请求范围)中获取真实的目标对象并且委托方法调用到真正的对象上。

 注意

你不需要使用 <aop:scoped-proxy/> 来结合singletons 或者 prototypes范围的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:aop="http://www.springframework.org/schema/aop"    xsi:schemaLocation="http://www.springframework.org/schema/beans        http://www.springframework.org/schema/beans/spring-beans.xsd        http://www.springframework.org/schema/aop        http://www.springframework.org/schema/aop/spring-aop.xsd">    <!-- an HTTP Session-scoped bean exposed as a proxy -->    <bean id="userPreferences" class="com.foo.UserPreferences" scope="session">        <!-- instructs the container to proxy the surrounding bean -->        <aop:scoped-proxy/>    </bean>    <!-- a singleton-scoped bean injected with a proxy to the above bean -->    <bean id="userService" class="com.foo.SimpleUserService">        <!-- a reference to the proxied userPreferences bean -->        <property name="userPreferences" ref="userPreferences"/>    </bean></beans>

    创建一个这样的代理,就要插入一个子元素<aop:scoped-proxy/>到有范围的Bean的定义中。(参见the section called “Choosing the type of proxy to create”和Chapter 33, XML Schema-based configuration.)为什么 requestsessionglobalSession 和自定义级别的Bean需要 <aop:scoped-proxy/>元素?我们看看下面的单例Bean定义,并将它和你需要定义的上述范围来比较一下。(下面的userPreferences 的定义不是完整的

<bean id="userPreferences" class="com.foo.UserPreferences" scope="session"/>

<bean id="userManager" class="com.foo.UserManager">    <property name="userPreferences" ref="userPreferences"/></bean>

In the preceding example, the singleton bean userManager is injected with a reference to the HTTP Session-scoped bean userPreferences. The salient point here is that theuserManager bean is a singleton: it will be instantiated exactly once per container, and its dependencies (in this case only one, the userPreferences bean) are also injected only once. This means that the userManager bean will only operate on the exact same userPreferences object, that is, the one that it was originally injected with.

This is not the behavior you want when injecting a shorter-lived scoped bean into a longer-lived scoped bean, for example injecting an HTTP Session-scoped collaborating bean as a dependency into singleton bean. Rather, you need a single userManager object, and for the lifetime of an HTTP Session, you need a userPreferences object that is specific to said HTTP Session. Thus the container creates an object that exposes the exact same public interface as the UserPreferences class (ideally an object that is aUserPreferences instance) which can fetch the real UserPreferences object from the scoping mechanism (HTTP request, Session, etc.). The container injects this proxy object into the userManager bean, which is unaware that this UserPreferences reference is a proxy. In this example, when a UserManager instance invokes a method on the dependency-injected UserPreferences object, it actually is invoking a method on the proxy. The proxy then fetches the real UserPreferences object from (in this case) the HTTP Session, and delegates the method invocation onto the retrieved real UserPreferences object.

Thus you need the following, correct and complete, configuration when injecting request-session-, and globalSession-scoped beans into collaborating objects:

    在上面的例子中,单例Bean UserManger是用HTTP session范围的Bean UserPreferences的引用注入的。这里突出的一点就是userManager Bean是单例的:那么对于容器来说,它只会被实例化一次,并且他的依赖关系(本例中只有一个依赖,就是UserPreferences Bean)也只会被注入一次。这就意味着UserManager Bean只会对同一个UserPreferences对象继续操作,也就是说,是最初被注入的那个一个对象。

    这并不是你想要的行为,即注入生命周期范围短的Bean到生命周期长的Bean中,比如注入HTTP session范围的协作Bean作为依赖到单例Bean中。相反,你需要一个单独的userManager对象,生命周期和HTTP session一样,你需要一个UserPreferences对象来特定于HTTP session。因此容器创建对象,公开相同的公共接口,而UserPreferences类(理想情况下是UserPreferences实例的对象)从范围机制(HTTP请求,session等等)来获取真正的UserPreferences对象。容器注入这个代理对象到UserManager Bean中,这个Bean不能感知UserPreferences的引用就是代理。在这个示例中,当UserManager实例调用依赖注入的UserPreferences对象的方法时,那么其实是在代理上调用这个方法。然后代理从HTTP session中获取真实的UserPreferences对象,并且委托方法调用到获取的真实的UserPreferences对象中。

    因此,当注入request,session,globalSession范围的Bean到协作对象时,你需要下面的,正确的,完整的配置:

<bean id="userPreferences" class="com.foo.UserPreferences" scope="session">    <aop:scoped-proxy/></bean><bean id="userManager" class="com.foo.UserManager">    <property name="userPreferences" ref="userPreferences"/></bean>
选择创建代理类型

    默认情况下,当Spring容器为被<aop:scoped-proxy/>元素标记的Bean创建代理时,基于CGLIB的类代理就被创建了。

 注意

CGLIB代理仅仅拦截public方法的调用!不要用这样一个代理调用非公开方法;他们不会被授予范围的目标对象

    另外,你可以配置Spring容器为有范围的Bean创建标准的基于JDK接口的代理,通过指定<aop:scoped-proxy/> 元素中的proxy-target-class属性为false。使用基于JDK接口的代理意味着你不需要在应用程序的类路径下添加额外的类库来是代理生效。然而,他也意味着有范围Bean的类必须实现至少一个接口,并注入到有范围Bean的所有协作者必须通过它的一个接口来引用Bean。

<!-- DefaultUserPreferences implements the UserPreferences interface --><bean id="userPreferences" class="com.foo.DefaultUserPreferences" scope="session">    <aop:scoped-proxy proxy-target-class="false"/></bean><bean id="userManager" class="com.foo.UserManager">    <property name="userPreferences" ref="userPreferences"/></bean>

关于选择基于类或者接口的代理更多详情,参见 Section 8.6, “Proxying mechanisms”.

4.5.5 自定义范围

    Bean范围机制是可扩展的;你可以定义自己的范围,或者重新定义已有的范围,尽管后者被认为是不良实践而且你不能覆盖内建的singleton和prototype范围。

创建一个自定义范围

    整合自定义范围到Spring容器中,那么你需要实现org.springframework.beans.factory.config.Scope接口,这会在本节中介绍。对于如何实现你自己的范围的想法,可以参考Spring Framework本身提供的Scope实现和Scope JavaDoc,这里会介绍你需要实现的方法的更多细节。

  Scape接口有四个方法来从作用域范围中获取对象,从作用域范围中移除对象还允许他们被销毁。

  下面的方法从底层范围中返回对象。比如,会话范围的实现,返回会话范围的Bean(如果它不存在,在绑定到会话后为将来的引用,这个方法返回Bean的新的实例)。

Object get(String name, ObjectFactory objectFactory)

    下面这个方法从底层范围中移除对象。比如,会话范围的实现,从底层的会话中移除会话范围的Bean。对象应该被返回,但是如果特定名字的对象没有被发现的话可以返回null。

Object remove(String name)

    下面这个方法注册当被销毁或者指定范围的对象被销毁时,相关范围应用执行的回调函数。参考JavaDoc文档或者Spring范围实现来获取更多有关销毁回调的信息。

void registerDestructionCallback(String name, Runnable destructionCallback)

    下面的这个方法得到一个底层范围的会话标识符。这个标识符对每种范围都是不同的。对于会话范围的实现,这个标识符可以是会话标识符。

String getConversationId()

使用自定义范围

    在你编写并测试一个或多个自定义 Scope 实现之后,你需要让 Spring 容器感知你的新 的范围。下面的方法是用 Spring 容器注册新的 Scope 的核心方法。

void registerScope(String scopeName, Scope scope);

    这个方法在 ConfigurableBeanFactory 接口中声明,也在大多数的 ApplicationContext 实现类中可用,通过 BeanFactory 属性跟着 Spring。

    registerScope(..)方法的第一个参数是和范围相关的唯一名称;在 Spring 容器本身这样的名字就是 singleton 和 prototype。 registerScope(..)方法的第二个参数 是你想注册并使用的自定义 Scope 实现的真实实例。 

    假设你编写了自定义的 Scope 实现,之后便如下注册了它。

 注意

下面的示例使用了 SimpleThreadScope,这是包含在 Spring 当中的,但模式是没有 注册的。这些指令和你自己自定义的 Scope 的实现是相同的。

Scope threadScope = new SimpleThreadScope();beanFactory.registerScope("thread", threadScope);

You then create bean definitions that adhere to the scoping rules of your custom Scope:

<bean id="..." class="..." scope="thread">

使用自定义的 Scope 实现,并没有被限制于程序注册。你也可以进行声明式的 Scope注册,使用 CustomScopeConfigurer 类:

<?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:aop="http://www.springframework.org/schema/aop"    xsi:schemaLocation="http://www.springframework.org/schema/beans        http://www.springframework.org/schema/beans/spring-beans.xsd        http://www.springframework.org/schema/aop        http://www.springframework.org/schema/aop/spring-aop.xsd">    <bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">        <property name="scopes">            <map>                <entry key="thread">                    <bean class="org.springframework.context.support.SimpleThreadScope"/>                </entry>            </map>        </property>    </bean>    <bean id="bar" class="x.y.Bar" scope="thread">        <property name="name" value="Rick"/>        <aop:scoped-proxy/>    </bean>    <bean id="foo" class="x.y.Foo">        <property name="bar" ref="bar"/>    </bean></beans>
 注意

当在 FactoryBean 的实现中放置<aop:scoped- proxy/>时,那就是工厂 bean 本身被放 置到范围中,而不是从 getObject()方法返回的对象。

4.6 自定义Bean的性质

4.6.1 声明周期回调

    为了和容器管理 bean 的生命周期进行交互,你可以实现 Spring 的 InitializingBean 和 DisposableBean 接口。容器为前者调用afterPropertiesSet()方法,为后者调用 destroy()方法在 bean 的初始化和销毁阶段允许 bean 执行特定的动作。

 Tip

JSR-250 @PostConstruct 和 @PreDestroy 注解通常被认为是用于接收生命周期调用在一个现代化Spring应用程序的最佳实践。使用这些注解意味着你的Bean并不耦合到Spring特定的接口。有关详细信息,参见Section 4.9.7, “@PostConstruct and @PreDestroy”.

如果你不想使用JSR-250注解,但你还在寻找去除耦合考虑使用初始化方法和销毁,方法对象定义元素。

    从内部来说,Spring Framework使用了beanPostProcessor的实现来处理任何回调接口,他可以发现并回调适合的方法。如果你需要自定义特性或其它生命周期行为,Spring 不会提供开箱,你可以自行实现 BeanPostProcessor 接口。要获取更多信息,可以参考 Section 4.8, “Container Extension Points”.

    除了初始化和销毁回调,Spring 管理的对象也会实现 Lifecycle 接口,所以那些对象 可以参与到启动和关闭过程,来作为容器自己的生命周期。 

    生命周期回调接口在本节中来说明。

初始化回调

    org.springframework.beans.factory.InitializingBean 接口允许 bean 在 所有必须的属性被容器设置完成之后来执行初始化工作。InitializingBean 接口中仅仅指定了一个方法:

void afterPropertiesSet() throws Exception;

    推荐不要使用 InitializingBean 接口,因为它不必要地将代码耦合到 Spring 中。 另外,可以使用 @PostConstruct 或者指定一个 POJO 初始化方法。在基于 XML 配置元数据的情形中,你可以使用 init-method 属性来指定签名为无返回值,无参数的方法的名称。比如,下面的定义:

<bean id="exampleInitBean" class="examples.ExampleBean" init-method="init"/>
public class ExampleBean {    public void init() {        // do some initialization work    }}

这和下面的做法是完全相同的:

<bean id="exampleInitBean" class="examples.AnotherExampleBean"/>
public class AnotherExampleBean implements InitializingBean {    public void afterPropertiesSet() {        // do some initialization work    }}

但是没有耦合任何代码到 Spring 中。

销毁回调

    实现 org.springframework.beans.factory.DisposableBean 接口,当容器 包含的 bean 被销毁时,允许它获得回调。DisposableBean 接口中仅仅有一个方法:

void destroy() throws Exception;

    推荐不要使用 DisposableBean 接口,因为它不必要地将代码耦合到 Spring 中。另外, 可以使用 @PreDestroy或者指定一个由 bean 支持的通用的方法。使用基于 XML 配置元数据,你可以使用<bean/> 元素中的 destroy-method 属性。比如,下面的定义:

<bean id="exampleInitBean" class="examples.ExampleBean" destroy-method="cleanup"/>
public class ExampleBean {    public void cleanup() {        // do some destruction work (like releasing pooled connections)    }}

这和下面的做法是完全相同的:

<bean id="exampleInitBean" class="examples.AnotherExampleBean"/>
public class AnotherExampleBean implements DisposableBean {    public void destroy() {        // do some destruction work (like releasing pooled connections)    }}

但是没有耦合代码到 Spring 中。

默认的初始化和销毁方法

    当使用 Spring 指定的 InitializingBean 和 DisposableBean 回调接口来编写初始化和销毁方法回调时,典型地做法就是编写名称为 init(), initialize(), dispose()等方法.理想的情况,这样的生命周期回调方法的名称在一个项目中是标准化的,那么所有的开发人员可以使用相同的方法名称来保证一致性。

    你可以配置 Spring 容器在每个 bean 中来查看命名的初始化和销毁回调方法名称。这就 是说,作为应用程序开发人员,你可以编写应用类并且使用初始化回调方法 init(),而不 需要在每个 bean 的定义中来配置 init-method="init"属性。当 bean 被创建时(并按

照前面描述的标准的生命周期回调合约),Spring 的 IoC 容器调用这个方法。这个特性也对 初始化和销毁方法回调强制执行了一致的命名约定。

    假设你的初始化回调方法命名为 init(),销毁回调方法命名为 destroy()。那么你 就可以按照如下示例来装配类:

public class DefaultBlogService implements BlogService {    private BlogDao blogDao;    public void setBlogDao(BlogDao blogDao) {        this.blogDao = blogDao;    }    // this is (unsurprisingly) the initialization callback method    public void init() {        if (this.blogDao == null) {            throw new IllegalStateException("The [blogDao] property must be set.");        }    }}
<beans default-init-method="init">    <bean id="blogService" class="com.foo.DefaultBlogService">        <property name="blogDao" ref="blogDao" />    </bean></beans>

    顶层<beans/>元素的 default-init-method 属性的存在,就会让 Spring 的 IoC 容 器意识到在 bean 中的一个叫做 init 的方法就是初始化回调方法。当一个 bean 被创建和装 配时,如果 bean 类有这样一个方法,那么它就会在合适的时间被调用。

    相似地(也就是在 XML 中 ), 你 可 以 使 用 顶 层 元 素 <beans/> 的 default-destroy-method 属性来配置销毁回调方法。

    在已经存在的 bean 类中,有命名差异的回调方法,你可以指定(也就是在 XML 中) <bean/>本身的 init-method 和 destroy-method 属性来覆盖默认的方法。

    Spring 容器保证了在给 bean 提供所有的依赖后,配置的初始化回调方法会立即被调用。因此初始化回调也被称为原始的 bean 引用,也就意味着 AOP 的拦截器等尚未应用到 bean 中。目标 bean 首先被完全创建,之后 AOP 代理(比方说)和它的拦截器链就会被应用上。 如果目标 bean 和代理分开来定义了,你的代码仍然可以绕开代理和原始目标 bean 来交互。 因此,引用拦截器到初始化方法中会是不一致的,因为这么来做了会和它的代理/拦截耦合 目标 bean 的合生命周期,当你的代码直接和原始目标 bean 交互时,会留下奇怪的语义。

组合生命周期机制

    在 Spring 2.5 中,对控制 bean 的生命周期行为有三种选择: InitializingBean 和 DisposableBean回调接口;自定义 init()和 destroy()方法;@PostConstruct 和@PreDestroy 注解。你可以组合这些机制来控制给定的bean。

 注意

如果对一个 bean 配置了多个生命周期机制,并且每种机制都用不同的方法名称来配置,那么每个配置方法就会以下面列出的顺序来执行。然而,如果配置了相同的方法名称,比如 说,init()对于初始化方法,多于一个生命周期机制,那么这个方法就执行一次,这在前 面章节解释过了。

相同的 bean 配置了不同初始化方法的多个生命周期机制,那么就按下面顺序调用:

  • 使用@PostConstruct 注解的方法
  • 由 InitializingBean 回调接口定义的 afterPropertiesSet()方法
  • 自定义配置的 init()方法

销毁方法也是同样顺序调用:

  • 使用@PreDestroy 注解的方法
  • 由 DisposableBean 回调接口定义的 destroy ()方法
  • 自定义配置的 destroy ()方法

启动和关闭回调

    Lifecycle 接口定义了对于任意有它自己生命周期需求(比如,开始和结束一些后台 进程)对象的必要方法:

public interface Lifecycle {    void start();    void stop();    boolean isRunning();}

    任何 Spring 管理的对象可以实现这个接口。那么,当 ApplicationContext 本身启动和关 闭时,它会级联那些定义在上下文中,对所有生命周期实现的调用。它会委托给 LifecycleProcessor 来做:

public interface LifecycleProcessor extends Lifecycle {    void onRefresh();    void onClose();}

    要注意 LifecycleProcessor 本身是扩展了 Lifecycle 接口。而且它添加了两个 方法来对上下文的刷新和关闭做出反应。

    启动和关闭调用的顺序是很重要的。如果一个“依赖”关系存在于两个任意对象间,那 么需要依赖的一方会在它的依赖启动之后启动,并且在它的依赖关闭之前停止。然而,有时 直接的依赖关系是未知的。你可能仅仅知道确定类型的对象应该优先于另外一种类型对象的 

启动。在那些情形下, SmartLifecycle 接口定义了另外一种选择,命名为 getPhase() 方法,在它的父接口 Phased 中来定义。

public interface Phased {    int getPhase();}
public interface SmartLifecycle extends Lifecycle, Phased {    boolean isAutoStartup();    void stop(Runnable callback);}

    当 启 动时 ,最 低层 的对 象首 先启 动, 当停 止时 ,是 相反的。 因此 ,实 现 了 SmartLifecycle 接口,并且 getPhase()方法返回 Integer.MIN_VALUE 的对象就会在最 先开始和最后停止的中间。在范围的另外一端,Integer.MIN_VALUE 的层次值表示对象应该最后启动并且最先停止(可能是因为它取决于其它进程的执行)。当考虑层次值时,知道任意“普通”对象并且没有实现 Lifecycle 接口的默认的层次是 0,这也是很重要的。 因此,任意负的层次值就表示对象应该在那些标准组件之前启动(在它们之后停止) ,对于任意正的层次值,反之亦然。

    正如你看到由 SmartLifecycle 定义的停止方法接收回调。任何实现类必须在实现类关闭进程完成之后,调用回调方法 run()。这使得必要时可以进行异步关闭,因为默认的 LifecycleProcessor 接口的实现,DefaultLifecycleProcessor , ,会等待每个阶 段一组对象来调用回调的超时值。每个阶段的默认超时是 30 秒。你可以在上下文中定义名 为“lifecycleProcessor”的 bean 来覆盖默认的生命周期进程实例。如果你仅仅想去修改超时 时间,那么定义下面的 bean 就足够了:

<bean id="lifecycleProcessor" class="org.springframework.context.support.DefaultLifecycleProcessor">    <!-- timeout value in milliseconds -->    <property name="timeoutPerShutdownPhase" value="10000"/></bean>

    正如所提到的, LifecycleProcessor 接口定义了刷新和关闭上下文的回调方法。后 者将驱动关闭进程,就好像明确地调用了 stop()方法,但是当上下文关闭时它才会发生。另 一方面,‘刷新’回调使得 SmartLifecycle bean 的另外一个特性可用。当上下文刷新时 

(在所有对象都被实例化和初始化后),才会调用回调方法,在那时,默认的生命周期进程 会检查每个 SmartLifecycle 对象的 isAutoStartup()方法返回的布尔值。如果是 “true”,那时对象将会被启动,而不是等待明确的上下文调用或它自己的 start()方法(不像 

上下文刷新,对标准的上下文实现来说,上下文启动不会自动发生)。“阶段”值和任意“依 赖”关系会按照上面所描述的相同方式来决定启动的顺序。

在非web应用中,优雅的关闭Spring IoC容器

 注意

本节仅对非 Web 应用来说。在相关 Web 应用程序关闭时,Spring 的基于 Web 的ApplicationContext 实现已经有代码来优雅地关闭 Spring 的 Ioc 容器了。

    如果你在非 Web 应用环境中使用 Spring 的 IoC 容器;比如,在富客户端桌面环境中; 你在 JVM 中注册了一个关闭的钩子。 这么来做就保证了优雅地关闭并且在单例 bean 中调用 相关销毁方法,那么所有资源都会被释放。当然,你必须正确配置并实现这些销毁回调方法。 

    要注册一个关闭钩子,可以调用 AbstractApplicationContext 类中声明的 registerShutdownHook()方法:

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...    }}

4.6.2 ApplicationContextAware和BeanNameAware

    当 ApplicationContext 创建实现了 org.springframework.context.ApplicationContextAware 接口的类时,这个类 就被提供了一个 ApplicationContext 的引用。

public interface ApplicationContextAware {    void setApplicationContext(ApplicationContext applicationContext) throws BeansException;}

    因此 bean 就可以编程来操作创建它们的 ApplicationContext ,通过 ApplicationContext 接口,或者转换到这个接口的已知子类型,比如 

ConfigurableApplicationContext,会公开更多的功能。一种使用方式就是编程式获 取其它的 bean。有时候这种能力是很有用的。然而,通常你应该避免这么来做,因为它会 耦合代码到 Spring 中,还没有协作者作为属性提供给 bean 的控制反转的风格。应用上下文 

的其它方法提供了访问文件资源,公共应用事件和访问消息资源的方法。这些补充特性为在 “Additional Capabilities of the ApplicationContext”来说明。

    在 Spring 2.5 中,自动装配是获取 ApplicationContext 引用的另外一种方式。“传统的” constructor 和 byType 自动装配模式(在 Section 4.4.5, “Autowiring collaborators”中说明的)可以分别为构造方法参数或 setter 方法参数提供 ApplicationContext 类型的依赖。为了更大的灵活性,包括自动装配字段和多参数方法的能力,还有使用新的基于注解的自动装 配特性。如果字段,构造方法或者普通方法进行@Autowired 注解,而且你这么做了,ApplicationFactory 就会自动装配到期望 BeanFactory 类型的字段,构造方法参数或方法参数。要获取更多信息,可以参考  Section 4.9.2, “@Autowired”.

    当 应 用 上 下 文 创 建 实 现 了 org.springframework.beans.factory.BeanNameAware 接口的类时,这个类会被提供一个在相关对象中定义的命名引用。

public interface BeanNameAware {    void setBeanName(string name) throws BeansException;}

    回 调 函数 在 普通 bean 的 属性 被 填充 之后 并 且在 如 InitializingBean 的 afterPropertiesSet 或自定义初始化方法的初始化回调之前被调用。

4.6.3 其他Aware 接口

    除了上面讨论的 ApplicationContextAware 和 BeanNameAware,Spring 还提供 了很多 Aware 接口,它们允许 bean 来告诉容器它们需要确定的基础的依赖。最重要的Aware 接口在下面总结出来了-作为一个通用的规则,名称就很好地说明了依赖的类型:

表 4.4. Aware 接口

NameInjected DependencyExplained in…

ApplicationContextAware

声明ApplicationContext

Section 4.6.2, “ApplicationContextAware and BeanNameAware”

ApplicationEventPublisherAware

包含在 ApplicationContext内的事件发布

Section 4.16, “Additional Capabilities of the ApplicationContext”

BeanClassLoaderAware

用于加载Bean类的类加载器

Section 4.3.2, “Instantiating beans”

BeanFactoryAware

声明BeanFactory

Section 4.6.2, “ApplicationContextAware and BeanNameAware”

BeanNameAware

声明Bean的名称

Section 4.6.2, “ApplicationContextAware and BeanNameAware”

BootstrapContextAware

容器运行的资源适配器BootstrapContext 。典型地是仅仅在JCA感知的ApplicationContext 中可用

Chapter 25, JCA CCI

LoadTimeWeaverAware

在加载时为处理类定义的织入器

Section 8.8.4, “Load-time weaving with AspectJ in the Spring Framework”

MessageSourceAware

解析消息的配置策略 (支持参数化和国际化)

Section 4.16, “Additional Capabilities of the ApplicationContext”

NotificationPublisherAware

Spring JMX 通知发布

Section 24.7, “Notifications”

PortletConfigAware

容 器 运 行 的 当 前 的 PortletConfig。仅在感知 

Web 的 Spring 的 ApplicationContext 中有

Chapter 19, Portlet MVC Framework

PortletContextAware

容 器 运 行 的 当 前 的 PortletContext。 仅在感知 

Web 的 Spring 的 ApplicationContext 中有

Chapter 19, Portlet MVC Framework

ResourceLoaderAware

为低级别的资源访问配置的加 载器

Chapter 5, Resources

ServletConfigAware

容 器 运 行 的 当 前 的 ServletConfig。仅在感知Web 的 Spring 的 ApplicationContext 中有

Chapter 16, Web MVC framework

ServletContextAware

容 器 运 行 的 当 前 的 ServletContext。 仅在感知 

Web 的 Spring 的 ApplicationContext 中有

Chapter 16, Web MVC framework


    请再次注意绑定你的代码到 Spring API 的这些接口的使用,并不再遵循控制反转风格。 因此,它们被推荐使用到基础的 bean 中,那些 bean 需要编程式地访问容器。

4.7 Bean定义的继承

    bean 的定义可以包含很多配置信息包括构造方法参数,属性值和容器指定的信息,比 如初始化方法,静态工厂方法名称等。子 bean 定义继承从父 bean 中获得的配置元数据。子 bean 可以覆盖一些值或者添加其它所需要的。使用父子 bean 定义可以节省很多输入。 

实际上,这是一种模板形式。

    如 果 你编 程 式地 使 用 ApplicationContext 接口,子 bean 的 定 义可 以 由 ChildBeanDefinition 类代表。 很多用户不使用这个级别的方法,而是在类似于ClassPathXmlApplicationContext.中声明式地配置 bean 的信息。当使用基于 XML的配置元数据时,你可以使用 parent 属性来标识一个子 bean,使用这个属性的值来标识 

父 bean。

<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 使用父 bean 的类,但也可以覆盖它。在这种情形中,子 bean 的类必须和父 bean 兼容,也就是说,它必须接受父 bean 的属性值。

    子 bean 的定义继承构造方法参数值,属性值,还有父 bean 的方法覆盖,添加新值的 选择。任何你指定的初始化方法,销毁方法,和/或 static 工厂方法设置会覆盖对应父 bean 中的设置。 

    剩下的设置通常是从子 bean 来定义:依赖,自动装配模式,依赖检测,单例,范围, 延迟初始化。 

    前面的示例明确地使用了 abstract 属性来标识了父 bean 的定义。如果父 bean 没有 指定类,那么明确地标识父 bean 就必须要有abstract,如下所示:

<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 不会被实例化,因为它自己是不完整的,而且也明确地被 abstract 标记。当 bean 的定义是 abstract 这样的,那么它也仅仅被用做纯 bean 定义的模板,作为子 bean 定义的父 bean。尝试使用这种自身是 abstract 的父 bean,作为另外一个 bean 参考的属 性来指代它,或者使用父 bean 的 id 来明确使用 getBean()方法调用,会返回错误。相似地,容器内部的 preInstantiateSingletons()方法忽略了抽象 bean 的定义。

 注意

默认情况下,预实例化所有单例 bean。因此,如果你有仅仅想用作模板的(父) bean, 而且这个 bean 指定了一个类,那么必须将 abstract 属性设置为 true,这点是很重要的。否则,应用上下文就会(尝试)预实例化 abstract 的 bean。

4.8 容器扩展点

    通常情况下,应用程序开发人员不需要编写 ApplicationContext 实现类的子类。相反,Spring 的 IoC 容器可以通过插件的方式来扩展,就是实现特定的整合接口。下面的几 个章节会来说明这些整合接口。

4.8.1 使用BeanPostProcessor来自定义Bean

    BeanPostProcessor 接口定义了你可以提供实现你自己的(或覆盖容器默认的)实 例逻辑,依赖解析逻辑等的回调方法。如果你想在 Spring 容器完成实例化,配置和初始化 bean 之后实现一些自定义逻辑,那么你可以使用一个或多个 BeanPostProcessor 实现类 的插件。

    你可以配置多个 BeanPostProcessor 实例,而且你还可以通过设置 order 属性来 控制这些 BeanPostProcessor 执行的顺序。 仅当 BeanPostProcessor 实现了 Ordered 接口你才可以设置设个属性; 如果你想编写你自己的 BeanPostProcessor,你 也应该考虑实现 Ordered 接口。要 了解更多细节,可 以参考 JavaDoc 文档中的 BeanPostProcessor 和 Ordered 接口。 

 注意

    BeanPostProcessor 操作 bean(对象)实例;那也就是说,Spring 的 IoC 容器 实例化 bean 的实例,之后 BeanPostProcessor 来做它们要做的事情。 

    BeanPostProcessor 的范围是对于每一个容器来说的。如果你使用了容器继承,那么这才有所关联。如果你在一个容器中定义了 BeanPostProcessor,那么它仅仅 会在那个容器中后处理 bean。换句话说,一个容器中定义的 bean 不会被另外一个容器

中定义的 BeanPostProcessor 来进行后处理,即便是两个容器都是相同继承链上的 一部分。

    要修改真正的 bean 定义(也就是说,定义 bean 的蓝图),你可以使用Section 4.8.2, “Customizing configuration metadata with a BeanFactoryPostProcessor”描 述的 BeanFactoryPostProcessor 来进行。


    org.springframework.beans.factory.config.BeanPostProcessor 接口 由两个回调方法构成。如果在容器中有一个类注册为后处理器,对于容器创建的每个 bean 的实例,后处理器从容器中获得回调方法,在容器初始化方法之前(比如 InitializingBean 的 afterPropertiesSet()方法和任意声明为初始化的方法)被调用,还有在 bean 初始化回调之后 被调用。后处理器可以对 bean 实例采取任何动作,包括完整忽略回调。通常来说 bean 的 后处理器会对回调接口进行检查,或者会使用代理包装 bean。一些 Spring 的 AOP 基类也会 作为 bean 的后处理器实现来提供代理包装逻辑。

    ApplicationContext 会自动检测任意实现了 BeanPostProcessor 接口的 bean 定义的配置元数据。ApplicationContext 注册这些 bean 作为后处理器,那么它们可以 在 bean 创建之后被调用。Bean 的后处理器可以在容器中部署,就像其它 bean 那样。

 注意

BeanPostProcessor 和 AOP 自动代理

     实现了 BeanPostProcessor 接口的类是特殊的,会被容器不同对待。所有它们参照的 BeanPostProcessor 和 bean 会在启动时被实例化,作为 ApplicationContext 启 动 阶 段 特殊 的 一 部 分 。 接 下 来 , 所 有 的 BeanPostProcessor 以排序的方式注册并应用于容器中的其它 bean。因为 AOP 自动 代理作为 BeanPostProcessor 本身的实现,它们为自动代理资格的直接引用的既不 是 BeanPostProcessor 也不是 bean,因此没有织入它们的方面。 

      对于这样的 bean,你应该看到一个信息级的日志消息:“Bean foo 没有由所有 BeanPostProcessor 接口处理的资格(比如:没有自动代理的资格) ”。 

 注意

BeanPostProcessors and AOP auto-proxying

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.

For any such bean, you should see an informational log message: "Bean foo is not eligible for getting processed by all BeanPostProcessor interfaces (for example: not eligible for auto-proxying)".

Note that if you have beans wired into your BeanPostProcessor using autowiring or @Resource (which may fall back to autowiring), Spring might access unexpected beans when searching for type-matching dependency candidates, and therefore make them ineligible for auto-proxying or other kinds of bean post-processing. For example, if you have a dependency annotated with @Resource where the field/setter name does not directly correspond to the declared name of a bean and no name attribute is used, then Spring will access other beans for matching them by type.

 下面的示例展示了如何在 ApplicationContext 中编写,注册和使用 BeanPostProcessor。

示例: BeanPostProcessor风格的Hello World

    第一个示例说明了基本的用法。示例展示了一个自定义的 BeanPostProcessor 实现 类来调用每个由容器创建 bean 的 toString()方法并打印出字符串到系统的控制台中。 

    自定义 BeanPostProcessor 实现类的定义:

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 脚本支持 的 bean。Spring动态语言支持在Chapter 28, 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@272961org.springframework.scripting.groovy.GroovyMessenger@272961

示例: The RequiredAnnotationBeanPostProcessor

    配合自定义的 BeanPostProcessor 实现使用回调接口或者注解,是扩展 Spring IoC容器的一种通用方式。Spring 的 RequiredAnnotationBeanPostProcessor 就是一个例子 – 一种 BeanPostProcessor 实现,随着 Spring 一起发布,来保证 bean 中被(任意)注解所标记的 JavaBean 属性在真正(配置)注入时有值。

4.8.2 使用 BeanFactoryPostProcessor 自定义配置元数据

    下 一 个 扩 展 点 我 们 要 来 看 看org.springframework.beans.factory.config.BeanFactoryPostProcessor 。这个接口的语义和那些 BeanPostProcessor 是相似的, 但有一个主要的不同点:BeanFactoryPostProcessor 操作 bean 的配置元数据;也就是说 Spring 的 IoC 容器允许BeanFactoryPostProcessor 来读取配置元数据并在容器实例化BeanFactoryPostProcessor 以外的任何 bean 之前可以修改它。

    你可以配置多个 BeanFactoryPostProcessor,并且你也可以通过 order 属性来控 制 这 些 BeanFactoryPostProcessor 执 行 的 顺 序 。 然 而 , 仅 当BeanFactoryPostProcessor 实现 Ordered 接口时你才能设置这个属性。如果编写你自己的 BeanFactoryPostProcessor,你也应该考虑实现 Ordered 接口。参考 JavaDoc文档来获取 BeanFactoryPostProcessor 和 Ordered 接口的更多细节。

 Note

         如果你想改变真实的 bean 实例 (也就是说,从配置元数据中创建的对象),那么你 需要使用 BeanPostProcessor (在上面 4.8.1 节,“使用 BeanPostProcessor 来自定义 bean ”中描述)来代替。在 BeanFactoryPostProcessor (比如使用 BeanFactory.getBean())中来使用这些 bean 的实例虽然在技术上是可行的,但 这么来做会引起 bean 过早实例化,违反标准的容器生命周期。这也会引发一些副作用, 比如绕过 bean 的后处理。

        而且,BeanFactoryPostProcessor 的范围也是对每一个容器来说的。如果你使用了容器的继承的话,这就是唯一相关的点了。如果你在一个容器中定义了 BeanFactoryPostProcessor,那么它只会用于在那个容器中的 bean。一个容器中 Bean 的定义不会被另外一个容器中的 BeanFactoryPostProcessor 后处理,即便 两个容器都是相同继承关系的一部分。

    当在 ApplicationContext 中声明时,bean 工厂后处理器会自动被执行,这就可以 对定义在容器中的配置元数据进行修改。Spring 包含了一些预定义的 bean 工厂后处理器, 比如 PropertyOverrideConfigurer 和 PropertyPlaceholderConfigurer.。自定 义的 BeanFactoryPostProcessor 也可以来用,比如,注册自定义的属性编辑器。

    ApplicationContext 会自动检测任意部署其中,且实现了BeanFactoryPostProcessor 接口的 bean。在适当的时间,它用这些 bean 作为 bean 工厂后处理器。你可以部署这些后处理器 bean 作为你想用的任意其它的 bean。 

 注意

和 BeanPostProcessor 一样,通常你不会想配置 BeanFactoryPostProcessor 来进行延迟初始化。如果没有其它 bean 引用 Bean(Factory)PostProcessor,那么后 处理器就不会被初始化了。因此,标记它为延迟初始化就会被忽略,即便你在<beans/>元

素声明中设置 default-lazy-init 属性为 true,那么 Bean(Factory)PostProcessor 也会正常被初始化。

示例: 类名替换PropertyPlaceholderConfigurer

    你可以使用来对使用了标准 Java Properties 格式的分离文件中定义的 bean 来声明属 性值。这么来做可以使得部署应用程序来自定义指定的环境属性,比如数据库的连接 URL 和密码,不会有修改容器的主 XML 定义文件或其它文件的复杂性和风险。 

    考虑一下下面这个基于 XML 的配置元数据代码片段,这里的 dataSource 就使用了占 位符来定义。这个示例展示了从 Properties 文件 中配置属性的方法。在运行时, PropertyPlaceholderConfigurer 就会用于元数据并为数据源替换一些属性。指定替 换的值作为${属性-名}形式中的占位符,这里应用了 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.jdbcDriverjdbc.url=jdbc:hsqldb:hsql://production:9002jdbc.username=sajdbc.password=root

    因此,字符串${jdbc.username}在运行时会被值’ sa’ 替换,对于其它占位符来说也是相同 的 , 匹 配 到 了 属 性 文 件 中 的 键 就 会 用 其 值 替 换 占 位 符 。 PropertyPlaceholderConfigurer 在很多 bean 定义的属性中检查占位符。此外,对 占位符可以自定义前缀和后缀。

    使用 Spring 2.5 引入的 context 命名空间,也可以使用专用的配置元素来配置属性占 位符。在 location 属性中,可以提供一个或多个以逗号分隔的列表。

<context:property-placeholder location="classpath:com/foo/jdbc.properties"/>

    PropertyPlaceholderConfigurer 不仅仅查看在 Properties 文件中指定的属 性。默认情况下,如果它不能在指定的属性文件中发现属性,它也会检查 Java System 属性。 你可以通过设置 systemPropertiesMode 属性,使用下面整数的三者之一来自定义这种 行为:

  • never (0): 从不检查系统属性
  • fallback (1):如果没有在指定的属性文件中解析到属性,那么就检查系统属性。这是默 认的情况。
  • override (2):在检查指定的属性文件之前,首先去检查系统属性。这就允许系统属性覆 盖其它任意的属性资源。

查看 PropertyPlaceholderConfigurer 的 JavaDoc 文档来获取更多信息。

 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}"/>

如果类在运行时不能解析成一个有效的类,那么在即将创建时,bean 的解析就失 败了,这是 ApplicationContext 在 对 非 延 迟 初 始 化 bean 的 preInstantiateSingletons()阶段发生的。

示例: the PropertyOverrideConfigurer

    PropertyOverrideConfigurer , 另 外 一 种 bean 工 厂 后 处 理 器 , 类 似 于 PropertyPlaceholderConfigurer,但不像后者,对于所有 bean 的属性,原始定义可 以有默认值或没有值。如果一个 Properties 覆盖文件没有特定 bean 的属性配置项,那么 就会使用默认的上下文定义。

    注意,bean 定义是不知道被覆盖的,所以从 XML 定义文件中不能立即明显反应覆盖配 置。在多个 PropertyOverrideConfigurer 实例的情况下,为相同 bean 的属性定义不同的值,那么最后一个有效,这就是覆盖机制。

    属性文件配置行像这种格式:

beanName.property=value

例如:

dataSource.driverClassName=com.mysql.jdbc.DriverdataSource.url=jdbc:mysql:mydb

    这个示例文件可以用于包含了 dataSource bean 的容器,它有 driver 和 url 属性。复合属性名也是支持的,除了最终的属性被覆盖,只要路径中的每个组件都是非空的(假设由构造方法初始化)。在这个例子中...

foo.fred.bob.sammy=123
  1. ... foo bean 的 fred 属性的 bob 属性的 sammy 属性的值设置为标量 123。
 注意

指定的覆盖值通常是文字值;它们不会被翻译成 bean 的引用。当 XML 中的 bean 定义的原始值指定了 bean 引用时,这个约定也适用。

    使用 Spring 2.5 引入的 context 命名空间,可以使用专用的配置元素来配置属性覆盖:

<context:property-override location="classpath:override.properties"/>

4.8.3 使用 FactoryBean 来自定义实例化逻辑

    实现了 org.springframework.beans.factory.FactoryBean 接口的对象它们 就是自己的工厂。

    FactoryBean 接口就是 Spring IoC 容器实例化逻辑的可插拔点。如果你的初始化代码 很复杂,那么相对于(潜在地)大量详细的 XML 而言,最好是使用 Java 语言来表达。你可 以创建自己的 FactoryBean ,在类中编写复杂的初始化代码,之后将你自定义的 FactoryBean 插入到容器中。

    FactoryBean 接口提供下面三个方法:

  • Object getObject(): 返回工厂创建对象的实例。这个实例可能被共享,那就是看这个 工厂返回的是单例还是原型实例了。
  • boolean isSingleton(): 如果 FactoryBean 返回单例的实例,那么该方法返回 true, 否则就返回 false。
  • Class getObjectType(): 返回由 getObject()方法返回的对象类型,或者事先不知 道类型时返回 null。

    FactoryBean 的概念和接口被用于 Spring Framework 中的很多地方;随 Spring 发行, 有超过 50 个 FactoryBean 接口的实现类。 

    当你需要向容器请求一个真实的 FactoryBean 实例,而不是它生产的 bean,当调用 ApplicationContext 的 getBean()方法时,在 bean 的 id 之前要有连字符(&)。所以 对于一个给定 id 为 myBean 的 FactoryBean,调用容器的 getBean("myBean")方法返 回的 FactoryBean 产品;而调用 getBean("&myBean")方法则返回 FactoryBean 实例本身。

0 0
原创粉丝点击