Spring 5 中文解析之核心篇-IoC容器(中)

技术交流群:

在这里插入图片描述

1.5 bean的作用域

当你创建一个bean的定义时候,你可以创建一个模版(recipe)通过bean定义的类定义去创建一个真实的实例。bean定义是模版(recipe)的概念很重要,因为这意味着,与使用类一样,你可以从一个模版(recipe)创建多个对象实例。

你不仅可以控制要插入到从特定bean定义创建的对象中的各种依赖项和配置值,还可以控制从特定bean定义创建的对象的作用域。这种方法是非常有用的和灵活的,因为你可以选择通过配置创建的对象的作用域,而不必在Java类级别上考虑对象的作用域。bean能够定义部署到一个或多个作用域。Spring框架支撑6种作用域,4种仅仅使用web环境。你可以创建定制的作用域。

下面的表格描述了支撑的作用域:

ScopeDescription
singleton(默认)将每个Spring IoC容器的单个bean定义范围限定为单个对象实例。
prototype将单个bean定义的作用域限定为任意数量的对象实例
request将单个bean定义的范围限定为单个HTTP请求的生命周期。也就是,每个HTTP请拥有一个被创建的bean实例。仅在Spring ApplicationContext Web容器有效
session将单个bean定义的范围限制在HTTP Session生命周期。仅在Spring ApplicationContext Web容器有效
application将单个bean定义的范围限制在ServletContext生命周期。仅在Spring ApplicationContext Web容器有效
websocket将单个bean定义限制在WebSocket生命周期。仅在Spring ApplicationContext Web容器有效

Spring3.0后,线程安全作用域是有效的但默认没有注册。更多的信息,查看文档 SimpleThreadScope。更多关于怎样去注册和自定义作用域,查看自定义作用域

1.5.1 单例bean作用域

单例bean仅仅只有一个共享实例被容器管理,并且所有对具有与该bean定义相匹配的IDbean的请求都会导致该特定bean实例被Spring容器返回。换一种方式,当你定义一个bean的定义并且它的作用域是单例的时候,Spring IoC容器创建通过bean定义的对象定义的实例。这个单例存储在缓存中,并且对命名bean的所有请求和引用返回的是缓存对象。下面图片展示了单例bean作用域是怎样工作的:

在这里插入图片描述

Spring的单例bean概念与在GoF设计模式书中的单例模式不同。GoF单例硬编码对应的作用域例如:只有一个特定类的对象实例对每一个ClassLoader只创建一个对象实例。最好将Spring单例的范围描述为每个容器和每个bean(译者:GoF设计模式中的单例bean是针对不同ClassLoader来说的,而Spring的单例是针对不同容器级别的)。这意味着,如果在单个Spring容器对指定类定义一个beanSpring容器通过bean定义的类创建一个实例。在Spring中单例作用域是默认的。在XML中去定义一个bean为单例,你可以定义一个bean类似下面例子:

<bean id="accountService" class="com.something.DefaultAccountService"/>
<!-- 通过scope指定bean作用域 单例:singleton ,原型:prototype-->
<bean id="accountService" class="com.something.DefaultAccountService" scope="singleton"/>
1.5.2 原型作用域

非单例原型bean的作用域部署结果是在每一次请求指定bean的时候都会创建一个bean实例。也就是,bean被注入到其他bean或在容器通过getBean()方法调用都会创建一个新bean。通常,为所有的无状态bean使用原型作用域并且有状态bean使用单例bean作用域。

下面的图说明Spring的单例作用域:

在这里插入图片描述

数据访问对象(DAO)通常不被配置作为一个原型,因为典型的DAO不会维持任何会话状态。我们可以更容易地重用单例图的核心。

下面例子在XML中定义一个原型bean

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

与其他作用域对比,Spring没有管理原型bean的完整生命周期。容器将实例化、配置或以其他方式组装原型对象,然后将其交给客户端,无需对该原型实例的进一步记录。因此,尽管初始化生命周期回掉函数在所有对象上被回掉而不管作用域如何,在原型情况下,配置销毁生命周期回掉是不被回掉。客户端代码必须清除原型作用域内的对象并释放原型Bean占用的昂贵资源。为了让Spring容器释放原型作用域bean所拥有的资源,请尝试使用自定义beanpost-processor后置处理器,该后处理器包含对需要清理的bean的引用(可以通过后置处理器释放引用资源)。

在某些方面,Spring容器在原型范围内的bean角色是Java new运算符的替代。所有超过该点的生命周期管理都必须由客户端处理。(更多关于在Spring容器中的bean生命周期,查看生命周期回掉)

1.5.3 单例bean与原型bean的依赖

当你使用依赖于原型bean的单例作用域bean时(单例引用原型bean),需要注意的是这些依赖项在初始化时候被解析。因此,如果你依赖注入一个原型bean到一个单例bean中,一个新原型bean被初始化并且依赖注入到一个单例bean。原型实例是唯一一个被提供给单例作用域bean的实例。(译者:单例引用原型bean时原型bean只会有一个)

然而,假设你希望单例作用域bean在运行时重复获取原型作用域bean的一个新实例。你不能依赖注入一个原型bean到一个单例bean,因为注入只发生一次,当Spring容器实例化单例bean、解析和注入它的依赖时。如果在运行时不止一次需要原型bean的新实例,查看方法注入

1.5.4 Request, Session, Application, and WebSocket Scopes

requestsessionapplication、和websocket作用域仅仅在你使用SpringApplicationContext实现(例如:XmlWebApplicationContext)时有效。如果你将这些作用域与常规的Spring IoC容器(例如ClassPathXmlApplicationContext)一起使用,则会抛出一个IllegalStateException异常,该错抛出未知的bean作用域。

  • 初始化Web配置

    为了支持这些bean的作用域在requestsessionapplication、和websocket级别(web作用域bean)。一些次要的初始化配置在你定义你的bean之前是需要的。(这个初始化安装对于标准的作用域是不需要的:singletonprototype)。

    如何完成这个初始化安装依赖于你的特定Servlet环境。

    如果在Spring Web MVC中访问作用域bean,实际上,在由Spring DispatcherServlet处理的请求中,不需要特殊的设置。DispatcherServlet已经暴露了所有相关状态。

    如果你使用Servlet 2.5 Web容器,请求处理在SpringDispatcherServlet外(例如:当使用JSFStructs),你需要去注册org.springframework.web.context.request.RequestContextListenerServletRequestListener。对于Servlet 3.0+,这可以通过使用WebApplicationInitializer接口以编程方式完成。或者,对于旧的容器,增加下面声明到你的web应用程序web.xml文件:

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

    或者,如果你的监听器设置有问题,考虑使用SpringRequestContextFilter。过滤器映射取决于周围的Web应用程序配置。因此你必须适当的改变它。下面的清单显示web应用程序filter的部分配置:

    <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请求对象绑定到为该请求提供服务的线程。这使得requestsession范围的bean在调用链的更下方可用。

  • Request作用域

    考虑下面的XML关于bean的定义:

    <!--请求作用域为request-->
    <bean id="loginAction" class="com.something.LoginAction" scope="request"/>
    

    Spring容器通过使用LoginAction bean定义为每个HTTP的请求创建一个LoginAction新实例bean。也就是说,loginAction bean的作用域在HTTP请求级别。你可以根据需要更改所创建实例的内部状态。因为从同一loginAction bean定义创建的其他实例看不到状态的这些变化。当这个请求处理完成,bean的作用域从request丢弃。(译者:scope="request" 每个请求是线程级别隔离的、互不干扰)

    当使用注解驱动组件或Java Config时,@RequestScope注解能够赋值一个组件到request作用域。下面的例子展示怎样使用:

    @RequestScope//指定作用域访问为request
    @Component
    public class LoginAction {
        // ...
    }
    
  • Session作用域

    考虑下面为bean定义的XML配置:

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

    Spring容器通过使用userPreferences的bean定义为单个HTTP Session的生命周期内的创建一个UserPreferences的新实例。换句话说,userPreferences bean有效地作用在HTTP会话级别。与请求范围的Bean一样,您可以根据需要任意更改所创建实例的内部状态,因为知道其他也在使用从同一``userPreferencesBean定义创建的实例的HTTP Session实例也看不到这些状态变化,因为它们特定于单个HTTP会话。当HTTP会话最终被丢弃时,作用于该特定HTTP会话的bean`也将被丢弃。

    当使用注解驱动组件或Java Config时,@SessionScope注解能够赋值一个组件到session作用域。下面的例子展示怎样使用:

    @SessionScope
    @Component
    public class UserPreferences {
        // ...
    }
    
  • Application作用域

    考虑下面的XML关于bean的定义:

    <bean id="appPreferences" class="com.something.AppPreferences" scope="application"/>
    

    Spring容器通过使用appPreferences的bean定义为整个Web应用创建一个AppPreferences的bean新实例。也就是说,appPreferences的作用域在ServletContext级别并且作为一个常规的ServletContext属性被储存。这个和Spring的单例bean类似,但有两个重要的区别:每个ServletContext是一个单例,而不是每个SpringApplicationContext(在给定的Web应用程序中可能有多个),并且它实际上是暴露的,因此作为ServletContext属性可见。

    当使用注解驱动组件或Java Config时,@ApplicationScope注解能够赋值一个组件到application作用域。下面的例子展示怎样使用:

    @ApplicationScope
    @Component
    public class AppPreferences {
        // ...
    }
    
  • 作用域bean作为依赖项

    Spring IoC容器不仅管理对象(bean)的实例化,而且还管理协作者(或依赖项)的连接。(例如)如果要将HTTP请求范围的Bean注入另一个作用域更长的Bean,则可以选择注入AOP代理来代替已定义范围的Bean。也就是说,你需要注入一个代理对象,该对象暴露与范围对象相同的公共接口,但也可以从相关范围(例如HTTP请求)中检索实际目标对象,并将方法调用委托给实际对象。

    在这些bean作用域是单例之间,你可以使用 <aop:scoped-proxy/>。然后通过一个可序列化的中间代理引用,从而能够在反序列化时重新获得目标单例bean

    当申明 <aop:scoped-proxy/>原型作用域bean,每个方法调用共享代理导致一个新目标实例被创建,然后将该调用转发到该目标实例。

    同样,作用域代理不是以生命周期安全的方式从较短的作用域访问bean的唯一方法。你也可以声明你的注入点(也就是,构造函数或者Setter参数或自动注入字段)例如:ObjectFactory<MyTargetBean>,允许getObject()调用在每次需要时按需检索当前实例-不保留实例或单独存储实例。

    作为一个扩展的变体,你可以声明ObjectProvider<MyTargetBean>,提供了一些附加的获取方式,包括getIfAvailablegetIfUnique

    JSR-330的这种变体称为Provider,并与Provider <MyTargetBean>声明和每次检索尝试的相应get()调用一起使用。有关整体JSR-330的更多详细信息,请参见此处

    在下面的例子中只需要一行配置,但是重要的是理解背后的原因:

    <?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
            https://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/aop
            https://www.springframework.org/schema/aop/spring-aop.xsd">
    
        <!-- 一个HTTP Session作用域的bean暴露为一个代理 -->
        <bean id="userPreferences" class="com.something.UserPreferences" scope="session">
            <!-- 指示容器代理周围的bean -->
            <aop:scoped-proxy/> //1.
        </bean>
    
        <!-- 一个单例作用域bean 被注入一个上面的代理bean -->
        <bean id="userService" class="com.something.SimpleUserService">
            <!-- a reference to the proxied userPreferences bean -->
            <property name="userPreferences" ref="userPreferences"/>
        </bean>
    </beans>
    
    1. 这行定义代理。

    创建一个代理,通过插入一个子<aop:scoped-proxy/>元素到一个作用域bean定义中(查看选择代理类型去创建基于Schema的XML配置)。为什么这些bean的定义在requestsession和自定义作用域需要<aop:scoped-proxy/>元素?考虑以下单例bean定义,并将其与需要为上述范围定义的内容进行对比(请注意,以下userPreferences bean定义不完整):

  <!--没有<aop:scoped-proxy/> 元素-->
<bean id="userPreferences" class="com.something.UserPreferences" scope="session"/>
  
<bean id="userManager" class="com.something.UserManager">
      <property name="userPreferences" ref="userPreferences"/>
  </bean>

在前面的例子中,单例bean (userManager) 被注入一个引用到HTTP Session作用域的bean (userPreferences)。这个显著点是userManager bean是一个单例bean:这个实例在每个容器值初始化一次,并且它的依赖(在这个例子仅仅一个,userPreferences bean)仅仅被注入一次。这意味着userManager bean运行仅仅在相同的userPreferences对象上(也就是,最初注入的那个)。

当注入一个短生命周期作用域的bean到一个长生命周期作用域bean的时候这个不是我们期望的方式(例如:注入一个HTTP Session作用域的协作者bean作为一个依赖注入到单例bean)。相反,你只需要一个userManager对象,并且在HTTP会话的生存期内,你需要一个特定于HTTP会话的userPreferences对象。因此,容器创建一个对象,该对象公开与UserPreferences类完全相同的公共接口(理想地,对象是UserPreferences实例),可以从作用域机制(HTTP 请求,Session,以此类推)获取真正的UserPreferences对象。容器注入这个代理对象到userManager bean,这并不知道此UserPreferences引用是代理。在这个例子中,当UserManager实例调用在依赖注入UserPreferences对象上的方法时,它实际上是在代理上调用方法。然后代理从(在本例中)HTTP会话中获取实际的UserPreferences对象,并将方法调用委托给检索到的实际UserPreferences对象。

因此,在将request-scopedsession-scoped的bean注入到协作对象中时,你需要以下(正确和完整)配置,如以下示例所示:

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

    默认情况下,当Spring容器为bean创建一个代理,这个bean通过<aop:scoped-proxy/>元素被标记,基于CGLIB的类代理被创建。

    CGLIB代理拦截器仅仅公共方法被调用!在代理上不要调用非公共方法。

    或者,你可以为作用域bean配置Spring容器创建标准的JDK基于接口的代理,通过指定<aop:scoped-proxy/>元素的proxy-target-class属性值为false。使用基于JDK接口的代理意味着你不需要应用程序类路径中的其他库即可影响此类代理(译者:意思是没有额外的依赖)。但是,这也意味着作用域Bean的类必须实现至少一个接口,并且作用域Bean注入到其中的所有协作者必须通过其接口之一引用该Bean。以下示例显示基于接口的代理:

    <!-- DefaultUserPreferences implements the UserPreferences interface -->
    <bean id="userPreferences" class="com.stuff.DefaultUserPreferences" scope="session">
      <!--基于接口代理-->
        <aop:scoped-proxy proxy-target-class="false"/>
    </bean>
    
    <bean id="userManager" class="com.stuff.UserManager">
        <property name="userPreferences" ref="userPreferences"/>
    </bean>
    

    更多详细信息冠以选择基于class或基于接口代理,参考代理机制

    参考代码:com.liyong.ioccontainer.starter.XmlBeanScopeIocContainer

1.5.5 自定义作用域

bean作用域机制是可扩展的。你可以定义你自己的作用域或者甚至重定义存在的作用域,尽管后者被认为是不好的做法,你不能覆盖内置的单例和原型范围。

  • 创建一个自定义作用域

    去集成你的自定义作用域到Spring容器中,你需要去实现org.springframework.beans.factory.config.Scope接口,在这章中描述。有关如何实现自己的作用域的想法,查看Scope实现提供关于Spring框架自身和Scope的文档,其中详细说明了你需要实现的方法。

    Scope接口有四个方法从作用域获取对象,从作用域移除它们,并且让它们销毁。

    例如:Sessonscope实现返回Season作用域bean(如果它不存在,这个方法返回一个新的bean实例,将其绑定到会话以供将来引用)。 下面的方法从底层作用域返回对象:

    Object get(String name, ObjectFactory<?> objectFactory)
    

    例如:Sessionscope实现移除Season作用域bean从底层的Session。对象应该被返回,但是如果这个对象指定的名称不存在你也可以返回null。下面的方法从底层作用域移除对象:

    Object remove(String name)
    

    以下方法注册在销毁作用域或销毁作用域中的指定对象时应执行的回调:

    void registerDestructionCallback(String name, Runnable destructionCallback)
    

    查看javadoc或者Spring作用域实现关于更多销毁回掉的信息。

    以下方法获取基础作用域的会话标识符:

    String getConversationId()
    

    这个表示每个作用域是不同的。对于Session作用域的实现,此标识符可以是Session标识符。

  • 使用自定义作用域

    在你写并且测试一个或更多自定义Scope实现,你需要去让Spring容器知道你的新作用域。以下方法是在Spring容器中注册新范围的主要方法:

    void registerScope(String scopeName, Scope scope);
    

    这个方法在ConfigurableBeanFactory接口上被定义,该接口可通过Spring附带的大多数具体ApplicationContext实现上的BeanFactory属性获得。

    registerScope(..) 方法第一个参数是唯一的名字关于作用域。Spring容器本身中的此类名称示例包括单例和原型。registerScope(..) 方法第二个参数是自定义Scope实现

    假设你写你的自定义Scope实现并且像下面的例子注册它。

    接下来例子使用SimpleThreadScope,它包括Spring但是默认是不被注册的。对于你自己的自定义范围实现,是相同的。

    Scope threadScope = new SimpleThreadScope();
    //注册自定义作用域
    beanFactory.registerScope("thread", threadScope);
    

    然后,你可以按照你的自定义范围的作用域规则创建bean定义,如下所示:

    <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
            https://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/aop
            https://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="thing2" class="x.y.Thing2" scope="thread">
            <property name="name" value="Rick"/>
            <aop:scoped-proxy/>
        </bean>
    
        <bean id="thing1" class="x.y.Thing1">
            <property name="thing2" ref="thing2"/>
        </bean>
    
    </beans>
    

    当在FactoryBean实现中配置<aop:scoped-proxy/>时,限定作用域的是工厂bean本身,而不是从getObject()返回对象。

    参考代码:com.liyong.ioccontainer.starter.XmlCustomScopeIocContainer

1.6 自定义bean的性质

Spring框架提供一些接口,你可以使用这些接口去自定义bean的性质。这个章节包括下面内容:

  1. 生命周期回调

  2. ApplicationContextAwareBeanNameAwar

  3. 其他的Aware接口

1.6.1 生命周期回调

为了与容器的bean的生命周期的管理交互,你可以实现Spring提供的InitializingBeanDisposableBean接口。容器为前者调用afterPropertiesSet()并为后者调用destroy(),以使Bean在初始化和销毁Bean时执行某些操作。

在现代化的Spring应用中,JSR-250@PostConstruct@PreDestroy注解是一般被考虑的最好实践去接收生命周期回调。使用这些注解意味着你的这些bean不需要耦合Spring规范接口。更多详情,查看@PostConstruct@PreDestroy

如果你不想去使用JSR-250注解,但是你仍然想移除耦合,考虑init-method和destroy-methodbean的元数据定义。

内部地,Spring框架使用BeanPostProcessor实现去处理任何的回调接口,它能找到和调用适合的方法。如果你需要定制特性或者其他的生命周期行为Spring默认没有提供,你需要自己实现一个BeanPostProcessor。关于更多的信息,查看容器扩展点

除了初始化和销毁回掉外,Spring管理的对象还可以实现Lifecycle接口以便这些对象可以在容器自身的生命周期的驱动下参与启动和关闭过程。

生命周期回调接口在这章节中描述。

  • 初始化回调

    org.springframework.beans.factory.InitializingBean接口允许在bean的所有属性被设置之前执行初始化工作。InitializingBean接口有一个简单方法:

    void afterPropertiesSet() throws Exception;
    

    我们推荐你不要去使用InitializingBean接口,因为它不必要的耦合Spring。或者,我们建议使用@PostConstruct注解或者指定一个POJO初始化方法。在基于XML配置元数据实例中,你可以使用init-method属性去指定方法的名称并且方法没有返回值和无参数签名。在JavaConfig中,你可以使用@BeaninitMethod属性。查看接收生命周期回调。考虑下面的例子:

    <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 {
    
        @Override
        public void afterPropertiesSet() {
            // do some initialization work
        }
    }
    

    然而,前面两个例子中的第一个没有耦合Spring代码。

    参考代码:com.liyong.ioccontainer.starter.XmlInitializationIocContainer

  • 销毁回调

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

    void destroy() throws Exception;
    

    我们推荐你不要使用DisposableBean回掉接口,因为它不必要的耦合Spring代码。或者,我们建议使用 @PreDestroy注解或者指定一个通用方法通过bean的定义支持。基于XML的配置元数据,你可以在上使用destroy-method属性。通过JavaConfig配置,你可以使用@BeandestroyMethod属性。查看接收生命周期回调。考虑下面的定义:

    <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 {
    
        @Override
        public void destroy() {
            // do some destruction work (like releasing pooled connections)
        }
    }
    

    然而,前面两个定义中的第一个没有耦合Spring代码。

    你可以赋值元素的destroy-method属性值,这指示Spring自动的在bean类上检测一个公共的close或者shutdown方法。(因此,任何实现java.lang.AutoCloseablejava.io.Closeable的类都将匹配)你可以在元素的default-destroy-method属性设置这个指定的值去应用这个行为到整个bean容器(查看默认初始化和销毁方法)。注意:这个一个默认行为在JavaConfig中。

    参考代码:com.liyong.ioccontainer.starter.XmlDestuctionIocContainer

  • 默认初始化和销毁方法

    当你写初始化和销毁方法回调的时候,不要使用Spring指定的InitializingBeanDisposableBean回调接口,典型的命名例如: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.");
            }
        }
    }
    

    然后,你可以在类似于以下内容的Bean中使用该类:

    <beans default-init-method="init">
    
        <bean id="blogService" class="com.something.DefaultBlogService">
            <property name="blogDao" ref="blogDao" />
        </bean>
    
    </beans>
    

    顶层元素属性default-init-method中描述导致Spring IoC容器在bean的类上去识别一个叫做init的方法作为初始化方法回调。当bean被创建和组装好,如果bean的类有一个方法,它在适当的时候被调用。

    你可以配置销毁方法回调类似(在XML)的通过使用在顶层元素上的default-destroy-method方法。

    现有的Bean类已经具有回调方法的名称,这些方法的命名方式与约定不符,你可以通过元素属性init-methoddestroy-method指定方法名称。

    Spring容器保证配置初始化方法在bean提供所有依赖后立即被调用。因此,在原始bean引用上调用初始化回调,这意味着AOP拦截器等还没有应用到bean上。首先完全创建目标bean,然后应用带有其拦截器链的AOP代理。如果目标Bean和代理分别定义,则你的代码甚至可以绕过代理与原始目标Bean进行交互。因此,将拦截器应用于init方法是不一致的,因为这样做会将目标bean的生命周期耦合到它的代理或拦截器,并在代码直接与原始目标bean交互时留下奇怪的语义。

  • 组合生命周期机制

    从Spring2.5后,你有三种可以选择的方式去控制生命周期行为:

    如果为bean配置多个生命周期机制并且每个机制被配置不同的方法名称,每个配置的方法都按照此注释后列出的顺序执行。但是,如果为多个生命周期机制中的多个生命周期机制配置了相同的方法名称(例如,为初始化方法使用init()),则该方法将执行一次,如上一节所述。

    为同一个bean配置的多种生命周期机制具有不同的初始化方法,如下所示:

    1. 方法注解@PostConstruct
    2. 通过InitializingBean回调接口定义afterPropertiesSet()
    3. 自定义配置init() 方法

    销毁方法的调用顺序相同:

    1. 方法注解@PreDestroy
    2. 通过DisposableBean回掉接口定义destroy()
    3. 自定义配置destroy()方法
  • 启动和关闭回调

    Lifecycle接口定义了必须要的方法为这些bean生命周期所必须(例如:启动和停止一些后台处理)。

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

    任何Spring管理的对象可能实现Lifecycle接口。然而,当ApplicationContext接收到启动和停止信号(例如:在运行时停止和重启场景),它将这些调用级联到在该上下文中定义的所有生命周期实现。通过代理到LifecycleProcessor处理,在下面清单显示:

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

    注意:LifecycleProcessorLifecycle接口的拓展。这个接口增加了两个额外的方法去接收上下文的刷新和关闭。

    注意:

    常规的org.springframework.context。生命周期接口是显式启动和停止通知的普通契约,并不意味着在上下文刷新时自动启动。为了更细粒度控制bean的自动启动(包括启动阶段),考虑使用org.springframework.context.SmartLifecycle替换。

    停止通知不会被保证在销毁之前到来。在常规的关闭上,所有Lifecyclebean第一次接受停止通知在销毁回掉被传播之前。但是,在上下文生命周期中进行热更新或更新尝试失败时,仅调用destroy方法。

    启动和关闭顺序调用是非常重要的。如果依赖关系存在任何对象之间,依赖侧开始在被依赖之后并且它的停止在它的依赖之前。然而,在运行时,这个直接依赖是未知的。你可能只知道某种类型的对象应该先于另一种类型的对象开始。在这种情况下,SmartLifecycle接口定义其他可选,即getPhase()方法在它的父接口被定义,Phased。下面的清单显示Phased接口的定义。

    public interface Phased {
    
        int getPhase();
    }
    

    下面清单显示SmartLifecycle接口定义

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

    启动时,阶段值最低的对象首先启动。停止时,顺序相反。因此,实现SmartLifecycle接口并且getPhase()方法返回Integer.MIN_VALUE的对象将在第一个启动和最后一个停止。另一种类型,Integer.MAX_VALUE阶段值指示这个对象最后一个被启动并且第一个被销毁 (可能因为它依赖其他处理运行)。当考虑这个阶段值的时候,重要的是要知道,任何未实现SmartLifecycle的“正常”生命周期对象的默认阶段为0。因此,任何负的阶段值表示对象应该启动在这些标准的组件之前(停止在它们之后)。对于任何正阶段值,情况正好相反。

    停止方法通过SmartLifecycle接受一个回调被定义。任何实现必须在实现的关闭处理完成后调用回掉的run()方法。这将在必要时启用异步关闭,因为LifecycleProcessor接口的默认实现DefaultLifecycleProcessor会等待其超时值,以等待每个阶段内的对象组调用该回调。每个阶段默认超时时间30秒。你可以通过定义一个bean 命名为lifecycleProcessor在上下文中覆盖这个默认生命周期处理实例。如果你想仅仅修改超时时间,定义以下内容就足够了:

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

    像前面提到的,LifecycleProcessor接口定义回调方法为上下文更好的刷新和关闭。后者驱动关闭过程,就好像已经显式调用了stop()一样,但是它在上下文关闭时发生。另一方面,“refresh”回调启用了SmartLifecycle bean的另一个特性。当这个上下文被刷新(所有的bean被初始化和实例化),即回调被调用。在那个阶段,默认的生命周期处理器通过每个SmartLifecycle对象的isAutoStartup()方法检测返回boolean值。如果true,对象在那个阶段被启动而不是等待上下文或者自身的start()方法显示调用(与上下文刷新不同,对于标准上下文实现,上下文启动不会自动发生)。像前面所描述的,phase值和任何依赖关系确定了启动顺序。

    参考代码:com.liyong.ioccontainer.starter.XmlLifecycleIocContainer

  • 在非Web应用程序中优雅关闭Spring IoC容器

    这个部分仅仅适用非web应用程序。Spring的基于WebApplicationContext实现提供优雅的关闭Spring IoC容器,当相关的web应用程序关闭的时候。

    如果你使用SpringIoC容器在非web应用环境(例如,在一个富客户端桌面环境),在JVM注册一个关闭钩子。这样做确保优雅的关闭和调用在单例bean上的销毁方法,因此所有资源被释放。你必须仍然正确地配置和实现这些销毁回调。

    去注册一个关闭钩子,调用registerShutdownHook()方法是在ConfigurableApplicationContext接口上声明,如下例子:

    import org.springframework.context.ConfigurableApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    public final class Boot {
    
        public static void main(final String[] args) throws Exception {
            ConfigurableApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
    
            //增加一个Hook构造回调
            ctx.registerShutdownHook();
    
            // app runs here...
    
            // main method exits, hook is called prior to the app shutting down...
        }
    }
    
1.6.2 ApplicationContextAwareBeanNameAware

ApplicationContext创建一个对象实例同时实现了org.springframework.context.ApplicationContextAware接口,这个实例提供一个对ApplicationContext的引用。下面的清单显示了ApplicationContextAware接口的定义:

public interface ApplicationContextAware {

    void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
}

因此,这些bean可以编程地操作ApplicationContext创建它们,通过ApplicationContext接口或者转换引用为已知的这个接口的子类(例如:ConfigurableApplicationContext,它暴露了附加的功能)。一种用途是通过编程方式检索其他bean。有时候这个能力非常有用。然而,一般的,你应该避免它,因为它耦合Spring的代码并且不遵循控制反转格式,将协调者作为bean的属性。ApplicationContext其他方法提供获取资源文件、发布应用事件和访问MessageSource。这些额外的特性在ApplicationContext的附加能力中描述

自动装配是获得对ApplicationContext的引用的另一种选择。传统的constructorbyType自动装配模式(在自动装配协作器中描述)能够为构造函数或者Setter方法参数提供一个ApplicationContext类型的依赖。为了获得更大的灵活性,包括自动装配字段和多个参数方法的能力,可以使用基于注解的自动装配特性。如果这样做,ApplicationContext自动装配到字段、构造参数或方法参数,如果字段、构造函数或方法带有@Autowired注解那么该参数期望ApplicationContext类型。更多的信息,查看使用@Autowired

ApplicationContext创建一个类并且实现org.springframework.beans.factory.BeanNameAware接口时,该类将获得对其关联对象定义中定义的名称的引用。下面清单显示BeanNameAware接口定义:

public interface BeanNameAware {

    void setBeanName(String name) throws BeansException;
}

这个回调在bean被填充之后被调用,但是在初始化回调之前例如InitializingBeanafterPropertiesSet或者自定义init-method

参考代码:com.liyong.ioccontainer.starter.XmlAwareIocContainer

1.6.3 其他Aware接口

除了ApplicationContextAwareBeanNameAware之外,Spring提供了广泛的Aware回调接口,这些接口使Bean向容器指示它们需要一些基础结构依赖性。作为基本规则,这个名字指示依赖类型。下面的表格总结最重要的Aware接口:

NameInjected DependencyExplained in…
ApplicationContextAware注入 ApplicationContext.ApplicationContextAwareand BeanNameAware
ApplicationEventPublisherAware注入ApplicationEventPublisher.Additional Capabilities of the ApplicationContext
BeanClassLoaderAwareClass loader used to load the bean classes.Instantiating Beans
BeanFactoryAware注入 BeanFactory.ApplicationContextAwareand BeanNameAware
BeanNameAware注入BeanNameApplicationContextAwareand BeanNameAware
BootstrapContextAware注入BootstrapContextJCA CCI
LoadTimeWeaverAware注入LoadTimeWeaver.Load-time Weaving with AspectJ in the Spring Framework
MessageSourceAware注入MessageSourceAdditional Capabilities of the ApplicationContext
NotificationPublisherAware注入NotificationPublisher.Notifications
ResourceLoaderAware注入ResourceLoaderResources
ServletConfigAware注入ServletConfig.Spring MVC
ServletContextAware注入ServletContext.Spring MVC

再次注意,使用这些接口关联到你的代码到Spring API并且不遵循控制反转的风格。我们推荐使用基础设施bean需要编程的去容器获取。

1.7 Bean Definition继承

bean定义包含一些配置信息,包括构造函数参数、属性值、和容器特定信息,例如初始化方法、静态工厂方法名等等。子bean定义继承父bean定义配置数据。子bean定义能够覆盖一些值或者增加其他需要。使用父bean和子bean定义能够保存一些类型。实际上,这是一种模版模式。

如果你编程式使用ApplicationContext接口,子bean定义通过ChildBeanDefinition类描述。大多数用户不在这个级别使用(译者:应用开发人员一般不会接触)。相反,它们在例如ClassPathXmlApplicationContext之类的类中声明性地配置bean定义。当你使用基于XML配置元数据,你可以通过使用parent属性指示一个子bean的定义,指定parent bean作为这个属性的值。下面例子显示怎样做:

<bean id="inheritedTestBean" abstract="true"
        class="org.springframework.beans.TestBean">
    <property name="name" value="parent"/>
    <property name="age" value="1"/>
</bean>
<!--parent指定继承父类-->
<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定义继承作用域、构造参数值、属性值、和覆盖父类方法,并可以添加新值。任何作用域、初始化方法、销毁方法或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。当一个定义是abstract,它仅仅作为一个bean定义的模版且父bean定义为子bean定义服务。尝试自己使用这样的抽象父bean,通过将其引用为另一个beanref属性或使用父bean ID进行显式的getBean()调用将返回错误。类似地,容器的内部preInstantiateSingletons()方法将忽略定义为抽象的bean定义。

默认情况下,ApplicationContext会预先实例化所有单例。因此,重要的是(至少对于单例bean),如果有一个(父)bean定义仅打算用作模板,并且此定义指定了一个类,则必须确保将abstract属性设置为true ,否则应用程序上下文将实际上(试图)预先实例化抽象Bean

参考实例:com.liyong.ioccontainer.starter.XmlBeanDefinitionInheritanceContainer

1.8 容器拓展点

典型地,应用程序开发者不需要实现ApplicationContext类。相反,Spring IoC容器通过插件实现指定的集成接口去扩展。下面的章节描述这些接口的集成。

1.8.1 通过使用BeanPostProcessor自定义bean

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

你可以配置多个BeanPostProcessor实例并且你可以通过设置order属性来控制这些BeanPostProcessor实例的执行顺序。仅仅BeanPostProcessor实现Ordered接口是可以设置这个属性。如果自己实现BeanPostProcessor,你应该考虑实现Ordered接口。更多详情,查看 BeanPostProcessorOrdered接口。参见有关以编程方式注册BeanPostProcessor实例的说明

BeanPostProcessor接口在bean(对象)实例上操作。也就是说,Spring IoC容器实例化一个bean实例,然后BeanPostProcessor实例执行它们的工作。(译者:在Spring容器初始化bean过程中执行相关的回调操作)

BeanPostProcessor实例是按容器划分作用域。仅仅在使用继承容器才有意义。如果在一个容器中定义BeanPostProcessor,仅仅在那个容器中的bean被后置处理。换句话说,定义在一个容器中的bean不会被在其他容器定义的BeanPostProcessor所处理,即使两个容器都属于同一层次结构。

改变实际bean定义(也就是定义bean的蓝图),你需要使用BeanFactoryPostProcessor,如使用BeanFactoryPostProcessor自定义配置元数据中所述

org.springframework.beans.factory.config.BeanPostProcessor接口恰好地由两个回调方法组成。当一个类作为后置处理起被注册到容器中时,对于每个被容器创建bean实例,后置处理器从容器初始化方法(例如:InitializingBean.afterPropertiesSet()或者任何被声明init方法)被调用之前,并且任何bean初始化回调之后获得回调。后置处理器能够处理bean实例任何操作,包括忽略所有的回调。Bean后处理器通常检查回调接口,或者可以用代理包装BeanSpring AOP基础设施类中实现bean的后置处理去提供一个代理包装逻辑。

ApplicationContext自动的检查所有bean,这些bean在配置元数据中实现了BeanPostProcessor接口。ApplicationContext注册这些bean作为后置处理器,以便以后在创建bean时可以调用它们。Bean后处理器可以与其他bean相同的方式部署在容器中。

注意,当通过在类上使用@Bean工厂方法声明BeanPostProcessor时,工厂方法返回类型应该是实现类本身或只是实现org.springframework.beans.factory.config.BeanPostProcessor接口,清晰地表明该bean的后处理器性质。否则,ApplicationContext无法在完全创建之前按类型自动检测它。由于BeanPostProcessor需要及早实例化才能应用于上下文中其他bean的初始化,因此这种早期类型检测至关重要。

编程式地注册BeanPostProcessor实例

推荐的去注册BeanPostProcessor方式是通过ApplicationContext自动检测(前面描述),可以使用addBeanPostProcessor方法以编程方式针对ConfigurableBeanFactory注册它们。当注册之前你需要评估条件逻辑甚至需要跨层次结构的上下文复制后置处理器时,这是非常有用的。注意,然而,BeanPostProcessor实例编程式的添加不遵循Ordered排序接口。在这里,注册的顺序决定了执行的顺序。需要注意是编程式的注册BeanPostProcessor实例总是在这些通过自动检测的后置处理器之后被处理,而不管显示的顺序

BeanPostProcessor实例和AOP自定代理

实现BeanPostProcessor接口的类是特殊的,并且容器对它们的处理方式有所不同。在启动时会实例化它们直接引用的所有BeanPostProcessor实例和Bean,这是ApplicationContext特殊启动阶段的一部分。接下来,以排序方式注册所有BeanPostProcessor实例,并将其应用于容器中的所有其他bean。因为AOP自动代理是作为BeanPostProcessor本身实现的,所以BeanPostProcessor实例或它们直接引用的bean都不适合进行自动代理,因此,没有编织的方面。

对于任何这样的bean,你应该查看日志消息:

Bean someBean is not eligible for getting processed by all BeanPostProcessor interfaces (for example: not eligible for auto-proxying).

如果你有通过使用自动装配或@Resource注入beanBeanPostProcessor中(可能回退到自动装配),当搜索类型匹配的依赖项候选者时,Spring可能会访问意外的Bean,因此使它们不适合进行自动代理或其他类型的Bean后处理。例如,如果你有一个用@Resource标注的依赖项,其中字段或Setter名称不直接与bean的声明名称相对应,并且不使用name属性,那么Spring将访问其他bean以按类型匹配它们。

下面的例子展示了在ApplicationContext中怎样去编写、注册和使用BeanPostProcessor接口。

例子:Hello World

第一个例子说明基础的使用。这个例子展示一个自定义BeanPostProcessor实现并调用通过容器创建的每个beantoString()方法并且打印结果字符串到控制台。

下面的清单展示了自定义BeanPostProcessor实现类定义:

package scripting;

import org.springframework.beans.factory.config.BeanPostProcessor;

public class InstantiationTracingBeanPostProcessor implements BeanPostProcessor {

    // simply return the instantiated bean as-is
    public Object postProcessBeforeInitialization(Object bean, String beanName) {
        return bean; // we could potentially return any object reference here...
    }

    public Object postProcessAfterInitialization(Object bean, String beanName) {
      //打印bean信息
        System.out.println("Bean '" + beanName + "' created : " + bean.toString());
        return bean;
    }
}

下面beans元素使用InstantiationTracingBeanPostProcessor

<?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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/lang
        https://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,可以像其他bean一样被依赖注入。(前面的配置还定义了一个由Groovy脚本支持的beanSpring动态语言支持详情在“动态语言支持”一章中。)

下面的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 = ctx.getBean("messenger", Messenger.class);
        System.out.println(messenger);
    }

}

前面的应用输出结果类似下面:

Bean 'messenger' created : org.springframework.scripting.groovy.GroovyMessenger@272961
org.springframework.scripting.groovy.GroovyMessenger@272961

例子:RequiredAnnotationBeanPostProcessor

将回调接口或与自定义BeanPostProcessor实现结合使用是扩展Spring IoC容器常用方法。SpringRequiredAnnotationBeanPostProcessor是一个示例,它是Spring发行版附带的BeanPostProcessor实现,可以确保标有(任意)注解的bean上的JavaBean属性实际上(配置为)依赖注入了一个值。说明:就是常用的依赖注入。

参考代码:com.liyong.ioccontainer.starter.XmlBeanPostProcessorIocContainer

1.8.2 BeanFactoryPostProcessor自定义配置元数据

下一个拓展点是org.springframework.beans.factory.config.BeanFactoryPostProcessor。这个接口语义类似BeanPostProcessor,一个重要的不同点:BeanFactoryPostProcessor是操作在bean的配置元数据上。也就是说,Spring IoC容器允许除BeanFactoryPostProcessor实例外其他任何bean被BeanFactoryPostProcessor读取配置元数据和改变它。

你可以配置多个BeanFactoryPostProcessor实例,并且你可以通过设置order属性在这些BeanFactoryPostProcessor实例上来控制顺序。然而,如果BeanFactoryPostProcessor实现Ordered接口才能设置这个属性。如果你写自己的BeanFactoryPostProcessor,你应该考虑实现Ordered接口。更多关于BeanFactoryPostProcessorOrdered接口详细信息。

如果想去改变实际bean实例(也就是,这个对象是从配置元数据被创建的),你需要使用BeanPostProcessor(前面描述通过使用BeanPostProcessor自定义bean)。从技术上讲,可以在BeanFactoryPostProcessor中使用Bean实例(例如,通过使用BeanFactory.getBean()),这样做导致过早的初始化bean,违反了标准容器生命周期。这会导致负面的影响,例如:绕过后置处理。

同样,BeanFactoryPostProcessor实例是按容器划分。(如果你使用分层的容器才会有意义) 如果你定义在容器中定义一个BeanFactoryPostProcessor,它仅适用于该容器中的bean定义。在一个容器中的bean定义不会被其他的容器中BeanFactoryPostProcessor后置处理,即使两个容器在同一个层级

Bean工厂后处理器在ApplicationContext中声明时会自动执行,以便将更改应用于定义容器的配置元数据。Spring包括一些预定义的工厂后置处理器,例如PropertyOverrideConfigurer和

PropertySourcesPlaceholderConfigurer。你可以使用一个自定义的BeanFactoryPostProcessor-例如,注册一个自定义属性编辑器。

ApplicationContext自动地检测任何部署到容器中并实现BeanFactoryPostProcessor接口的实例bean。使用这些bean作为bean工厂后置处理器,在适当的时间。你可以像其他ban一样部署这些后置处理器。

与BeanPostProcessors一样,通常不希望配置BeanFactoryPostProcessors进行延迟初始。如果没有其他bean引用Bean(Factory)PostProcessor,这个后置处理器不会被初始化。因此,标记它为延迟初始化将被忽略并且Bean(Factory)PostProcessor将被提前初始化即使你的声明default-lazy-init属性为true。

参考代码:com.liyong.ioccontainer.starter.XmlBeanFactoryPostProcessorIocContainer

例如:类名替换PropertySourcesPlaceholderConfigurer

可以使用PropertySourcesPlaceholderConfigurer通过使用标准Java属性格式将属性值从bean定义外部化到一个单独的文件中(意思是:bean配置数据可以配置到一个单独文件中)。这样做使部署应用程序的人员可以自定义特定于环境的属性,例如数据库URL和密码,而无需为修改容器的主要XML定义文件而复杂或冒风险。

考虑下面基于XML配置元数据片段,DataSource使用占位符值定义:

<bean class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
    <property name="locations" value="classpath:com/something/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>

这个例子显示从外部化Properties文件的属性配置。在运行时,PropertySourcesPlaceholderConfigurer应用元数据替换DataSource的属性值。将要替换的值指定为$ {property-name}格式的占位符,该格式遵循Ant、log4j和JSP EL样式。

真实值来自于标准的Properties格式的其他文件:

jdbc.driverClassName=org.hsqldb.jdbcDriver
jdbc.url=jdbc:hsqldb:hsql://production:9002
jdbc.username=sa
jdbc.password=root

因此,${jdbc.username}字符串在运行时被替换为sa值,并且同样应用到其他的占位符在properties文件中匹配这些key。PropertySourcesPlaceholderConfigurer检查Bean定义的大多数属性和属性中的占位符。此外,你可以自定义占位符的前缀和后缀。

context命名空间在Spring2.5中被引入,你可以配置一个专用配置元素的占位符。在location属性中你可以提供一个或多个location通过逗号分隔,类似下面例子:

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

PropertySourcesPlaceholderConfigurer不仅仅在你指定的Properties中查找属性。默认情况,如果在指定的属性文件中没有找到属性,它会再次检查Spring Environment属性和常规的Java System属性。

你可以使用PropertySourcesPlaceholderConfigurer去替代类名称,有时候者非常有用,当你在运行时必须选择一个特定的实现类。下面例子展示怎样去做:

<bean class="org.springframework.beans.factory.config.PropertySourcesPlaceholderConfigurer">
 <property name="locations">
     <value>classpath:com/something/strategy.properties</value>
 </property>
 <property name="properties">
     <value>custom.strategy.class=com.something.DefaultStrategy</value>
 </property>
</bean>

<bean id="serviceStrategy" class="${custom.strategy.class}"/>

如果在运行时无法将类解析为有效的类,则在即将创建bean时,即在非lazy-init bean的ApplicationContext的preInstantiateSingletons()阶段,bean的解析将失败。

.参考代码:com.liyong.ioccontainer.starter.XmlPropertySourcesPlaceholderConfigurerIocContainer

例子:PropertyOverrideConfigurer

另一个bean工厂后处理器PropertyOverrideConfigurer类似于PropertySourcesPlaceholderConfigurer,但与后者不同,原始定义可以具有bean属性的默认值,也可以完全没有值。如果覆盖的属性文件对于一些bean属性没有符合内容,默认的上下文定义被使用。

注意:bean定义没有意识到被覆盖,因此从XML定义文件中不能立即看出覆盖的配置正在被使用。由于存在覆盖机制,在多个PropertyOverrideConfigurer实例情况下对应相同bean属性不同的值,最后一个将被使用。

属性文件配置格式:

beanName.property=value

下面清单显示格式化例子:

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

此示例文件可与容器定义一起使用,容器定义包含一个名为dataSource的bean,该bean具有driver和url属性。

复合属性名也是被支持的,只要路径的每个组件(被覆盖的最终属性除外)都是非空的(可能是由构造函数初始化的)。下面的例子,bean的tom属性fred的sammy属性设置只123:

tom.fred.bob.sammy=123

指定的重写值总是文本值。它们没有被转译为bean引用。当XML bean定义中的原始值指定bean引用时,这种约定也适用。

context命名空间在Spring2.5中被引入,可以使用专用配置元素配置属性覆盖,类似下面例子:

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

参考代码:com.liyong.ioccontainer.starter.XmlPropertyOverrideConfigurerIocContainer

1.8.3 FactoryBean自定义初始化逻辑

可以为本身就是工厂的对象实现org.springframework.beans.factory.FactoryBean接口。

这个FactoryBean接口是Spring IoC容器的实例化逻辑可插拔点。如果你有一个复杂的初始化代码在Java中比冗余XML更好的表达,你可以创建你自己的FactoryBean,在实现类中写复杂的初始化逻辑,然后插入你的自定义FactoryBean到容器中。

FactoryBean接口提供三个方法:

  • Object getObject():返回这个工厂创建的一个实例。这个实例可能被共享,依赖于这个工厂返回的是单例或原型。
  • boolean isSingleton():如果FactoryBean返回一个单例返回true否则为false
  • Class getObjectType():返回由getObject()方法返回的对象类型;如果类型未知,则返回null

FactoryBean概念和接口在Spring框架中一些地方被使用到。Spring有超过50个FactoryBean接口的实现。

当你需要向容器获取一个实际的FactoryBean实例本身而不是由它产生的bean时请在调用ApplicationContext的getBean()方法时在该bean的ID前面加上一个符号。因此,对于id为myBean的给定FactoryBean,在容器上调用getBean(“myBean”)将返回FactoryBean的产生的bean,而调用getBean(“&myBean”)将返回FactoryBean实例本身。

参考代码:com.liyong.ioccontainer.service.CustomFactorBean

1.9 基于注解的容器配置

Spring配置注解比XML配置更好?

基于注解的配置介绍抛出一个问题,是否比XML方式更好。简单的回答是看场景。具体的描述是每种方式各有利弊,通常的,这个由开发者去决定更适合他们的策略。由于这种定义的方式,注解在声明中提供了大量的上下文,导致更短和更简洁的配置。XML擅长于连接组件,而不需要修改它们的源代码或重新编译它们。一些开发人员倾向于闭源,而另一些人则认为被注释的类不再是pojo,而且配置变得分散且更难控制。

无论怎么选择,Spring能够兼容两种风格甚至是混合使用。值得指出的是通过JavaConfig,Spring允许以无侵入式方式使用,不需要接触目标组件源代码。在工具方面,所有的配置风格通过Spring的Tools Eclipse工具支持。

基于注解的配置提供了XML设置的替代方法,它依靠字节码元数据来连接组件,而不是尖括号声明(不需要xml的格式配置)。替换使用XML去描述bean,开发者只需移动配置到类本身并通过在关联的类、方法、字段上使用注解。在RequiredAnnotationBeanPostProcessor中提到,结合使用BeanPostProcessor和注释是扩展Spring IoC容器的常用方法。例如,Spring 2.0引入了使用@Required注释强制执行必需属性的可能性。Spring 2.5使遵循相同的通用方法来驱动Spring的依赖注入成为可能。实际上,@Autowired注解提供了相同的能力,在 Autowiring Collaborators中被描述,但是提供了更细粒度和更多的能力。Spring2.5增加对JSR250支持 ,例如:@PostConstruct、@PreDestroy。Spring3.0增加对JSR-330 (Java的依赖注入)注解包含在包javax.inject,例如@Inject和@Named。更多关于注解详情能在相关的文章中找到。

注解注入在XML注入之前被执行。因此,XML配置覆盖注解对属性的两种连接方式。

你可以注册他们作为独立的bean定义,但是他们也可以通过基于XML包含下面的标签被隐的注册(注意包含context命名空间)。

<?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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <context:annotation-config/>

</beans>

隐式的注册后置处理器包括:

AutowiredAnnotationBeanPostProcessorCommonAnnotationBeanPostProcessor, PersistenceAnnotationBeanPostProcessor和前面提到的RequiredAnnotationBeanPostProcessor

<context:annotation-config />仅在定义它的相同应用程序上下文中查找关于bean的注解。意思是,如果你在WebApplicationContext中为DispatcherServlet配置< context:annotation-config/> ,它仅仅检查@Autowired在你的Control层并且不会在你的Service层。查看DispatcherServlet 更多信息。

1.9.1 @Required

@Required注解应用到bean属性的Setter方法,类似下面例子:

public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Required
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // ...
}

这个注解表示bean属性在运行时必须被填充赋值,在一个bean定义中显示赋值或自动装配。如果受影响的bean属性没有被填充值容器抛出一个异常。这允许更早的显示异常,避免以后再出现NullPointerException实例等。我们仍然推荐你把断言放入到bean类本身中(例如,放入初始化方法)。这样做会强制执行那些必需的引用和值,即使你在容器外部使用该类也是如此。

@Required注解在Spring5.1中被正式的不推荐使用,更好的方式是,使用构造函数注入要求的设置(或InitializingBean.afterPropertiesSet()的自定义实现以及bean属性设置器方法)。

1.9.2 @Autowired

JSR 330的@Inject注解能够使用在Spring的@Autowired注解的地方。

可以在构造方法上使用@Autowired,类似下面例子:

public class MovieRecommender {

    private final CustomerPreferenceDao customerPreferenceDao;

    @Autowired
    public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
        this.customerPreferenceDao = customerPreferenceDao;
    }

    // ...
}

从Spring4.3开始,如果目标bean仅仅定义以一个构造函数开始,那么构造函数上的@Autowired注解是不必要的。然而,如果有一些构造函数是有效的并且没有主要默认的构造函数,则至少有一个构造函数必须被注解为@Autowired,目的是引导容器选择其中一个去使用。查看构造方法解析详情。

你也可以应用@Autowired注解到传统的Setter方法,类似下面例子:

public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Autowired
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // ...
}

你可以应用@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 {

    private final CustomerPreferenceDao customerPreferenceDao;

    @Autowired
    private MovieCatalog movieCatalog;

    @Autowired
    public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
        this.customerPreferenceDao = customerPreferenceDao;
    }

    // ...
}

确保你的目标组件(例如,MovieCatalog 或CustomerPreferenceDao)是一致性的通过类型声明,也就是使用基于@Autowired注入点。否则,注入可能由于在运行时出现“找不到类型匹配”错误而失败。

对于基于XML定义的bean或者组件类通过类路径扫描,容器通常知道具体的类型。然而,对于@Bean工厂方法,你需要确保声明的返回类型足够的表达。对于实现多个接口的组件,或者对于实现类型可能引用的组件,请考虑在工厂方法中声明最具体的返回类型。至少与指向bean的注入点所要求的一样具体(译者:尽可能返回具体的实现类型)。

你还可以引导Spring从ApplicationContext中提供特定类型的所有bean,通过将@Autowired注解添加到需要该类型数组的字段或方法中,类似下面例子:

public class MovieRecommender {

    @Autowired
    private MovieCatalog[] movieCatalogs;

    // ...
}

类型化集合也是如此,类似下面例子:

public class MovieRecommender {

    private Set<MovieCatalog> movieCatalogs;

    @Autowired
    public void setMovieCatalogs(Set<MovieCatalog> movieCatalogs) {
        this.movieCatalogs = movieCatalogs;
    }

    // ...
}

如果你想数组或集合中的元素在指定的顺序下被排序,那么你的目标bean可以实现org.springframework.core.Ordered接口、@Order或标准的@Priority注解。否则,它们的顺序遵循在容器中对应目标bean定义注册的顺序。

你可以在目标类级别和在@Bean方法上申明@Order注解,可能是针对单个bean定(多个定义情况下使用相同的bean类)。@Order值可能会影响注入点的优先级,不会影响单例bean的启动顺序,这是由依赖关系和@DependsOn声明确定。

注意:标准的javax.annotation.Priority注解在@Bean级别是无效的,因为它不能被声明在方法上。它的语义可以通过@Order值与@Primary结合在每种类型的单个bean上。

甚至类型Map实例也能被自动装配只要期望的key类型是String。map的值包含了所有期望类型的bean,并且这些key包含对应bean的名称,像下面的例子展示:

public class MovieRecommender {

    private Map<String, MovieCatalog> movieCatalogs;

    @Autowired
    public void setMovieCatalogs(Map<String, MovieCatalog> movieCatalogs) {
        this.movieCatalogs = movieCatalogs;
    }

    // ...
}

默认情况下,当没有匹配的候选bean对应给定的注入点的时候自动装配将失败。在这种情况下申明数组、集合或map至少有一个期望匹配的元素。

默认行为是将带注解的方法和字段视为指示所需的依赖项。你可以改变这种行为,像下面这个例子,通过将框架标记为不需要,从而使框架可以跳过不满意的注入点(通过在@Autowired属性中的required设置为false)。说明:满足注意条件就注入,不满足条件就跳过注入。

public class SimpleMovieLister {

    private MovieFinder movieFinder;
		//自动注入不满足就不注入
    @Autowired(required = false)
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // ...
}

如果一个非必需的方法的依赖项(或者它的一个依赖项,在有多个参数的情况下)不可用,那么这个方法将不会被调用。在这种情况下,完全不需要填充非必需字段,将其默认值保留在适当的位置。注入构造函数和工厂方法参数是一种特殊情况,因为由于Spring的构造函数解析算法可能要处理多个构造函数,@Autowired中的required属性有一些不同的含义。

注入的构造函数和工厂方法参数是一种特殊情况,因为由于Spring的构造函数解析算法可能会处理多个构造函数,因此@Autowired中必填属性的含义有所不同。默认情况下,有效地需要构造函数和工厂方法参数,但是在单构造函数场景中有一些特殊规则,例如,如果没有匹配的Bean可用,则将多元素注入点(数组,集合,映射)解析为空实例。这允许一种通用的实现模式,其中所有依赖项都可以在唯一的多参数构造函数中声明-例如,申明一个简单的公共构造函数不需要@Autowired注解。

任何给定的bean类只有一个构造函数可以声明@Autowired并设置属性required为true,指示当使用Spring Bean时构造函数自动装配。因此,如果必填属性保留为默认值true,则只能使用@Autowired注释单个构造函数。如果有多个构造函数声明注解,那么它们都必须声明required=false,以便被认为是自动装配的候选对象(类似于XML中的autowire=constructor)。将选择通过匹配Spring容器中的bean可以满足的依赖关系数量最多的构造函数如果没有一个候选者满意,则将使用主要的/默认构造函数(如果存在)。同样,如果一个类声明了多个构造函数,但都没有使用@Autowired进行注解,则将使用主要的/默认构造函数(如果存在)。如果一个类仅声明一个单一的构造函数开始,即使没有注释,也将始终使用它。请注意,带注解的构造函数不必是公共的。

在setter方法上,建议使用@Autowired的required属性,而建议使用@Required注解。设置required属性为false则表明这个属性不是必须的对于自动装配意图,并且这个属性如果不能被自动装配将被忽略。换句话说,@Required更强大,因为它强制通过容器支持的任何方式设置属性,如果没有定义值,则会引发相应的异常。

另外,你可以通过Java 8的java.util.Optional来表达特定依赖项的非必需性质,类似以下示例所示:

public class SimpleMovieLister {

    @Autowired
    public void setMovieFinder(Optional<MovieFinder> movieFinder) {
        ...
    }
}

从Spring5.0后,你可以使用@Nullable注解(任何包装中的任何种类-例如,JSR-305的javax.annotation.Nullable)或者只是利用Kotlin内置的null安全支持:

public class SimpleMovieLister {

    @Autowired
    public void setMovieFinder(@Nullable MovieFinder movieFinder) {
        ...
    }
}

你可以为这些接口使用@Autowired,这是众所周知的可注入的依赖:BeanFactory, ApplicationContext, Environment, ResourceLoader,ApplicationEventPublisher, 和 MessageSource。这些接口和它们的拓展接口,例如,ConfigurableApplicationContext或ResourcePatternResolver是自动地解析,没有特殊的安装需要。下面的例子主动注入一个ApplicationContext对象:

public class MovieRecommender {

    @Autowired
    private ApplicationContext context;

    public MovieRecommender() {
    }

    // ...
}

@Autowired、@Inject、 @Value和@Resource注解是通过Spring的BeanPostProcessor实现处理的。这意味着你不能应用这些注解在你自己的BeanPostProcessor和BeanFactoryPostProcessor类中。这些类型必须被通过XML或者Spring @Bean方法连接。

参考代码:com.liyong.ioccontainer.starter.XmlIocAnnotationContainerConfigrationntainer

1.9.3 使用@Primary对基于注解的自动装配调整

因为通过类型的自动装配可能导致多个候选者,通常有必要需要更多的选择处理。一种可以完成的方式是使用Spring的@Primary注解。@Primary表示当多个bean自动装配到单值依赖时这个bean是我们更期望的值。如果主要的bean在候选bean中是,它将被自动装配。

考虑下面的配置,这个配置定义firstMovieCatalog作为主要的MovieCatalog类型候选值:

@Configuration
public class MovieConfiguration {

    @Bean
    @Primary
    public MovieCatalog firstMovieCatalog() { ... }

    @Bean
    public MovieCatalog secondMovieCatalog() { ... }

    // ...
}

通过前面的配置,下面的MovieRecommender类中自动装配firstMovieCatalog:

public class MovieRecommender {

    @Autowired
    private MovieCatalog movieCatalog;

    // ...
}

对应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:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <context:annotation-config/>
	 <!--作为主要的bean-->
    <bean class="example.SimpleMovieCatalog" primary="true">
        <!-- inject any dependencies required by this bean -->
    </bean>

    <bean class="example.SimpleMovieCatalog">
        <!-- inject any dependencies required by this bean -->
    </bean>

    <bean id="movieRecommender" class="example.MovieRecommender"/>

</beans>
1.9.4 使用Qualifiers对基于注解的自动装配调整

@Primary是一种根据类型使用自动装配的有效方法,当可以确定一个主要候选对象时,可以使用多个实例。当你需要更多的控制选择处理时,你可以使用Spring的@Qualifier注解。你可以将限定符值与特定的参数相关联,从而缩小类型匹配的范围,以便每个参数选择特定的bean。在这个简单的例子中,这可以是简单的描述性值,如以下示例所示:

public class MovieRecommender {

    @Autowired
    @Qualifier("main")
    private MovieCatalog movieCatalog;

    // ...
}

你也可以指定@Qualifier注解在各个构造函数参数或方法参数上,类似下面的例子:

public class MovieRecommender {

    private MovieCatalog movieCatalog;

    private CustomerPreferenceDao customerPreferenceDao;

    @Autowired
    public void prepare(@Qualifier("main") MovieCatalog movieCatalog,
            CustomerPreferenceDao customerPreferenceDao) {
        this.movieCatalog = movieCatalog;
        this.customerPreferenceDao = customerPreferenceDao;
    }

    // ...
}

下面的例子显示对应的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:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <context:annotation-config/>

    <bean class="example.SimpleMovieCatalog">
        <qualifier value="main"/> //1

        <!-- inject any dependencies required by this bean -->
    </bean>

    <bean class="example.SimpleMovieCatalog">
        <qualifier value="action"/> //2

        <!-- inject any dependencies required by this bean -->
    </bean>

    <bean id="movieRecommender" class="example.MovieRecommender"/>

</beans>
  1. 具有main限定符值的Bean与构造函数参数连接,构造函数参数使用相同的值进行限定
  2. 带有action限定符值的bean与构造函数参数连接,构造函数参数使用相同的值进行限定。

为了回退匹配,bean的名字是默认的限定符。因此,你可以定义bean的id为main替代嵌入的限定符元素,导致相同的匹配结果。然而,即使你能使用这个约定通过名字指定bean,@Autowired基本上是关于带有可选语义限定符的类型驱动的注入(意思是@Autowired中带有require限定符)。这意味着限定符值总是有缩小类型匹配集合的语义,即使bean名为回退名称。他们从语义上不能表达引用一个唯一bean的id。好的限定符值是mainEMEApersistent,它们表示独立于bean id的特定组件的特征,对于匿名bean定义(如前面示例中的定义),可以自动生成这些特征。

限定符也能应用到类型化的集合,像前面讨论的-例如,Set。在这种例子中,所有匹配的bean,根据声明的限定符,作为一个集合被注入。这暗示限定符没有必要是唯一的。相反,它们构成了过滤标准。例如,你可以定义具有相同限定符值action的多个MovieCatalog Bean,所有这些都注入到以@Qualifier(“ action”)注解的Set 中。

在类型匹配的候选者中,让限定符值选择目标bean名称,在注入点不需要@Qualifier注解。如果没有其他解析指示器(例如限定符或primary标记),对于非唯一依赖情况,Spring将注入点名(即字段名或参数名)与目标bean名相匹配,并选择同名的候选项(如果有的话)。

也就是说,如果你打算试图通过名称表示注解驱动注入,不要主要地使用@Autowired,即使它有通过名称在类型匹配的候选者间选择的能力。相反,使用JSR-250@Resource注解,它是语义地通过使用唯一名称定义标识一个指定目标组件,声明的类型与匹配过程无关。@Autowired有不同的语义:按类型选择候选bean之后,指定的字符串限定符值仅在那些类型选择的候选中被考虑(例如,匹配一个account限定符与bean被标记相同的限符标签)。

对于定义为集合、Map、数组类型的bean,@Resource是一个好的解决方案,通过唯一的名称引用指定的集合或数组bean。也就是说,在Spring4.3以后你可以匹配一个Map和数组类型通过Spring的@Autowired类型匹配算法,只要元素类型信息保留在@Bean返回类型签名或集合继承层次结构中。在这种场景中,你可以使用限符值去相同类型集合选择,如前一段所述。

在Spring4.3以后,@Autowired还考虑了注入的自我引用(也就是说,引用回当前注入的bean)。注意自我注入是一种后备。对其他组件的常规依赖始终优先。从这个意义上说,自我推荐不参与常规的候选人选择,因此尤其是绝不是主要的。相反,它们总是以最低优先级结束。实际上,你应该仅将自我引用用作最后的手段(例如,在相同实例上通过bean的事物代理调用其他方法)。在这种情况下,考虑将受影响的方法分解为单独的委托Bean。或者,你可以使用@Resource,它可以通过其唯一名称获取返回到当前bean的代理。

尝试将@Bean方法的结果注入相同的配置类也是一种有效的自引用方案。要么在实际需要的方法签名中懒惰地解析此类引用(与配置类中的自动装配字段相对),要么将受影响的@Bean方法声明为静态,将其与包含的配置类实例及其生命周期脱钩。否则,仅在回退阶段考虑此类Bean,而将其他配置类上的匹配Bean选作主要候选对象(如果可用)。

@Autowired应用到字段、构造函数和多参数方法,在参数级别允许使用限定符注解缩小选择范围。相反,@Resource仅仅支持字段和bean属性Setter方法简单参数。因此,如果注入目标是构造函数或多参数方法,则应坚持使用限定符。

你可以创建你自己的自定义限符注解。为了这样做,定义一个注解和提供@Qualifier注解在你的定义中,类似下面例子:

@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Genre {

    String value();
}

然后,你可以提供自定义限符在自动装配字段和参数上,类似下面例子:

public class MovieRecommender {

    @Autowired
    @Genre("Action")
    private MovieCatalog actionCatalog;

    private MovieCatalog comedyCatalog;

    @Autowired
    public void setComedyCatalog(@Genre("Comedy") MovieCatalog comedyCatalog) {
        this.comedyCatalog = comedyCatalog;
    }

    // ...
}

接下来,你可以为候选者bean定义提供信息。你可以增加标签的子标签并且指定typevalue为匹配你的自定义限定符注解。这个类型是注解的全限定类名的匹配。或者,为方便起见,如果不存在名称冲突的风险,则可以使用简短的类名。下面例子展示两种方式:

<?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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <context:annotation-config/>

    <bean class="example.SimpleMovieCatalog">
      <!--类名简写-->
        <qualifier type="Genre" value="Action"/>
        <!-- inject any dependencies required by this bean -->
    </bean>

    <bean class="example.SimpleMovieCatalog">
      <!--全限定类名-->
        <qualifier type="example.Genre" value="Comedy"/>
        <!-- inject any dependencies required by this bean -->
    </bean>

    <bean id="movieRecommender" class="example.MovieRecommender"/>

</beans>

Classpath扫描和组件管理中,你可以看到在XML中基于注解另类的提供限定符元数据,查看Providing Qualifier Metadata with Annotations

在某些场景中,使用注解不需要值可能就满足。这是非常有用的,当注解应用更一般的用途和可以应用于几种不同类型的依赖项时。例如,你可以提供一个脱机目录,当没有Internet连接可用时可以进行搜索。首先,定义相同的注解,类似下面的例子:

@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Offline {

}

然后,增加注解到字段或者属性去自动装配,类似下面例子:

public class MovieRecommender {

    @Autowired
    @Offline //1
    private MovieCatalog offlineCatalog;

    // ...
}
  1. 这一行添加@Offline注解

接下来,bean的定义仅需要一个限定符type,类似下面例子:

<bean class="example.SimpleMovieCatalog">
    <qualifier type="Offline"/> //1
    <!-- inject any dependencies required by this bean -->
</bean>
  1. 这个元素指定了限定符

你还可以定义自定义限定符注解,这些注解除了接受简单value属性之外,还可以接受命名属性。如果随后在要自动装配的字段或参数上指定了多个属性值,则Bean定义必须与所有此类属性值匹配才能被视为自动装配候选。例如,请考虑以下注释定义:

@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface MovieQualifier {

    String genre();

    Format format();
}

在这个例子中Format是一个枚举,定义如下:

public enum Format {
    VHS, DVD, BLURAY
}

字段装配字段是被自定义限定符注解并且保护genre和format属性值,类似下面例子:

public class MovieRecommender {

    @Autowired
    @MovieQualifier(format=Format.VHS, genre="Action")
    private MovieCatalog actionVhsCatalog;

    @Autowired
    @MovieQualifier(format=Format.VHS, genre="Comedy")
    private MovieCatalog comedyVhsCatalog;

    @Autowired
    @MovieQualifier(format=Format.DVD, genre="Action")
    private MovieCatalog actionDvdCatalog;

    @Autowired
    @MovieQualifier(format=Format.BLURAY, genre="Comedy")
    private MovieCatalog comedyBluRayCatalog;

    // ...
}

最后,bean定义应该包含匹配的限定符值。这个例子展示你可以使用bean元数据属性替换元素。如果可用,元素及其属性优先,但是如果不存在这样的限定符,则自动装配机制将退回到标记内提供的值,如下面示例中的最后两个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:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <context:annotation-config/>

    <bean class="example.SimpleMovieCatalog">
        <qualifier type="MovieQualifier">
            <attribute key="format" value="VHS"/>
            <attribute key="genre" value="Action"/>
        </qualifier>
        <!-- inject any dependencies required by this bean -->
    </bean>

    <bean class="example.SimpleMovieCatalog">
        <qualifier type="MovieQualifier">
            <attribute key="format" value="VHS"/>
            <attribute key="genre" value="Comedy"/>
        </qualifier>
        <!-- inject any dependencies required by this bean -->
    </bean>

    <bean class="example.SimpleMovieCatalog">
        <meta key="format" value="DVD"/>
        <meta key="genre" value="Action"/>
        <!-- inject any dependencies required by this bean -->
    </bean>

    <bean class="example.SimpleMovieCatalog">
        <meta key="format" value="BLURAY"/>
        <meta key="genre" value="Comedy"/>
        <!-- inject any dependencies required by this bean -->
    </bean>

</beans>
1.9.5 使用泛型作为自定装配限定

除了@Qualifier注解,你也可以使用Java泛型类型作为隐士的限定。例如,假设你有下面的配置:

@Configuration
public class MyConfiguration {

    @Bean
    public StringStore stringStore() {
        return new StringStore();
    }

    @Bean
    public IntegerStore integerStore() {
        return new IntegerStore();
    }
}

假设前面的bean实现一个泛型接口,(也就是说,Store和Store),你可以使用@Autowire装配Store接口并且泛型作为一个限定符,类似下面的例子:

@Autowired
private Store<String> s1; // <String> qualifier, injects the stringStore bean String限定符

@Autowired
private Store<Integer> s2; // <Integer> qualifier, injects the integerStore bean Integer限定符

当自动装配集合、Map接口和数组的时泛型限定符也可以使用。下面的例子装配Store泛型为integer的List:

// 注入所有泛型为<Integer> 的Store的bean
// Store<String> 的bean不会出现在List中
@Autowired
private List<Store<Integer>> s;

参考代码:com.liyong.ioccontainer.starter.XmlGenericsQualifierIocContainer

1.9.6 使用CustomAutowireConfigurer

CustomAutowireConfigurer是一个BeanFactoryPostProcessor,它允许你注册你自己的自定义限定符注解类型,甚至他们不被注解Spring的@Qualifier。下面的例子展示怎样去使用CustomAutowireConfigurer

<bean id="customAutowireConfigurer"
        class="org.springframework.beans.factory.annotation.CustomAutowireConfigurer">
    <property name="customQualifierTypes">
        <set>
            <value>example.CustomQualifier</value>
        </set>
    </property>
</bean>

AutowireCandidateResolver通过下面确定自动装配候选者:

  • 每个bean定义的autowire-candidate
  • 在元素上任何default-autowire-candidates模式有效
  • @Qualifier注解和任何自定义注解存在注册CustomAutowireConfigurer

当多个bean限定符作为自动装配候选者时,primary的确定类似下面:如果候选者bean中有一个bean被定义primary属性值被设置为true,它将被选择。

参考代码:com.liyong.ioccontainer.starter.XmlQualifierIocContainer

1.9.7 @Resource注入

Spring也支持通过使用JSR-250注解(javax.annotation.Resource)在字段或bean属性Setter方法上注入。这是在JavaEE通用的模式,在JSF管理bean和JAX-WS端点。Spring提供这个模式去管理Spring的bean。

@Resource采用一个名字属性。默认情况下,Spring将该值解释为要注入的Bean名称。换而言之,它遵循通过名字的语义,如下所示:

public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Resource(name="myMovieFinder") //1
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }
}
  1. 这行注入@Resource

如果显示指定名称,这个默认名是从字段或Setter方法名获取。如果是字段,则采用字段名称。在使用setter方法的情况下,它采用bean属性名称。以下示例将把名为movieFinder的bean注入其setter方法中:

public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Resource
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }
}

与注解一起提供的名称被CommonAnnotationBeanPostProcessor所感知的ApplicationContext解析为bean名称。如果你显示的配置Spring的SimpleJndiBeanFactory,这个名字能通过JNDI解析。然而,我们推荐你依赖默认的行为并且使用Spring的JNDI能力去保留间接的级别。

在没有明确指定名称的@Resource使用的特殊情况下(类似于@Autowired), @Resource会找到一个主类型匹配而不是一个特定的命名bean,并解析我们熟知的可解析依赖项:BeanFactoryApplicationContextResourceLoader, ApplicationEventPublisherMessageSource接口。

因此,在下面的例子中,customerPreferenceDao字段首先查找bean名字为customerPreferenceDao然后回退查找CustomerPreferenceDao主要匹配的类型:

public class MovieRecommender {

    @Resource
    private CustomerPreferenceDao customerPreferenceDao;

    @Resource
    private ApplicationContext context; //1

    public MovieRecommender() {
    }

    // ...
}
  1. context字段是基于已知的可解析依赖项类型:ApplicationContext注入的。
1.9.8 使用@Value

@Value典型的应用去注入外部属性:

@Component
public class MovieRecommender {

    private final String catalog;

    public MovieRecommender(@Value("${catalog.name}") String catalog) {
        this.catalog = catalog;
    }
}

下面是配置:

@Configuration
@PropertySource("classpath:application.properties")
public class AppConfig { }

application.properties配置

catalog.name=MovieCatalog

在这种情况下,catalog参数和字段将等于MovieCatalog值。

Spring提供了一个默认的宽松内嵌值解析器。它将尝试去解析属性值并且如果不能被解析,属性名(例如,${catalog.name})将被作为值注入。如果要严格控制不存在的值,则应声明一个PropertySourcesPlaceholderConfigurer bean,如以下示例所示:

@Configuration
public class AppConfig {
     @Bean
     public static PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurer() {
           return new PropertySourcesPlaceholderConfigurer();
     }
}

当使用JavaConfig配置PropertySourcesPlaceholderConfigurer@Bean方法必须是static。

如果任何${}占位符不能被解析,使用上面的配置能够确保Spring初始化失败。它也可以使用方法类似setPlaceholderPrefixsetPlaceholderSuffixsetValueSeparator去自定义占位符。

Spring Boot 通过默认的PropertySourcesPlaceholderConfigurerbean配置,它能从application.propertiesapplication.yml文件获取属性。

Spring提供的内置转换器支持允许自动处理简单的类型转换(例如,转换为Integer或int)。多个逗号分隔的值能够自动的转换为String数组不需要额外的操作。

它也可能提供一个默认值类似下面:

@Component
public class MovieRecommender {

    private final String catalog;
    //defaultCatalog为默认值
    public MovieRecommender(@Value("${catalog.name:defaultCatalog}") String catalog) {
        this.catalog = catalog;
    }
}

Spring BeanPostProcessor在使用ConversionService处理将@Value中的String值转换为目标类型的过程。如果你想提供转换支持为你自己的自定义类型,你可以提供你自己的ConversionService bean实例类似下面例子:

@Configuration
public class AppConfig {
    @Bean
    public ConversionService conversionService() {
        DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService();
        conversionService.addConverter(new MyCustomConverter());
        return conversionService;
    }
}

@Value包含SpEl表达式时候,这个值将被在运行时动态地计算,类似下面例子:

@Component
public class MovieRecommender {

    private final String catalog;

    public MovieRecommender(@Value("#{systemProperties['user.catalog'] + 'Catalog' }") String catalog) {
        this.catalog = catalog;
    }
}

SpEL还可以使用更复杂的数据结构:

@Component
public class MovieRecommender {

    private final Map<String, Integer> countOfMoviesPerCatalog;

    public MovieRecommender(
            @Value("#{{'Thriller': 100, 'Comedy': 300}}") Map<String, Integer> countOfMoviesPerCatalog) {
        this.countOfMoviesPerCatalog = countOfMoviesPerCatalog;
    }
}

参考代码:com.liyong.ioccontainer.starter.XmlValueIocContainer

1.9.9 使用@PostConstruct@PreDestroy

CommonAnnotationBeanPostProcessor不仅仅识别@Resource注解也能识别JSR-250生命周期注解:javax.annotation.PostConstructjavax.annotation.PreDestroy。在Spring 2.5中引入的对这些注解的支持为初始化回调和销毁回调中描述的生命周期回调机制提供了一种替代方法。CommonAnnotationBeanPostProcessor在Spring的ApplicationContext中被注入,在生命周期的同一点上(译者:同一类回调方法,比如:销毁方法),与相应的Spring生命周期接口方法或显式声明的回调方法调用带有其中一个注解的方法。在下面的例子中,在以下示例中,缓存在初始化时预先填充,并在销毁时清除。

public class CachingMovieLister {

    @PostConstruct
    public void populateMovieCache() {
        // 填充缓存
    }

    @PreDestroy
    public void clearMovieCache() {
        // 清除缓存
    }
}

有关组合各种生命周期机制的详细信息,查看组合生命周期机制

类似@Resource一样,@PostConstruct@PreDestroy注解类型是从JDK6到JDK8的标准Java库的一部分。然而,整个javax.annotation包与JDK 9中的核心Java模块分离,最终在JDK 11中删除。如果需要,javax.annotation-api可以通过Maven中央库获取,简单地增加到应用的类路径下面和其他库一样。

参考代码:com.liyong.ioccontainer.starter.XmlLifecycleIocContainer

1.10 类路径扫描和管理组件

本章中的大多数示例都使用XML来指定在Spring容器中生成每个BeanDefinition的配置元数据。前面的部(基于注解的容器配置)分展示怎样去提供一些配置元素数据通过源码级别的注解。然而,即使在这些示例中,基本bean定义也是在XML文件中显式定义的,而注解只驱动依赖项注入。本节介绍通过扫描类路径来隐式检测候选组件的选项。候选组件是符合过滤条件的类,并在容器中注册了相应的Bean定义。这消除了使用XML进行bean注册的需要。相反,你可以使用注释(例如@Component)、AspectJ类型表达式或你自己的自定义筛选条件来选择哪些类具有在容器中注册的bean定义。

Spring3.0开始,通过Spring JavaConfig项目提供的许多Spring核心特性部分。这允许你使用Java定义bean而不是使用传统的XML文件。查看@Configuration@Bean@Import@DependsOn注解例子怎样去使用这些新特性。

1.10.1 @Component和其他构造型注解

@Repository注解是任何满足存储库角色或构造型(也称为数据访问对象或DAO)的类的标记。该标记的用途包括自动转译异常,如“异常转译”中所述。

Spring提供提供更进一步的构造型注解:@Component@Service@Controller@Component是一个通用的构造型注解对于任何Spring管理的组件。@Repository@Service@Controller是特殊化的@Component为更多特定的使用场景(在持久化、服务、表现层中)。因此,你可以使用@Component注解组件类,但是通过使用@ Repository@Service@Controller注解组件类,你的类更适合通过工具进行处理或与aspects相关联。例如,这些构造型注释成为切入点的理想目标。@Repository@Service@Controller在Spring框架未来的发布中可能增加额外的语义。因此,如果你在使用@Component@Service之间选择为你的服务层,@Service是更简洁的选择。类似地,如前所述,@Repository已经被支持作为持久化层中自动异常转换的标记。

1.10.2 使用元注解和组合组件

通过Spring提供的许多注解在你自己代码中能够被作为元注解使用。元注解也是注解它能够被应用到其他注解上。例如,前面提到的@Service注解使用@Component进行元注解,类似下面例子:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component //1
public @interface Service {

    // ...
}
  1. @Component导致@Service的处理方式与@Component相同。

你可以联合元注解去创建组合注解。例如,Spring MVC的RestController注解由@Controller@ResponseBody组成。

此外,组合注解可以选择从元注解中重新声明属性,以允许自定义。当你想去仅仅暴露一个元数据的属性子集的时候特别地有用。例如,Spring的@SessionScope注解硬编码作用域名称session但是仍然允许proxyMode的自定义。下面的列表显示SessionScope注解的定义:

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Scope(WebApplicationContext.SCOPE_SESSION)
public @interface SessionScope {

    /**
     * Alias for {@link Scope#proxyMode}.
     * <p>Defaults to {@link ScopedProxyMode#TARGET_CLASS}.
     */
    @AliasFor(annotation = Scope.class)
    ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;

}

你可以使用@SessionScope无需声明proxyMode类似下面:

@Service
@SessionScope
public class SessionScopedService {
    // ...
}

你可以覆盖proxyMode值,类似下面例子:

@Service
@SessionScope(proxyMode = ScopedProxyMode.INTERFACES)
public class SessionScopedUserService implements UserService {
    // ...
}

更多详情,查看Spring注解编程模型wiki页。

1.10.3 自动地检查类和注册BeanDefinition

Spring可以自动检测构造型类并向ApplicationContext注册相应的BeanDefinition实例。例如,下面两个类进行自动检查:

@Service
public class SimpleMovieLister {

    private MovieFinder movieFinder;

    public SimpleMovieLister(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }
}
@Repository
public class JpaMovieFinder implements MovieFinder {
    // implementation elided for clarity
}

去自动检查这些和注册对于bean,你需要增加@ComponentScan到你的@Configuration类,basePackages属性是两个类的共同的父包。(另外,你可以指定一个逗号或分号或空格分隔的列表,其中包括每个类的包路径):

@Configuration
@ComponentScan(basePackages = "org.example")
public class AppConfig  {
    // ...
}

为了简洁,前面的例子可以使用注解的value属性(也就是,@ComponentScan("org.example"))。

以下替代方法使用XML:

<?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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="org.example"/>

</beans>

< context:component-scan>使用隐式地激活< context:annotation-config>功能。当使用< context:component-scan>时,通常情况无需包含< context:annotation-config>元素。

类路径包扫描需要在类路径中对应的目录实体存在。使用Ant构建JAR时,请确保未激活JAR任务的文件专用开关。此外,在某些环境中,基于安全策略可能不会公开类路径目录-例如,在JDK 1.7.0_45及更高版本上的独立应用程序(这需要在清单中设置“信任的库” —请参见https://stackoverflow.com/questions/19394570/java-jre-7u45-breaks-classloader-getresources) 。

在JDK9的模块路径,Spring的类路径扫描通常按预期工作。然而,确保你的组件类在你的module-info描述被暴露。如果你期望Spring调用你的类非公共的成员,确保他们被打开(也就是,他们在module-info描述符中使用了一个opens声明而不是exports声明)。

此外,当你使用component-scan元素时,AutowiredAnnotationBeanPostProcessorCommonAnnotationBeanPostProcessor两者隐式地包含。也就是说两个组件被自动检查和连接在一起-所有这些都没有XML提供的任何bean配置元数据。

你可以通过设置annotation-config属性值为false禁止AutowiredAnnotationBeanPostProcessorCommonAnnotationBeanPostProcessor注册。

参考代码:com.liyong.ioccontainer.starter.AutoCheckAndRegisterBeanDefinitionIocContainer

1.10.4 使用Filer去自定义扫描

默认情况下,仅使用@Component@Repository@Service@Controller@Configuration进行注解的类或使用@Component进行注解的自定义注解是唯一被检测到的候选组件。然而,你可以修改和拓展这个行为通过使用自定义过滤器。增加@ComponentScan注解(在XML配置中context:component-scan子元素 <context:include-filter /><context:exclude-filter />)的includeFiltersexcludeFilters属性。每个过滤器元素需要typeexpression属性。下面表格描述过滤器可选项:

Filter TypeExample ExpressionDescription
annotation (default)org.example.SomeAnnotation在目标组件的类型级别上呈现或元呈现的注释(译者:元组件描述形式)。
assignableorg.example.SomeClass目标组件可分配给(扩展或实现)的类(或接口)。
aspectjorg.example..*Service+目标组件要匹配的AspectJ类型表达式。
regexorg.example.Default.*要与目标组件的类名匹配的正则表达式。
customorg.example.MyTypeFilterorg.springframework.core.type.TypeFilter接口的自定义实现.

下面的例子展示配置文件忽略所有@Repository注解且使用stub仓库替换。

@Configuration
@ComponentScan(basePackages = "org.example",
        includeFilters = @Filter(type = FilterType.REGEX, pattern = ".*Stub.*Repository"),
        excludeFilters = @Filter(Repository.class))
public class AppConfig {
    ...
}

下面清单显示相同的作用的XML配置:

<beans>
    <context:component-scan base-package="org.example">
        <context:include-filter type="regex"
                expression=".*Stub.*Repository"/>
        <context:exclude-filter type="annotation"
                expression="org.springframework.stereotype.Repository"/>
    </context:component-scan>
</beans>

你还可以通过在注解上设置useDefaultFilters = false或通过将use-default-filters =“ false”作为元素的属性来禁用默认过滤器。这有效地禁用了自动检测使用@Component@Repository@Service@Controller@RestController@Configuration注解或元注解的类的功能。

代码示例:com.liyong.ioccontainer.starter.CustomizeScanByFilterIocContainer

1.10.5 在组件中定义bean元数据

Spring组件有助于bean定义元数据到容器。在类上,你可以使用@Configuration定义同时在方法上使用@Bean即可定义元数据。下面例子展示怎样使用:

@Component
public class FactoryMethodComponent {

    @Bean
    @Qualifier("public")
    public TestBean publicInstance() {
        return new TestBean("publicInstance");
    }

    public void doWork() {
        // Component method implementation omitted
    }
}

前面的类是一个Spring组件,它有一个在它的doWork()方法特定于应用的代码。但是,它也提供了一个具有工厂方法的bean定义,该工厂方法引用了方法publicInstance()@Bean注解标识工厂方法和其他bean定义的属性,例如,通过@Qualifier注解指定限定符。其他方法级别注解能够被指定为@Scope@Lazy和自定义限定符注解。

除了它的组件初始化角色外,你可以在注入点放置@Lazy注解标记@Autowired@Inject。在这种情况下,它导致注入了惰性解析代理。

如前所述,自动装配字段和方法是被支持的同时附加的支持@Bean方法的自动装配。下面的例子展示怎样去做:

@Component
public class FactoryMethodComponent {

    private static int i;

    @Bean
    @Qualifier("public")
    public TestBean publicInstance() {
        return new TestBean("publicInstance");
    }

    // use of a custom qualifier and autowiring of method parameters
    @Bean
    protected TestBean protectedInstance(
            @Qualifier("public") TestBean spouse,
            @Value("#{privateInstance.age}") String country) {
        TestBean tb = new TestBean("protectedInstance", 1);
        tb.setSpouse(spouse);
        tb.setCountry(country);
        return tb;
    }

    @Bean
    private TestBean privateInstance() {
        return new TestBean("privateInstance", i++);
    }

    @Bean
    @RequestScope
    public TestBean requestScopedInstance() {
        return new TestBean("requestScopedInstance", 3);
    }
}

这个示例自动将String方法参数country连接到另一个名为privateInstance的bean上的age属性的值。Spring表达式语言元素定义属性值通过符号#{ }。对应@Value注解,表达式解析器已经预先配置当解析表达式文本的时候查找bean的名称。

自Spring4.3以后,你可以声明一个类型InjectionPoint(或其更具体的子类:DependencyDescriptor)工厂方法参数来访问触发当前bean创建的请求注入点。请注意,这仅适用于实际创建bean实例,不会注入已经存在的实例。这个特性大多数用在单例bean作用域。对于其他作用域,factory方法只能看到在给定作用域中触发创建新bean实例的注入点(例如,依赖触发懒加载单例bean的创建)。在这种情况下,可以将提供的注入点元数据与语义一起使用。下面的例子展示怎样使用InjectionPoint

@Component
public class FactoryMethodComponent {

    @Bean @Scope("prototype")
    public TestBean prototypeInstance(InjectionPoint injectionPoint) {
        return new TestBean("prototypeInstance for " + injectionPoint.getMember());
    }
}

常规Spring组件中的@Bean方法的处理方式与Spring 的@Configuration类中的@Bean方法不同。不同点是@Component这些类不会被CGLIB增强去拦截方法和字段的调用。CGLIB代理是调用@Configuration类中的@Bean方法中的方法或字段来创建对协作对象的bean元数据引用的方法。这些方法不会通过常规的Java语法调用,而是通过容器,去提供常规的生命周期管理和Spring bean的代理,即使通过对@Bean方法的编程调用来引用其他bean。相反,在普通@Component类中的@Bean方法中调用方法或字段具有标准Java语义,没有其他特定的CGLIB处理或约束。说明:@Component中调用@Bean注解的方法和常规的方法调用一样,而在@Configuration中的@Bean调用则是通过容器去查找Bean和生成bean的元数据。

你可以声明@Bean方法为static的,这个配置类实例在容器中没有被创建时也允许调用这个方法(译者:脱离了Configuration实例)。当定义后置处理器bean(例如,BeanFactoryPostProcessorBeanPostProcessor类型)时这是特殊的场景,因为在容器生命周期中更早的获取bean实例并且应该在那时避免触发配置类中的其他部分。说明:把@Bean方法标记为static后脱落了bean实例管理所以在需要提前触发场景可以使用,这样避免未被实例化的bean其他@Bean方法被触发。

静态@Bean方法的调用永远不会被容器拦截,即使在@Configuration类中也是如此(在这个章节前面被描述),由于技术限制:CGLIB子类仅仅能够覆盖非静态类。因此,直接调用另一个@Bean方法具有标准的Java语义,从而导致直接从工厂方法本身直接返回一个独立的实例。

@Bean方法的Java语言可见性不会对Spring容器中的最终bean定义产生直接影响。你可以在非@Configuration类中自由声明自己的工厂方法,也可以在任何地方声明静态方法。常规的在@Configuration类中的@Bean方法需要可覆盖的。也就是说,他们不能被声明未privatefinal

@Bean方法在给定的组件或配置类的基类中也是可以被发现的,以及在Java 8默认方法中由组件或配置类实现的接口中声明的方法。这为组合复杂的配置安排提供了很大的灵活性,从Spring 4.2开始,通过Java 8默认方法甚至可以进行多重继承。

最后,一个类可以包含同一个bean的多个@Bean方法,根据运行时可用的依赖关系,可以安排使用多个工厂方法。这与在其他配置方案中选择“最贪婪”的构造函数或工厂方法的算法相同。在构造时会选择具有最大可满足依赖性的变量,这类似于容器在多个@Autowired构造函数之间进行选择的方式。

参考代码:com.liyong.ioccontainer.starter.XmlBeanDefinationWithinComponetIocContainer

1.10.6 命名自动检查组件

在扫描过程中自动检测到组件时,其Bean名称由该扫描程序已知的BeanNameGenerator策略生成。默认情况,任何Spring的构造型注解(@Component@Repository@Service@Controller)包含一个名字value,从而提供名称和对应bean的定义。

如果注解包含没有名字的value或者其他的检查组件(例如通过自定义过滤器),默认bean名称生成器返回没有大写字母的非限定类名称。例如,如果下面的组件类被检查到,他们的名字将会是myMovieListermovieFinderImpl

@Service("myMovieLister")
public class SimpleMovieLister {
    // ...
}
@Repository
public class MovieFinderImpl implements MovieFinder {
    // ...
}

如果你不想依赖默认的bean命名策略,你可以提供一个自定义的bean命名策略。首先,实现BeanNameGenerator 接口,并且确保包含无参构造函数。然后,当配置扫描器的时候,提供全限定类名称,类似下面例子注释和bean定义。

如果由于多个自动检测到的组件具有相同的非限定类名而导致命名冲突(具有相同名称但位于不同包中的类),你可能需要配置一个BeanNameGenerator,该名称默认为生成的Bean名称的完全限名的类名称(译者:类路径全限定名称)。从Spring Framework 5.2.3开始,位于org.springframework.context.annotation包中的FullyQualifiedAnnotationBeanNameGenerator可以用于此目的。

@Configuration
@ComponentScan(basePackages = "org.example", nameGenerator = MyNameGenerator.class)
public class AppConfig {
    // ...
}
<beans>
    <context:component-scan base-package="org.example"
        name-generator="org.example.MyNameGenerator" />
</beans>

作为一般规则,当其他组件可能显式引用该名称时,请考虑使用注释指定该名称。另一方面,只要容器负责连接(连接其他组件),自动生成的名称就足够了。

代码示例:com.liyong.ioccontainer.starter.BeanNameGeneratorIoCContainer

1.10.7 为自动检查组件提供作用域

一般情况下Spring管理的组件,对于自动检查组件默认情况下是singleton作用域。然而,有时候你需要不同的作用域时可以通过@Scope注解指定。你可以提供在注解中的作用域名称,类似下面的例子:

@Scope("prototype")
@Repository
public class MovieFinderImpl implements MovieFinder {
    // ...
}

@Scope注解仅在具体的bean类(用于带注解的组件)或工厂方法(对于@Bean方法)上进行内省。与XML的bean定义对比,这里没有bean定义继承的概念,并且类级别的继承层次结构与元数据目的无关。

更多详情在Spring上下文中在特定的web作用域,例如requestsession,查看Request、 Session、 Application和 WebSocket 作用域。与这些作用域的预构建注解一样,你可以通过使用Spring的元注解方法构建你自己的作用域注解:例如,自定义元注解@Scope("prototype"),尽可能地声明一个自定义scoped-proxy模式。

你可以实现ScopeMetadataResolver接口,去提供一个自定义策略为作用域解析,而不是依赖基于注解的方法。确保包含一个无参构造函数。当配置扫描器的时候你需要提供全限定类名,类似下面注解和bean定义例子:

@Configuration
@ComponentScan(basePackages = "org.example", scopeResolver = MyScopeResolver.class)
public class AppConfig {
    // ...
}
<beans>
    <context:component-scan base-package="org.example" scope-resolver="org.example.MyScopeResolver"/>
</beans>

当使用一些非单例bean作用时,可能需要为作用域对象声明代理。这个原因在 Scoped Beans as Dependencies中描述。为了这个目的,在component-scan元素上可以使用scoped-proxy属性。有三种可以的值时:nointerfacestargetClass。例如,下面的配置是标准的JDK动态代理:

@Configuration
@ComponentScan(basePackages = "org.example", scopedProxy = ScopedProxyMode.INTERFACES)
public class AppConfig {
    // ...
}
<beans>
    <context:component-scan base-package="org.example" scoped-proxy="interfaces"/>
</beans>
1.10.8 为注解提供限定元数据

@Qualifie注解在基于注解细粒度调整限定符中已经讨论。本节中的示例演示了在解析autowire候选对象时使用@Qualifier注解和自定义qualifier注解来提供细粒度控制。因为这些例子是基于在XML定义的基础上的,因此限定符元数据是在XML中通过使用qualifier或bean子元素meta在候选者bean定义上被提供的。当依靠类路径扫描为组件自动检测时,可以在候选者类中为限定符元数据提供类型级别的注解。下面三个例子演示了这个技术:

@Component
@Qualifier("Action")
public class ActionMovieCatalog implements MovieCatalog {
    // ...
}
@Component
@Genre("Action")//自定义限定符
public class ActionMovieCatalog implements MovieCatalog {
    // ...
}
@Component
@Offline//自定义限定符
public class CachingMovieCatalog implements MovieCatalog {
    // ...
}

与大多数基于注释的替代方法一样,请记住,注释元数据绑定到类定义本身,而XML的使用允许相同类型的多个bean在其限定符元数据中提供变体,因为元数据是针对每个实例而不是针对每个类提供的。

代码示例:com.liyong.ioccontainer.starter.XmlGenericsQualifierIocContainer

1.10.9 生成候选者组件索引

虽然类扫描非常快,在编译的时候通过创建一个静态的候选者清单去改善大型项目的启动性能。在这种模式下,作为组件扫描目标的所有模块都必须使用此机制。

现有的@ComponentScan<context:component-scan>指令必须保持不变,以便请求上下文扫描某些包中的候选者。当ApplicationContext检测索引的时候,它自动地使用索引而不是去扫描类路径。

去生成这个索引,增加一个附加的依赖到每个模块,它包含组件是指令扫描的目标。下面的例子显示怎样使用Maven:

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context-indexer</artifactId>
        <version>5.2.6.RELEASE</version>
        <optional>true</optional>
    </dependency>
</dependencies>

在Gradle 4.5已经之前的版本,依赖需要在compileOnly配置中声明,类似下面的例子:

dependencies {
    compileOnly "org.springframework:spring-context-indexer:5.2.6.RELEASE"
}

在Gradle 4.5已经之前的版本,在annotationProcessor配置中被声明,类似下面的例子:

dependencies {
 annotationProcessor "org.springframework:spring-context-indexer:{spring-version}"
}

处理过程在jar文件中生成一个META-INF/spring.components文件。

在IDE中使用这种模式时,必须将spring-context-indexer注册为注解处理器,以确保在更新候选组件时索引是最新的。

当在类路径中找到META-INF/spring.components时索引被自动地激活。如果某些库(或用例)的索引部分可用,但无法为整个应用程序构建索引,你可以通过将spring.index.ignore设置为true来回退到常规的类路径扫描(就像根本没有索引一样),要么在系统属性或者类路径的根目录下spring.properties文件中配置。

作者

个人从事金融行业,就职过易极付、思建科技、某网约车平台等重庆一流技术团队,目前就职于某银行负责统一支付系统建设。自身对金融行业有强烈的爱好。同时也实践大数据、数据存储、自动化集成和部署、分布式微服务、响应式编程、人工智能等领域。同时也热衷于技术分享创立公众号和博客站点对知识体系进行分享。

博客地址:http://youngitman.tech

CSDN:https://blog.csdn.net/liyong1028826685

微信公众号:

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

青年IT男

您的打赏就是对我的肯定!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值