Spring-IoC容器文档(5.0.7BUILD-SNAPSHOT)

1.1 Spring IoC容器和bean简介

本章介绍了Spring Framework实现的控制反转(IoC)原理。IoC也称为依赖注入(DI)。

IoC是一个过程,通过这个过程,对象定义它们的依赖关系,即它们使用的其他对象,只能通过构造函数参数,工厂方法的参数,或者在构造或从工厂方法返回后在对象实例上设置的属性。 然后容器在创建bean时注入这些依赖项。这个过程和我们平常使用的流程是相反的,因此称为控制反转,bean本身通过使用类的直接构造或诸如服务定位器模式之类的机制来控制其依赖关系的实例化或位置。

org.springframework.beansorg.springframework.context包是Spring框架的IoC容器的基础。该 BeanFactory接口提供了一种能够管理任何类型对象的高级配置机制。 ApplicationContextBeanFactory的一个子接口。它增加了与Spring的AOP功能的更容易的集成; message source处理(用于国际化),事件发布和特定于应用程序层的上下文,例如WebApplicationContext 在Web应用程序中使用的上下文。

简而言之,BeanFactory提供了配置框架和基本功能,ApplicationContext添加了更多企业特定的功能。

在Spring中,构成应用程序主干并由Spring IoC 容器管理的对象称为bean。bean是一个由Spring IoC容器实例化,组装和管理的对象。否则,bean只是应用程序中众多对象之一。Bean及其之间的依赖 关系反映在容器使用的配置元数据中。

1.2 容器概述

接口org.springframework.context.ApplicationContext代表Spring IoC容器,负责实例化,配置和组装上述bean。容器通过读取配置元数据获取有关要实例化,配置和组装的对象的指令。配置元数据以XML,Java注释或Java代码表示。它允许您表达组成应用程序的对象以及这些对象之间丰富的相互依赖性。

下图是Spring工作原理的高级视图。您的应用程序类与配置元数据相结合,以便在ApplicationContext创建和初始化之后,您拥有完全配置且可执行的系统或应用程序。


Spring IOC容器

1.2.1 配置元数据

如上图所示,Spring IoC容器使用一种配置元数据形式 ; 此配置元数据表示您作为应用程序开发人员如何告诉Spring容器在应用程序中实例化,配置和组装对象。

传统上,配置元数据以简单直观的XML格式提供,本章大部分内容用于传达Spring IoC容器的关键概念和功能。

  • 基于注释的配置:Spring 2.5引入了对基于注释的配置元数据的支持。

  • 基于Java的配置:从Spring 3.0开始,Spring JavaConfig项目提供的许多功能成为核心Spring Framework的一部分。因此,您可以使用Java而不是XML文件在应用程序类外部定义bean。要使用这些新功能,请参阅@Configuration@Bean@Import@DependsOn注释。

Spring配置由容器必须管理的至少一个且通常不止一个bean定义组成。基于XML的配置元数据显示这些bean配置为<bean/>顶级元素内的<beans/>元素。Java配置通常@Bean在@Configuration类中使用带注释的方法。

1.2.2 实例化容器

实例化Spring IoC容器非常简单。提供给ApplicationContext构造函数的位置路径实际上是资源字符串,允许容器从各种外部资源(如本地文件系统,Java等)加载配置元数据CLASSPATH。

ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");

以下示例显示了服务层对象(services.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"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- services -->

    <bean id="petStore" class="org.springframework.samples.jpetstore.services.PetStoreServiceImpl">
        <property name="accountDao" ref="accountDao"/>
        <property name="itemDao" ref="itemDao"/>
        <!-- additional collaborators and configuration for this bean go here -->
    </bean>

    <!-- more bean definitions for services go here -->

</beans>

编写基于XML的配置元数据
让bean定义跨越多个XML文件会很有用。通常,每个单独的XML配置文件都代表架构中的逻辑层或模块。

您可以使用应用程序上下文构造函数从所有这些XML片段加载bean定义。此构造函数采用多个Resource位置,如上一节中所示。或者,使用一个或多个<import/>元素来从另一个或多个文件加载bean定义。例如:

<beans>
    <import resource="services.xml"/>
    <import resource="resources/messageSource.xml"/>
    <import resource="/resources/themeSource.xml"/>

    <bean id="bean1" class="..."/>
    <bean id="bean2" class="..."/>
</beans>

在前面的例子中,外部bean定义是从三个文件加载: services.xml,messageSource.xml,和themeSource.xml。所有位置路径都与执行导入的定义文件相关,因此services.xml必须与执行导入的文件位于相同的目录或类路径位置, messageSource.xml而且themeSource.xml必须位于resources导入文件位置下方的位置。正如您所看到的,忽略了一个前导斜杠,但考虑到这些路径是相对的,最好不要使用斜杠。<beans/>根据Spring Schema,要导入的文件的内容(包括顶级元素)必须是有效的XML bean定义。

1.2.3 使用容器

ApplicationContext是高级工厂的接口,能够维护不同bean及其依赖项的注册表。使用该方法,T getBean(String name, Class<T> requiredType)可以检索Bean的实例。

在ApplicationContext可以读取bean定义并访问它们,如下所示:

//创建并配置beans
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");

// 回调实例
PetStoreService service = context.getBean("petStore", PetStoreService.class);

// 使用实例
List<String> userList = service.getUsernameList();

如果需要,可以在ApplicationContext不同的配置源中读取相同的读取bean定义。

然后,可以使用getBean()方法来检索Bean的实例。该ApplicationContext接口还有一些其他方法可用于检索bean,但理想情况下,应用程序代码绝不应使用它们。实际上,应用程序代码根本不应该调用该 getBean()方法,这样就不会依赖于Spring API。例如,Spring与Web框架的集成为各种Web框架组件(如控制器和JSF托管bean)提供依赖注入,允许通过元数据(例如自动装配注释)声明对特定bean的依赖性。

1.3 Bean概述

  Bean元数据和手动提供的单例实例需要尽早注册,以便容器在自动装配和其他步骤中正确推理它们。重写现有元数据和单例在某种程度上受到支持,但在运行时注册新的Bean(与实时访问工厂同时)并为得到正式支持,并且可能导致并发访问异常或者Bean状态不一致。

1.3.1 命名Bean

Bean的ID唯一性仍由容器容器强制执行,不再由XML解析器执行。

大型系统中,每个子系统都有自己的一组对象定义,所以可以使用如下别名:

<alias name="fromName" alias="toName"/>
<!-- 多个引用一个,或者用@Bean-->
<alias name="subsystemA-dataSource" alias="subsystemB-dataSource"/>
<alias name="subsystemA-dataSource" alias="myApp-dataSource" />

1.3.2 实例化Bean

Bean本意是创建一个或多个对象的配方(recipe)。容器被询问时查看命名bean的配方(recipe),并使用由该bean定义封装的配置来创建(或获取)实际对象。

如果使用基于XML的配置元数据,可以在<bean/>的class属性中指定对象的类型。这个属性是BeanDefinition实例中的Class属性,通常是强制的。可以通过一下Class两种方式之一使用class属性:

  • 通过反射调用构造函数,与new操作符的Java代码相似

  • 调用static工厂方法创建该Bean(情况较少)

内部类名称
如果要为static嵌套类配置一个bean定义,必须使用嵌套类的二进制名称。
例如,com.example.Foo$Bar。Foo是com.example中的一个类,Bar是Foo的static的嵌套类。

(一)用构造函数
通过构造函数创建Bean方法时,所有普通类都可以被Spring使用兼容,只需要指定Bean类(XML或者@Bean注解)。但是,根据不同情况,可能需要一个默认(空)构造函数。其实,Spring容器几乎能够管理任何类,不限于JavaBeans(默认构造器、getter和setter方法)。

<!--基于XML配置,可以按如下方式指定Bean类-->
<bean id="exampleBean" class="examples.ExampleBean"/>
<bean id="anotherExample" class="examples.ExampleBeanTwo"/>

使用静态工厂方法实例化
在使用静态工厂方法创建Bean 时,可以使用factory-method指定工厂方法本身的名称的属性。

<!--XML-->
<bean id="clientService"
    class="examples.ClientService"
    factory-method="createInstance"/>
//Java中实现
public class ClientService {
    private static ClientService clientService = new ClientService();
    private ClientService() {}

    public static ClientService createInstance() {
        return clientService;
    }
}

(二)使用实例工厂方法实例化
使用实例工厂的实例化从容器调用现有bean非静态方法来创建新bean。要使用此机制,需要将class属性留空,并在factory-bean属性指定当前(或父/祖代)容器的bean名称,该容器包含要调用创建对象的实例方法。

<!-- bean工厂:包含createClientServiceInstance -->
<bean id="serviceLocator" class="examples.DefaultServiceLocator">
</bean>

<!-- 通过bean工厂创建bean -->
<bean id="clientService"
    factory-bean="serviceLocator"
    factory-method="createClientServiceInstance"/>
public class DefaultServiceLocator {

    private static ClientService clientService = new ClientServiceImpl();

    public ClientService createClientServiceInstance() {
        return clientService;
    }
}

说白了,所谓工厂就是一个包含多个返回值方法的类,这些返回值是其它类(比如返回上面的ClientService)。相比之下,SpringBoot注解模式能加简洁和便利,省去了XML大段的配置。

当然一个工厂类也可以拥有多个工厂方法,如图所示,一个标准的XML写法:

<!--此XML对应的Java类省略-->
<bean id="serviceLocator" class="examples.DefaultServiceLocator">
    <!-- inject any dependencies required by this locator bean -->
</bean>

<bean id="clientService"
    factory-bean="serviceLocator"
    factory-method="createClientServiceInstance"/>

<bean id="accountService"
    factory-bean="serviceLocator"
    factory-method="createAccountServiceInstance"/>

在Spring文档中,工厂bean指的是Spring容器中配置的bean,它通过实例或静态工厂方法创建对象。相反,FactoryBean(注意大写字母)是指特定于Spring的FactoryBean。

1.4 依赖

1.4.1 依赖注入

依赖注入是一个过程,简单来说就是,对于那些【被调用/被依赖】的类,Spring在最开始的时候生成对象实例(通过构造器成或工厂方法返回),然后再将构造器参数、工厂方法参数、属性值赋进去。容器在创建bean时会注入这些依赖关系。

夸张一点理解,DI就是你程序中所有需要new的对象,你给打上一个@Autowired标记。程序启动的时候,Spring扫描标记,帮你都new好,按类名和类值归档,存放在一个Map中。最后扫描程序,看见打了标记的类,就去Map中找对应的类名,然后赋值,就像A a = map.get("A"),当然=号后面都被隐藏了。

(一)基于构造函数的依赖注入

下面代码,MovieFinder 代表一个依赖

public class SimpleMovieLister {

    //SimpleMovieLister依赖于MovieFinder
    private MovieFinder movieFinder;

    // 有构造器使得Spring容器能够注入MovieFinder
    public SimpleMovieLister(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // 以下是各种使用movieFinder的各种操作,寻找各种小电影...
}

(二)基于Setter的依赖注入
基于Setter的 DI通过调用无参数构造函数或无参数static工厂方法来实例化bean之后,调用bean上的容器调用setter方法来完成。大多数人用XML bean定义,或是注释组件(@Component,@Controller),或是在@Configuration下面使用@Bean

public class SimpleMovieLister {


    private MovieFinder movieFinder;

    // 差别在这里
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

}

Spring主张构造器注入,因为这样子应用程序组件就变成了不可变对象,并确保依赖关系不为null。但是这意味着这个类可能有太多的责任,需要重构。
而Setter注入主要只应用于可选的依赖关系,这些依赖关系可以在类中分配合理的默认值。否则,必须在代码使用依赖关系的任何地方执行非空检查。setter注入的一个好处是setter方法使得该类的对象可以重新配置或稍后重新注入(一般程序都应用setter注入)。

(三)依赖性解决过程
容器执行bean依赖性解析如下:

【创建bean】:使用ApplicationContext描述所有bean的创建并初始化该元素。配置可以通过XML,Java代码或注释来完成。
 ↓ ↓ ↓
【建立依赖关系】:当bean被实际创建时,属性、构造函数参数被提供给bean 。
 ↓ ↓ ↓
【依赖关系赋值】:每个属性或构造函数参数都是要设置实际值,或者是对容器中另一个bean的引用。
 ↓ ↓ ↓
【赋值时的转换】:默认情况下,Spring能够转换成字符串格式提供给所有的内置类型,比如数值int, long,String,boolean等。

Spring容器在创建时验证每个bean的配置。但是,在 实际创建 bean之前,bean属性本身不会被设置。一般只有在请求时才创建bean,除非Beans是单例并且被设置为预先实例化的(默认的)才会在创建容器时创建的。创建一个bean可能会导致创建一个bean图,因为bean的依赖关系及其依赖关系的依赖关系(等等)被创建和分配。请注意,即使首次创建bean,这些依赖项之间的不匹配也可能会很晚出现。

循环依赖
例如:类A需要通过 构造函数注入 的类B的实例,而类B需要通过 构造函数注入 的类A的实例。如果将类A和B的Bean配置为相互注入,则Spring IoC容器会在运行时检测到此循环引用,并引发一个 BeanCurrentlyInCreationException。

一种可能的解决方案是避免构造函数注入并仅使用setter注入。【setter可行是因为加入三级缓存,但是加入singletonFactories三级缓存的前提是执行了构造器,所以构造器的循环依赖没法解决。】

如果不存在循环依赖关系,则当一个或多个协作bean被注入到一个依赖bean中时,每个协作bean都被注入到依赖bean 之前完全配置。这意味着如果bean A对bean B有依赖性,Spring IoC容器在调用bean A上的setter方法之前完全配置bean B

(四)依赖注入的例子
基于Setter的配置:

<bean id="exampleBean" class="examples.ExampleBean">
    <!-- 使用ref元素来实现setter方式的依赖注入,xml是嵌套的 -->
    <property name="beanOne">
        <ref bean="anotherExampleBean"/>
    </property>

    <!-- 使用ref元素来实现setter方式的依赖注入 -->
    <property name="beanTwo" ref="yetAnotherBean"/>
    <property name="integerProperty" value="1"/>
</bean>

<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
public class ExampleBean {

    private AnotherBean beanOne;

    private YetAnotherBean beanTwo;

    private int i;

    public void setBeanOne(AnotherBean beanOne) {
        this.beanOne = beanOne;
    }

    public void setBeanTwo(YetAnotherBean beanTwo) {
        this.beanTwo = beanTwo;
    }

    public void setIntegerProperty(int i) {
        this.i = i;
    }
}

基于构造器的配置:

<bean id="exampleBean" class="examples.ExampleBean">
    <!-- constructor injection using the nested ref element -->
    <constructor-arg>
        <ref bean="anotherExampleBean"/>
    </constructor-arg>

    <!-- constructor injection using the neater ref attribute -->
    <constructor-arg ref="yetAnotherBean"/>
    <constructor-arg type="int" value="1"/>
</bean>

<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
public class ExampleBean {

    private AnotherBean beanOne;

    private YetAnotherBean beanTwo;

    private int i;

    public ExampleBean(
        AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) {
        this.beanOne = anotherBean;
        this.beanTwo = yetAnotherBean;
        this.i = i;
    }
}

1.4.2 详细的配置

(一)直接值
下面是标准的配置,但如果不用IDEA这样的IDE,错字将会在运行时被发现而不是再设计时。

<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    <!-- results in a setDriverClassName(String) call -->
    <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://localhost:3306/mydb"/>
    <property name="username" value="root"/>
    <property name="password" value="masterkaoli"/>
</bean>

(二)内部Bean
内部bean定义不需要定义的id或名称; 如果指定,容器不使用这样的值作为标识符。容器scope在创建时也会忽略该标志:内部bean 始终是匿名的,并且它们始终使用外部bean创建。这是不是可以内部bean注入到协作bean以外进入封闭豆或独立访问它们。

<bean id="outer" class="...">
    <!-- instead of using a reference to a target bean, simply define the target bean inline -->
    <property name="target">
        <bean class="com.example.Person"> <!-- 内部 bean -->
            <property name="name" value="Fiona Apple"/>
            <property name="age" value="25"/>
        </bean>
    </property>
</bean>

(三)集合
在<list/>,<set/>,<map/>,和<props/>元素,你将Java的性能和参数Collection类型List,Set,Map,和Properties分别。

<bean id="moreComplexObject" class="example.ComplexObject">
    <!-- java.util.Properties -->
    <property name="adminEmails">
        <props>
            <prop key="administrator">administrator@example.org</prop>
            <prop key="support">support@example.org</prop>
            <prop key="development">development@example.org</prop>
        </props>
    </property>
    <!-- list-->
    <property name="someList">
        <list>
            <value>a list element followed by a reference</value>
            <ref bean="myDataSource" />
        </list>
    </property>
    <!-- map -->
    <property name="someMap">
        <map>
            <entry key="an entry" value="just some string"/>
            <entry key ="a ref" value-ref="myDataSource"/>
        </map>
    </property>
    <!-- set  -->
    <property name="someSet">
        <set>
            <value>just some string</value>
            <ref bean="myDataSource" />
        </set>
    </property>
</bean>

以下示例演示了集合合并:

<beans>
    <bean id="parent" abstract="true" class="example.ComplexObject">
        <property name="adminEmails">
            <props>
                <prop key="administrator">administrator@example.com</prop>
                <prop key="support">support@example.com</prop>
            </props>
        </property>
    </bean>
    <bean id="child" parent="parent">
        <property name="adminEmails">
            <!-- the merge is specified on the child collection definition -->
            <props merge="true">
                <prop key="sales">sales@example.com</prop>
                <prop key="support">support@example.co.uk</prop>
            </props>
        </property>
    </bean>
<beans>

子集继承了父类所有的元素:

administrator=administrator@example.com 
sales=sales@example.com 
support = support@example.co.uk

1.4.3 使用依赖

如果一个bean是另一个bean的依赖,那通常意味着一个bean被设置为另一个bean的属性。通常,您可以使用基于XML的配置元数据中的<ref/>元素完成此操作。但是,有时bean之间的依赖性不那么直接; 例如,类中的静态初始化器需要被触发,例如数据库驱动程序注册。在depends-on使用此元素的bean被初始化之前,该属性可以显式强制一个或多个bean被初始化。

<bean id="beanOne" class="ExampleBean" depends-on="manager,accountDao">
    <property name="manager" ref="manager" />
</bean>

<bean id="manager" class="ManagerBean" />
<bean id="accountDao" class="x.y.jdbc.JdbcAccountDao" />

bean定义中的depends-on属性可以指定一个初始化时间依赖关系,并且在只有singleton bean 的情况下可以指定一个相应的销毁时间依赖关系。定义在depends-on中的bean首先被销毁。因此depends-on也可以控制关​​机顺序。

1.4.4 Lazy-initialized beans

一个延迟初始化bean告诉IoC容器在第一次请求时创建一个bean实例,而不是在启动时。

<bean id="lazy" class="com.foo.ExpensiveToCreateBean" lazy-init="true"/>
<bean name="not.lazy" class="com.foo.AnotherBean"/>

然而,当一个延迟初始化bean是一个未经过延迟初始化的单例bean的依赖关系时 ,它ApplicationContext会在启动时创建延迟初始化bean,因为它必须满足单例的依赖关系。延迟初始化的bean被注入到其他地方的singleton bean中,而不是延迟初始化的。
我们还可以通过使用元素default-lazy-init上的属性来控制容器级别的延迟初始化,例如:

<beans default-lazy-init="true">
    <!--没有bean会被预加载... -->
</beans>

1.4.6 方法注入

似乎在Spring-AOP中有更好的实现。本节可以忽略

在大多数应用场景中,容器中的大部分都是单例bean。当单例bean需要与另一个单例bean协作,或者非单例bean需要与另一个非单例bean协作时,通常通过将一个bean定义为另一个bean的属性来处理依赖。当bean生命周期不同时会出现问题。假设单例bean A需要使用非单例(原型)bean B,可能在A上的每个方法调用上。容器只创建一次单例bean A,因此只有一次机会来设置属性。每次需要时,容器都不能向bean A提供bean B的新实例。

解决方法有:
(一)Lookup method 注入

package fiona.apple;

// 不需要引入其他Spring类

public abstract class CommandManager {

    public Object process(Object commandState) {
        // grab a new instance of the appropriate Command interface
        Command command = createCommand();
        // set the state on the (hopefully brand new) Command instance
        command.setState(commandState);
        return command.execute();
    }

    // 那么我们在哪里实现这个方法?
    protected abstract Command createCommand();
}

在包含要注入的方法的客户机类(CommandManager在这种情况下)中,要注入的方法需要以下形式的签名:<public|protected> [abstract] <return-type> theMethodName(no-arguments);
如果是abstract方法,则动态生成;否则,动态生成的子类会覆盖原始类中定义的具体方法。例如:

<!-- a stateful bean deployed as a prototype (non-singleton) -->
<bean id="myCommand" class="fiona.apple.AsyncCommand" scope="prototype">
    <!-- inject dependencies here as required -->
</bean>

<!-- commandProcessor uses statefulCommandHelper -->
<bean id="commandManager" class="fiona.apple.CommandManager">
    <lookup-method name="createCommand" bean="myCommand"/>
</bean>

或者

public abstract class CommandManager {

    public Object process(Object commandState) {
        Command command = createCommand();
        command.setState(commandState);
        return command.execute();
    }

    @Lookup("myCommand")
    protected abstract Command createCommand();
}

(二)任意方法替换

//想要替换的类
public class MyValueCalculator {

    public String computeValue(String input) {
        // some real code...
    }

    // some other methods...
}

/**
 * 通过重写computeValue(String)方法来实现
 * implementation in MyValueCalculator
 */
public class ReplacementComputeValue implements MethodReplacer {

    public Object reimplement(Object o, Method m, Object[] args) throws Throwable {
        // get the input value, work with it, and return a computed result
        String input = (String) args[0];
        ...
        return ...;
    }
}

<!-- 部署原始类并指定方法覆盖的bean定义如下所示:-->
<bean id="myValueCalculator" class="x.y.z.MyValueCalculator">
    <!-- arbitrary method replacement -->
    <replaced-method name="computeValue" replacer="replacementComputeValue">
        <arg-type>String</arg-type>
    </replaced-method>
</bean>

<bean id="replacementComputeValue" class="a.b.c.ReplacementComputeValue"/>

1.5 Bean的作用域

范围 描述
singleton (默认)每个Spring IoC容器将单个bean定义作用于单个对象实例。
prototype 单个bean对应大量的对象实例
request 将单个bean定义作用于单个HTTP请求的生命周期; 也就是说,每个HTTP请求都有自己的实例,这个实例是在单个bean定义的背后创建的。只有在Web认知的Spring环境中才有效ApplicationContext。
session 将一个单一的bean定义作用于HTTP的生命周期Session。只有在Web认知的Spring环境中才有效ApplicationContext。
application 将范围定义为a的生命周期的单个bean定义ServletContext。只有在Web认知的Spring环境中才有效ApplicationContext。
websocket 将范围定义为a的生命周期的单个bean定义WebSocket。只有在Web认知的Spring环境中才有效ApplicationContext。

1.5.1 Singleton范围

当你定义一个bean定义并将其作为一个singleton作用域时,Spring IoC容器恰好创建了该bean定义的对象的一个实例。这个单实例存储在这些单例bean的缓存中,并且该命名bean的所有后续请求和引用都会返回缓存的对象。

singleton.png

Spring的单例bean概念与四人帮(GoF)模式书中定义的Singleton模式不同。GoF Singleton对对象的范围进行硬编码,以便每个ClassLoader创建一个特定类的唯一实例。Spring单例的范围最好按容器和每个bean来描述。这意味着如果您为单个Spring容器中的特定类定义一个bean,那么Spring容器将创建该bean定义所定义的类的一个且仅有的一个实例。单例作用域是Spring中的默认作用域。要将一个bean定义为XML中的单例,您可以编写,例如:

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

<!-- 等价与上面的方式(singleton是默认的) -->
<bean id="accountService" class="com.foo.DefaultAccountService" scope="singleton"/>

1.5.2 prototype范围

每次都会创建一个新的Bean。


prototype.png

XML中的定义:

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

原型就是典型的管杀不管埋。在某些方面,Spring容器在原型范围bean方面的作用是Java new操作符的替代。在原型的情况下,调用配置的销毁 生命周期回调。客户端代码必须清理原型范围的对象并释放原型bean持有的昂贵资源。为了让Spring容器释放原型范围bean所拥有的资源,请尝试使用自定义bean后期处理器,它拥有对需要清理的bean的引用。

1.5.3 具有原型bean依赖关系的单例bean

若单例bean中引用的原型的bean,那么,在实例化时会解析依赖关系。然后将实例化(new)一个新的原型bean,然后依赖注入到单例bean中。这个新的原型实例和单例bean唯一对应。

上面是一一对应的,意味着你不能再次依赖注入一个新的原型,因为这个注入只发生一次,如果想要单例bean重复获取原型bean的新实例,可以参考上面的方法注入。

1.5.4 请求,会话,应用程序和WebSocket范围

request,session,application和websocket范围仅仅是基于web的Spring ApplicationContext实现(像XmlWebApplicationContext)。但是如果你在这些范围内使用常规的Spring IoC容器,比如ClassPathApplicationContext,那么IllegalStateException就会抛出未知的bean范围错误。

如果你在Spring Web MVC中访问范围化的bean,实际上,在由Spring处理的请求中DispatcherServlet,不需要特别的设置: DispatcherServlet已经公开了所有相关的状态。

(一)Request scope

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

或者

@RequestScope
@Component
public class LoginAction {
    // ...
}

(二)Session scope

<bean id="userPreferences" class="com.foo.UserPreferences" scope="session"/>
@SessionScope
@Component
public class UserPreferences {
    // ...
}

(三)Application scope

<bean id="appPreferences" class="com.foo.AppPreferences" scope="application"/>
@ApplicationScope
@Component
public class AppPreferences {
    // ...
}

依赖关系

如果想将HTTP请求范围的bean注入(例如)一个更长寿的另一个bean中,可以选择AOP代理来代替bean。也就是说,需要注入一个代理对象,该对象公开与作用域对象相同的公共接口,但也可以从相关作用域(例如HTTP请求)中检索目标对象,并将方法调用委托给实际对象。

以下示例只有一行,但了解“原因”以及它背后的“原理”很重要。

<?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定义中。让我们来看看下面的单例bean定义,并将其与您需要为上述范围定义的内容进行对比(请注意,下面的 userPreferencesbean定义是不完整的)。

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

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

这里的要点是 userManagerbean是一个单独的实例:每个容器只会实例化一次,并且它的依赖关系(在本例中只有一个userPreferencesbean)也只能被注入一次。这意味着这个userManagerbean只会在完全相同的userPreferences对象上运行,也就是它最初注入的那个对象。

在这个例子中,当一个UserManager 实例在依赖注入UserPreferences对象上调用一个方法时,它实际上是在代理上调用一个方法。代理然后 UserPreferences从HTTP(在本例中)Session获取真实UserPreferences对象,并将方法调用委托给检索到的真实对象。

所以,注入时需要准确、完整的配置 request-和session-scoped 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代理只拦截公共方法调用!不要在这样的代理上调用非公共方法; 它们不会被委派给实际的作用域目标对象。

或者,可以通过指定元素false的proxy-target-class属性值来配置Spring容器,以便为此类范围的bean创建标准的基于JDK接口的代理<aop:scoped-proxy/>。但是,这意味着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>

1.5.5 自定义范围

创建一个自定义范围

要将自定义作用域集成到Spring容器中,需要实现org.springframework.beans.factory.config.Scope本节所述的接口。

以下方法从基础范围返回对象。例如,会话范围实现返回会话范围的bean(如果它不存在,该方法在绑定到会话以供将来参考之后返回该bean的新实例)。

Object get(String name, ObjectFactory objectFactory)

以下方法将该对象从基础范围中移除。例如,会话范围实现从基础会话中删除会话范围的bean。应该返回该对象,但如果找不到具有指定名称的对象,则可以返回null。

Object remove(String name)

以下方法注册范围在销毁时或范围内的指定对象被销毁时应执行的回调。有关销毁回调的更多信息,请参阅javadocs或Spring范围实现。

void registerDestructionCallback(String name, Runnable destructionCallback)

以下方法获取基础范围的对话标识符。这个标识符对于每个范围是不同的。对于会话范围的实现,这个标识符可以是会话标识符。

String getConversationId()

也可以实现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>

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

1.6 自定义bean的本质

1.6.1 生命周期回调

要与容器进行bean生命周期的交互,可以实现Spring InitializingBean和DisposableBean的接口。容器在初始化和销毁时,调用afterPropertiesSet()前和destroy()后允许bean执行特定的操作。

JSR-250 @PostConstruct和@PreDestroy注解通常被认为是在现代Spring应用程序中接收生命周期回调的最佳实践,使用这些注释意味着bean没有耦合到Spring特定的接口。
如果不想使用JSR-250注释,但仍想要移除耦合,请考虑使用init-method和destroy-method对象定义元数据。

在框架内部,Spring使用BeanPostProcessor实现来处理它可以找到的任何回调接口并调用适当的方法。如果需要自定义功能或其他生命周期行为,Spring不提供开箱即用功能,我们可以自行实施BeanPostProcessor。

本节描述生命周期回调接口。

初始化回调

什么是回调函数?粗俗的说,回调函数就是你妈妈在做饭,你在玩电脑。妈妈做饭是回调函数,饭做完了会通知你,随后你再执行关闭电脑方法去吃饭。没有回调函数的话,在妈妈做完饭以前,你不能做其他事,只能等。做完饭了,你结束等待,去吃饭.

org.springframework.beans.factory.InitializingBean接口允许bean在bean的所有必要属性已由容器设置后执行初始化工作。该InitializingBean接口指定了一种方法:

void afterPropertiesSet() throws Exception;

不建议使用该接口,因为它会将不必要的代码耦合到Spring。或者使用@PostConstruct注释或指定POJO初始化方法。对于基于XML的配置元数据,可以使用该init-method属性来指定具有void无参数签名的方法的名称。对于Java配置,可以使用@Bean的initMethod属性。例如,以下内容:

<bean id="exampleInitBean" class="examples.ExampleBean" init-method="init"/>
public class ExampleBean {

    public void init() {
        // do some initialization work
    }
}

上面和下面的配置看上去相同,但是上面的配置和Spring没有耦合:

<bean id="exampleInitBean" class="examples.AnotherExampleBean"/>
public class AnotherExampleBean implements InitializingBean {

    public void afterPropertiesSet() {
        // do some initialization work
    }
}

销毁回调

实现org.springframework.beans.factory.DisposableBean接口允许bean在包含它的容器被销毁时获得回调。该 DisposableBean接口指定了一种方法:

void destroy() throws Exception;

建议不要使用DisposableBean回调接口,因为它不必要地将代码耦合到Spring。或者,使用@PreDestroy注释或指定bean定义支持的通用方法。使用基于XML的配置元数据时,可以使用该destroy-method属性<bean/>。使用Java配置时,请使用@Bean的destroyMethod属性。例如:

<bean id="exampleInitBean" class="examples.ExampleBean" destroy-method="cleanup"/>
public class ExampleBean {

    public void cleanup() {
        // do some destruction work (like releasing pooled connections)
    }
}

不需要继承DisposableBean,这样就不与Spring耦合了。

默认初始化和销毁方法

当你写的初始化和销毁不使用Spring的具体方法回调InitializingBean和DisposableBean回调接口,你通常写有名字,如方法init(),initialize(),dispose(),等等。理想情况下,此类生命周期回调方法的名称在项目中标准化,以便所有开发人员使用相同的方法名称并确保一致性。

您可以将Spring容器配置look为命名初始化并销毁每个 bean 上的回调方法名称。这意味着,作为应用程序开发人员,您可以编写应用程序类并使用调用的初始化回调函数 init(),而无需为init-method="init"每个bean定义配置属性。Spring IoC容器在创建bean时(并根据前面描述的标准生命周期回调协议)调用该方法。此功能还为初始化和销毁​​方法回调强制执行一致的命名约定。

假设您的初始化回调方法已命名,init()并且销毁回调方法已命名destroy()。下面是在beans为所有的bean中配置了默认init方法。

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>

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

结合生命周期机制

从Spring 2.5开始,您有三个控制bean生命周期行为的选项:InitializingBean和 DisposableBeancallback接口;init()和destroy()方法; @PostConstruct和@PreDestroy注释。可以结合这些机制来控制给定的bean。

如果为bean配置了多个生命周期机制,并且每个机制都配置了不同的方法名称,那么每个配置的方法都按照下面列出的顺序执行。但是,如果init()为这些生命周期机制中的多个生命周期机制配置了相同的方法名称(例如, 初始化方法),则该方法将执行一次,如前一部分所述。

为相同的bean配置多种生命周期机制,使用不同的初始化方法,如下所示:

  • 用注释的方法 @PostConstruct

  • afterPropertiesSet()由InitializingBean回调接口定义

  • 自定义配置的init()方法

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

  • 用注释的方法 @PreDestroy

  • destroy()由DisposableBean回调接口定义

  • 自定义配置的destroy()方法

启动和关闭回调

Lifecycle接口为任何具有自己生命周期要求的对象定义了基本方法(例如启动和停止一些后台进程):

public interface Lifecycle {

    void start();

    void stop();

    boolean isRunning();
}

任何Spring管理的对象都可以实现该接口。然后,当它 ApplicationContext本身接收到启动和停止信号时,例如在运行时停止/重新启动的情况下,它会将这些调用级联到Lifecycle在该上下文中定义的所有实现。它通过委托给LifecycleProcessor:

public interface LifecycleProcessor extends Lifecycle {

    void onRefresh();

    void onClose();
}

注意,LifecycleProcessor它本身就是Lifecycle 界面的扩展。它还添加了两种其他方法来对正在刷新和关闭的上下文作出反应。

请注意,常规org.springframework.context.Lifecycle接口只是显式启动/停止通知的普通合同,并不意味着在上下文刷新时自动启动。要考虑到实现org.springframework.context.SmartLifecycle接口对特定bean的自动启动(包括启动阶段)的细粒度控制。另外,请注意,停止通知不保证在销毁之前发生:在正常关闭时,所有Lifecyclebean将在传播一般销毁回调之前首先收到停止通知; 然而,在上下文的生命周期中的热刷新或中止刷新尝试时,只会调用销毁方法。

启动时,相位最低的物体首先启动,停止时跟随相反的顺序。因此,实现SmartLifecycle并且用getPhase()方法返回Integer.MIN_VALUE值将是第一个开始而且最后一个停止的对象。另一方面,相位值 Integer.MAX_VALUE将指示对象应该最后开始并且首先停止(可能是因为它取决于要运行的其他进程)。当考虑相位值时,没有实现SmartLifecycle接口的“普通”Lifecycle的对象的默认相位为0。 因此,任何负相位值都表示对象应该在这些标准组件之前开始生成(并且在他们关闭之后关闭),反之亦然。

正如你所看到的定义的停止方法SmartLifecycle接受回调。任何实现必须run()在该实现的关闭过程完成后调用该回调的方法。这可以在必要时启用异步关闭,因为LifecycleProcessor接口 的默认实现DefaultLifecycleProcessor将等待每个阶段中的对象组的超时值来调用该回调。每个阶段的默认超时时间是30秒。可以通过在上下文中定义一个名为“lifecycleProcessor”的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()方法返回的布尔值 。如果为真,对象将被立即启动而不是等待明确的上下文调用或是自己的start()方法被调用(不像上下文刷新(onRresh),上下文启动(start)不会自动去实现标准上下文功能(Lifecycle))。

在非web应用程序中正常关闭Spring IoC容器

本节仅适用于非Web应用程序。ApplicationContext当相关的Web应用程序关闭时,Spring的基于Web的 实现已经有适当的代码来正常关闭Spring IoC容器。

要注册关闭挂钩,请调用接口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");

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

1.6.2 ApplicationContextAware和BeanNameAware

当ApplicationContext创建一个实现该org.springframework.context.ApplicationContextAware接口的对象实例时,该实例提供了对该 接口的引用ApplicationContext。

public interface ApplicationContextAware {

    void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
}

因此,bean可以ApplicationContext通过编程操作来创建它们,通过ApplicationContext接口,或者通过将引用强制转换为此接口的已知子类,例如ConfigurableApplicationContext,可以公开其他功能。一个用途是对其他豆的程序化检索。有时候这种能力是有用的; 然而,通常你应该避免它,因为它将代码耦合到Spring,并且不遵循Inversion of Control风格,其中协作者被提供给bean作为属性。ApplicationContext提供对文件资源的访问,发布应用程序事件和访问文件的其他方法 MessageSource。

自Spring 2.5起,自动装配是另一种可供参考的选择 ApplicationContext。如果这样做,ApplicationContext则自动装入到字段、构造函数参数或方法参数中,ApplicationContext如果所涉及的字段,构造函数或方法携带@Autowired注释,则该参数将对应该类型。
当ApplicationContext创建一个实现 org.springframework.beans.factory.BeanNameAware接口的类时,该类将被提供对其关联对象定义中定义的名称的引用。

public interface BeanNameAware {

    void setBeanName(String name) throws BeansException;
}

这个回调函数是在正常bean属性填充之后,但在初始化回调函数(如InitializingBean afterPropertiesSet或自定义init方法)之前调用的。

1.6.3 其他Aware接口

感知接口

名称 注入依赖
ApplicationContextAware 声明 ApplicationContex
BeanClassLoaderAware 用于加载Bean类的类加载器。
BeanNameAware 声明bean的名称
ServletConfigAware 当前容器运行的ServletConfig.只有在spring web-aware的applicationContext中有效
ServletContextAware 当前容器运行的ServletContext,只在Spring web中的ApplicationContext范围内有效

再次注意,这些接口的使用将您的代码绑定到Spring API,并且不遵循控制反转样式。因此,它们被推荐用于需要对容器进行编程访问的基础架构bean。

1.7 Bean继承

一个bean定义可以包含很多配置信息,包括构造函数参数,属性值和特定于容器的信息,例如初始化方法,静态工厂方法名称等等。子bean定义从父定义继承配置数据。根据需要,子定义可以覆盖一些值或添加其他值。使用父和子bean定义可以节省大量的输入。实际上,这是一种模板形式。

如果以ApplicationContext编程方式使用接口,子ChildBeanDefinition类定义由类表示。大多数用户在这个级别上不使用它们,而是用类似的方式声明性地配置bean定义ClassPathXmlApplicationContext。当您使用基于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>

上例通过使用该abstract属性将父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。

1.8 容器扩展点

通常,应用程序开发人员不需要ApplicationContext 实现类的子类。相反,Spring IoC容器可以通过插入特殊集成接口的实现来扩展。接下来的几节将介绍这些集成接口

1.8.1 使用BeanPostProcessor定制Bean

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

你可以配置一个或多个BeanPostProcessor实例,如果这些BeanPostProcessor实现了Order接口,那么,你可以设置order参数让这些BeanPostProcessor按顺序执行。

BeanPostProcessor操作bean或者对象的实例。也就是说,Spring容器生成Bean实例后由BeanPostProcessor执行后续工作。
在不同的容器等级里,BeanPostProcessor在每个容器内是有作用域的。如果你在某个容器定义了BeanPostProcessor,那么这个BeanPostProcessor只能后处理这个容器中的beans。换句话说就是bean被定义在一个容器内就不能被其他容器中的BeanPostProcessor处理(后处理),即使这两个容器都是同一层次结构的一部分。
要改变bean的定义,需要BeanFactoryPostProcessor。

BeanPostProcessor包含了两个回调方法。当这个类被注册为容器的后处理器时,对于容器创建的每个bean实例,后处理器都会在容器初始化方法(如InitializingBean的afterPropertiesSet()之前和容器声明的Init方法)以及任何bean初始化回调之后被调用。后处理可以对bean实例执行任何操作,包括完全忽略回调。一个bean post-processor通常检查回调接口,或者一个代理包装的bean。一些Spring AOP基础类被实现为bean后处理器,以提供代理包装逻辑。

ApplicationContext自动检测在配置中实现BeanPostProcessor接口的beans。ApplicationContext将这些beans注册成为后处理器,以便稍后在创建bean时调用它们。Bean后处理器可以像其他任何bean一样部署在容器中。

注意,在配置类中声明BeanPostProcessor使用@Bean方法时,工厂方法的返回类型应该是实现类本身或至少是org.springframework.beans.factory.config.BeanPostProcessor接口,以清楚的表明该bean的后处理特性。否则ApplicationContext在完全创建它之前,将无法通过类型对其进行自动检测。由于BeanPostProcessor需要尽早实例化以适用于上下文中其他bean的初始化,因此这种早期类型检查非常关键。

以编程方式注册BeanPostProcessor
BeanPostProcessor一般是以ApplicationContext自动扫描方式(如上所述)注册,但也可以通过ConfigurableBeanFactory的addBeanPostProcessor方法。这在注册之前评估条件逻辑,或者甚至跨层次结构中的上下文复制Bean后处理器时非常有用。但是请注意,BeanPostProcessor以编码方式添加时不遵从Order接口。

BeanPostProcessor和AOP自动代理

/**
  * 实现BeanPostProcessor接口的类是特殊的,并且容器会对其有不同的处理。ApplicationContext直接引用的所有BeanPostProcessor和bean,
  * 在启动时都被当成特殊启动阶段的一部分。然后,所有的BeanPostProcessor都以已排序的方式注册并应用于容器中的其他bean。
  * 由于AOP自动代理是作为一个BeanPostProcessor来实现的,所以无论是BeanPostProcessor还是它直接引用的bean,
  * 都是不能自动代理的,所以并没有将切面织进去。
  *
  * 对任何这样的bean,会有一条信息性消息日志:“Bean Foo 不适合通过BeanPostProcessor接口进行处理(例如:不适合自动代理)”。
  *
  * 注意,如果bean是BeanPostProcessor,并且使用自动装配或@ Resource(可能回退到自动装配),
  * Spring可能会在搜索类型匹配的依赖关系时意外访问bean,因此它们不适合自动代理或者其他方式的bean后处理。
  * 例如,如果你有一个依赖注释,@ Resource其中的 field/setter 名称并不直接与bean的声明名称相对应,
  * 并且没有使用名称属性,那么Spring将访问其他bean以便按类型匹配它们。

例如:Hello World,BeanPostProcessor风格
这第一个例子说明了基本用法。该示例显示了一个自定义 BeanPostProcessor实现,它调用toString()每个bean 的方法,因为它是由容器创建的,并将生成的字符串打印到系统控制台。

在自定义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) {
        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>
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.scripting.Messenger;

public final class Boot {

    public static void main(final String[] args) throws Exception {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("scripting/beans.xml");
        Messenger messenger = (Messenger) ctx.getBean("messenger");
        System.out.println(messenger);
    }

}

前面的应用程序的输出类似于以下内容:

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

1.8.2 使用BeanFactoryPostProcessor来定制你的配置

BeanFactoryPostProcessor和BeanPostProcessor的主要区别在于:BeanFactoryPostProcessor可以修改bean的配置数据;换句话说就是在实例化除了BeanFactoryPostProcessor之外的所有bean之前,Spring容器允许BeanFactoryPostProcessor读取bean的配置数据并且改变配置

BeanFactoryPostProcessor可以配置多个,并且可以通过设置order属性来控制执行顺序。如果是自己写的BeanFactoryPostProcessor,应该要考虑实现Ordered接口。

如果想更改实际的bean实例(在配置中创建的对象),需要使用BeanPostProcessor。虽然技术上可以在一个BeanFactoryPostProcessor(例如使用BeanFactory.getBean())bean中使用bean实例,但这样做会导致bean过早的实例化,从而违反标准容器生命周期。
另外,每个容器BeanFactoryPostProcessor都有作用域。这只有在使用容器层次结构才有意义。如果在一个容器定义一个容器,它将只应用于该容器中的bean定义。一个容器的bean定义不会由另一个容器的BeanFactoryPostProcessor后处理,即使他们有相同的父容器。

在一个ApplicationContext中被声明后,bean工厂的后处理会立即自动执行,以便将更改应用在容器的配置中。Spring包含许多预定义的bean工厂处理器,比如PropertyOverrideConfiguer和PropertyPlaceholderConfiguer。自定义BeanFactoryPostProcessor也可以使用,比如注册一个自定义属性编辑器。

通常不会将BeanFactoryPostProcessor配置成延迟加载。而且即使被配置了延迟加载,也会被刻意忽略,BeanfactoryPostProcessor被引用时会立刻实例化。

例如:类名替换PropertyPlaceholderConfigurer

PropertyPlaceholderConfiguer使用标准Java Properties格式将bean定义中的属性值在单独的文件中进行外部化。

<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <property name="locations" value="classpath:com/foo/jdbc.properties"/>
</bean>

<bean id="dataSource" destroy-method="close"
        class="org.apache.commons.dbcp.BasicDataSource">
    <property name="driverClassName" value="${jdbc.driverClassName}"/>
    <property name="url" value="${jdbc.url}"/>
    <property name="username" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
</bean>

实际值来自标准Java Properties格式的另一个文件:

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

因此,该字符串${jdbc.username}在运行时被替换为值“sa”,同样适用于与属性文件中的键匹配的其他占位符值。使用contextSpring 2.5中引入的命名空间,可以使用专用配置元素配置属性占位符。一个或多个位置可以作为location属性中的逗号分隔列表提供。

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

请查阅PropertyPlaceholderConfigurer javadoc以获取更多信息。

可以使用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}"/>

如果不能在运行时被解析为有效类,那么在应用上下文预实例化(preInstantiateSingletons())这个类时将会失败。

例如:PropertyOverrideConfigurer

PropertyOverrideConfigurer是另外一种后处理的bean工厂,与PropertyPlaceholderConfigurer很像,但是不同的是,后者可以有缺省值或者有根本没有值的bean属性。如果重写properties文件没有某个bean属性的条目,则使用默认的上下文定义。

值得注意的是,bean并不知道被重写,所以如果XML定义文件中不会立即明显的看到正在使用覆盖器配置。如果多个PropertyOverrideConfigurer实例为同一个bean赋值,那么由于重载机制,最后一个实例会写入最终值。
举个例子,内容是这样的:

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

通过contextSpring 2.5中引入的命名空间,可以使用专用配置元素配置属性覆盖:

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

1.8.3 使用FactoryBean定制实例化逻辑

org.springframework.beans.factory.FactoryBean为自己工厂的对象实现接口 。

该FactoryBean接口是Spring IoC容器实例化逻辑的可插入点。如果你有复杂的初始化代码,用Java来表达更好,而不是(可能)冗长的XML,你可以创建自己的代码 FactoryBean,在该类中编写复杂的初始化代码,然后将自定义FactoryBean插入到容器中。

  • Object getObject():返回FactoryBean创建的Object实例。这个实例能否被共享,取决于返回的是单例还是原型。
  • boolean isSingleton():
  • Class getObjectType():返回getObject()方法的返回值。如果无法解析类型则返回Null.

如果需要向容器请求一个FactoryBean实例而不是FactoryBean产生的bean,在调用getBean()方法的到时候,用&符号来标记bean的id。对于给定的FactoryBean的id,myBean,getBean("myBean")在容器上调用FactoryBean产生的bean;而调用getBean("&myBean")返回FactoryBean实例本身。

1.9 基于注解的容器配置

注解注入是在XML注入之前执行的,因此后者配置将覆盖前者。

与往常一样,可以将他们注册成为单独的bean定义。但也可以通过XML在Spring配置中隐式注册它们(注意包括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
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

    <context:annotation-config/>

</beans>

(该隐式注册的后处理器包括AutowiredAnnotationBeanPostProcessor,CommonAnnotationBeanPostProcessor,persistenceAnnotationBeanPostProcessor,以及RequiredAnnotationBeanPostProcessor)。

<context:annotation-config/>只在与其定义的应用程序上下文中查找bean的注释。这意味着,如果你<context:annotation-config/>输入一个WebApplicationContext for DispatcherServlet,它只会检查@Autowired控制器中的bean,而不是你的服务。更多信息,请参阅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属性尚未填充,容器将引发异常。

1.9.2 @Autowired

从Spring Framework 4.3开始,@Autowired如果目标bean只定义了一个构造函数,那么对这样的构造函数的注释就不再需要了。但是,如果有几个构造函数可用,则必须至少注明一个构造函数来教授容器使用哪一个。

如果您希望数组或列表中的项目按特定顺序排序,您的目标bean可以实现org.springframework.core.Ordered接口或使用@Order或标准@Priority注释。否则,他们的顺序将遵循容器中相应目标bean定义的注册顺序。
请注意,由于标准javax.annotation.Priority注释无法@Bean在方法中声明,因此在该 级别上不可用。它的语义可以通过@Order结合@Primary每种类型的单个bean的值来建模。

默认情况下,只要没有bean可用,自动装配失败; 默认行为是将注释的方法,构造函数和字段视为指示所需的依赖项。这种行为可以改变,如下所示。

public class SimpleMovieLister {

    private MovieFinder movieFinder;

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

    // ...
}

或者,您可以通过Java 8来表达特定依赖项的非必需性质java.util.Optional:

public class SimpleMovieLister {

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

从Spring Framework 5.0开始,您也可以使用@Nullable注释(任何包中的任何类型的注释,例如javax.annotation.NullableJSR-305):

public class SimpleMovieLister {

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

您还可以使用@Autowired对于那些众所周知的解析依赖接口:BeanFactory,ApplicationContext,Environment,ResourceLoader, ApplicationEventPublisher,和MessageSource。这些接口及其扩展接口(如ConfigurableApplicationContext或ResourcePatternResolver)会自动解析,而无需进行特殊设置。

public class MovieRecommender {

    @Autowired
    private ApplicationContext context;

    public MovieRecommender() {
    }

    // ...
}

1.9.3 使用@Primary微调基于注释的自动装配

由于按类型自动装配可能会导致多个候选人,因此通常需要对选择过程有更多的控制权。一种方法是通过Spring的@Primary注解来实现这一点 。@Primary表示当多个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;

    // ...
}

1.9.4 使用限定符对基于注解的自动装配进行微调

@Primary是可以确定一个主要候选人时使用多个实例的自动装配的有效方法。当需要对选择过程进行更多控制时,@Qualifier可以使用Spring的注释。您可以将限定符值与特定参数相关联,缩小匹配类型的集合,以便为每个参数选择特定的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定义如下。具有限定符值"main"的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
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

    <context:annotation-config/>

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

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

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

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

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

</beans>

对于回退匹配,bean名称被视为默认限定符值。因此,您可以使用id“main”而不是嵌套的限定符元素来定义bean,从而得到相同的匹配结果。但是,尽管您可以使用此约定来通过名称引用特定的bean,但@Autowired基本上是使用可选的语义限定符进行类型驱动的注入。这意味着限定符值(即使使用bean名称后备)也会在匹配类型集合内缩小语义; 它们不会在语义上表达对唯一bean id的引用。良好的限定符值是“主要”或“EMEA”或“持久性”,表示与bean无关的特定组件的特征id,

例如,限定符也适用于键入的集合,如上所述 Set<MovieCatalog>。在这种情况下,根据声明的限定符的所有匹配的Bean将作为集合注入。这意味着限定词不必是唯一的; 它们只是构成过滤标准。例如,您可以定义多个MovieCatalog具有相同限定符值“action”的bean,所有这些bean都将被注入到Set<MovieCatalog>注释中@Qualifier("action")。

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

也就是说,如果您打算按名称表示注解驱动的注入@Autowired,即使可以在类型匹配的候选中使用bean名称进行选择,也不要主要使用(@Autowired("xxxx")。相反,使用JSR-250 @Resource注释,该注释在语义上定义为通过其唯一名称来标识特定目标组件,并且声明类型与匹配过程无关。@Autowired具有相当不同的语义:在按类型选择候选bean之后,指定的字符串限定符值将仅在这些类型选择的候选者中被考虑,例如,将"account"限定符与相同限定符标签标记的bean相匹配。

对于本身被定义为collection/map或者array类型的bean,@Resource是一个很好的解决方案,通过唯一引用名特定集合或数组的bean。也就是说,@Autowired只要元素类型信息保留在@Bean返回类型签名或集合继承层次结构中,就可以通过Spring的类型选择匹配算法来匹配集合/map和数组类型。在这种情况下,可以使用限定符值在相同的集合中进行选择。

从4.3开始,@Autowired也考虑自引用注入,即引用回当前注入的bean。请注意,自我注入是后备;对其他组件的依赖是优先权。从这个意义上说,自我引用不参与正规的候选人选择,因此尤其不是主要的; 相反,它们总是以最低优先权结束。实际上,只能使用自引用作为最后的手段,例如通过bean的事务代理来调用同一实例上的其他方法:考虑在这种情况下将受影响的方法分解为单独的委托bean。或者,使用@Resource它可以通过其唯一名称获取当前bean的代理。

**@Autowired适用于字段,构造函数和多参数方法,允许通过参数级别的限定符注释进行缩小。相比之下,@Resource 仅支持具有单个参数的字段和bean属性设置方法。因此,如果注入目标是构造函数或多参数方法,则坚持使用限定符。

可以创建自己的自定义限定符注释。只需定义一个注释并@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定义的信息。您可以添加 <qualifier/>标签作为标签的子元素,<bean/>然后指定type和 value匹配您的自定义限定符注释。该类型与注释的完全限定类名相匹配。或者,如果没有相互冲突名称存在的风险,为了方便起见,您可以使用短名称。以下示例演示了这两种方法。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

    <context:annotation-config/>

    <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中的限定符元数据。具体来说,请参阅提供限定符元数据和注释

在某些情况下,使用没有值的注释可能就足够了。当注释提供更通用的用途并且可以应用于多种不同类型的依赖关系时,这可能很有用。例如,您可以提供一个脱机 目录,当没有可用的Internet连接时将搜索该目录。首先定义简单的注释:

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

    //或者默认
    public String value() default "";
}

现在bean定义只需要一个限定符type:

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

还可以定义接受指定属性的自定义限定符注释,而不是简单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 元属性来代替 <qualifier/>子元素。如果可用,则<qualifier/>其及其属性优先,但<meta/>如果不存在此类限定符,则自动装配机制会回退标签中提供的值 ,如以下示例中的最后两个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
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

    <context:annotation-config/>

    <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<String>和 Store<Integer>,你可以@Autowire在Store界面和通用将作为一个限定:

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

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

自动装配也适用于Lists,Maps,Arrays:

// Inject all Store beans as long as they have an <Integer> generic
// Store<String> beans will not appear in this list
@Autowired
private List<Store<Integer>> s;

1.9.6 CustomAutowireConfigurer

CustomAutowireConfigurer是一个在BeanFactoryPostProcessor上注册自己的自定义限定符注释类型,即使它们没有用Spring的@Qualifier注释注释。

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

通过以下方式AutowireCandidateResolver确定autowire候选人:

  • autowire-candidate每个bean定义的值

  • 元素上default-autowire-candidates可用的任何模式<beans/>

  • @Qualifier注释的存在以及注册的任何自定义注释CustomAutowireConfigurer

当多个bean被认定为自动装配候选者时,“主要”的确定如下:如果候选者中恰好有一个bean定义primary 属性设置为true,则它将被选中。

1.9.7 @Resource

Spring还支持@Resource在字段或bean属性setter方法上使用JSR-250 注释进行注入。这是Java EE 5和6中的常见模式,例如在JSF 1.2托管bean或JAX-WS 2.0端点中。Spring也支持Spring管理对象的这种模式。

@Resource需要一个名称属性,默认情况下,Spring将该值解释为要注入的bean名称。换句话说,它遵循名义语义,如本例所示:

public class SimpleMovieLister {

    private MovieFinder movieFinder;

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

如果未明确指定名称,则默认名称是从字段名称或setter方法派生的。如果是字段,则需要字段名称; 在setter方法的情况下,它采用bean属性名称。所以下面的例子将把名为“movieFinder”的bean注入到它的setter方法中:

public class SimpleMovieLister {

    private MovieFinder movieFinder;

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

一个专属的没有明确名字的@Resource规定的示例是,@Resource找到主类型匹配来替代名称匹配,并且解决了依赖:BeanFactory, ApplicationContext,ResourceLoader,ApplicationEventPublisher,和MessageSource 接口。

因此,在以下示例中,该customerPreferenceDao字段首先查找名为customerPreferenceDaobean,然后返回到该类型的主类型匹配 CustomerPreferenceDao。“上下文”字段是基于已知的可解析依赖类型注入的ApplicationContext

public class MovieRecommender {

    @Resource
    private CustomerPreferenceDao customerPreferenceDao;

    @Resource
    private ApplicationContext context;

    public MovieRecommender() {
    }

    // ...
}

CommonAnnotationBeanPostProcessor不仅承认了@Resource注解也是JSR-250 的生命周期注解。在Spring 2.5中引入的对这些注释的支持为初始化回调函数销毁回调函数提供了另一种替代方案 。假设CommonAnnotationBeanPostProcessorSpring在Spring中注册 ApplicationContext,则在生命周期的相同时间点调用携带这些注释中的一个的方法,如相应的Spring生命周期接口方法或显式声明的回调方法。在下面的示例中,缓存将在初始化时预填充,并在销毁时清除。

public class CachingMovieLister {

    @PostConstruct
    public void populateMovieCache() {
        // populates the movie cache upon initialization...
    }

    @PreDestroy
    public void clearMovieCache() {
        // clears the movie cache upon destruction...
    }
}

1.10 类路径扫描和托管组件

本节介绍用于隐式检测候选组件的选项 通过扫描类路径。候选组件是与过滤条件相匹配的类,并具有在容器中注册的相应的bean定义。这消除了使用XML来执行bean注册的需要; 相反,您可以使用注释(例如@Component),AspectJ类型表达式或您自己的自定义过滤条件来选择哪些类将具有注册到容器的bean定义。

1.10.1 @Component和更多固定的注解

@Repository注释是用于满足所述角色或任何类的标记 构造型的存储库(也被称为数据访问对象或DAO)。

Spring提供进一步典型化注解:@Component,@Service,和 @Controller。@Component是任何Spring管理组件的通用原型。 @Repository,@Service和,并且@Controller是@Component更具体的用例的特化,例如,分别在持久性,服务和表示层中。因此,你可以用你的注解组件类 @Component,但如果用注解它们@Repository,@Service或者@Controller ,你的类能更好地被工具处理,或与切面进行关联。例如,这些刻板印象注解是切入点的理想目标。这也有可能是@Repository,@Service和@Controller可能会在Spring Framework的未来版本中增加额外的语义。因此,如果您选择使用@Component或@Service为您的服务层,@Service显然是更好的选择。同样,如上所述,@Repository已经被支持作为持久层中自动异常转换的标记。

1.10.2 元注释

Spring提供的许多注释可以在您自己的代码中用作元注释。元注释只是一个可以应用于其他注释的注释。例如,@Service上面提到的注释用@Component以下元注释 :

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component // Spring will see this and treat @Service in the same way as @Component
public @interface Service {

    // ....
}

元注释也可以组合以创建组合注释。例如,@RestController从Spring MVC的注解组成的@Controller和 @ResponseBody。

此外,组合注释可以选择性地从元注释重新声明属性以允许用户定制。当您只想暴露元注释属性的子集时,这可能特别有用。例如,Spring的 @SessionScope注解将范围名称硬编码,session但仍允许自定义proxyMode。

@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 自动检测类并注册bean定义

Spring可以自动检测,并使用它注册相应的 BeanDefinitions ApplicationContext。例如,以下两个类有资格进行这种自动检测:

@Service
public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Autowired
    public SimpleMovieLister(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }
}
@Repository
public class JpaMovieFinder implements MovieFinder {
    // 为了清楚起见,执行过程被忽
}

要自动检测这些类并注册相应的bean,您需要添加 @ComponentScan到您的@Configuration类中,其中该basePackages属性是这两个类的常见父级包。(或者,您可以指定包含每个类的父包的以逗号/分号/空格分隔的列表。)

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

以下是使用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
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

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

</beans>

<context:component-scan>隐式使用的使能功能 <context:annotation-config>。<context:annotation-config>使用时通常不需要包含 元素<context:component-scan>。

类路径包的扫描要求类路径中存在相应的目录条目。在使用Ant构建JAR时,请确保不要 激活JAR任务的仅文件开关。此外,在某些环境中,类路径目录可能不会基于安全策略公开,例如JDK 1.7.0_45和更高版本上的独立应用程序(这需要在您的清单中设置“受信任的库”;请参阅 http://stackoverflow.com/questions/ 19394570 / java-jre-7u45-breaks-classloader-getresources)。
在JDK 9的模块路径(Jigsaw)中,Spring的类路径扫描一般按预期工作。但是,请确保您的组件类在您的module-info 描述符中导出; 如果你希望Spring调用你的类的非公共成员,确保它们是'打开'的(即使用opens声明而不是描述符中的exports 声明module-info)。

此外,AutowiredAnnotationBeanPostProcessor和 CommonAnnotationBeanPostProcessor都隐含当您使用组件扫描元件包括在内。这意味着这两个组件是自动检测并 连接在一起的 - 所有这些都没有在XML中提供任何bean配置元数据。

可以禁用的登记AutowiredAnnotationBeanPostProcessor和 CommonAnnotationBeanPostProcessor通过包括注解的配置具有值属性false。

1.10.4 使用过滤器来自定义扫描

默认情况下,类注有@Component,@Repository,@Service, @Controller,或者本身都标注有一个自定义的注释@Component是唯一检测到的候选组件。但是,只需应用自定义过滤器即可修改和扩展此行为。将它们添加为注释的includeFilters或excludeFilters 参数@ComponentScan(或者包含 元素的include-filter或exclude-filter子component-scan元素)。每个过滤器元素都需要type 和expression属性。下表介绍了过滤选项。

过滤器类型 示例表达式 描述
annotation(默认) org.example.SomeAction 注释将出现在目标组件的类型级别上。
assignable(分配) org.example.SomeClass 目标组件可分配给的类(或接口)(扩展/实现)。
AspectJ org.example..*Service+ 一个由目标组件匹配的AspectJ类型表达式。
regex(正则) org.example.Default.* 一个正则表达式要与目标组件类名匹配。
custom(自定义) org.example.MyTypeFilter org.springframework.core.type .TypeFilter接口的自定义实现。

                表5.过滤器类型

以下示例显示了忽略所有@Repository注释并使用“存根”存储库的配置。

@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>

1.10.5 在组件中定义bean元数据

Spring组件也可以将bean定义元数据提供给容器。您可以使用与@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自动注入到bean名为privateInstance的age属性中。Spring Expression Language通过#{ <expression> }符号定义属性的值。对于@Value注释,表达式解析器预先配置为在解析表达式文本时查找bean名称。
从Spring FrameWork4.3开始,你还可以声明类型InjectPoint(或其更具体的子类DependecyDescriptor)工厂参数,以便访问触发bean的请求注入点。请注意,这仅适用于实例创建的bean,而不是自动注入现有实例。因此,对于原型范围的bean来说,这个特性最有意义。对于其他作用域,factory只会看到在给定范围内触发创建新bean实例的注入点:例如,触发创建惰性单例bean的依赖关系。在这种情况下使用提供的注入点元数据和语义保护。

@Component
public class FactoryMethodComponent {

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

@Bean方法在普通的Spring组件中和在@Configuration中处理是不相同的。区别在于@Component类不是使用CGLIB来拦截调用的方法和字段。CGLIB代理是通过在@Configuration调用@Bean方法和字段来创建协作对象的bean的引用的手段;这种方法不是用普通的Java语义来调用的,而是通过容器来提供Spring bean的通常的生命周期管理和代理,即使通过对@Bean方法的编程调用来引用其他bean也是如此。相反,@Bean在普通@Component 类中的方法中调用方法或字段具有 标准的Java语义,没有特殊的CGLIB处理或其他限制条件。

你或许可以声明一个static的@Bean方法。允许他们在没有配置类的情况下被当成实例调用。这在定义BeanFactoryPostProcessor和BeanPostProcessor有特殊的意义,因为这些bean类在容器生命周期的早期被初始化,并且应避免触发其他部分的配置。
【即BeanPostProcessor是没办法对static的bean做拦截后进行处理的,因为static的bean生成的比BeanPostProcessor还早;因此,配置是要注意不要再出发其他的配置了,可能会让其他bean也比BeanPostProcessor类还早生成】

注意到调用static的@Bean方法永远不会被容器拦截,即使在@Configuration类中也是如此(参见上文)。这是由于技术限制:CGLIB子类永远只能覆盖非静态类。因此,直接调用另一个@Bean方法将具有标准的Java语义,导致独立的实例从工厂方法本身直接返回。

方法的Java语言可见性@Bean不会立即影响Spring容器中的结果bean定义。你可以自由地声明你的工厂方法,因为你认为它适用于非@Configuration类,也适用于任何地方的静态方法。但是,类中的常规@Bean方法@Configuration需要被覆盖,即不能将其声明为private或final。

@Bean仍然要在给定的组件类(component)或者配置类(Configuration)这样基础类中检测,就像Java 8的default方法依旧要在component或者configuration的接口实现中声明。这为构建复杂的配置安排提供了很大的灵活性,从Spring 4.2起,通过Java 8默认方法甚至可以实现多重继承。
最后,请注意,单个类可能@Bean为同一个bean 保存多个方法,因为要根据运行时可用的依赖关系来使用多个工厂方法。这与在其他配置方案中选择“最贪婪的”构造函数或工厂方法的算法相同:在构建时将选择具有最大可满足依赖项数的变体,类似于容器如何在多个@Autowired构造函数之间进行选择。

1.10.6 自动检测命名的组件

默认情况下,任何Spring注释(@Component,@Repository,@Service,和 @Controller包含)的名字 value将因此提供的名字相应的bean定义。

如果这样的注释不包含任何名称 value或其他任何检测到的组件(例如自定义过滤器发现的组件),那么默认的bean名称生成器会返回未注册的非限定类名称。例如,如果检测到以下组件类别,名称将是myMovieLister和movieFinderImpl:

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

如果你不想依赖默认的bean命名策略,你可以提供一个自定义的bean命名策略。首先,实现BeanNameGenerator 接口,并确保包含一个默认的无参数构造函数。然后,在配置扫描仪时提供完全合格的类名称:

@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>

作为一般规则,当其他组件可能对其进行明确引用时,请考虑在注释中指定名称。另一方面,只要容器负责布线,自动生成的名称就足够了。

1.10.7 自动检测组件范围

与一般的Spring管理组件一样,自动检测组件的默认和最常见的作用域是singleton。但是,有时您需要可以通过@Scope注释指定的不同范围。只需在注释中提供范围的名称即可:

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

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

要为范围解析提供自定义策略,而不是依赖基于注释的方法,请实现 ScopeMetadataResolver 接口,并确保包含默认的无参数构造函数。然后,在配置扫描仪时提供完全合格的类名称:

@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-proxy属性。三个可能的值是:no,interfaces和targetClass。例如,以下配置将导致标准的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 为注释提供限定符元数据

当依靠类路径扫描来自动检测组件时,您在候选类上提供了限定符元数据和类型级别注释。以下三个示例演示了这种技术:

@Component
@Qualifier("Action")
public class ActionMovieCatalog implements MovieCatalog {
    // ...
}
@Component
@Genre("Action")
public class ActionMovieCatalog implements MovieCatalog {
    // ...
}
@Component
@Offline
public class CachingMovieCatalog implements MovieCatalog {
    // ...
}

1.10.9 生成候选组件的索引

虽然类路径扫描速度非常快,但通过在编译时创建候选静态列表,可以提高大型应用程序的启动性能。在这种模式下,应用程序的所有模块都必须使用这种机制,因为当 ApplicationContext检测到这样的索引时,它将自动使用它而不是扫描类路径。
要生成索引,只需向包含组件扫描指令目标组件的每个模块添加附加依赖项即可:

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

或者,使用Gradle:

dependencies {
    compileOnly("org.springframework:spring-context-indexer:5.0.7.RELEASE")
}

该过程将生成一个META-INF/spring.components将被包含在jar中的文件。

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

当META-INF/spring.components在类路径上找到a时,索引会自动启用。如果索引部分可用一些库(或用例),但整个应用程序无法建立,你可以回退到普通类路径列(如没有索引存在的话)通过设置spring.index.ignore到 true,无论是作为一个系统属性或位于spring.properties类路径根目录的文件中。

1.11使用JSR 330标准注释

从Spring 3.0开始,Spring提供对JSR-330标准注释(依赖注入)的支持。这些注释的扫描方式与Spring注释相同。你只需要在你的类路径中有相关的jar。

如果您使用的是Maven,javax.inject则可以在标准Maven存储库中使用该工件(http://repo1.maven.org/maven2/javax/inject/javax.inject/1/)。您可以将以下依赖项添加到您的文件pom.xml中:

<dependency>
    <groupId>javax.inject</groupId>
    <artifactId>javax.inject</artifactId>
    <version>1</version>
</dependency>

1.11.1 依赖注入@Inject和@Named

@javax.inject.Inject可以使用如下:

import javax.inject.Inject;

public class SimpleMovieLister {

    private MovieFinder movieFinder;

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

    public void listMovies() {
        this.movieFinder.findMovies(...);
        ...
    }
}

与之一样@Autowired,可以@Inject在字段级别,方法级别和构造函数参数级别使用。此外,您可以将注入点声明为a Provider,允许按需访问较短范围的bean或通过Provider.get()调用懒惰地访问其他bean 。作为上述示例的变体:

import javax.inject.Inject;
import javax.inject.Provider;

public class SimpleMovieLister {

    private Provider<MovieFinder> movieFinder;

    @Inject
    public void setMovieFinder(Provider<MovieFinder> movieFinder) {
        this.movieFinder = movieFinder;
    }

    public void listMovies() {
        this.movieFinder.get().findMovies(...);
        ...
    }
}

如果您想为应该注入的依赖项使用限定名称,则应该@Named按如下方式使用注释:

import javax.inject.Inject;
import javax.inject.Named;

public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Inject
    public void setMovieFinder(@Named("main") MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // ...
}

像@Autowired,@Inject也可以用于java.util.Optional或 @Nullable。这在这里更加适用,因为@Inject它没有required属性。

public class SimpleMovieLister {

    @Inject
    public void setMovieFinder(Optional<MovieFinder> movieFinder) {
        ...
    }
}
public class SimpleMovieLister {

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

1.11.2 @Named和@ManagedBean:@Component注释的标准等价物

不同于@Component,@javax.inject.Named或javax.annotation.ManagedBean可以被使用如下:

import javax.inject.Inject;
import javax.inject.Named;

@Named("movieListener")  // @ManagedBean("movieListener") could be used as well
public class SimpleMovieLister {

    private MovieFinder movieFinder;

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

    // ...
}

这是非常常见的使用@Component而不指定组件的名称。 @Named可以以类似的方式使用:

import javax.inject.Inject;
import javax.inject.Named;

@Named
public class SimpleMovieLister {

    private MovieFinder movieFinder;

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

    // ...
}

使用@Named或@ManagedBean时,可以像使用Spring注释时一样使用组件扫描:

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

相比之下@Component,JSR-330 @Named和JSR-250 ManagedBean 注释不可组合。请使用Spring的构造型模型来构建自定义组件注释。

1.11.3标准注释的局限性

在使用标准注释时,重要的是要知道一些重要的功能不可用,如下表所示:

Spring javax.inject.* javax.inject的限制/注释
@Autowired @Inject @Inject没有“必需”属性; 可以与Java 8一起使用Optional。
@Component @Named / @ManagedBean JSR-330不提供可组合模型,只是识别命名组件的一种方法。
@Scope("singleton") @Singleton JSR-330的默认范围就像Spring's一样prototype。然而,为了保持它与Spring的一般默认值一致,Spring容器中声明的JSR-330 bean是singleton默认的。为了使用除以外的范围singleton,您应该使用Spring的@Scope注释。javax.inject还提供了 @Scope注释。然而,这个仅用于创建自己的注释。
@Qualifier @Qualifier / @Named javax.inject.Qualifier只是构建自定义限定符的元注释。具体字符串限定符(如@Qualifier带有值的Spring )可以通过javax.inject.Named关联
@Value - 没有相同的
@Required - 没有相同的
@Lazy - 没有相同的
ObjectFactory Provider javax.inject.Provider是一个直接替代Spring的方法ObjectFactory,只需要更短的get()方法名称。它也可以与Spring组合使用,@Autowired或者使用未注释的构造函数和setter方法。

1.12 容器基于Java的配置

1.12.1 基本概念:@Bean和@Configuration

在Spring中新的Java配置支持的中心构件是@Configuration注解的类和@Bean注解的方法。

该@Bean注释被用于指示一个方法实例,配置和初始化为通过Spring IoC容器进行管理的新对象。对于那些熟悉Spring的<beans/>XML配置的人来说,@Bean注释和<bean/>元素具有相同的作用。您可以@Bean在任何Spring中使用带注释的方法 @Component,但是,它们通常与@Configurationbean一起使用。

使用注释类@Configuration表示它的主要目的是作为bean定义的来源。此外,@Configuration类允许通过简单地调用@Bean同一类中的其他方法来定义bean间依赖关系。最简单的@Configuration类可以读作如下:

@Configuration
public class AppConfig {

    @Bean
    public MyService myService() {
        return new MyServiceImpl();
    }
}

The AppConfig class above would be equivalent to the following Spring <beans/> XML:

<beans>
    <bean id="myService" class="com.acme.services.MyServiceImpl"/>
</beans>

全面的@Configuration VS '精简'的@Bean模式?
当@Bean方法在没有注释的类中被声明时, @Configuration它们被称为在'精简'模式下处理。在一个@Component或者一个普通的旧类中声明的Bean方法将被认为是'精简'的,其中包含类的一个不同的主要目的和一个@Bean方法仅仅是那里的一种奖励。例如,服务组件可能会通过@Bean每个适用组件类的附加方法向container公开管理视图。在这种情况下,@Bean方法是一种简单的通用工厂方法机制。
与全面的@Configuration不同,精简的 @Bean方法不能声明bean间依赖关系。@Bean对其内部组件的内部状态进行操作,并且可选地对它们可以声明的参数进行操作。这种@Bean方法无法引用其他 @Bean方法; 每个这样的方法实际上只是一个特定的bean引用的工厂方法,没有任何特殊的运行时语义。这里的积极副作用是,在运行时不需要应用CGLIB子类,所以在类设计方面没有限制(即,包含的类可能是final等等)。
在常见的场景中,@Bean方法将在@Configuration类中声明,确保始终使用“完整”模式,因此交叉方法引用将被重定向到容器的生命周期管理。这样可以防止@Bean通过常规的Java调用意外调用相同的 方法,这有助于减少在“精简”模式下操作时难以追踪的细微错误。

这些注释@Bean和@Configuration注释将在下面的部分进行深入讨论。首先,我们将介绍使用基于Java的配置创建Spring容器的各种方法。

1.12.2 使用AnnotationConfigApplicationContext实例化Spring容器

下面的部分记录了Spring的AnnotationConfigApplicationContext,Spring 3.0中的新内容。这种多功能的ApplicationContext实现不仅能够接受@Configuration类作为输入,还能够接受 @Component用JSR-330元数据注释的普通类和类。

当@Configuration提供类作为输入时,@Configuration类本身被注册为一个bean定义,并且@Bean该类中所有声明的方法也被注册为bean定义。

当@Component被提供和JSR-330类,它们被登记为bean定义,并且假定DI元数据,例如@Autowired或者@Inject是这些类中使用的必要。

(一)结构简单
与实例化Spring XML文件时用作输入的方式大致相同,在实例化一个类时 ClassPathXmlApplicationContext,@Configuration类可以用作输入AnnotationConfigApplicationContext。这允许完全无XML地使用Spring容器:

public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
    MyService myService = ctx.getBean(MyService.class);
    myService.doStuff();
}

如上所述,AnnotationConfigApplicationContext不仅限于与@Configuration的类一起工作。任何@Component或JSR-330注释类可作为输入提供给构造函数。例如:

public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(MyServiceImpl.class, Dependency1.class, Dependency2.class);
    MyService myService = ctx.getBean(MyService.class);
    myService.doStuff();
}

上面假设MyServiceImpl,Dependency1并Dependency2使用Spring依赖注入注释如@Autowired。

(二)以编程方式使用寄存器构建容器(Class <?> ...)
一个AnnotationConfigApplicationContext可以使用无参数构造函数实例化,然后使用该register()方法进行配置。这种方法在以编程方式构建时特别有用AnnotationConfigApplicationContext。

public static void main(String[] args) {
    AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
    ctx.register(AppConfig.class, OtherConfig.class);
    ctx.register(AdditionalConfig.class);
    ctx.refresh();
    MyService myService = ctx.getBean(MyService.class);
    myService.doStuff();
}

(三)使用扫描启用组件扫描(String ...)
要启用组件扫描,只需@Configuration按照以下步骤注释您的类:

@Configuration
@ComponentScan(basePackages = "com.acme")
public class AppConfig  {
    ...
}

有经验的Spring用户将熟悉Spring的context:命名空间中的XML声明:

<beans>
    <context:component-scan base-package="com.acme"/>
</beans>

在上面的例子中,com.acme软件包将被扫描,寻找任何 @Component-annotated的类,并且这些类将被注册为容器中的Spring bean定义。AnnotationConfigApplicationContext公开该 scan(String…​)方法以允许相同的组件扫描功能:

public static void main(String[] args) {
    AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
    ctx.scan("com.acme");
    ctx.refresh();
    MyService myService = ctx.getBean(MyService.class);
}

请记住,@Configuration类是用元注释@Component,所以它们是组件扫描的候选对象!在上面的例子中,假设AppConfigcom.acme包(或下面的任何包)中声明了它,它将在调用过程中被拾取scan(),并且在refresh()其所有@Bean方法中将被处理并在容器中注册为bean定义。

(三)使用AnnotationConfigWebApplicationContext支持Web应用程序
一个WebApplicationContext变体AnnotationConfigApplicationContext可用AnnotationConfigWebApplicationContext。当配置Spring ContextLoaderListenerservlet侦听器,Spring MVC DispatcherServlet等时,可以使用此实现。接下来是web.xml配置典型Spring MVC Web应用程序的片段。请注意使用contextClasscontext-param和init-param:

<web-app>
    <!-- 设置ContextLoaderListener使用AnnotationConfigWebApplicationContext
        替代原来的XmlWebApplicationContext -->
    <context-param>
        <param-name>contextClass</param-name>
        <param-value>
            org.springframework.web.context.support.AnnotationConfigWebApplicationContext
        </param-value>
    </context-param>

    <!-- 配置的位置必须由一个或多个逗号或者空格分隔
        合法的@Configuration类组成. 可以为组件扫描指定完全限定的软件包-->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>com.acme.AppConfig</param-value>
    </context-param>

    <!-- 像往常一样使用ContextLoaderListener引导根应用程序上下文 -->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <!-- 像原来一样声明 Spring MVC DispatcherServlet-->
    <servlet>
        <servlet-name>dispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!-- 设置ContextLoaderListener使用AnnotationConfigWebApplicationContext
        替代原来的XmlWebApplicationContext -->
        <init-param>
            <param-name>contextClass</param-name>
            <param-value>
                org.springframework.web.context.support.AnnotationConfigWebApplicationContext
            </param-value>
        </init-param>
        <!-- 配置的位置必须由一个或多个逗号或者空格分隔
        合法的@Configuration类组成-->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>com.acme.web.MvcConfig</param-value>
        </init-param>
    </servlet>

    <!-- map all requests for /app/* to the dispatcher servlet -->
    <servlet-mapping>
        <servlet-name>dispatcher</servlet-name>
        <url-pattern>/app/*</url-pattern>
    </servlet-mapping>
</web-app>

1.12.3使用@Bean注释

@Bean是方法级注释和XML <bean/>元素的直接模拟。注释支持一些由其提供的属性<bean/>,例如: init-methoddestroy-methodautowiringname

您可以在@Bean注释@Configuration或 注释类中使用注释@Component。

声明一个bean

要声明bean,只需使用注释注释方法即可@Bean。您可以使用此方法在ApplicationContext指定为方法返回值的类型中注册一个bean定义。默认情况下,bean名称将与方法名称相同。以下是@Bean方法声明的一个简单示例:

@Configuration
public class AppConfig {

    @Bean
    public TransferServiceImpl transferService() {
        return new TransferServiceImpl();
    }
}

前面的配置完全等同于以下Spring XML:

<beans>
    <bean id="transferService" class="com.acme.TransferServiceImpl"/>
</beans>

这两个声明都将一个名为transferService可用 的bean ApplicationContext,绑定到一个类型为object的对象实例TransferServiceImpl:

transferService  - > com.acme.TransferServiceImpl

你也可以@Bean用接口(或基类)返回类型声明你的方法:

@Configuration
public class AppConfig {

    @Bean
    public TransferService transferService() {
        return new TransferServiceImpl();
    }
}

然而,这意味着提前预见到指定接口的类型(TransferService),然后,一旦被扫描到的bean实例化,容器就会知道完整类型。非延迟化的singleton beans根据它们的声明顺序得到实例化,所以你可能会看到不同的类型匹配结果,这取决于另一个组件试图通过非声明类型进行匹配的时间(比如@Autowired TransferServiceImpl 只有在“transferService”bean已经被解析实例化)。

如果您始终通过声明的服务接口来引用您的类型,那么您的 @Bean返回类型可以安全地加入该设计决策。但是,对于实现多个接口的组件或可能由其实现类型引用的组件,声明最具体的返回类型是可能的(至少按照注入点对bean引用的要求)是比较安全的。

Bean依赖关系

@Bean注解的方法可以具有任意数量的参数。例如,如果我们TransferService 需要一个,AccountRepository我们可以通过一个方法参数来实现这个依赖关系:

@Configuration
public class AppConfig {

    @Bean
    public TransferService transferService(AccountRepository accountRepository) {
        return new TransferServiceImpl(accountRepository);
    }
}

解析机制与基于构造函数的依赖注入非常相似。

接受声明周期回调

任何使用@Bean注释定义的类都支持常规生命周期回调,并且可以使用JSR-250中的注释@PostConstruct@PreDestroy注释。
常规的Spring 生命周期回调也被完全支持。如果一个bean实现了InitializingBeanDisposableBean或者Lifecycle,它们各自的方法被容器调用。
*Aware诸如BeanFactoryAware,BeanNameAware,MessageSourceAware,ApplicationContextAware等的标准接口也完全受支持。

该@Bean注释支持指定任意初始化和销毁​​回调方法,就像Spring XML init-method和元素destroy-method上的属性一样bean:

public class Foo {

    public void init() {
        // initialization logic
    }
}

public class Bar {

    public void cleanup() {
        // destruction logic
    }
}

@Configuration
public class AppConfig {

    @Bean(initMethod = "init")
    public Foo foo() {
        return new Foo();
    }

    @Bean(destroyMethod = "cleanup")
    public Bar bar() {
        return new Bar();
    }
}

默认情况下,使用具有公共close或shutdown 方法的Java配置定义的bean 将自动列入销毁回调。如果你有一个public close或shutdownmethod,并且你不希望在容器关闭时调用它,只需添加@Bean(destroyMethod="")到你的bean定义来禁用默认(inferred)模式。
您可能希望为通过JNDI获取的资源默认执行此操作,因为其生命周期在应用程序外部进行管理。

@Bean(destroyMethod="")
public DataSource dataSource() throws NamingException {
    return (DataSource) jndiTemplate.lookup("MyDS");
}

此外,通过@Bean方法,您通常会选择使用编程式JNDI查找:使用Spring的JndiTemplate/ JndiLocatorDelegatehelper或直接InitialContext使用JNDI ,但JndiObjectFactoryBean不会强制您将返回类型声明为FactoryBean类型而不是实际目标类型,很难在@Bean其他方法中用于参照所提供的资源的交叉引用调用。

当然,就上述情况而言, 在构造期间直接Foo调用该init()方法同样有效:

@Configuration
public class AppConfig {

    @Bean
    public Foo foo() {
        Foo foo = new Foo();
        foo.init();
        return foo;
    }

    // ...
}

直接使用Java进行工作时,可以对对象执行任何喜欢的操作,并且不总是需要依赖容器生命周期!

指定bean作用域

默认范围是singleton,但可以使用@Scope注释覆盖它:

@Configuration
public class MyConfiguration {

    @Bean
    @Scope("prototype")
    public Encryptor encryptor() {
        // ...
    }
}
@Scope和scoped-proxy

在使用XML配置时创建此类代理的最简单方法就是<aop:scoped-proxy/>元素。使用@Scope注释在Java中配置bean提供了与proxyMode属性等效的支持。默认值是no proxy(ScopedProxyMode.NO),但可以指定ScopedProxyMode.TARGET_CLASS或ScopedProxyMode.INTERFACES。
如果将范围代理示例从XML参考文档(请参阅前面的链接)移植到@Bean使用的Java中,它将如下所示:

// an HTTP Session-scoped bean exposed as a proxy
@Bean
@SessionScope
public UserPreferences userPreferences() {
    return new UserPreferences();
}

@Bean
public Service userService() {
    UserService service = new SimpleUserService();
    // a reference to the proxied userPreferences bean
    service.setUserPreferences(userPreferences());
    return service;
}

自定义bean命名

默认情况下,配置类使用@Bean方法的名称作为结果bean的名称。但是,可以使用该name属性覆盖此功能。

@Configuration
public class AppConfig {

    @Bean(name = "myFoo")
    public Foo foo() {
        return new Foo();
    }
}

bean别名

有时需要为单个bean提供多个名称,称为bean别名。 @Bean注解的name属性为此接受一个String数组。

@Configuration
public class AppConfig {

    @Bean(name = { "dataSource", "subsystemA-dataSource", "subsystemB-dataSource" })
    public DataSource dataSource() {
        // instantiate, configure and return DataSource bean...
    }
}

bean描述

@Configuration
public class AppConfig {

    @Bean
    @Description("Provides a basic example of a bean")
    public Foo foo() {
        return new Foo();
    }
}

1.12.4 使用@Configuration注释

@Configuration是一个类级注释,指示对象是bean定义的来源。@Configuration类通过公共@Bean注释方法声明bean 。调用@Bean的方法@Configuration类也可以用于定义bean间的依赖关系。

注入bean间依赖关系

当@Bean彼此依赖,表达该依赖性就像一个bean方法调用另一个一样简单:

@Configuration
public class AppConfig {

    @Bean
    public Foo foo() {
        return new Foo(bar());
    }

    @Bean
    public Bar bar() {
        return new Bar();
    }
}

在上面的例子中,foo bean接收到bar通过构造函数注入的引用。

这种声明bean间依赖关系的@Bean方法只有在方法在@Configuration类中声明时才有效。您不能使用普通@Component类声明bean间依赖关系。【用Component时,bar()会调用两次,第一次生成bean的时候,第二次new foo的时候调用的,而在@Configuration时,bar()只在new foo中的时候调用一次,@Bean并没有起作用,下一节会讲解为什么。】

方法查找注入

如前所述,查找方法注入是一种您很少使用的高级功能。在单例范围的bean对原型范围的bean具有依赖关系的情况下,它很有用。对这种类型的配置使用Java提供了实现这种模式的自然方法。

public abstract class CommandManager {
    public Object process(Object commandState) {
        // grab a new instance of the appropriate Command interface
        Command command = createCommand();
        // set the state on the (hopefully brand new) Command instance
        command.setState(commandState);
        return command.execute();
    }

    // okay... but 它的实现方法在哪里?
    protected abstract Command createCommand();
}

使用Java配置支持,您可以创建CommandManager抽象createCommand()方法被重写的子类,以便查找新的(原型)命令对象:

@Bean
@Scope("prototype")
public AsyncCommand asyncCommand() {
    AsyncCommand command = new AsyncCommand();
    //俺需要拦截依赖
    return command;
}

@Bean
public CommandManager commandManager() {
    // return new anonymous implementation of CommandManager with command() overridden
    // to return a new prototype Command object
    return new CommandManager() {
        protected Command createCommand() {
            return asyncCommand();
        }
    }
}

有关基于Java的配置如何在内部工作的更多信息

以下示例显示了@Bean被调用两次的带注释的方法:

@Configuration
public class AppConfig {

    @Bean
    public ClientService clientService1() {
        ClientServiceImpl clientService = new ClientServiceImpl();
        clientService.setClientDao(clientDao());
        return clientService;
    }

    @Bean
    public ClientService clientService2() {
        ClientServiceImpl clientService = new ClientServiceImpl();
        clientService.setClientDao(clientDao());
        return clientService;
    }

    @Bean
    public ClientDao clientDao() {
        return new ClientDaoImpl();
    }
}

clientDao()在clientService1()里被调用了一次,在clientService2里也被调用了一次。由于这个方法新建并返回了一个ClientDaoImpl实例,可以预见这创建了两个实例(每个service一个)。但这无疑是一个问题:在Spring中,实例化的beans默认是singleton范围。所以这个看起来神奇的过程其实是这样的:所有的@Configuration标记的类在启动时会被CGLIB生成子类。在子类中,子类在调用父类方法并生成实例之前先检查容器缓存(scope)是否已经存在。在Spring3.2及以后,没有必要在classpath里新增CGLIB,因为CGLIB已经被整合到spring-core JAR的org.framework.cglib包下面了。

这些行为根据bean的范围不同而有所改变。这里我们只讨论了singleton。

CGLIB在启动时动态新增特性有一点小的限制,比如被当成配置的类不能是final的。在Spring 4.3以后,任何构造函数都可以在被当成配置的类中使用,包括使用@Autowire或者注入非默认构造器。
如果你想避免CGLIB的限制,可以考虑在非@Configuration类中声明@Bean注解的方法,比如用@Component类代替。@Bean之间的跨方法调用不会被拦截,所以你需要额外的引用依赖。

代码如下:

@Configuration
public class AppConfig {
    
    @Autowired
    Bar bar;

    @Bean
    public Foo foo() {
        return new Foo(bar);
    }

    @Bean
    public Bar bar() {
        return new Bar();
    }
}

1.13 环境抽象

Enviroment是一个集成在容器中的抽象,它模拟的应用程序的两个关键方面:配置文件属性

一个profile是bean定义一个命名的逻辑组,只有是有效的才能在容器中注册。bean可以由XML或者注释来定义。beans无论是通过XML或者注解都可以被分配成profile。环境对象和profile的角色关系在哪些是现在活跃,哪些是默认活跃时确定。

属性在几乎所有的应用程序中都扮演着重要的角色,可能来自各种来源:属性文件,JVM系统属性,系统环境变量,JNDI,servlet上下文参数,ad-hoc属性对象,地图等等。Environment与属性相关的对象的作用是为用户提供方便的服务接口,用于配置属性源和从中解析属性。

1.13.1 Bean定义配置文件

Bean定义配置文件是核心容器中的一种机制,允许在不同的环境中注册不同的Bean。环境这个词对不同的用户可能意味着不同的东西,这个特性可以帮助很多用例,包括:

  • 针对开发中的内存数据源,在QA或生产环境中查找来自JNDI的相同数据源
  • 仅在将应用程序部署到性能环境时注册监视基础架构
  • 为客户A和客户B部署注册定制的bean实现

第一个用例,在测试环境中,配置可能如下:

@Bean
public DataSource dataSource() {
    return new EmbeddedDatabaseBuilder()
        .setType(EmbeddedDatabaseType.HSQL)
        .addScript("my-schema.sql")
        .addScript("my-test-data.sql")
        .build();
}

现在让我们考虑如何将此应用程序部署到QA或生产环境,假定应用程序的数据源注册在JNDI目录中:

@Bean(destroyMethod="")
public DataSource dataSource() throws Exception {
    Context ctx = new InitialContext();
    return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
}

问题是如何在基于当前环境使用这两种变化之间切换。随着时间的推移,Spring用户已经设计了许多方法来完成这个任务,通常依赖于系统环境变量和<import/>包含${placeholder}令牌的XML 语句的组合,这些令牌根据环境变量的值解析为正确的配置文件路径。Bean定义配置文件是提供解决此问题的核心容器功能。

如果我们概括一下特定于环境的bean定义的上面的示例用例,我们最终需要在特定的上下文中注册某些bean定义,而不是其他的定义。你可以说你想在情况A中注册一个特定的bean定义配置文件,在情形B中需要另一个配置文件。我们先看看我们如何更新我们的配置以反映这种需求。

@Profile

@Profile注释允许你表明组件有资格登记时的一个或多个指定的简档是活动的。使用上面的示例,我们可以dataSource按如下方式重写配置:

@Configuration
@Profile("development")
public class StandaloneDataConfig {

    @Bean
    public DataSource dataSource() {
        return new EmbeddedDatabaseBuilder()
            .setType(EmbeddedDatabaseType.HSQL)
            .addScript("classpath:com/bank/config/sql/schema.sql")
            .addScript("classpath:com/bank/config/sql/test-data.sql")
            .build();
    }
}
@Configuration
@Profile("production")
public class JndiDataConfig {

    @Bean(destroyMethod="")
    public DataSource dataSource() throws Exception {
        Context ctx = new InitialContext();
        return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
    }
}

@Profile可以用作元注释,以创建自定义组合注释。以下示例定义了一个自定义@Production注释,可用作以下内容的 替代 @Profile("production")

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Profile("production")
public @interface Production {
}

如果@Configuration注解的类也标记了@Profile,除非指定的文件已经被配置,否则所有的@Bean方法和@Import声明都不会生成。如果@Component或者@Configuration类被标记成@Profile({"p1","p2"}),如果配置文件p1和p2没有被激活,那么这个类不会被注册或是运行。给定的配置文件有(!)前缀的话,在配置文件没被激活的时候,声明的元素将会被注册执行。举个例子就是,给定的@Profile({"p1","!p2"}),如果配置文件“p1”处于活动状态或配置文件“p2”未处于活动状态,则会发生注册。

@Profile 也可以在方法级别声明为仅包含配置类的一个特定的bean,例如用于特定bean的替代变体:

@Configuration
public class AppConfig {

    @Bean("dataSource")
    @Profile("development")
    public DataSource standaloneDataSource() {
        return new EmbeddedDatabaseBuilder()
            .setType(EmbeddedDatabaseType.HSQL)
            .addScript("classpath:com/bank/config/sql/schema.sql")
            .addScript("classpath:com/bank/config/sql/test-data.sql")
            .build();
    }

    @Bean("dataSource")
    @Profile("production")
    public DataSource jndiDataSource() throws Exception {
        Context ctx = new InitialContext();
        return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
    }
}

使用@Profileon 的@Bean方法,可能会应用特殊方案:对于@Bean相同Java方法名称的重载方法(类似于构造函数重载),@Profile需要在所有重载方法上一致地声明条件。如果条件不一致,则只有重载方法中第一个声明的条件才重要。因此因此@Profile不能用于选择具有特定参数签名的重载方法; 同一个bean的所有工厂方法之间的解析度在创建时遵循Spring的构造函数解析算法。
如果要定义具有不同配置文件条件的备用Bean,请使用通过@Beanname属性指向同一bean名称的不同Java方法名称,如上例所示。如果参数签名都是相同的(例如,所有变体都具有no-arg工厂方法),这是首先在有效的Java类中表示这种排列的唯一方法(因为只有一种方法可以一个特定的名字和参数签名)。

XML方式的配置文件

<beans profile="development"
    xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:jdbc="http://www.springframework.org/schema/jdbc"
    xsi:schemaLocation="...">

    <jdbc:embedded-database id="dataSource">
        <jdbc:script location="classpath:com/bank/config/sql/schema.sql"/>
        <jdbc:script location="classpath:com/bank/config/sql/test-data.sql"/>
    </jdbc:embedded-database>
</beans>
<beans profile="production"
    xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:jee="http://www.springframework.org/schema/jee"
    xsi:schemaLocation="...">

    <jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/>
</beans>

也可以避免<beans/>在同一个文件中拆分和嵌套元素:

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:jdbc="http://www.springframework.org/schema/jdbc"
    xmlns:jee="http://www.springframework.org/schema/jee"
    xsi:schemaLocation="...">

    <!-- other bean definitions -->

    <beans profile="development">
        <jdbc:embedded-database id="dataSource">
            <jdbc:script location="classpath:com/bank/config/sql/schema.sql"/>
            <jdbc:script location="classpath:com/bank/config/sql/test-data.sql"/>
        </jdbc:embedded-database>
    </beans>

    <beans profile="production">
        <jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/>
    </beans>
</beans>

激活配置文件

现在我们已经更新了配置,我们仍然需要指示Spring哪个配置文件处于活动状态。如果我们现在开始我们的示例应用程序,我们会看到NoSuchBeanDefinitionException抛出,因为容器找不到名为的Spring bean dataSource。

激活配置文件可以通过多种方式完成,但最直接的方法是通过以下方式对EnvironmentAPI进行 编程ApplicationContext:

AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.getEnvironment().setActiveProfiles("development");
ctx.register(SomeConfig.class, StandaloneDataConfig.class, JndiDataConfig.class);
ctx.refresh();

此外,还可以通过spring.profiles.active属性以声明方式激活配置文件,该 属性可以通过系统环境变量,JVM系统属性,servlet上下文参数web.xml或甚至作为JNDI中的条目来指定。

配置文件可以一次激活多个配置文件。以编程方式,只需为setActiveProfiles()方法提供多个配置文件名称,该 方法接受String…​varargs:

ctx.getEnvironment().setActiveProfiles("profile1", "profile2");
-Dspring.profiles.active="profile1,profile2"

默认配置文件

如果没有激活配置文件,dataSource将创建上述配置文件; 这可以看作是为一个或多个bean 提供默认定义的一种方法。如果启用了任何配置文件,则默认配置文件将不适用。

@Configuration
@Profile("default")
public class DefaultDataConfig {

    @Bean
    public DataSource dataSource() {
        return new EmbeddedDatabaseBuilder()
            .setType(EmbeddedDatabaseType.HSQL)
            .addScript("classpath:com/bank/config/sql/schema.sql")
            .build();
    }
}

1.13.2.PropertySource抽象

Spring的环境抽象提供了可配置的属性源的层次结构的搜索操作。要完整解释,请考虑以下事项:

ApplicationContext ctx = new GenericApplicationContext();
Environment env = ctx.getEnvironment();
boolean containsFoo = env.containsProperty("foo");
System.out.println("Does my environment contain the 'foo' property? " + containsFoo);

在上面的代码片段中,我们看到了一种向Spring询问是否foo为当前环境定义属性的高级方法。要回答此问题,Environment对象将对一组对象执行搜索PropertySourcePropertySource是对任何键值对源的简单抽象,Spring StandardEnvironment配置有两个PropertySource对象 - 一个表示JVM系统属性集System.getProperties(),另一个表示系统环境变量集System.getenv()

具体来说,在使用时StandardEnvironment,env.containsProperty("foo") 如果在运行时存在foo系统属性或foo环境变量,则调用将返回true 。

执行的搜索是分层的。默认情况下,系统属性优先于环境变量,因此如果foo在调用期间恰好在两个位置都设置了属性env.getProperty("foo"),则系统属性值将“获胜”并优先于环境变量返回。请注意,属性值不会被合并,而是被前面的条目完全覆盖。
对于公共StandardServletEnvironment层次结构,完整层次结构如下所示,最高优先级条目位于顶部:

  • ServletConfig参数(如果适用,例如在DispatcherServlet上下文的情况下)
  • ServletContext参数(web.xml context-param条目)
  • JNDI环境变量(“java:comp / env /”条目)
  • JVM系统属性(“-D”命令行参数)
  • JVM系统环境(操作系统环境变量)

最重要的是,整个机制是可配置的。也许您有自定义的属性源,您希望将其集成到此搜索中。没问题 - 只需实现并实例化您自己的PropertySource并将其添加到PropertySources当前的集合中Environment:

ConfigurableApplicationContext ctx = new GenericApplicationContext();
MutablePropertySources sources = ctx.getEnvironment().getPropertySources();
sources.addFirst(new MyPropertySource());

1.13.3. @PropertySource

@PropertySource注解提供便利和声明的机制添加PropertySource 到Spring的Environment

给定包含键/值对的文件“app.propertiestestbean.name=myTestBean,以下@Configuration类@PropertySource以这样的方式使用,即调用testBean.getName()将返回“myTestBean”。

@Configuration
@PropertySource("classpath:/com/myco/app.properties")
public class AppConfig {

    @Autowired
    Environment env;

    @Bean
    public TestBean testBean() {
        TestBean testBean = new TestBean();
        testBean.setName(env.getProperty("testbean.name"));
        return testBean;
    }
}

资源位置中存在的任何占位符${…​},@PropertySource将根据已针对环境注册的属性源集进行解析。例如:

@Configuration
@PropertySource("classpath:/com/${my.placeholder:default/path}/app.properties")
public class AppConfig {

    @Autowired
    Environment env;

    @Bean
    public TestBean testBean() {
        TestBean testBean = new TestBean();
        testBean.setName(env.getProperty("testbean.name"));
        return testBean;
    }
}

假设“my.placeholder”存在于已注册的其中一个属性源中,例如系统属性或环境变量,则占位符将被解析为相应的值。如果没有,则“default / path”将用作默认值。如果未指定默认值且无法解析属性,则将抛出IllegalArgumentException 。

根据Java 8规定,@PropertySource注释是可重复的。但是,所有这些@PropertySource注释都需要在同一级别声明:直接在配置类上或在同一自定义注释中的元注释。不建议混合直接注释和元注释,因为直接注释将有效地覆盖元注释。

1.13.4。占位符的描述

从历史上看,元素中占位符的值只能针对JVM系统属性或环境变量进行解析。现在情况不再如此。因为环境抽象集成在整个容器中,所以很容易通过它来解决占位符的分辨。这意味着您可以以任何您喜欢的方式配置解析过程:更改搜索系统属性和环境变量的优先级,或者完全删除它们; 根据需要将您自己的属性源添加到混合中。

具体而言,无论customer 属性在何处定义,以下语句都可以工作,只要它在以下位置可用Environment:

<beans>
    <import resource="com/bank/service/${customer}-config.xml"/>
</beans>

1.14。注册LoadTimeWeaver

Spring用LoadTimeWeaver来动态转换被Java虚拟机(JVM)加载的类。

要启用加载时编织(Weaver),请添加@EnableLoadTimeWeaver到其中一个@Configuration类:

@Configuration
@EnableLoadTimeWeaver
public class AppConfig{
}

或者对于XML配置使用context:load-time-weaver元素:

<beans>
    <context:load-time-weaver/>
</beans>

一旦配置为ApplicationContext。其中的任何bean都ApplicationContext 可以实现LoadTimeWeaverAware,从而接收对load-time weaver实例的引用。这与Spring的JPA支持结合使用特别有用, 其中JPA类转换可能需要加载时编织。有关LocalContainerEntityManagerFactoryBean更多详细信息,请参阅javadocs。有关AspectJ加载时编织的更多信息,请参阅Spring Framework中使用AspectJ进行加载时编织

1.15。ApplicationContext的其他功能

正如章节介绍中所讨论的,该org.springframework.beans.factory 提供了包括编程方式在内的管理和操作bean的基本功能。除了扩展其他接口以外 ,该org.springframework.context软件包还添加了 ApplicationContext扩展BeanFactory接口的接口,以更加面向应用程序框架的方式提供附加功能。许多人ApplicationContext以完全声明的方式使用它,甚至不以编程方式创建它,而是依赖于支持类,例如ContextLoader自动实例化 ApplicationContext作为Java EE Web应用程序的正常启动过程的一部分。

为了BeanFactory以更加面向框架的样式增强功能,上下文包还提供以下功能:

  • 通过MessageSource界面访问i18n风格的消息。
  • 通过ResourceLoader界面访问URL和文件等资源。
  • 事件发布即beans通过ApplicatiionEventPublisher接口实现了ApplicationListener接口。
  • 加载多个(分层)上下文,允许每个上下文通过HierarchicalBeanFactory接口聚焦在一个特定层上,例如应用程序的Web层 。

1.15.1。使用MessageSource进行国际化
该ApplicationContext接口扩展了一个名为的接口MessageSource,因此提供了国际化(i18n)功能。Spring还提供了接口HierarchicalMessageSource,可以分层次地解析消息。这些接口共同提供了Spring影响消息解析的基础。这些接口上定义的方法包括:

  • String getMessage(String code, Object[] args, String default, Locale loc):用于从中检索消息的基本方法MessageSource。如果未找到指定区域设置的消息,则使用默认消息。传入的任何参数都使用MessageFormat标准库提供的功能成为替换值。
  • String getMessage(String code, Object[] args, Locale loc):与前一个方法基本相同,但有一点不同:不能指定默认消息; 如果找不到该消息,NoSuchMessageException则抛出a。
  • String getMessage(MessageSourceResolvable resolvable, Locale locale):前面方法中使用的所有属性也包装在一个名为的类中 MessageSourceResolvable,您可以使用此方法。

当ApplicationContext被加载时,它自动搜索MessageSource 在上下文中定义的bean。bean必须具有名称messageSource。如果找到这样的bean,则对前面方法的所有调用都被委托给消息源。如果未找到任何消息源,则ApplicationContext尝试查找包含具有相同名称的bean的父级。如果是,它将使用该bean作为MessageSource。如果 ApplicationContext找不到任何消息源,DelegatingMessageSource则实例化为空 以便能够接受对上面定义的方法的调用。

Spring提供了两种MessageSource实现方式,ResourceBundleMessageSource和 StaticMessageSource。两者都是HierarchicalMessageSource为了进行嵌套消息传递而实现的。在StaticMessageSource很少使用,但提供了编程的方式向消息源添加消息。在ResourceBundleMessageSource被示出在下面的例子:

<beans>
    <bean id="messageSource"
            class="org.springframework.context.support.ResourceBundleMessageSource">
        <property name="basenames">
            <list>
                <value>format</value>
                <value>exceptions</value>
                <value>windows</value>
            </list>
        </property>
    </bean>
</beans>

1.16。BeanFactory

BeanFactory提供了Spring IoC底层的功能,但它只是直接用于和其他第三方框架的集成,对现在大多数Spring用户来说是历史。BeanFactory和相关接口,比如BeanFactoryAware,InitializingBean,DisposableBean,出于Spring要向后兼容与Spring集成的第三方框架而保存。但通常第三方组件,不能使用最新的@PostConstruct和@PreDestory来避免对JSR-250的依赖。

1.16.1。BeanFactory还是ApplicationContext?

推荐使用ApplicationContext。除非你有特殊的理由使用BeanFactory。

ApplicationContext包含了BeanFactory的所有功能。除了少数情况,例如在资源受限的设备上运行的嵌入式应用程序中,内存消耗可能是关键的,而一些额外的千字节可能会产生影响的情况下会推荐使用BeanFactory。但是对大多数企业应用程序和系统,你会更偏爱使用ApplicationContext。Spring大量使用BeanPostProcessor扩展点(以实现代理等)。如果只是简单的使用BeanFactory,transaction和AOP这样的大量辅助功能将不被支持。如果你额外的去实现这些功能也是可以的。这种情况可能会令人困惑,因为配置实际上没有任何问题。

下表列出了提供的功能BeanFactory和 ApplicationContext接口和实现。

特征 BeanFactory ApplicationContext
Bean的实例化 支持 支持
自动BeanFactoryProcessor注册 不支持 支持
自动BeanFactoryPostProcessor 不支持 支持
方便MessageSource访问 不支持 支持
Application publication 不支持 支持

要使用实现显式注册bean后处理器BeanFactory,需要编写如下代码:

DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
// populate the factory with bean definitions

// 注册postProcessor的实例
MyBeanPostProcessor postProcessor = new MyBeanPostProcessor();
factory.addBeanPostProcessor(postProcessor);

// 现在,可以使用factory了

要BeanFactoryPostProcessor在使用BeanFactory 实现时显式注册,必须编写如下代码:

DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
reader.loadBeanDefinitions(new FileSystemResource("beans.xml"));

// bring in some property values from a Properties file
PropertyPlaceholderConfigurer cfg = new PropertyPlaceholderConfigurer();
cfg.setLocation(new FileSystemResource("jdbc.properties"));

// now actually do the replacement
cfg.postProcessBeanFactory(factory);

在这两种情况下,显式注册步骤都不方便,这是为什么各种 实现在绝大多数Spring支持的应用程序ApplicationContext中优先于普通BeanFactory实现的一个原因,特别是在使用BeanFactoryPostProcessors和BeanPostProcessors时。这些机制实现了重要的功能,例如属性占位符替换和AOP。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345