Spring框架文档(二 )

原文英文链接:

https://docs.spring.io/spring/docs/5.2.3.BUILD-SNAPSHOT/spring-framework-reference/core.html#spring-core

https://github.com/spring-projects/spring-framework

(由于鄙人英文水平以及技术能力有限,可能无法做到 信达雅,如有不当之处,烦请在评论区指出,非常感谢!)

 

Core Technologies

Version 5.2.3.BUILD-SNAPSHOT


参考文档的这一部分涵盖了Spring框架必不可少的所有技术。

其中最重要的是Spring框架的控制反转(IoC)容器。对Spring框架的IoC容器进行彻底处理之后,将全面介绍Spring的面向方面编程(AOP)技术。Spring框架拥有自己的AOP框架,该框架在概念上易于理解,并且成功解决了Java企业编程中AOP要求的80%的难题。

还提供了Spring与AspectJ的集成(就功能而言,目前是最丰富的-当然肯定是Java企业领域中最成熟的AOP实现)。


 

1. The IoC Container

本章介绍了Spring的控制反转(IoC)容器。


1.1. Introduction to the Spring IoC Container and Beans

本章介绍了控制反转(IoC)原理的Spring框架实现。IoC也称为依赖注入(DI)。在此过程中,对象仅通过构造函数参数,工厂方法的参数或从工厂方法构造或返回对象实例后,通过在其上设置的属性来定义其依赖项(即与之一起工作的其他对象)的过程 。然后,容器在创建bean时注入那些依赖项。此过程跟bean自己控制实例化,或者通过直接构造类或服务定位器模式定位其依赖项的方式相反(因此称为控制反转)。

org.springframework.beansorg.springframework.context包是Spring框架的IoC容器的基础。

 BeanFactory 接口提供了一种高级配置机制,能够管理任何类型的对象。

 ApplicationContext 是的子接口BeanFactory。它增加了:

  • 与Spring的AOP功能轻松集成

  • 消息资源处理(用于国际化)

  • 事件发布

  • 应用层特定的上下文,例如WebApplicationContext 用于Web应用程序中的。

简而言之,BeanFactory提供了配置框架和基本功能,并ApplicationContext增加了更多针对企业的功能。该ApplicationContextBeanFactory的完整的超集,在本章中专门描述Spring的IoC容器。有关使用BeanFactory而不是ApplicationContext的信息,参见 BeanFactory获得更多信息。

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


1.2. Container Overview

org.springframework.context.ApplicationContext接口代表Spring IoC容器,并负责实例化,配置和组装Bean。容器通过读取配置元数据来获取有关要实例化,配置和组装的对象的指令。配置元数据以XML,Java注解或Java代码表示。它定义了组成应用程序的对象以及这些对象之间的相互依赖关系。

Spring提供了ApplicationContext接口的几种实现。在独立应用程序中,通常创建ClassPathXmlApplicationContext 或FileSystemXmlApplicationContext的实例 。尽管XML是定义配置元数据的传统格式,但是您可以通过Java注解或代码作为元数据格式,并提供少量XML配置,来声明对这些附加元数据格式的支持。

在大多数应用场景中,不需要显式用户代码即可实例化一个Spring IoC容器的一个或多个实例。例如,在Web应用程序场景中,应用程序文件中的简单八行 web.xml通常就足够了(请参阅Web应用程序的便捷ApplicationContext实例化)。如果使用 Spring Tool Suite(基于Eclipse的开发环境),则只需单击几次鼠标或击键即可轻松创建此样板配置。

下图显示了Spring的工作原理的高级视图。您的应用程序类与配置元数据结合在一起,因此,在ApplicationContext创建和初始化后,您将拥有一个完全配置且可执行的系统或应用程序。

 

container magic

                                                图1. Spring IoC容器

 

1.2.1. Configuration Metadata

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

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

 

基于XML的元数据不是配置元数据的唯一允许形式。Spring IoC容器本身与实际写入此配置元数据的格式完全脱钩。如今,许多开发人员为他们的Spring应用程序选择 基于Java的配置

有关在Spring容器中使用其他形式的元数据的信息,请参见:

 

  • 基于注解的配置: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类中使用带注解的方法。

这些bean定义对应于组成应用程序的实际对象。通常,您定义服务层对象,数据访问对象(DAO),表示对象(例如Struts Action实例),基础结构对象(例如Hibernate SessionFactories,JMS Queues等)。通常,不会在容器中配置细粒度的域对象,因为创建和加载域对象通常是DAO和业务逻辑的职责。但是,您可以使用Spring与AspectJ的集成来配置在IoC容器的控制范围之外创建的对象。请参阅使用AspectJ与Spring依赖注入域对象

以下示例显示了基于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
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="..." class="...">  
        <!-- collaborators and configuration for this bean go here -->
    </bean>

    <bean id="..." class="...">
        <!-- collaborators and configuration for this bean go here -->
    </bean>

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

</beans>

 

id属性是标识单个bean定义的字符串。

 

class属性定义Bean的类型,并使用完全限定的类名。

id属性的值是指协作对象。在此示例中未显示用于引用协作对象的XML。有关更多信息,请参见 依赖项

 

1.2.2. Instantiating a Container


提供给ApplicationContext构造函数的位置路径是资源字符串,它允许容器从各种外部资源(如本地文件系统、Java类路径等)装载配置元数据。

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


在了解了Spring的IoC容器之后,您可能想了解更多有关Spring的 Resource抽象(如参考资料中所述),它提供了一种方便的机制,用于从URI语法中定义的位置读取InputStream。具体而言,Resource应用程序上下文和资源路径中所述, 路径用于构造应用程序上下文

以下示例显示了服务层对象(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
        https://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>

以下示例显示了数据访问对象daos.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
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="accountDao"
        class="org.springframework.samples.jpetstore.dao.jpa.JpaAccountDao">
        <!-- additional collaborators and configuration for this bean go here -->
    </bean>

    <bean id="itemDao" class="org.springframework.samples.jpetstore.dao.jpa.JpaItemDao">
        <!-- additional collaborators and configuration for this bean go here -->
    </bean>

    <!-- more bean definitions for data access objects go here -->

</beans>

 

在前面的示例中,服务层由PetStoreServiceImpl类和JpaAccountDao和JpaItemDao类型的两个数据访问对象(基于JPA对象-关系映射标准)组成。property name元素引用JavaBean属性的名称,而ref元素引用另一个bean定义的名称。id和ref元素之间的这种链接表达了协作对象之间的依赖关系。有关配置对象依赖项的详细信息,请参阅 依赖关系

构建基于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.xmlmessageSource.xml,和themeSource.xml。所有位置路径都相对于进行导入的定义文件(相对路径),因此services.xml必须与进行导入的文件位于同一目录或类路径位置, messageSource.xml和themeSource.xml必须位于resources导入文件位置下方的位置。如您所见,斜杠被忽略。但是,鉴于这些路径是相对的,最好不要使用任何斜线。<beans/>根据Spring Schema,导入的文件的内容(包括顶层元素)必须是有效的XML bean定义。

 

可以但不建议使用相对的“ ../”路径引用父目录中的文件。这样做会创建对当前应用程序外部文件的依赖。特别是,对于运行时解析过程选择“最近的”类路径根目录然后查找其父目录的classpath:URL,不建议使用此引用(例如classpath:../services.xml)。类路径配置的更改可能导致选择其他错误的目录。

您始终可以使用完全限定的资源位置来代替相对路径:例如file:C:/config/services.xmlclasspath:/config/services.xml。但是,请注意,您正在将应用程序的配置耦合到特定的绝对位置。通常最好为这样的绝对位置保留一个间接寻址,例如,通过在运行时针对JVM系统属性解析的“ $ {…}”占位符。

命名空间本身提供了导入指令功能。Spring提供的一系列XML命名空间(例如contextutil命名空间)中提供了超出普通bean定义的其他配置功能。

Groovy Bean定义DSL

作为外部化配置元数据的另一个示例,Bean定义也可以在Spring的Groovy Bean定义DSL中表达,如从Grails框架中了解的那样。通常,这种配置位于“ .groovy”文件中,其结构如以下示例所示:

beans {
    dataSource(BasicDataSource) {
        driverClassName = "org.hsqldb.jdbcDriver"
        url = "jdbc:hsqldb:mem:grailsDB"
        username = "sa"
        password = ""
        settings = [mynew:"setting"]
    }
    sessionFactory(SessionFactory) {
        dataSource = dataSource
    }
    myService(MyService) {
        nestedBean = { AnotherBean bean ->
            dataSource = dataSource
        }
    }
}

这种配置样式在很大程度上等效于XML bean定义,甚至支持Spring的XML配置名称空间。它还允许通过importBeans指令导入XML bean定义文件。

1.2.3. Using the Container

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

ApplicationContext允许您读取bean定义并访问它们,如下面的示例所示:

// create and configure beans
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");

// retrieve configured instance
PetStoreService service = context.getBean("petStore", PetStoreService.class);

// use configured instance
List<String> userList = service.getUsernameList();

使用Groovy配置,引导看起来非常相似。它有一个不同的上下文实现类,该类可识别Groovy(但也了解XML Bean定义)。以下示例显示了Groovy配置:

ApplicationContext context = new GenericGroovyApplicationContext("services.groovy", "daos.groovy");

最灵活的变体是GenericApplicationContext与读取器委托结合使用,例如,与XmlBeanDefinitionReaderXML文件结合使用,如以下示例所示:

GenericApplicationContext context = new GenericApplicationContext();
new XmlBeanDefinitionReader(context).loadBeanDefinitions("services.xml", "daos.xml");
context.refresh();

您还可以为Groovy文件使用GroovyBeanDefinitionReader,如下面的示例所示:

GenericApplicationContext context = new GenericApplicationContext();
new GroovyBeanDefinitionReader(context).loadBeanDefinitions("services.groovy", "daos.groovy");
context.refresh();

您可以在相同的ApplicationContext上混合和匹配这样的reader委托,从不同的配置源读取bean定义。

然后可以使用getBean检索bean的实例。ApplicationContext接口有一些用于检索bean的其他方法,但在理想情况下,应用程序代码不应该使用它们。实际上,您的应用程序代码应该完全不调用getBean()方法,因此完全不依赖于Spring api。例如,Spring与web框架的集成为各种web框架组件(如控制器和jsf管理的bean)提供了依赖项注入,允许您通过元数据(如自动装配注释)声明对特定bean的依赖项。

 


 

1.3. Bean Overview

Spring IoC容器管理一个或多个bean。这些bean是使用您提供给容器的配置元数据创建的(例如,以XML <bean/>定义的形式 )。

在容器本身内,这些bean定义表示为BeanDefinition 对象,其中包含(除其他信息外)以下元数据:

  • 包限定的类名:通常,定义了Bean的实际实现类。

  • Bean行为配置元素,用于声明Bean在容器中的行为(作用域,生命周期回调等)。

  • 对其他bean进行工作所需的引用。这些引用也称为协作者或依赖项。

  • 要在新创建的对象中设置的其他配置设置,例如,池的大小限制或要在管理连接池的bean中使用的连接数。

此元数据转换为构成每个bean定义的一组属性。下表描述了这些属性:

Table 1. The bean definition
PropertyExplained in…​

Class

Instantiating Beans

Name

Naming Beans

Scope

Bean Scopes

Constructor arguments

Dependency Injection

Properties

Dependency Injection

Autowiring mode

Autowiring Collaborators

Lazy initialization mode

Lazy-initialized Beans

Initialization method

Initialization Callbacks

Destruction method

Destruction Callbacks

除了包含有关如何创建特定bean的信息的bean定义之外,ApplicationContext实现还允许注册在容器外部(由用户)创建的现有对象。这是通过getBeanFactory()方法访问ApplicationContext的BeanFactory来实现的,该方法返回BeanFactory的DefaultListableBeanFactory实现。DefaultListableBeanFactory通过registerSingleton(..)和registerBeanDefinition(..)方法支持这种注册。但是,典型的应用程序只使用通过常规bean定义元数据定义的bean。

 Bean元数据和手动提供的单例实例需要尽早注册,以便容器在自动装配和其他内省步骤期间正确地推断它们。虽然在某种程度上支持覆盖现有的元数据和现有的单例实例,但是在运行时注册新bean(同时对工厂进行实时访问)并没有得到官方支持,这可能会导致并发访问异常、bean容器中的不一致状态,或者两者都有。

1.3.1. Naming Beans

每个bean具有一个或多个标识符。这些标识符在承载Bean的容器内必须是唯一的。一个bean通常只有一个标识符。但是,如果需要多个,则可以将多余的别名视为别名。

在基于XML配置文件,您可以使用id属性,该name属性,或两者来指定bean标识符。该id属性使您可以精确指定一个ID。按照惯例,这些名称是字母数字(“ myBean”,“ someService”等),但它们也可以包含特殊字符。如果要为bean引入其他别名,也可以在name 属性中指定它们,并用逗号(,),分号(;)或空格分隔。作为历史记录,在Spring 3.1之前的版本中,该id属性被定义为一种xsd:ID类型,该类型限制了可能的字符。从3.1开始,它被定义为xsd:string类型。请注意,Bean id的唯一性仍由容器强制执行,尽管不再由XML解析器执行。

您不需要为bean提供名称或id。如果您没有显式地提供名称或id,则容器将为该bean生成唯一的名称。但是,如果希望通过名称引用该bean,则必须通过使用ref元素或服务定位器样式查找来提供名称。不提供名称的动机与使用内部bean和自动装配协作者有关。

                                                                                   Bean命名规范

 

约定是在命名bean时使用标准Java约定作为实例字段名。也就是说,bean名称以小写字母开头,并从那里开始采用驼峰格式。此类名称的示例包括accountManager、accountService、userDao、loginController等。

一致地命名bean使您的配置更容易阅读和理解。另外,如果您使用Spring AOP,在将建议应用到一组按名称关联的bean时,它会有很大帮助。


通过在类路径中进行组件扫描,Spring会按照前面描述的规则为未命名的组件生成Bean名称:从本质上讲,采用简单的类名称并将其初始字符转换为小写。但是,在(不寻常的)特殊情况下,如果有多个字符并且第一个和第二个字符均为大写字母,则会保留原始大小写。这些规则与java.beans.Introspector.decapitalize(由Spring在此处使用)定义的规则相同。

 

在Bean定义之外使用别名

在bean定义本身中,可以通过使用由id属性指定的最多一个名称和该属性中任意数量的其他名称的组合来为bean提供多个名称name。这些名称可以是同一个bean的等效别名,并且在某些情况下很有用,例如通过使用特定于该组件本身的bean名称,让应用程序中的每个组件都引用一个公共依赖项。

但是,在实际定义bean的地方指定所有别名并不总是足够的。有时需要为在别处定义的bean引入别名。这在大型系统中通常是这种情况,在大型系统中,配置在每个子系统之间分配,每个子系统都有自己的对象定义集。在基于XML的配置元数据中,您可以使用<alias/>元素来完成此任务。以下示例显示了如何执行此操作:

<alias name="fromName" alias="toName"/>

在这种情况下,fromName在使用该别名定义之后,命名为Bean(位于同一容器中)的bean也可以称为toName

例如,子系统A的配置元数据可以使用的名称引用数据源subsystemA-dataSource。子系统B的配置元数据可以使用的名称引用数据源subsystemB-dataSource。组成使用这两个子系统的主应用程序时,主应用程序使用名称引用数据源myApp-dataSource。要使所有三个名称都引用相同的对象,可以将以下别名定义添加到配置元数据中:

<alias name="myApp-dataSource" alias="subsystemA-dataSource"/>
<alias name="myApp-dataSource" alias="subsystemB-dataSource"/>

现在,每个组件和主应用程序都可以通过唯一的名称引用数据源,并保证不与任何其他定义冲突(有效地创建名称空间),但它们引用的是同一bean。

                                                                             Java配置

如果使用Java配置,则@Bean注解可用于提供别名。有关详细信息,请参见使用@Bean注解

 

1.3.2. Instantiating Beans

Bean定义本质上是创建一个或多个对象的方法。当需要创建Bean时,容器将查看命名bean的创建方法,并使用该bean定义封装的配置元数据来创建(或获取)实例对象。

如果使用基于xml的配置元数据,则指定要在元素的class属性中实例化的对象的类型(或类)。这个类属性(在内部是BeanDefinition实例上的一个类属性)通常是强制性的。(请参见使用实例工厂方法和Bean定义继承进行实例化)。你可以通过以下两种方式之一使用Class属性:

  • 通常,在容器本身通过反射调用其构造函数直接创建bean的情况下,指定要构造的bean类,这有点类似于使用新操作符的Java代码。

  • 要指定包含用于创建对象的静态工厂方法的实际类,在不太常见的情况下,容器调用类的静态工厂方法来创建bean。从静态工厂方法调用返回的对象类型可以是同一个类,也可以完全是另一个类。

内部类名称

如果希望为静态嵌套类配置bean定义,则必须使用嵌套类的二进制名。

例如,如果您在com中有一个类名为SomeThing。这个类有一个静态的嵌套类叫做OtherThing, bean定义上的class属性的值是com.example。SomeThing$OtherThing。

注意,在名称中使用$字符将嵌套的类名与外部类名分隔开。

用构造方法实例化

当通过构造方法创建一个bean时,所有普通类都可以被Spring使用并与之兼容。也就是说,正在开发的类不需要实现任何特定的接口或以特定的方式进行编码。只需指定bean类就足够了。但是,根据您用于该特定bean的IoC的类型,您可能需要一个默认(空)构造函数。

Spring IoC容器几乎可以管理您要管理的任何类。它不仅限于管理真正的JavaBean。大多数Spring用户更喜欢实际的JavaBean,它们仅具有默认(无参数)构造函数,并具有根据容器中的属性建模的适当的setter和getter。您还可以在容器中具有更多奇特的非bean样式类。例如,如果您需要使用绝对不符合JavaBean规范的历史的连接池,则Spring也可以对其进行管理。

使用基于XML的配置元数据,您可以如下指定bean类:

<bean id="exampleBean" class="examples.ExampleBean"/>

<bean name="anotherExample" class="examples.ExampleBeanTwo"/>

有关向构造方法提供参数(如果需要)并在构造对象之后设置对象实例属性的机制的详细信息,请参见 注入依赖项

用静态工厂方法实例化

在定义使用静态工厂方法创建的bean时,请使用class 属性指定包含static工厂方法的类,并使用命名factory-method为属性的属性来指定工厂方法本身的名称。您应该能够调用此方法(带有可选参数,如后面所述),并返回一个活动对象,该对象随后将被视为已通过构造函数创建。这种bean定义的一种用法是static用旧版代码调用工厂。

以下bean定义指定通过调用工厂方法来创建bean。该定义不指定返回对象的类型(类),而仅指定包含工厂方法的类。在此示例中,该createInstance() 方法必须是静态方法。以下示例显示如何指定工厂方法:

<bean id="clientService"
    class="examples.ClientService"
    factory-method="createInstance"/>

以下示例显示了可与前面的bean定义一起使用的类:

public class ClientService {
    private static ClientService clientService = new ClientService();
    private ClientService() {}

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

有关为工厂方法提供(可选)参数并在从工厂返回对象之后设置对象实例属性的机制的详细信息,请参见Dependencies and Configuration in Detail

使用实例工厂方法实例化

类似于通过静态工厂方法实例化,使用实例工厂方法实例化从容器中调用现有bean的非静态方法以创建新bean。要使用此机制,请将class属性保留为空,并在factory-bean属性中指定当前(或父容器或祖先容器)中包含要创建对象的实例方法的Bean的名称。使用factory-method属性设置工厂方法本身的名称。以下示例显示了如何配置此类Bean:

<!-- the factory bean, which contains a method called createInstance() -->
<bean id="serviceLocator" class="examples.DefaultServiceLocator">
    <!-- inject any dependencies required by this locator bean -->
</bean>

<!-- the bean to be created via the factory bean -->
<bean id="clientService"
    factory-bean="serviceLocator"
    factory-method="createClientServiceInstance"/>

以下示例显示了相应的类:

public class DefaultServiceLocator {

    private static ClientService clientService = new ClientServiceImpl();

    public ClientService createClientServiceInstance() {
        return clientService;
    }

一个工厂类也可以包含一个以上的工厂方法,如以下示例所示:

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

以下示例显示了相应的类:

public class DefaultServiceLocator {

    private static ClientService clientService = new ClientServiceImpl();

    private static AccountService accountService = new AccountServiceImpl();

    public ClientService createClientServiceInstance() {
        return clientService;
    }

    public AccountService createAccountServiceInstance() {
        return accountService;
    }
}

这种方法表明,工厂Bean本身可以通过依赖项注入(DI)进行管理和配置。详细信息,请参见依赖性和配置


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

 


 

1.4. Dependencies

典型的企业应用程序不由单个对象(或Spring术语中的bean)组成。即使是最简单的应用程序,也有一些对象可以协同工作,以呈现最终用户视为一致的应用程序。下一部分将说明如何从定义多个独立的Bean定义到实现对象协作以实现目标的完全实现的应用程序。

1.4.1. Dependency Injection

依赖注入(DI)是一个过程,通过该过程,对象仅通过构造函数参数,工厂方法的参数或在构造或创建对象实例后在对象实例上设置的属性来定义其依赖关系(它们使用的其他对象)。然后容器在创建bean时注入这些依赖项。这个过程基本上是bean本身的逆过程(因此称为控制反转),通过直接构造类或服务定位器模式来控制依赖项的实例化或位置。

使用DI原理,代码更加简洁,当为对象提供依赖项时,解耦会更有效。该对象不查找其依赖项,并且不知道依赖项的位置或类。结果,您的类变得更易于测试,尤其是当依赖项依赖于接口或抽象基类时,它们允许在单元测试中使用stub或mock实现。

基于构造方法的依赖注入

基于构造方法的DI是通过容器调用具有多个参数的构造方法来完成的,每个参数表示一个依赖项。调用static带有特定参数的工厂方法来构造Bean几乎是等效的,并且本次讨论将构造方法和static工厂方法的参数视为类似。以下示例显示了只能通过构造方法注入进行依赖项注入的类:

public class SimpleMovieLister {

    // the SimpleMovieLister has a dependency on a MovieFinder
    private MovieFinder movieFinder;

    // a constructor so that the Spring container can inject a MovieFinder
    public SimpleMovieLister(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // business logic that actually uses the injected MovieFinder is omitted...
}

注意,该类没有什么特别的。它是一个POJO,不依赖于特定于容器的接口,基类或注释。

构造方法参数解析

构造方法参数解析匹配通过使用参数的类型进行。如果Bean定义的构造方法参数中不存在潜在的歧义,则在实例化Bean时,在Bean定义中定义构造方法参数的顺序就是将这些参数提供给适当的构造方法的顺序。考虑以下类别:

package x.y;

public class ThingOne {

    public ThingOne(ThingTwo thingTwo, ThingThree thingThree) {
        // ...
    }
}

假定ThingTwoThingThree类不是通过继承关联的,则不存在潜在的歧义。因此,以下配置可以正常工作,并且您无需在<constructor-arg/> 元素中显式指定构造方法参数索引或类型。

<beans>
    <bean id="beanOne" class="x.y.ThingOne">
        <constructor-arg ref="beanTwo"/>
        <constructor-arg ref="beanThree"/>
    </bean>

    <bean id="beanTwo" class="x.y.ThingTwo"/>

    <bean id="beanThree" class="x.y.ThingThree"/>
</beans>

当引用另一个bean时,类型是已知的,并且可以发生匹配(与前面的示例一样)。当使用诸如的简单类型时 <value>true</value>,Spring无法确定值的类型,因此在没有帮助的情况下无法按类型进行匹配。考虑以下类别:

package examples;

public class ExampleBean {

    // Number of years to calculate the Ultimate Answer
    private int years;

    // The Answer to Life, the Universe, and Everything
    private String ultimateAnswer;

    public ExampleBean(int years, String ultimateAnswer) {
        this.years = years;
        this.ultimateAnswer = ultimateAnswer;
    }
}
构造函数参数类型匹配

在上述情况下,如果通过使用type属性显式指定构造函数参数的类型,则容器可以使用简单类型的类型匹配。如以下示例所示:

<bean id="exampleBean" class="examples.ExampleBean">
    <constructor-arg type="int" value="7500000"/>
    <constructor-arg type="java.lang.String" value="42"/>
</bean>
构造函数参数索引

您可以使用该index属性来明确指定构造函数参数的索引,如以下示例所示:

<bean id="exampleBean" class="examples.ExampleBean">
    <constructor-arg index="0" value="7500000"/>
    <constructor-arg index="1" value="42"/>
</bean>

除了解决多个简单值的歧义性之外,指定索引还可以解决歧义,其中构造函数具有两个相同类型的参数。

 索引从0开始。

 

构造函数参数名称

您还可以使用构造函数参数名称来消除歧义,如以下示例所示:

<bean id="exampleBean" class="examples.ExampleBean">
    <constructor-arg name="years" value="7500000"/>
    <constructor-arg name="ultimateAnswer" value="42"/>
</bean>

请记住,要立即使用该功能,必须在启用调试标志的情况下编译代码,以便Spring可以从构造函数中查找参数名称。如果您不能或不想使用debug标志编译代码,则可以使用 @ConstructorProperties JDK注释显式命名构造函数参数。然后,样本类必须如下所示:

package examples;

public class ExampleBean {

    // Fields omitted

    @ConstructorProperties({"years", "ultimateAnswer"})
    public ExampleBean(int years, String ultimateAnswer) {
        this.years = years;
        this.ultimateAnswer = ultimateAnswer;
    }
}

 

基于Setter的依赖注入

基于Setter的DI是通过在调用无参数构造函数或无参数static工厂方法以实例化您的bean 之后,在您的bean上调用setter方法来完成的。

下面的示例显示只能通过使用纯setter注入来依赖注入的类。此类是常规的Java。它是一个POJO,不依赖于特定于容器的接口,基类或注释。

public class SimpleMovieLister {

    // the SimpleMovieLister has a dependency on the MovieFinder
    private MovieFinder movieFinder;

    // a setter method so that the Spring container can inject a MovieFinder
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // business logic that actually uses the injected MovieFinder is omitted...
}

ApplicationContext支持基于构造函数和的Setter DI为它所管理的bean类。在通过构造函数方法注入了某些依赖项之后,它还支持基于setter的DI。您可以将依赖项配置为BeanDefinition的形式,将其与PropertyEditor实例结合使用,将属性从一种格式转换为另一种格式。但是,大多数Spring用户并不直接使用这些类(即以编程方式),而是使用XML bean定义、带注释的组件(即使用@Component、@Controller等进行注释的类)或基于java的@Configuration类中的@Bean方法。然后,这些元数据在内部转换为BeanDefinition实例,并用于加载整个Spring IoC容器实例。

 

 基于构造函数或基于setter的DI
 

由于可以混合使用基于构造函数的DI和基于setter的DI,因此,将构造函数用于强制性依赖项并将setter方法或配置方法用于可选依赖性是一个很好的经验法则。请注意,可以 在setter方法上使用@Required批注,以使该属性成为必需的依赖项。但是,最好使用带有参数的程序验证的构造函数注入。

Spring团队通常提倡构造函数注入,因为它可以让您将应用程序组件实现为不可变对象,并确保不存在必需的依赖项null。此外,构造函数注入的组件始终以完全初始化的状态返回到客户端(调用)代码。附带说明一下,大量的构造函数自变量是一种不好的代码味道,这意味着该类可能承担了太多的职责,应该对其进行重构以更好地解决关注点分离问题。

Setter注入主要应仅用于可以在类中分配合理的默认值的可选依赖项。否则,必须在代码使用依赖项的任何地方执行非空检查。setter注入的一个好处是,setter方法使该类的对象在以后可以重新配置或重新注入。因此,通过JMX MBean进行管理是用于setter注入的引人注目的用例。

使用最适合特定class的DI风格。有时,在处理您没有源代码的第三方类时,将为您做出选择。例如,如果第三方类未公开任何setter方法,则构造函数注入可能是DI的唯一可用形式。

 

依赖性解析过程

容器执行bean依赖项解析,如下所示:

  • 使用ApplicationContext描述所有bean的配置元数据创建和初始化。可以通过XML,Java代码或注解指定配置元数据。

  • 对于每个bean,其依赖关系都以属性,构造函数参数或static-factory方法的参数的形式表示(如果使用它而不是普通的构造函数)。实际创建Bean时,会将这些依赖项提供给Bean。

  • 每个属性或构造函数参数都是要设置的值的实际定义,或者是对容器中另一个bean的引用。

  • 每个值的属性或构造函数参数都将从其指定的格式转换为该属性或构造函数参数的实际类型。默认情况下,Spring能够以String类型提供值转换成所有内置类型,比如int, longStringboolean,等等。

在创建容器时,Spring容器会验证每个bean的配置。但是,在实际创建Bean之前,不会设置Bean属性本身。创建容器时,将创建具有单例作用域并设置为预先实例化(默认)的Bean。范围在Bean范围中定义。否则,仅在请求时才创建Bean。创建和分配bean时可能会导致创建一个bean图。因为bean的依赖项及其依赖项的依赖项(etc)会被创建和分配。

请注意,这些依赖项之间的解析匹配可能会显示得较晚,即在首次创建受影响的bean时。

 

循环依赖

如果主要使用构造函数注入,则可能会创建无法解决的循环依赖方案。

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

一种可能的解决方案是编辑某些类的源代码,这些类的源代码由设置者而不是构造函数来配置。或者,避免构造函数注入,而仅使用setter注入。换句话说,尽管不建议这样做,但是您可以使用setter注入配置循环依赖项。

与典型情况(没有循环依赖关系)不同,Bean A和Bean B之间的循环依赖关系迫使其中一个Bean在完全初始化之前被注入另一个Bean(经典的“鸡与蛋”场景)。

通常,您可以信任Spring做正确的事。它在容器加载时检测配置问题,例如对不存在的Bean的引用和循环依赖项。在实际创建bean时,Spring设置属性并尽可能晚地解决依赖关系。这意味着已经正确加载的Spring容器以后可以在请求对象时生成异常,如果创建该对象或其依赖项之一时遇到问题-例如,Bean由于缺少或无效的属性会引发异常。对于这样的配置问题,这种潜在的延迟可见性是是ApplicationContext实现默认预实例化单例bean的原因。在实际需要这些bean之前,要花一些前期时间和内存来创建它们,您会在创建bean时发现配置问题ApplicationContext,而不是稍后发现。您仍然可以重写此默认行为,以便单例bean延迟初始化,而不是预先实例化。

如果不存在循环依赖关系,则在将一个或多个协作Bean注入到从属Bean中时,每个协作Bean在注入到从属Bean中之前都已完全配置。这意味着,如果bean A依赖于bean B,则Spring IoC容器会在对bean A调用setter方法之前完全配置beanB。换句话说,bean被实例化(如果不是预先实例化的singleton ),设置其依赖项,并调用相关的生命周期方法(例如已配置的init方法 或InitializingBean回调方法)。

依赖注入的例子

以下示例将基于XML的配置元数据用于基于setter的DI。Spring XML配置文件的一小部分指定了一些bean定义,如下所示:

<bean id="exampleBean" class="examples.ExampleBean">
    <!-- setter injection using the nested ref element -->
    <property name="beanOne">
        <ref bean="anotherExampleBean"/>
    </property>

    <!-- setter injection using the neater ref attribute -->
    <property name="beanTwo" ref="yetAnotherBean"/>
    <property name="integerProperty" value="1"/>
</bean>

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

以下示例显示了相应的ExampleBean类:

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

在前面的示例中,声明了setter以与XML文件中指定的属性匹配。以下示例使用基于构造函数的DI:

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

以下示例显示了相应的ExampleBean类:

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

 

Bean定义中指定的构造函数参数用作的构造函数的参数ExampleBean

现在考虑这个示例的一个变体,在该变体中,不是使用构造函数,而是告诉Spring调用static工厂方法以返回对象的实例:

<bean id="exampleBean" class="examples.ExampleBean" factory-method="createInstance">
    <constructor-arg ref="anotherExampleBean"/>
    <constructor-arg ref="yetAnotherBean"/>
    <constructor-arg value="1"/>
</bean>

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

以下示例显示了相应的ExampleBean类:

public class ExampleBean {

    // a private constructor
    private ExampleBean(...) {
        ...
    }

    // a static factory method; the arguments to this method can be
    // considered the dependencies of the bean that is returned,
    // regardless of how those arguments are actually used.
    public static ExampleBean createInstance (
        AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) {

        ExampleBean eb = new ExampleBean (...);
        // some other operations...
        return eb;
    }
}

static工厂方法的参数由<constructor-arg/>元素提供,就好像实际使用了构造函数一样。工厂方法返回的类的类型不必与包含静态工厂方法的类具有相同的类型(尽管在此示例中是)。实例(非静态)工厂方法可以以基本相同的方式使用(除了使用factory-bean属性代替class属性之外),因此在此不讨论这些细节(译者注:见1.3 Bean的概述实例工厂方法)。

1.4.2. Dependencies and Configuration in Detail

如上节所述,自定义的Bean属性或者构造函数参数可以引用其他被管理的Bean,或者定义内联的值。Spring基于XML配置的元数据可以在<property/><constructor-arg/>元素中的配置子元素类型来实现这一功能。

直接值(基本类型,字符串等)

元素的value属性值将属性或构造函数参数指定为人类可读的字符串表示形式。Spring的转换服务用于将这些值从字符串转换为属性或参数的实际类型。下面的例子显示了设置的各种值:

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

下面的示例使用p-namespace进行更简洁的XML配置:

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    https://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource"
        destroy-method="close"
        p:driverClassName="com.mysql.jdbc.Driver"
        p:url="jdbc:mysql://localhost:3306/mydb"
        p:username="root"
        p:password="masterkaoli"/>

</beans>

前面的XML更简洁。但是,除非在创建bean定义时使用支持自动属性完成的IDE(例如IntelliJ IDEASpring Tool Suite),否则错字是在运行时而不是设计时发现的。强烈建议您使用此类IDE帮助。

您还可以配置java.util.Properties实例,如下所示:

<bean id="mappings"
    class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">

    <!-- typed as a java.util.Properties -->
    <property name="properties">
        <value>
            jdbc.driver.className=com.mysql.jdbc.Driver
            jdbc.url=jdbc:mysql://localhost:3306/mydb
        </value>
    </property>
</bean>

Spring容器使用JavaBeans属性编辑器机制将<value/>元素中的文本转换为java.util.Properties实例,这是一个很好的快捷方式。并且是Spring团队确实支持使用嵌套<value/>元素而非value属性样式的少数几个地方之一。

idref元素

idref元素只是将容器中另一个bean的id(字符串值,而不是引用)传递给<property/>或者<constructor-arg/>元素的一种防止错误方法。下面的例子展示了如何使用它:

<bean id="theTargetBean" class="..."/>

<bean id="theClientBean" class="...">
    <property name="targetName">
        <idref bean="theTargetBean"/>
    </property>
</bean>

前面的bean定义片段(在运行时)与下面的片段完全等效:

<bean id="theTargetBean" class="..." />

<bean id="client" class="...">
    <property name="targetName" value="theTargetBean"/>
</bean>

第一种形式优于第二种形式,因为使用idref标记可以使容器在部署时验证所引用的命名Bean实际上是否存在。在第二个变体中,不对传递给Bean targetName属性的值进行验证client。拼写错误仅在client实际实例化bean 时发现(最有可能导致致命的结果)。如果该client bean是原型 bean,则可能在部署容器很长时间后才发现此错字和所产生的异常。

在4.0 bean XSD中不再支持idref元素的local属性,因为它不再提供常规bean ref之上的值(译者注:详见idref和ref的区别)。升级到4.0模式时,将现有的idref local 引用更改为idref bean。

<idref/>元素的一个常用的作用(至少在Spring 2.0之前的版本)是用在 ProxyFactoryBeanbean中定义的AOP拦截里。<idref/>在指定拦截器名称时使用元素可防止您拼写错误的拦截器ID。

 

引用其他Beans

ref元素是定义元素<constructor-arg/><property/>中的最后一个元素。在这里,将bean的指定属性的值设置为对容器管理的另一个bean的引用。所引用的bean是要设置其属性的bean的依赖项,并且在设置属性之前根据需要对其进行初始化(如果合作者是单例bean,那么容器可能已经对其进行了初始化)。所有引用最终都是对另一个对象的引用。作用域和验证会根据bean、local或parent属性指定另一个对象的ID或name而做相应的变动。

通过<ref/>标记的bean属性指定目标bean是最常见的形式,并且允许在同一个容器或父容器中创建对任何bean的引用,而不管它是否在同一个XML文件中。bean属性的值可以与目标bean的id属性相同,也可以与目标bean的name属性中的值相同。下面的例子展示了如何使用ref元素:

<ref bean="someBean"/>
通过 parent属性指定目标Bean 将创建对当前容器的父容器中的Bean的引用。该 parent 属性的值可以与目标Bean 的 id属性或目标Bean的 name属性中的值之一相同。目标Bean必须位于当前容器的父容器中。当你使用层级的容器结构时,并且希望与父Bean同名的代理 将现有的Bean 包装在父容器中时,就可以使用parent属性。
 
以下清单对显示了如何使用该 parent属性:
<!-- in the parent context -->
<bean id="accountService" class="com.something.SimpleAccountService">
    <!-- insert dependencies as required as here -->
</bean>
<!-- in the child (descendant) context -->
<bean id="accountService" <!-- bean name is the same as the parent bean -->
    class="org.springframework.aop.framework.ProxyFactoryBean">
    <property name="target">
        <ref parent="accountService"/> <!-- notice how we refer to the parent bean -->
    </property>
    <!-- insert other configuration and dependencies as required here -->
</bean>

元素 的local属性在ref4.0 bean XSD中不再受支持,因为它不再提供常规bean引用上的值。升级到4.0模式时,将现有ref local引用更改ref bean
内部Beans
<property/><constructor-arg/>元素内部的<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"> <!-- this is the inner bean -->
            <property name="name" value="Fiona Apple"/>
            <property name="age" value="25"/>
        </bean>
    </property>
</bean>

内部bean定义不需要定义的ID或名称。如果指定,则容器不使用该值作为标识符。容器还会忽略scope创建时的标志,因为内部Bean始终是匿名的,并且始终与外部Bean一起创建。不可能独立地访问内部bean或将其注入到协作bean中而不是封装到封闭bean中。

作为一个特例,可以从自定义范围接收destruction 回调,例如,对于单例bean中包含的请求范围内的bean。内部bean实例的创建与其包含的bean绑定在一起,但是destruction 回调使它可以参与请求范围的生命周期。这不是常见的情况。内部bean通常只共享其包含bean的作用域。

 

Collections

<list/><set/><map/>,和<props/>元素分别用来设置Java的Collection类型ListSetMap,和Properties。以下示例显示了如何使用它们:

<bean id="moreComplexObject" class="example.ComplexObject">
    <!-- results in a setAdminEmails(java.util.Properties) call -->
    <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>
    <!-- results in a setSomeList(java.util.List) call -->
    <property name="someList">
        <list>
            <value>a list element followed by a reference</value>
            <ref bean="myDataSource" />
        </list>
    </property>
    <!-- results in a setSomeMap(java.util.Map) call -->
    <property name="someMap">
        <map>
            <entry key="an entry" value="just some string"/>
            <entry key ="a ref" value-ref="myDataSource"/>
        </map>
    </property>
    <!-- results in a setSomeSet(java.util.Set) call -->
    <property name="someSet">
        <set>
            <value>just some string</value>
            <ref bean="myDataSource" />
        </set>
    </property>
</bean>

下面的属性可以作为map的key或者value,也可以作为set的value:

bean | ref | idref | list | set | map | props | value | null

集合合并

Spring容器还支持合并集合。应用程序开发人员可以定义父级<list/><map/><set/><props/>元素,并有子级<list/><map/><set/><props/>元素继承和重写父集合的值。也就是说,子集合的值是合并父集合和子集合的元素的结果,子集合的元素将覆盖父集合中指定的值。

本节讨论了关于合并的父子bean机制。不熟悉父bean和子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>

注意在子bean定义的adminEmails属性的<props/>元素上使用了merge=true属性。当子bean由容器解析并实例化时,生成的实例具有一个adminEmails的集合,该集合包含将子项的集合与父项的集合合并的结果 。以下清单显示了结果:

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

子Properties集合的值继承父集合中所有属性元素<props/>,子集合支持覆盖父集合中的值。

这一合并行为同样适用于<list/><map/><set/> 集合类型。<list/>元素比较特殊(即值是有序集合),父list的值位于所有子级list的值之前。对于MapSetProperties集合类型,不存在排序。因此,对于容器内部使用的关联映射、集合和属性实现类型下的集合类型,没有有效的排序语义。

 

集合合并的局限性

您不能合并不同的集合类型(例如映射和列表)。如果您尝试这样做,将抛出一个适当的异常。merge属性必须在较低的继承子定义上指定。在父集合定义上指定merge属性是冗余的,并且不会导致所需的合并。

强类型集合

通过在Java 5中引入泛型类型,您可以使用强类型集合。也就是说,可以声明一个集合类型,使它只能包含(例如)字符串元素。如果您使用Spring来依赖地将强类型集合注入到bean中,则可以利用Spring的类型转换支持,以便在将强类型集合实例的元素添加到集合之前将其转换为适当的类型。下面的Java类和bean定义说明了如何做到这一点:

public class SomeClass {

    private Map<String, Float> accounts;

    public void setAccounts(Map<String, Float> accounts) {
        this.accounts = accounts;
    }
}
<beans>
    <bean id="something" class="x.y.SomeClass">
        <property name="accounts">
            <map>
                <entry key="one" value="9.99"/>
                <entry key="two" value="2.75"/>
                <entry key="six" value="3.99"/>
            </map>
        </property>
    </bean>
</beans>

当something bean的accounts属性准备注入时,强类型映射的元素类型的泛型信息通过反射得到。因此,Spring的类型转换基础结构将各种值元素识别为Float类型,并将字符串值(9.99、2.75和3.99)转换为实际的Float类型。

Null and Empty String Values

Spring将属性的空参数等作为空字符串处理。以下基于xml的配置元数据片段将email属性设置为空字符串值("")。

<bean class="ExampleBean">
    <property name="email" value=""/>
</bean>

上面的例子相当于下面的Java代码:

exampleBean.setEmail("");

<null/>元素处理null值。以下清单显示了一个示例:

<bean class="ExampleBean">
    <property name="email">
        <null/>
    </property>
</bean>

上面的例子相当于下面的Java代码:

exampleBean.setEmail(null);

XML Shortcut with the p-namespace

p-namespace允许您使用bean元素的属性(而不是嵌套的<property/>元素)来描述协作bean的属性值,或者两者都使用。

Spring支持带有命名空间的可扩展配置格式,这些命名空间基于XML模式定义。本章讨论的bean配置格式是在XML模式文档中定义的。但是,p-namespace并没有在XSD文件中定义,它只存在于Spring的核心中。

下面的示例显示了解析为相同结果的两个XML片段(第一个使用标准XML格式,第二个使用p-namespace):

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean name="classic" class="com.example.ExampleBean">
        <property name="email" value="someone@somewhere.com"/>
    </bean>

    <bean name="p-namespace" class="com.example.ExampleBean"
        p:email="someone@somewhere.com"/>
</beans>

该示例在bean定义中显示了p-namespace中名为email的属性。这告诉Spring这是一个属性声明。如前所述,p-namespace没有模式定义,因此可以将property name设置为属性名。

下一个例子包括两个以上的bean定义,它们都有对另一个bean的引用:

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean name="john-classic" class="com.example.Person">
        <property name="name" value="John Doe"/>
        <property name="spouse" ref="jane"/>
    </bean>

    <bean name="john-modern"
        class="com.example.Person"
        p:name="John Doe"
        p:spouse-ref="jane"/>

    <bean name="jane" class="com.example.Person">
        <property name="name" value="Jane Doe"/>
    </bean>
</beans>

这个示例不仅包含一个使用p-namespace的属性值,而且还使用一种特殊格式来声明属性引用。第一个bean定义使用来创建bean john对bean jane的引用,而第二个bean定义使用p:spouse-ref="jane"作为属性来完成完全相同的工作。在本例中,spouse是属性名,而 -ref 部分指出这不是一个直接的值,而是对另一个bean的引用。


p命名空间不如标准XML格式灵活。例如,用于声明属性引用的格式与以ref的属性发生冲突,而标准XML格式则没有。我们建议您谨慎选择方法,并将其传达给团队成员,以避免同时使用这三种方法生成XML文档。

XML Shortcut with the c-namespace

与p-namespace的XML快捷方式类似,在Spring 3.1中引入的c-namespace允许配置构造函数参数的内联属性,而不是嵌套的构造函数-参数元素。

下面的例子使用c:命名空间来做与基于构造函数的依赖注入相同的事情:

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:c="http://www.springframework.org/schema/c"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="beanTwo" class="x.y.ThingTwo"/>
    <bean id="beanThree" class="x.y.ThingThree"/>

    <!-- traditional declaration with optional argument names -->
    <bean id="beanOne" class="x.y.ThingOne">
        <constructor-arg name="thingTwo" ref="beanTwo"/>
        <constructor-arg name="thingThree" ref="beanThree"/>
        <constructor-arg name="email" value="something@somewhere.com"/>
    </bean>

    <!-- c-namespace declaration with argument names -->
    <bean id="beanOne" class="x.y.ThingOne" c:thingTwo-ref="beanTwo"
        c:thingThree-ref="beanThree" c:email="something@somewhere.com"/>

</beans>

c命名空间使用与p命名空间有相同的约定,通过名字设置构造函数时(-ref代表着引用)。类似地,即使没有在XSD模式中定义它(它存在于Spring核心中),也需要在XML文件中声明它。

对于构造函数参数名不可用的少数情况(通常如果编译字节码时没有调试信息),可以使用回退参数索引,如下所示:

<!-- c-namespace index declaration -->
<bean id="beanOne" class="x.y.ThingOne" c:_0-ref="beanTwo" c:_1-ref="beanThree"
    c:_2="something@somewhere.com"/>

由于XML语法的原因,索引表示法要求使用前导_,因为XML属性名称不能以数字开头(即使某些IDE允许)。相应的索引符号也可用于<constructor-arg>元素,但并不常用,因为声明的简单顺序通常就足够了。

Compound Property Names

设置bean属性时,可以使用复合属性名称或嵌套属性名称,只要路径中除最终属性名称之外的所有组件都没有null。考虑以下bean定义:

<bean id="something" class="things.ThingOne">
    <property name="fred.bob.sammy" value="123" />
</bean>

something bean有一个fred属性,fred属性有一个bob属性,bob属性有一个sammy属性,最后一个sammy属性被设置为123。为了使其工作,在构造bean之后,fred的属性和fred的bob属性不能为空。否则,将抛出NullPointerException。

1.4.3. Using depends-on

如果一个bean是另一个bean的依赖项,这通常意味着将一个bean设置为另一个bean的属性。通常使用基于xml的配置元数据中的元素来完成此任务。然而,有时候bean之间的依赖关系不那么直接。例如,当需要触发类中的静态初始化器时,例如注册数据库驱动程序时。依赖属性可以显式地强制在使用此元素的bean初始化之前对一个或多个bean进行初始化。下面的示例使用depends-on属性来表示对单个bean的依赖:

<bean id="beanOne" class="ExampleBean" depends-on="manager"/>
<bean id="manager" class="ManagerBean" />

要表达对多个bean的依赖关系,请提供一个bean名称列表作为该depends-on属性的值(逗号,空格和分号是有效的分隔符):

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

depends-on属性既可以指定一个初始化时间依赖项,也可以指定一个对应的销毁时间依赖项(仅在单例bean情况下)。与给定bean定义依赖关系的依赖bean首先被销毁,先于给定bean本身被销毁。因此,依赖也可以控制销毁顺序。

1.4.4. Lazy-initialized Beans

默认情况下,ApplicationContext实现将创建和配置所有的单例bean作为初始化过程的一部分。通常,这种预实例化是可取的,因为配置或周围环境中的错误会立即被发现,而不是几小时甚至几天之后。如果不想使用这种方式,您可以通过将bean定义标记为延迟初始化来防止单例bean的预实例化。延迟初始化的bean告诉IoC容器在第一次请求时创建bean实例,而不是在启动时

在XML中,这种行为是由元素上的lazy-init属性控制的,如下面的示例所示:

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

当前面的配置被ApplicationContext使用时,当ApplicationContext启动时,lazy bean不会被预先实例化,而not lazy bean则会被预先实例化。

然而,当延迟初始化的bean是未延迟初始化的单例bean的依赖项时,ApplicationContext在启动时创建延迟初始化的bean,因为它必须满足单例的依赖项。延迟初始化的bean被注入到一个没有延迟初始化的单例bean中。

您还可以使用元素上的default-lazy-init属性来控制容器级别的延迟初始化,如下面的示例所示:

<beans default-lazy-init="true">
    <!-- no beans will be pre-instantiated... -->
</beans>

1.4.5. Autowiring Collaborators

Spring容器可以自动创建协作bean之间的关系。通过检查ApplicationContext的内容,您可以让Spring为您的bean自动解析协作者(其他bean)。自动装配有以下优点:

自动装配可以大大减少指定属性或构造函数参数的需要。(本章其他地方讨论的其他机制,如bean模板,在这方面也很有价值。)

自动装配可以随着对象的演化更新配置。例如,如果需要向类添加依赖项,则可以自动满足该依赖项,而不需要修改配置。因此,自动装配在开发过程中特别有用,当代码库变得更加稳定时,自动装配可以避免切换到显式连接的选项。

在使用基于xml的配置元数据(请参阅依赖项注入)时,可以使用<bean/>元素的autowire属性为bean定义指定autowire模式。自动装配功能有四种模式。您可以为每个bean指定自动装配,因此可以选择自动装配哪些bean。下表描述了四种自动装配模式:

表2.自动装配模式
模式说明

no

(默认)无自动装配。Bean引用必须由ref元素定义。对于较大的部署,建议不要更改默认设置,因为明确指定协作者可以提供更好的控制和清晰度。在某种程度上,它记录了系统的结构。

byName

按属性名称自动装配。Spring寻找与需要自动装配的属性同名的bean。例如,如果一个bean定义被设置为按名称自动装配并且包含一个master属性(即它具有一个 setMaster(..)方法),那么Spring将查找名为master的bean定义,master并使用它来设置该属性。

byType

如果容器中恰好只有一个该属性类型的bean,则使该属性自动装配。如果存在多个,则会引发致命异常,这表明您不能用byType对该bean 使用自动装配。如果没有匹配的bean,则什么都不会发生事情(未设置该属性)。

constructor

类似于byType但适用于构造函数参数。如果容器中不存在构造函数参数类型的一个bean,则将引发致命错误。

使用byTypeconstructor自动装配模式,可以装载数组和类型化集合。在这种情况下,将提供容器中与期望类型匹配的所有自动装配的候选者,以满足依赖关系。如果期望的key类型为字符串,则可以自动装载强类型Map实例。自动装配Map实例的值由所有匹配期望类型的bean实例组成,Map实例的key包含相应的bean名称。

Limitations and Disadvantages of Autowiring

自动装配在跨项目一致使用时效果最好。如果通常不使用自动装配,那么开发人员使用它来连接一个或两个bean定义可能会感到困惑。

考虑自动装配的局限性和缺点:

属性和构造参数设置中的显式依赖项总是覆盖自动装配。您不能自动生成简单的属性,如原语、字符串和类(以及这些简单属性的数组)。这种限制是由设计造成的。

自动装配不如显式连接精确。尽管如前面的表中所述,Spring小心地避免猜测,以免产生可能产生意外结果的歧义。spring管理对象之间的关系不再明确记录。

连接信息可能对从Spring容器生成文档的工具不可用。

容器中的多个bean定义可能与要自动装配的setter方法或构造函数参数指定的类型匹配。对于数组、集合或映射实例,这不一定是个问题。然而,对于期望单个值的依赖项,这种模糊性不是任意解决的。如果没有唯一的bean定义可用,则抛出异常。

在后一种情况下,你有几个选择:

放弃自动装配,支持显式连接。

通过将bean定义的autowire-candidate属性设置为false来避免自动装配,如下一节所述。

通过将其元素的主属性设置为true,将单个bean定义指定为主候选。

实现基于注释的配置提供的更细粒度的控制,如基于注释的容器配置中所述。

从自动装配中排除一个Bean

在每个bean的基础上,可以将bean排除在自动装配之外。在Spring的XML格式中,将元素的autowire-candidate属性设置为false。容器使特定的bean定义对自动装配基础设施(包括诸如@Autowired之类的注释样式配置)不可用。

autowire-candidate属性被设计为只影响基于类型的自动装配。它不会影响按名称显示的引用,即使指定的bean没有标记为autowire候选bean,也会解析这些引用。因此,如果名字匹配,自动装配仍然会注入一个bean。

您还可以根据bean名称的模式匹配来限制自动装配候选对象。顶级元素在其default-autowire-candidate属性中接受一个或多个模式。例如,要将autowire候选状态限制为名称以Repository结尾的任何bean,请提供一个值*Repository。要提供多个模式,请在逗号分隔的列表中定义它们。bean定义的autowire-candidate属性的显式值true或false总是优先。对于这样的bean,不适用模式匹配规则。

这些技术对于那些您不希望通过自动装配将其注入到其他bean中的bean非常有用。这并不意味着不能通过使用自动装配来配置被排除的bean。相反,bean本身不是自动装配其他bean的候选对象。

 

1.4.6. Method Injection

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

解决的办法是放弃一些控制反转。您可以通过实现ApplicationContextAware 接口,并在每次bean A需要时调用容器的getBean(“B”)来请求(通常是一个新的)bean B实例,从而使bean A知道容器。下面的例子展示了这种方法:

// a class that uses a stateful Command-style class to perform some processing
package fiona.apple;

// Spring-API imports
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

public class CommandManager implements ApplicationContextAware {

    private ApplicationContext applicationContext;

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

    protected Command createCommand() {
        // notice the Spring API dependency!
        return this.applicationContext.getBean("command", Command.class);
    }

    public void setApplicationContext(
            ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}

前面的内容是不理想的,因为业务代码知道并耦合到Spring框架。方法注入是Spring IoC容器的一项高级功能,使您可以干净地处理此用例。

您可以在此博客条目中了解有关方法注入动机的更多信息 。

 

Lookup Method Injection

查找方法注入是容器重写CommandManager Bean上的方法并返回容器中另一个命名Bean。查找通常涉及原型bean,如上一节中所述。Spring框架通过使用从CGLIB库生成字节码来动态生成覆盖该方法的子类来实现此方法注入。

 
  • 为了使此动态子类起作用,Spring Bean容器子类的类也不能为final,并且要覆盖的方法也不能为final

  • 对具有abstract方法的类进行单元测试需要您自己对该类进行子类化,并提供该abstract方法的stub实现。

  • 组件扫描也需要具体方法,这需要具体的类别。

  • 另一个关键限制是,查找方法不适用于工厂方法,尤其不适用于配置类中的@Bean方法,因为在这种情况下,容器不负责创建实例,因此无法在其上动态创建运行时生成的子类。

对于CommandManager前面的代码片段中的类,Spring容器动态地覆盖该createCommand() 方法的实现。该CommandManager班没有任何Spring的依赖,正如重新编写的示例所示:

package fiona.apple;

// no more Spring imports!

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 where is the implementation of this method?
    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>

标识为commandManager的bean在需要myCommand bean的新实例时调用自己的createCommand()方法。如果实际需要的话,您必须小心地将myCommand bean部署为原型。如果它是单例的,那么每次都会返回相同的myCommand bean实例。

另外,在基于注释的组件模型中,您可以通过@Lookup注释声明一个查找方法,如以下示例所示:

public abstract class CommandManager {

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

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

或者,更惯用的是,您可以依赖于目标bean根据lookup方法的声明的返回类型来解析:

public abstract class CommandManager {

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

    @Lookup
    protected abstract MyCommand createCommand();
}

请注意,您通常应该使用具体的存根实现来声明此类带注释的查找方法,以便使它们与Spring的组件扫描规则兼容,其中抽象类在缺省情况下被忽略。此限制不适用于显式注册或显式导入的bean类。

访问范围不同的目标bean的另一种方法是ObjectFactory/ Provider注入点。将作用域bean视为依赖项。

您还可以在org.springframe .bean .factory中找到ServiceLocatorFactoryBean配置包.

 

Arbitrary Method Replacement(任意方法替换)

与查找方法注入相比,一种不太有用的方法注入形式是能够用另一种方法实现替换托管bean中的任意方法。您可以安全地跳过本节的其余部分,直到您真正需要此功能为止。

对于基于xml的配置元数据,您可以使用replace -method元素将已部署bean的现有方法实现替换为其他方法实现。考虑下面的类,它有一个我们想要覆盖的叫做computeValue的方法:

public class MyValueCalculator {

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

    // some other methods...
}

实现该org.springframework.beans.factory.support.MethodReplacer 接口的类提供了新的方法定义,如以下示例所示:

/**
 * meant to be used to override the existing 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"/>

您可以在< replaced-method />元素中使用一个或多个< arg-type />元素来指示被覆盖方法的方法签名。只有在方法重载且类中存在多个变量时,才需要对参数进行签名。为了方便起见,参数的类型字符串可以是完全限定类型名称的子字符串。例如,以下所有匹配java.lang.String:

java.lang.String
String
Str

因为参数的数量通常足以区分每个可能的选择,所以通过让您仅键入与参数类型匹配的最短字符串,此快捷方式可以节省很多输入。

 


1.5. Bean Scopes

当你创建一个bean定义时,你将创建一个recipe用于创建由该bean定义定义的类的实际实例。bean定义是recipe的想法很重要,因为它意味着,与类一样,你可以从一个recipe创建多个对象实例。

你不仅可以控制要插入到由特定bean定义创建的对象中的各种依赖项和配置值,还可以控制由特定bean定义创建的对象的范围。这种方法强大而灵活,因为你可以选择通过配置创建的对象的范围,而不必在Java类级别上考虑对象的范围。可以将bean定义为部署在多种作用域中的一种。Spring框架支持6种作用域,其中4种只有在使用web-aware的ApplicationContext时才可用。你还可以创建自定义范围。

下表描述了支持的范围:

表3. Bean作用域
范围描述
singleton

(默认)将每个Spring IoC容器的单个bean定义范围限定为单个对象实例。

prototype

将单个bean定义的作用域限定为任意数量的对象实例。

request

将单个bean定义的范围限定为单个HTTP请求的生命周期。也就是说,每个HTTP请求都有一个在单个bean定义后面创建的bean实例。仅在可感知网络的Spring上下文中有效ApplicationContext

session

将单个bean定义的范围限定为HTTP的生命周期Session。仅在web-aware的Spring ApplicationContext中有效。

application

将单个bean定义的作用域限定为的生命周期ServletContext。仅在web-aware的Spring ApplicationContext中有效。

websocket

将单个bean定义的作用域限定为的生命周期WebSocket。仅在web-aware的Spring ApplicationContext中有效。

 从Spring 3.0开始,线程作用域可用,但默认情况下未注册。有关更多信息,请参见的文档 SimpleThreadScope。有关如何注册此自定义范围或任何其他自定义范围的说明,请参阅《 使用自定义范围》

1.5.1. The Singleton Scope

只管理一个单例bean的一个共享实例,所有与该bean ID匹配的bean的请求都会导致spring容器返回这个特定的bean实例。

换句话说,当您定义一个bean定义并将其定义为一个单例对象时,Spring IoC容器只创建该bean定义的对象的一个实例。此单一实例存储在此类单例bean的缓存中,该指定bean的所有后续请求和引用都将返回缓存的对象。下图显示了单例范围的工作方式:

singleton

Spring的单例bean概念与四人组(GoF)模式书中定义的单例模式不同。单例对象对对象的作用域进行硬编码,这样每个类加载器只能创建一个特定类的实例。Spring单例的范围是针对每个容器和每个bean。这意味着,如果您在单个Spring容器中为特定类定义一个bean,那么Spring容器将创建由该bean定义的类的有且仅有一个实例。单例范围是Spring的默认范围。要在XML中将bean定义为单例,您可以定义如下例所示的bean:

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

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

1.5.2. The Prototype Scope

bean部署的非单例Prototype 范围导致在每次发出对特定bean的请求时创建一个新的bean实例。也就是说,bean被注入到另一个bean中,或者您通过容器上的getBean()方法调用请求它。通常,您应该为所有有状态bean使用Prototype 范围,为无状态bean使用单例范围。

下图说明了Spring原型的作用域:

prototype

(数据访问对象(DAO)通常不配置为原型,因为典型的DAO不拥有任何对话状态。对于我们而言,使用单例模式更方便。)

以下示例将bean定义为XML原型:

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

与其他作用域不同,Spring不管理Prototype bean的完整生命周期。容器实例化、配置或以其他方式组装原型对象并将其交给客户端,而不对该Prototype 实例继续记录。因此,尽管初始化生命周期回调方法在所有对象上都被调用,而与范围无关,但是在原型的情况下,配置的销毁生命周期回调不会被调用。客户端代码必须清理原型作用域的对象,并释放原型bean持有的昂贵资源。为了让Spring容器释放原型作用域bean所持有的资源,可以尝试使用一个自定义bean后处理器(bean post-processor),它持有一个需要清理的bean引用。

在某些方面,Spring容器在Prototype 作用域bean方面的角色可以替代Java new操作符。所有超过那个点的生命周期管理都必须由客户端处理。(有关Spring容器中bean生命周期的详细信息,请参阅生命周期回调。)

1.5.3. Singleton Beans with Prototype-bean Dependencies

具有原型Bean依赖关系的Singleton Bean

当你使用依赖于原型bean的单例作用域bean时,请注意依赖项是在实例化时解析的。因此,如果你依赖地将一个原型作用域的bean注入到一个单例作用域的bean中,一个新的原型bean将被实例化,然后依赖地注入到单例bean中。这个原型实例是提供给单例bean的唯一实例。

但是,假设您希望单例作用域bean在运行时重复获取原型作用域bean的新实例。您不能依赖地将一个原型作用域的bean注入到您的单例bean中,因为这种注入只发生一次,当Spring容器实例化单例bean并解析和注入它的依赖项时。如果您需要在运行时多次使用原型bean的新实例,请参阅方法注入。

1.5.4. Request, Session, Application, and WebSocket Scopes

只有在使用web-aware的Spring ApplicationContext实现(如XmlWebApplicationContext)时,请求、会话、应用程序和websocket作用域才可用。如果您将这些作用域与常规的Spring IoC容器(如ClassPathXmlApplicationContext)一起使用,则会抛出一个IllegalStateException,它会报错一个未知的bean作用域。

Initial Web Configuration

为了在请求、会话、应用程序和websocket级别(web范围的bean)上支持bean的作用域,需要在定义bean之前进行一些小的初始配置。(标准范围:单例和原型不需要这个初始设置。)

如何完成这个初始设置取决于特定的Servlet环境。

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

如果您使用Servlet 2.5 web容器,并在Spring的DispatcherServlet之外处理请求(例如,在使用JSF或Struts时),您需要注册org.springframe .web.context.request。RequestContextListener ServletRequestListener。对于Servlet 3.0+,这可以通过使用WebApplicationInitializer接口以编程方式实现。或者,对于较旧的容器,将以下声明添加到web应用程序的web.xml文件中:

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

 

另外,如果监听器设置有问题,可以考虑使用Spring的RequestContextFilter。filter-mapping映射依赖于周围的web应用程序配置,因此您必须对其进行适当的更改。下面的清单显示了web应用程序的过滤部分:

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

DispatcherServlet、RequestContextListener和RequestContextFilter都做完全相同的事情,即将HTTP请求对象绑定到服务该请求的线程。这使得在请求和会话范围内的bean可以在调用链的更底层使用。

Request scope

考虑以下XML配置来定义bean:

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

通过为每个HTTP请求使用LoginAction bean定义,Spring容器创建了LoginAction bean的新实例。也就是说,loginAction bean的作用域在HTTP请求级别。您可以随意更改创建的实例的内部状态,因为从相同的loginAction bean定义创建的其他实例在状态中看不到这些更改。它们是特定于个人请求的。当请求完成处理时,作用域为请求的bean被丢弃。

在使用注释驱动的组件或Java配置时,可以使用@RequestScope注释将组件分配给请求范围。下面的例子演示了如何做到这一点:

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

Session Scope

考虑以下XML配置来定义bean:

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

Spring容器通过为单个HTTP会话的生命周期使用UserPreferences bean定义来创建UserPreferences bean的新实例。换句话说,userPreferences bean有效地限定在HTTP会话级别。与请求范围内bean一样,你可以改变内部状态的实例创建尽可能多的你想要的,知道其他HTTP会话实例也使用相同的实例创建userPreferences bean定义看不到这些变化状态,因为他们是特定于一个单独的HTTP会话。当HTTP会话最终被丢弃时,作用域为该特定HTTP会话的bean也被丢弃。

在使用注解驱动的组件或Java配置时,可以使用@SessionScope注解将组件分配给会话范围。

@SessionScope
@Component
public class UserPreferences {
    // ...
}

Application Scope

考虑以下XML配置来定义bean:

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

通过为整个web应用程序使用一次AppPreferences bean定义,Spring容器创建了AppPreferences bean的一个新实例。也就是说,appPreferences bean的作用域在ServletContext级别,并存储为一个常规的ServletContext属性。这有点类似于spring 单例bean,但在两个重要方面不同:它是每个ServletContext的单例而不是每个spring ApplicationContext的单例(可能有几个在任何给定的web应用程序),它实际上是公开的,因此作为ServletContext属性可见。

在使用注解驱动的组件或Java配置时,可以使用@ApplicationScope注解将组件分配给应用程序范围。下面的例子演示了如何做到这一点:

@ApplicationScope
@Component
public class AppPreferences {
    // ...
}

Scoped Beans as Dependencies

Spring IoC容器不仅管理对象(bean)的实例化,还管理协作者(或依赖项)的连接。如果您想将(例如)一个HTTP请求作用域的bean注入到另一个更大的作用域的bean中,您可以选择注入一个AOP代理来代替作用域的bean。也就是说,您需要注入一个代理对象,它与作用域对象公开相同的公共接口,但也可以从相关作用域(如HTTP请求)检索实际目标对象,并将方法调用委托给实际对象。

你可以在作用域为单例的bean上使用<aop:scoped-proxy/>来引用beans,然后引用将通过可序列化的中间代理,因此可以在反序列化时重新获得目标单例Bean。

prototype bean声明<aop:scoped-proxy/>时,共享代理上的每个方法调用都会导致创建一个新的目标实例,然后将该调用转发到该目标实例。

同样,作用域代理不是以生命周期安全的方式从较短的作用域访问bean的唯一方法。您还可以将注入点(即,构造函数或setter参数或自动装配字段)声明为ObjectFactory<MyTargetBean>,从而允许getObject()每次需要时调用按需检索当前实例,而无需保留该实例或将其单独存储。

作为扩展变体,您可以声明ObjectProvider<MyTargetBean>,它提供了几个附加的访问变体,包括getIfAvailablegetIfUnique

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

以下示例中的配置只有一行,但是了解其背后的“原因”和“方式”很重要:

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

    <!-- an HTTP Session-scoped bean exposed as a proxy -->
    <bean id="userPreferences" class="com.something.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.something.SimpleUserService">
        <!-- a reference to the proxied userPreferences bean -->
        <property name="userPreferences" ref="userPreferences"/>
    </bean>
</beans>

要创建这样的代理,您可以将子<aop:scoped proxy/>元素插入到scoped bean定义中(请参见选择要创建的代理类型和基于XML模式的配置)。为什么在请求、会话和自定义作用域级别定义作用域的bean需要<aop:scoped proxy/>元素?考虑下面的singleton bean定义,并将其与您需要为上述作用域定义的内容进行对比(注意下面的userPreferences bean定义是不完整的):

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

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

在前面的示例中,单例bean (userManager)被注入了对HTTP会话范围的bean的引用(userPreferences)。这里的要点是userManager bean是单例的:它仅针对每个容器实例化一次,其依赖项(在本例中只有一个,即userPreferences bean)也仅注入一次。这意味着userManager bean只对同一个userPreferences对象(即最初注入它的对象)进行操作。

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

因此,在将请求和会话范围的bean注入到协作对象中时,需要以下(正确和完整的)配置,如下面的示例所示:

<bean id="userPreferences" class="com.something.UserPreferences" scope="session">
    <aop:scoped-proxy/>
</bean>

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

Choosing the Type of Proxy to Create

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


CGLIB代理仅拦截公共方法调用!不要在这样的代理上调用非公共方法。它们没有被委派给实际的作用域目标对象。

或者,您可以配置Spring容器来为此类作用域bean创建标准的基于JDK接口的代理,通过为<aop:scoped-proxy/>元素的代理目标类属性的值指定false。使用基于JDK接口的代理意味着在应用程序类路径中不需要额外的库来影响这样的代理。然而,这也意味着作用域bean的类必须实现至少一个接口,并且作用域bean被注入的所有协作者必须通过其接口之一引用bean。以下示例显示基于接口的代理:

<!-- DefaultUserPreferences implements the UserPreferences interface -->
<bean id="userPreferences" class="com.stuff.DefaultUserPreferences" scope="session">
    <aop:scoped-proxy proxy-target-class="false"/>
</bean>

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

有关选择基于类或基于接口的代理的详细信息,请参阅代理机制

1.5.5. Custom Scopes

bean的作用域机制是可扩展的。您可以定义自己的作用域,甚至重新定义现有作用域,尽管后者被认为是不好的做法,您不能覆盖内置作用域singletonprototype作用域。

Creating a Custom Scope

要将自定义范围集成到Spring容器中,您需要实现org.springframework.beans.factory.config.Scope此部分中描述的 接口。有关如何实现自己的范围的想法,请参阅Scope Spring Framework本身和Scopejavadoc 附带的实现 ,其中详细说明了需要实现的方法。

Scope接口有四种方法可以从作用域中获取对象,将其从作用域中删除,然后将其销毁。

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

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

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

Object remove(String name)

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

void registerDestructionCallback(String name, Runnable destructionCallback)

以下方法获取基础范围的会话标识符:

String getConversationId()

有关 销毁回调的更多信息,请参见javadoc或Spring范围实现。

 

Using a Custom Scope

在编写和测试一个或多个自定义Scope实现之后,您需要使Spring容器意识到您的新作用域。以下方法是Scope在Spring容器中注册新方法的主要方法:

void registerScope(String scopeName, Scope scope);

此方法在ConfigurableBeanFactory接口上声明,该接口可通过spring附带的大多数具体ApplicationContext实现的BeanFactory属性获得。

registerScope(..)方法的第一个参数是与范围关联的唯一名称。Spring容器本身中的此类名称示例为singleton和 prototype。该registerScope(..)方法的第二个参数是Scope您希望注册和使用的自定义实现的实际实例。

假设您编写了自定义Scope实现,然后注册它,如下面的示例所示。

 下一个示例使用SimpleThreadScopeSpring附带的,但默认情况下未注册。对于您自己的自定义Scope 实现,说明将是相同的。

 

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

 

然后,您可以按照您的custom的作用域规则创建bean定义, Scope如下所示:

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

使用自定义作用域实现,不仅限于作用域的编程式注册,还可以通过使用CustomScopeConfigurer类以声明的方式进行作用域注册。

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

    <bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
        <property name="scopes">
            <map>
                <entry key="thread">
                    <bean class="org.springframework.context.support.SimpleThreadScope"/>
                </entry>
            </map>
        </property>
    </bean>

    <bean id="thing2" class="x.y.Thing2" scope="thread">
        <property name="name" value="Rick"/>
        <aop:scoped-proxy/>
    </bean>

    <bean id="thing1" class="x.y.Thing1">
        <property name="thing2" ref="thing2"/>
    </bean>

</beans>

当放置<aop:scoped-proxy/>FactoryBean实现中时,作用域是工厂Bean本身,而不是范围的对象getObject()

1.6. Customizing the Nature of a Bean(自定义Bean)

Spring框架提供了许多接口,您可以使用这些接口来定制bean的性质。本节将它们分组如下:

生命周期回调

应用程序ContextAware和BeanNameAware

其他aware接口

1.6.1. Lifecycle Callbacks

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

JSR-250 的@PostConstruct @PreDestroy 注解是⽣命周期回调的最推荐的⽅式,因为他们和 Spring 框架解耦。
如果你不想使⽤ JSR-250,⼜想解耦,那么可以考虑使⽤ init-method 和destroy-method 这样两个定义 bean 的元数
据。
在内部, Spring 框架使⽤ BeanPostProcessor 实现来处理它可以找到的任何回调接⼝,并调⽤适当的⽅法。如果你需要
⾃定义特性或其他 Spring 默认不提供的⽣命周期⾏为,你可以⾃⼰实现 BeanPostProcessor 。有关详细信息,请参⻅容
器扩展点。
 
除了初始化和销毁回调之外, Spring 管理的对象还可以实现⽣命周期接⼝,以便这些对象可以参与由容器⾃身⽣命周期
驱动的启动和关闭过程。
 
⽣命周期回调接⼝在本节中进⾏了描述。
初始化回调
 
org.springframework.beans.factory.InitializingBean 接⼝可以让 bean Spring 初始化之后,执⾏⾃定义的初始化流程,
接⼝只有⼀个⽅法InitializingBean:
void afterPropertiesSet() throws Exception;
但是我们不建议你使⽤ InitializingBean ,这个和 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
    }
}
上⾯例⼦和下⾯是等价的:
<bean id="exampleInitBean" class="examples.AnotherExampleBean"/>

 

public class AnotherExampleBean implements InitializingBean {

    @Override
    public void afterPropertiesSet() {
        // do some initialization work
    }
}
但是,最上⾯的例⼦和 Spring 是解耦的。
 
Destruction回调
 
要实现销毁回调,可以实现接口 org.springframework.beans.factory.DisposableBean, 他提供了⼀个DisposableBean ⽅法:
void destroy() throws Exception;
我们不建议你使⽤ DisposableBean ,因为他和 Spring 框架耦合,我们推荐你使⽤ @PreDestroy 注解,或者⼀个能被 bean定义⽀持的通⽤⽅法。在XML配置中,可以使⽤ <bean/> destroy-method属性。在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)
    }
}
上⾯例⼦和下⾯作⽤⼀致:
<bean id="exampleInitBean" class="examples.AnotherExampleBean"/>
public class AnotherExampleBean implements DisposableBean {

    @Override
    public void destroy() {
        // do some destruction work (like releasing pooled connections)
    }
}
当然,最上⾯的例⼦和 Spring 解耦。
可以为 <bean> 元素的 destroy-method属性指定⼀个特殊(推断)值,该值指示spring⾃动检测特定bean 类上的公共close shutdown 。(可以实现 java.lang.AutoCloseable或 java.io.Closeable ) 你还可以在<beans> 元素的默认 default-destroy-method属性上设置此特殊(推断)值,以将此⾏为应⽤于整个bean集(请参⻅默认初始化和销毁⽅法)。注意,这是 Java 配置的默认⾏为。
 
默认的InitializationDestroy⽅法
 
在编写不使⽤ Spring 特定的 InitializingBean DisposableBean 回调接⼝的初始化和销毁⽅法回调时,通常会命名为 init()、 initialize ()、 dispose ()。理想情况下,这样的⽣命周期回调⽅法的名称在项⽬中是标准化的,这样所有开发⼈员都使⽤相同的⽅法名称并确保⼀致性。你可以配置Spring容器去“查找”特定名字的初始化和销毁⽅法。这意味着,作为应⽤程序开发⼈员,你可以编写应⽤程序类并使⽤名为init()的初始化回调,⽽⽆需为每个bean定义配置init method=“init”属性。Spring IOC容器在创建bean时将会调⽤该⽅法。此功能强调命名的⼀致性。
假设初始化回调⽅法名为 init (),销毁回调⽅法名为 destroy ()。然后,你的类类似于以下示例:
public class DefaultBlogService implements BlogService {

    private BlogDao blogDao;

    public void setBlogDao(BlogDao blogDao) {
        this.blogDao = blogDao;
    }

    // this is (unsurprisingly) the initialization callback method
    public void init() {
        if (this.blogDao == null) {
            throw new IllegalStateException("The [blogDao] property must be set.");
        }
    }
}

接下来,你可以在xml中定义它:

<beans default-init-method="init">

    <bean id="blogService" class="com.something.DefaultBlogService">
        <property name="blogDao" ref="blogDao" />
    </bean>

</beans>

<beans>中的default-init-method表明所有的bean在Spring IOC容器进⾏初始化的时候,都会在合适的时间调⽤ init⽅法。 同样的,你可以在<beans>中配置default-destroy-method 。如果想覆盖默认的回调函数,可以在<bean>中使⽤init-method或者destroy-method。Spring容器保证在向bean提供所有依赖项之后⽴即调⽤配置的初始化回调。因此,在对原始bean引⽤调⽤初始化回调时,AOP拦截器等尚未作⽤于bean。

⾸先创建⼀个⽬标bean,然后才能应⽤⼀个带有拦截器链的AOP代理。如果⽬标bean和代理是单独定义的,那么你的代码甚⾄可以影响原始⽬标bean。因此,将拦截器应⽤于init⽅法会导致不⼀致的结果,因为这样做会将⽬标bean的⽣命周期耦合到其代理或拦截器,并在代码直接与原始⽬标bean交互时留下奇怪的语义。

总结⽣命周期机制

Spring2.5 中,你有 3 种⽅式来控制 bean 的⽣命周期:
 

InitializingBeanDisposableBean回调接⼝

⾃定义init() destroy() ⽅法。

@PostConstruct @PreDestroy

你可以将3种⽅式结合使⽤。

如果多种⽅式⼀起使⽤,每种⽅式都配置了⼀个不同的⽅法名,那么他们的执⾏顺序将会如下⾯的顺序所示。 但是如果他们都配置了同⼀个⽅法名,那么该⽅法只会执⾏⼀次。
 
如果为 initialization 配置了多种⽣命周期的多个名字,那么执⾏顺序如下:
@PostConstruct 的⽅法注解
InitializingBean 接⼝⾥⾯的 afterPropertiesSet() ⽅法
⾃定义的 init() ⽅法
 
Destroy 也是⼀样的顺序:
@PreDestroy 的⽅法注解
DisposableBean 接⼝⾥⾯的 destroy() ⽅法
⾃定义的 destroy() ⽅法
 
启动和关闭回调
Lifecycle 接⼝为需要管理⾃⼰⽣命周期的对象(例如需要启动和关闭⼀些后台进程)提供了最基本的⽅法。
public interface Lifecycle {

    void start();

    void stop();

    boolean isRunning();
}
任何 Spring 管理的对象都可以实现 Lifecycle 接⼝。当 ApplicationContext 收到⼀个start或者 stop 信号时,他会将该信号传递给所有的Lifecycle 接⼝的实现。这是通过 LifecycleProcessor 委托来实现的:
 
public interface LifecycleProcessor extends Lifecycle {

    void onRefresh();

    void onClose();
}
LifecycleProcessor Lifecycle 的扩展。它还添加了另外两种⽅法来响应正在刷新和关闭的上下⽂。
 
请注意, org.springframework.context.Lifecycle接口只是提供了⼀个简单的start和 stop 通知协议,但是并没有实现上下⽂刷新时⾃动启动的功能。如果想更好的控制⾃动启动的功能,可以实现 org.springframework.context.SmartLifecycle接⼝。 另外,stop通知不能保证⼀定在destruction之前执⾏。通常情况下所有的Lifecycle 都会收到⼀个stop通知在总的destruction回调执⾏之前。但是在热刷新或者终⽌刷新尝试时,只会执⾏ destroy ⽅法。
 
启动和关闭调⽤的顺序可能很重要。如果任何两个对象之间存在“依赖”关系,则依赖⽅在依赖之后开始,在依赖之前停 ⽌。然⽽,有时,直接依赖性是未知的。你可能只知道某个类型的对象应该先于另⼀个类型的对象开始。在这些情况下,SmartLifecycle接⼝定义了另⼀个选项,即在其⽗接⼝上定义的getPhase()⽅法。下表显示了Phased接⼝的定义:
public interface Phased {

    int getPhase();
}
下⾯是 SmartLifecycle 的定义:
public interface SmartLifecycle extends Lifecycle, Phased {

    boolean isAutoStartup();

    void stop(Runnable callback);
}

启动时,具有最低phase的对象⾸先启动。停⽌时,按相反顺序执⾏。因此,实现SmartLifecycle且其getPhase()⽅法返回Integer.MIN_VALUE值的对象将是第⼀个开始和最后⼀个停⽌的对象。

同样的,Integer.MAX_VALUE值的phase值将表⾯对象应在最后启动并⾸先停⽌(可能是因为它取决于要运⾏的其他进程)。在考虑phase值时,还必须知道不实现SmartLifecycle的任何“正常”⽣命周期对象的默认阶段是0。因此,任何负phase值都表示⼀个对象应该在这些标准组件之前开始(并在它们之后停⽌)。对于任何正phase值,顺序相反。

SmartLifecycle定义的Stop⽅法接受回调。任何实现都必须在该实现的关闭过程完成后调⽤该回调的run()⽅法。在需要的时候,可以实现异步的关闭,因为LifecycleProcessor接⼝的默认实现DefaultLifecycleProcessor在每个phase中的对象中调⽤该回调时,会等待⼀个超时值。每个phase默认超时为30秒。你可以通过在上下⽂中定义⼀个名为lifecycleProcessor的bean来覆盖默认的⽣命周期处理器实例。如果只想修改超时,那么定义以下内容就⾜够了:

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

如前所述,LifecycleProcessor接⼝还定义了⽤于刷新和关闭上下⽂的回调⽅法。当stop()被显示调⽤的时候会启动shutdown的进程,但是这个⽅法只会在上下⽂关闭时被调⽤。另⼀⽅⾯,“刷新”回调启⽤了SmartLifecycle bean的另⼀个功能。刷新上下⽂时(在所有对象都已实例化和初始化之后),将调⽤该回调。此时,默认的⽣命周期处理器检查每个SmartLifecycle对象的isAutoStartup()⽅法返回的布尔值。如果为true,则该对象将在该点启动,⽽不是等待显式调⽤上下⽂或其⾃身的start()⽅法(与上下⽂refresh不同,对于标准上下⽂实现,上下⽂start不会⾃动触发)。如前所述,phase值和任何“depends-on”关系决定启动顺序。

在⾮Web应⽤程序中优雅的关闭Spring IoC容器

本⼩节只适⽤于⾮ web 的应⽤程序。当 web 应⽤程序关闭时, Spring web 基础的 ApplicationContext 实现,已经
有代码优雅的关闭 Spring IoC 容器。

如果你使⽤Spring IOC容器在⾮web应⽤程序环境中(富⽂本桌⾯程序),需要注册⼀个shutdown hook到JVM中。这样将保证优雅的关闭,并且在单例bean中调⽤相关的销毁⽅法,让所有的资源得到释放。当然,你必须正确的配置和实现这些销毁⽅法。

调⽤ ConfigurableApplicationContext 接⼝中的 registerShutdownHook() 来注册⼀个shutdown hook, 如下所示:
 
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 and BeanNameAware

ApplicationContext 创建了 org.springframework.context.ApplicationContextAware接⼝的⼀个实现对象时。实例提供了⼀个对ApplicationContext的引⽤。下⾯是ApplicationContextAware的定义:
public interface ApplicationContextAware {

    void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
}

因此,bean可以⼿动控制创建他的ApplicationContext,可以通过ApplicationContext接⼝,也可以将这个引⽤转换成

ApplicationContext的⼦类(⽐如提供了额外功能的ConfigurableApplicationContext)。

他的⼀个应⽤就是在程序中获取其他的beans。这种使⽤⽅式有时候会很有⽤,但是因为其和Spring耦合在⼀起,违背了IOC原则,所以应该避免这样使⽤。应该将协作者设置成bean的属性值。

ApplicationContext的其他⽅法提供了⽂件资源,发布应⽤程序事件和访问MessageSource的功能。

在Spring2.5中,⾃动装载是另外⼀个获取到ApplicationContext引⽤的⽅法。传统的constructorbyType⾃动装载模式,能将ApplicationContext当做constructor的参数,或者setter⽅法的参数。为了更灵活的使⽤,⽐如⾃动装载字段或者⽅法的多个参数,可以使⽤@Autowired注解在字段或者⽅法上。这样ApplicationContext将会⾃动装配到需要的地⽅。

 

ApplicationContext创建了⼀个org.springframework.beans.factory.BeanNameAware的实现类时,这个类提供了对其在关联对象中定义的名字的引⽤。 如下所示:

public interface BeanNameAware {

    void setBeanName(String name) throws BeansException;
}

在填充普通bean属性之后,在初始化回调(如InitializingBeanafterPropertiesSet或⾃定义init⽅法)之前该回调会被调⽤。 

1.6.3. Other Aware Interfaces

除了 ApplicationContextAware BeanNameAware Spring 提供了很多 Aware 回调接⼝,让 bean 向容器表明它们需要依
赖某种基础结构。通常来说,名字表明了依赖的类型,下表是⾮常重要的 Aware 接⼝列表:
 
再次强调,使⽤这些接⼝将会把你的代码和 Spring API 相耦合,违背了控制反转原则。因此我们只推荐需要访问容器的
基础架构 bean 中使⽤它们。
 

1.7. Bean Definition Inheritance

bean定义可以包含很多配置信息,包括构造函数参数、属性值和容器特定的信息,例如初始化⽅法、静态⼯⼚⽅法名等。⼦bean定义从⽗定义继承配置数据。⼦定义可以覆盖⼀些值,或者根据需要添加其他值。使⽤⽗bean和⼦bean定义可以节省⼤量的输⼊。实际上,这是⼀种templating。

如果以编程⽅式使⽤ApplicationContext接⼝,则⼦bean定义由ChildBeanDefinition类表示。⼤多数⽤户并不需要直接使⽤这个类。相反,他们在类(如ClassPathXmlApplicationContext)中声明性地配置bean定义。使⽤基于XML的配置元数据时,可以通过使⽤parent属性来表名这是⼀个⼦bean定义,并将⽗bean指定为此属性的值。以下示例显示了如何执⾏此操作:

<bean id="inheritedTestBean" abstract="true"
        class="org.springframework.beans.TestBean">
    <property name="name" value="parent"/>
    <property name="age" value="1"/>
</bean>

<bean id="inheritsWithDifferentClass"
        class="org.springframework.beans.DerivedTestBean"
        parent="inheritedTestBean" init-method="initialize">  
    <property name="name" value="override"/>
    <!-- the age property value of 1 will be inherited from parent -->
</bean>

如果没有指定bean类,则⼦bean定义使⽤⽗定义中的bean类,但也可以覆盖它。在后⼀种情况下,⼦bean类必须与⽗类兼容(即,它必须接受⽗类的属性值)。

⼦bean定义从⽗bean继承作⽤域、构造函数参数值、属性值和⽅法重写,并可以选择添加新值。指定的任何范围、初始化⽅法、销毁⽅法或静态⼯⼚⽅法设置都将覆盖相应的⽗设置。

其余的设置总是取⾃⼦定义:依赖、⾃动装载模式、依赖项检查、singletonlazy init

前⾯的示例使⽤abstract属性显式地将⽗bean定义标记为abstract。如果⽗定义没有指定类,则需要显式地将⽗bean定义标记为抽象,如下示例所示:

<bean id="inheritedTestBeanWithoutClass" abstract="true">
    <property name="name" value="parent"/>
    <property name="age" value="1"/>
</bean>

<bean id="inheritsWithClass" class="org.springframework.beans.DerivedTestBean"
        parent="inheritedTestBeanWithoutClass" init-method="initialize">
    <property name="name" value="override"/>
    <!-- age will inherit the value of 1 from the parent bean definition-->
</bean>

bean不能单独实例化,因为它是不完整的,并且还显式地标记为抽象的。当定义是抽象的时,它只能⽤作纯模板bean定义,该定义⽤作⼦定义的⽗定义。试图单独使⽤这样⼀个抽象⽗bean,将其作为另⼀个bean的引⽤属性来引⽤,或者使⽤⽗bean id执⾏显式getbean()调⽤,都会返回⼀个错误。类似地,容器的内部preInstantiateSingletons()⽅法忽略定义为抽象的bean定义。

默认情况下,ApplicationContext预实例化所有单例。因此,重要的是(⾄少对于singleton bean⽽⾔),如果你有⼀个(⽗)bean定义,⽽该定义指定了⼀个类,那么你必须确保将abstract属性设置为true,否则应⽤程序上下⽂将实际(尝试)预实例化abstract bean

 


 

1.8. Container Extension Points

通常,应⽤程序开发⼈员不需要⼦类化ApplicationContext实现类。相反,可以通过插⼊特殊集成接⼝的实现来扩展 Spring IOC容器。接下来的⼏节将描述这些集成接⼝。

1.8.1. Customizing Beans by Using a BeanPostProcessor

BeanPostProcessor 接⼝定义了回调⽅法,你可以实现这些⽅法来提供⾃⼰的(或覆盖容器的默认)实例化逻辑、依赖 解析逻辑等。如果希望在Spring 容器完成对 bean 的实例化、配置和初始化之后实现⼀些⾃定义逻辑,可以插⼊⼀个或多个⾃定义BeanPostProcessor实现。
你可以配置多个 BeanPostProcessor 实例,并且可以通过设置 Order 属性来控制这些 BeanPostProcessor 实例的执⾏顺序。只有在BeanPostProcessor 实现 Ordered 接⼝时,才能设置此属性。如果你编写⾃⼰的 BeanPostProcessor ,那么也应该考虑实现Ordered 的接⼝。有关进⼀步的详细信息,请参阅BeanPostProcessor和有序接⼝的 JavaDoc 。另请参⻅BeanPostProcessor 实例的编程注册说明。
 
BeanPostProcessor 实例对 bean (或对象)实例进⾏操作。也就是说, Spring IOC 容器实例化⼀个bean实例,然 后beanPostProcessor 实例再继续完成它们的⼯作。
BeanPostProcessor 实例的作⽤域是每个容器。这仅在使⽤容器层次结构时才相关。如果你在⼀个容器中定义了beanPostProcessor,那么它只post-processes该容器中的bean。换句话说,在⼀个容器中定义的bean不会由在 另⼀个容器中定义的beanPostProcessor进⾏后处理,即使这两个容器是同⼀层次结构的⼀部分。
要更改实际的 bean 定义(即定义 bean 的蓝图),你需要使⽤ beanFactoryPostProcessor
 
org.springframework.beans.factory.config.BeanPostProcessor接⼝正好由两个回调⽅法组成。当此类注册为容器的post-processor时,对于容器创建的每个bean实例,post-processor在调⽤容器初始化⽅法(如InitializingBean.afterPropertiesSet()或任何声明的init⽅法)之前,以及在任何 bean 初始化之后,都会从容器中调⽤回调。post-processor可以对bean 实例执⾏任何操作,包括完全忽略回调。 bean post-processor通常检查回调接⼝,或者它可以⽤代理包装bean。为了提供代理包装逻辑,⼀些Spring AOP基础结构类被实现为 bean post-processors。
 
ApplicationContext ⾃动检测在实现 BeanPostProcessor 接⼝的配置元数据中定义的任何 bean ApplicationContext 将这些bean 注册为后处理器,以便在 bean 创建之后可以调⽤它们。 bean post-processors 可以与任何其他bean 相同的⽅式部署在容器中。
 
注意,当通过在 configuration 类上使⽤ @Bean factory ⽅法声明 BeanPostProcessor 时, factory ⽅法的返回类型应该是实现类本身,或者⾄少是org.springframework.beans.factory.config.BeanPostProcessor接⼝,并清楚地表⾯该bean是post-processor。否则,ApplicationContext⽆法在完全创建它之前按类型⾃动检测到它。由于需要尽早实例化BeanPostProcessor以应⽤于上下⽂中其他 bean 的初始化,因此这种早期类型检测⾮常关键。
 
以编程⽅式注册 BeanPostProcessor 实例虽然推荐的BeanPostProcessor 注册⽅法是通过 ApplicationContext ⾃动检测(如前所述),但你可以使⽤addBeanPostProcessor⽅法在 ConfigurableBeanFactory 中以编程⽅式注册它们。 当你需要在注册之前评估条件逻辑,或者甚⾄需要跨层次结构中的上下⽂复制bean post processors 时,这可能⾮常有⽤。但是,请注意,以编程⽅式添加的BeanPostProcessor 实例并不尊重有序的接⼝。在这⾥,登记的顺序决定执⾏的顺序。还要注意,以编程⽅式注册BeanPostProcessor 实例总是在注册为⾃动检测的实例之前进⾏处理,⽽不接收任何显式排序。
BeanPostProcessor 实例和 AOP ⾃动代理实现BeanPostProcessor 接⼝的类是特殊的,由容器进⾏不同的处理。作为 ApplicationContext 特殊启动阶段的⼀部分,所有BeanPostProcessor实例和这些实例直接引⽤的 bean 都在启动时实例化。
 
接下来,所有 beanPostProcessor 实例都以排序的⽅式注册,并应⽤于容器中所有其他 bean 。因为 AOP ⾃动代理是作为BeanPostProcessor 本身实现的,所以 BeanPostProcessor 实例和它们直接引⽤的 bean 都不符合⾃动代理的条件,因此它们中没有aspects woven
对于任何这样的 bean ,你都应该看到⼀条⽇志消息:bean somebean不适合被所有的 BeanPostProcessor 接⼝处理(例如:不适合⾃动代理)。如果你使⽤autowiring或 @Resource bean 连接到 BeanPostProcessor ,那么在搜索类型匹配的依赖项候选项时,Spring 可能会访问意外的 bean ,因此,它们不适合⾃动代理或选⽤其他类型的 post-processing⽅式。例如,如果你有⼀个⽤@Resource注解的依赖项,其中字段或setter名称与 bean 的声明名称不直接匹配,并且没有使⽤name属性,那么 spring 将通过类型来访问其他 bean
下⾯的例⼦展示了怎么在 ApplicationContext 中写,注册,和使⽤ BeanPostProcessor
例⼦ Hello World, BeanPostProcessor - style
第⼀个例⼦是基本应⽤,这个例⼦展示了⾃定义的BeanPostProcessor实现,调⽤ toString() ⽅法并打印到系统输出。
下⾯是⾃定义 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;
    }
}

下⾯的bean使⽤了InstantiationTracingBeanPostProcessor

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

    <lang:groovy id="messenger"
            script-source="classpath:org/springframework/scripting/groovy/Messenger.groovy">
        <lang:property name="message" value="Fiona Apple Is Just So Dreamy."/>
    </lang:groovy>

    <!--
    when the above bean (messenger) is instantiated, this custom
    BeanPostProcessor implementation will output the fact to the system console
    -->
    <bean class="scripting.InstantiationTracingBeanPostProcessor"/>

</beans>
注意 InstantiationTracingBeanPostProcessor 是如何定义的。它甚⾄没有名称,⽽且,因为它是⼀个bean,所以可以像注⼊任何其他bean⼀样注⼊依赖关系。(前⾯的配置还定义了⼀个由groovy脚本⽀持的bean。 Spring 动态语⾔⽀持在标题为“动态语⾔⽀持”的章节中详细介绍。)
下⾯是调⽤的 java 应⽤程序:
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.scripting.Messenger;

public final class Boot {

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

}
程序的输出如下:
Bean 'messenger' created : org.springframework.scripting.groovy.GroovyMessenger@272961
org.springframework.scripting.groovy.GroovyMessenger@272961
例⼦: RequiredAnnotationBeanPostProcessor
将回调接⼝或注解与⾃定义 BeanPostProcessor 实现结合使⽤是扩展 Spring IOC 容器的常⽤⽅法。⼀个例⼦是Spring的RequiredAnnotationBeanPostProcessor-⼀个随Spring发⾏版⼀起提供的BeanPostProcessor实现,它要求⽤(任意)注解标记的bean上的JavaBean 属性都需要⼀个真实值。
 
 

1.8.2. Customizing Configuration Metadata with a BeanFactoryPostProcessor

 

我们看到的下⼀个扩展点是org.springframework.beans.factory.config.BeanFactoryPostProcessor。此接⼝的语义与BeanPostProcessor的语义相似,但有⼀个主要区别:BeanFactoryPostProcessor对Bean配置元数据进⾏操作。也就 是说,Spring IOC容器允许BeanFactoryPostProcessor读取配置元数据,并可能在容器实例化BeanFactoryPostProcessor实例以外的任何bean之前对其进⾏更改。

你可以配置多个 BeanFactoryPostProcessor 实例,并且可以通过设置 Order 属性来控制这些 BeanFactoryPostProcessor实例的运⾏顺序。但是,只能在BeanFactoryPostProcessor 实现 Ordered 接⼝时设置此属性。如果你编写⾃⼰的BeanFactoryPostProcessor,那么也应该考虑实现 Ordered 的接⼝。有关更多详细信息,请参阅 BeanFactoryPostProcessor和 Ordered 接⼝的 JavaDoc
 
如果要更改实际的 bean 实例(即从配置元数据创建的对象),则需要使⽤ BeanPostProcessor (如前⾯使⽤
BeanPostProcessor ⾃定义 bean 中所述)。虽然在技术上可以在 BeanFactoryPostProcessor 中使⽤ bean 实例(例如,
通过使⽤ BeanFactory.getBean()),但这样做会导致过早的bean 实例化,违反标准容器⽣命周期。这可能会导致负
⾯影响,例如绕过 bean post 处理。
 
此外, BeanFactoryPostProcessor 实例的作⽤域是每个容器。这仅在使⽤容器层次结构时才相关。如果在⼀个容器中定 义BeanFactoryPostProcessor,那么它只应⽤于该容器中的bean 定义。⼀个容器中的bean定义不会由另⼀个容器中的BeanFactoryPostProcessor实例进⾏后处理,即使这两个容器属于同⼀层次结构。
 
bean ⼯⼚后处理器在 ApplicationContext 中声明时⾃动执⾏,以便将更改应⽤于定义容器的配置元数据。 Spring 包含许多预定义的bean ⼯⼚后处理器,例如 PropertyOverrideConfigurer PropertyPlaceholderConfigurer 。你还可以使⽤⾃定义BeanFactoryPostProcessor-来注册⾃定义属性编辑器。
 
ApplicationContext 会⾃动检测部署到其中实现 BeanFactoryPostProcessor 接⼝的任何 bean 。它在适当的时候将这些bean⽤作 bean ⼯⼚ post-processors。你可以像部署任何其他bean⼀样部署这些后处理器bean。
BeanPostProcessors⼀样,你通常不希望为延迟初始化配置BeanFactoryPostProcessors。如果没有其他 bean引⽤bean (⼯⼚)后处理器,则该后处理器将不会被实例化。因此,将其标记为 lazy 初始化将被忽略,并且即使在你的元素的声明中将default-lazy-init属性设置为true,Bean(Factory)PostProcessor 也将被优先实例化。
 
PropertyPlaceholderConfigurer类名替换

 

通过使⽤标准 Java Properties 格式,可以使⽤ PropertyPlaceholderConfigurer 从⼀个单独的⽂件中的bean定义外部化属性值。这样做可以使部署应⽤程序的⼈员⾃定义特定于环境的属性,如数据库URL 和密码,⽽不必为容器修改⼀个或多个XML定义主⽂件从⽽增加复杂性或⻛险。
考虑以下基于 XML 的配置元数据⽚段,其中定义了具有占位符值的数据源:
<bean class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
    <property name="locations" value="classpath:com/something/jdbc.properties"/>
</bean>

<bean id="dataSource" destroy-method="close"
        class="org.apache.commons.dbcp.BasicDataSource">
    <property name="driverClassName" value="${jdbc.driverClassName}"/>
    <property name="url" value="${jdbc.url}"/>
    <property name="username" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
</bean>
这个例⼦展示了属性被配置在外部的 Properties ⽂件中。在运⾏时,使⽤ PropertyPlaceholderConfigurer 将元数据替换成DataSource 中的某些属性。要替换的值被指定为 ${property-name}格式的占位符,该格式遵循ant log4j 以及 JSP EL 样式。
真实的值取⾃外部的 Java Properties 格式的⽂件:
jdbc.driverClassName=org.hsqldb.jdbcDriver
jdbc.url=jdbc:hsqldb:hsql://production:9002
jdbc.username=sa
jdbc.password=root
因此, ${jdbc.username} 在运⾏时被替换成'sa',其他的占位符也会被替换成相应的值。PropertyPlaceholderConfigurer 检查bean定义的⼤多数属性和属性中的占位符。此外,还可以⾃定义占位符前缀和后缀。
使⽤ Spring2.5 引⼊的 context 命名空间,可以使⽤专⽤配置元素配置属性占位符。可以在“位置”属性中以逗号分隔的列表形式提供⼀个或多个位置,如下示例所示:
<context:property-placeholder location="classpath:com/something/jdbc.properties"/>
PropertyPlaceholderConfigurer 不仅在指定的属性⽂件中查找属性。默认情况下,如果它在指定的属性⽂件中找不到属性,它也会检查Java 系统属性。可以通过使⽤以下三个⽀持的整数值之⼀设置配置器的systemPropertiesMode属性来⾃定义此⾏为:
never (0): 从不检查系统属性。
fallback (1): 如果属性在声明的⽂件⾥⾯找不到,则去检查系统属性。这个是默认的模式。
override (2): 先检查系统属性,这会让系统属性覆盖其他的来源。
你可以使⽤ PropertyPlaceholderConfigurer 来替换类的名称,这个在运⾏时需要类的特殊实现时很有⽤,如下例
⼦所示:
<bean class="org.springframework.beans.factory.config.PropertySourcesPlaceholderConfigurer">
    <property name="locations">
        <value>classpath:com/something/strategy.properties</value>
    </property>
    <property name="properties">
        <value>custom.strategy.class=com.something.DefaultStrategy</value>
    </property>
</bean>

<bean id="serviceStrategy" class="${custom.strategy.class}"/>
如果在运⾏时⽆法将类解析为有效的类,则在即将创建 bean 时, bean 的解析将失败,这是在⾮ lazy init bean
ApplicationContext preInstantiateSingletons ()阶段进⾏的。
 
例⼦PropertyOverrideConfigurer
PropertyOverrideConfigurer 是另外⼀个post-processor bean⼯程,和PropertyPlaceholderConfigurer相似,但是和它不
同的是,对 bean 的定义来说原始定义可以有默认值或者没有任何值。 如果 overriding Properties 没有 bean 某个特定属
性,那么会使⽤默认的上下⽂定义。
注意, bean 定义不知道被覆盖,所以从 XML 定义⽂件中看,并不知道使⽤的是 override configurer 。如果多个
PropertyOverrideConfigurer 实例为同⼀个bean属性定义不同的值,则最后⼀个实例将因覆盖机制⽽获胜。
属性⽂件配置⾏如下格式:
beanName.property=value
下⾯是例⼦:
dataSource.driverClassName=com.mysql.jdbc.Driver
dataSource.url=jdbc:mysql:mydb
此示例⽂件可以与包含名为 dataSource bean 的容器定义⼀起使⽤,该bean具有 driver url 属性。
也⽀持复合属性名,只要路径的每个组件(被重写的最终属性除外)已经⾮空(可能由构造函数初始化)。在下⾯的示
例中, tom bean fred 属性的 bob 属性的 sammy 属性设置为标量值 123
tom.fred.bob.sammy=123
指定的重写值始终是⽂本值。它们不会转换为 bean 引⽤。当 XML bean 定义中的原始值指定 bean 引⽤时,此约定也适
⽤。
使⽤ Spring2.5 引⼊的 context 命名空间,可以使⽤专⽤配置元素配置属性占位符。可以在“位置”属性中以逗号分隔的列表形式提供⼀个或多个位置,如下示例所示:
<context:property-override location="classpath:override.properties"/>

1.8.3. Customizing Instantiation Logic with a FactoryBean

你可以为本身是⼯⼚的对象实现 org.springframework.beans.factory.factorybean接⼝。
FactoryBean接⼝是Spring IOC容器实例化逻辑的⼀个可插⼊点。如果需要在java中实现初始化代码,⽽不是⼀个(潜在的)冗⻓的XML中,那么你可以创建⾃⼰的FactoryBean,在该类中编写复杂的初始化,然后将⾃定义的FactoryBean插⼊容器中。
 
FactoryBean 接⼝提供 3 个⽅法:
Object getObject(): 返回⼯⼚创建的实例,该实例可能是被共享的, 取决于该实例是单例还是多例模式。
boolean isSingleton(): 判断 FactoryBean 返回的是单例还是多例。
Class getObjectType(): 返回 getObject() ⽅法返回的类型,如果提前不知道类型,那么返回 null
factrybean 概念和接⼝在 Spring 框架中的许多地⽅使⽤。超过 50 FactoryBean 接⼝实现与 Spring 本身⼀起提供。
 
当需要向容器请求实际的 FactoryBean 实例本身⽽不是它⽣成的 bean 时,在调⽤ ApplicationContext getbean ()⽅法时,在bean ID 前⾯加上符号( & )。因此,对于 ID myBean 的给定 FactoryBean ,在容器上调⽤getBean(“myBean”)返回FactoryBean⽣成的bean,⽽调⽤ getBean (“&myBean”)则返回FactoryBean实例本身。
 
 

1.9. Annotation-based Container Configuration(基于注解的容器配置)

注解是不是⽐XML配置更好? 引⼊基于注解的配置提出了这样⼀个问题:这种⽅法是否⽐XML“更好”。简短的答案是“它取决于”。⻓的答案是:每种⽅法都有其优缺点,通常情况下,由开发⼈员决定哪种策略更适合它们。由于它们的定义⽅式,注解在其声明中提供了⼤量上下⽂,从⽽导致配置更简短。然⽽,XML擅⻓在不接触源代码或重新编译组件的情况下注⼊组件。⼀些开发⼈员更喜欢将注⼊靠近源代码,⽽其他⼈则认为带注解的类不再是POJO,⽽且配置变得分散且难以控制。 ⽆论选择哪⼀种,Spring都能容纳这两种⻛格,甚⾄可以将它们混合在⼀起。值得指出的是,通过它的JavaConfig选项,Spring允许以⾮侵⼊性的⽅式使⽤注解,⽽不接触⽬标组件源代码,并且在⼯具⽅⾯,所有配置样式都由Spring⼯具套件⽀持。

 

基于注解的配置提供了XML设置的替代⽅案,它依赖字节码元数据来注⼊组件,⽽不是使⽤尖括号声明。开发⼈员不使 ⽤XML来描述bean连接,⽽是使⽤相关类、⽅法或字段声明上的注解将配置移动到组件类本身。如示例中所述: RequiredAnnotationBeanPostProcessor与注解结合使⽤BeanPostProcessor是扩展SpringIOC容器的常⽤⽅法。

 

例如,Spring2.0引⼊了@Required注解,标记属性必须存在。Spring2.5使得遵循相同的⽅法来驱动Spring的依赖注⼊成为可能。本质上,@Autowired注解提供了与Autowiring Collaborators中描述的相同的功能,但是具有更细粒度的控 制和更⼴泛的适⽤性。Spring2.5还增加了对JSR-250注解的⽀持,如@PostConstruct@PreDstroySpring 3为 javax.inject包中包含的JSR-330(Java依赖注⼊)注解添加了⽀持,例如@Inject@Named。有关这些注解的详细信息,请参阅相关部分。

注解注⼊在XML注⼊之前执⾏。因此,当两个同时使⽤时,XML配置会覆盖注解注⼊的属性。

同样的,你可以将它们注册为单个 bean 定义,但也可以通过在基于 XML Spring 配置中包含以下标记来隐式注册它们
(注意上下⽂命名空间的包含):
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <context:annotation-config/>

</beans>
(隐式注册的 post-processors包括AutoWiredAnnotationBeanPostProcessor
CommonAnnotationBeanPostProcessor PersistenceAnnotationBeanPostProcessor 和上述
RequiredAnnotationBeanPostProcessor 。)
<context:annotation-config/> 仅在定义bean 的同⼀应⽤程序上下⽂中查找bean上的注解。这意味着,如果在 DispatcherServlet的 WebApplicationContext 中放置 <context:annotation-config/>,它只检查控制器中的@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 属性,则容器将引发异常。这就允许了早期和显式的失败,避免了后⾯的 NullPointerException 实例等。我们仍然
建议你将断⾔放⼊ bean 类本身(例如,放到 init ⽅法中)。这样做会强制那些必需的引⽤和值,即使在容器外部使⽤类
也是如此。
Spring Framework 5.1 开始, @Required 注解正式被弃⽤,取⽽代之的是将构造函数注⼊⽤于所需的设置(或
使⽤ InitializingBean.afterPropertiesSet ()的⾃定义实现以及 bean 属性 setter ⽅法)。
 

1.9.2. Using @Autowired

在本节包含的示例中, JSR 330 @Inject 注解可以代替 spring @Autowired 注解。有关详细信息,请参阅此处。
你可以将 @Autowired 注解到构造器中,如下所示:
public class MovieRecommender {

    private final CustomerPreferenceDao customerPreferenceDao;

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

    // ...
}
SpringFramework4.3 开始,如果⽬标 bean 只定义了⼀个构造函数,那么就不再需要在此类构造函数上使⽤@Autowired注解。但是,如果有多个构造函数可⽤,则必须⾄少对其中⼀个进⾏注解,以告诉容器使⽤哪⼀个。
@Autowired 也可以注解到传统的 setter ⽅法,如下例⼦所示:
public class SimpleMovieLister {

    private MovieFinder movieFinder;

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

    // ...
}

也可以把注解应⽤到任何名字和多个参数,如下所示:

public class MovieRecommender {

    private MovieCatalog movieCatalog;

    private CustomerPreferenceDao customerPreferenceDao;

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

    // ...
}
@Autowired 也可以⽤在字段上,和构造函数混合使⽤,如下所示:
public class MovieRecommender {

    private final CustomerPreferenceDao customerPreferenceDao;

    @Autowired
    private MovieCatalog movieCatalog;

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

    // ...
}
确保⽬标组件(例如 movieCatalog customerPreferenceDAO )始终通过类型来声明,这样才能⽤在
@Autowired 注⼊点。否则,由于在运⾏时找不到类型匹配,注⼊可能会失败。
对于通过类路径扫描找到的 XML 定义的 bean 或组件类,容器通常预先知道具体的类型。但是,对于 @Bean ⼯⼚
⽅法,你需要确保声明的返回类型具有⾜够的表现⼒。对于实现多个接⼝的组件或可能由其实现类型引⽤的组
件,考虑在⼯⼚⽅法上声明最特定的返回类型(⾄少与引⽤ bean 的注⼊点所要求的特定类型相同)。
还可以通过将注解添加到需要该类型数组的字段或⽅法,那么可以从 ApplicationContext 中获取到该特定类型的所有
bean ,如下例所示:
public class MovieRecommender {

    @Autowired
    private MovieCatalog[] movieCatalogs;

    // ...
}
这同样适⽤于类型化集合,如下示例所示:
public class MovieRecommender {

    private Set<MovieCatalog> movieCatalogs;

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

    // ...
}
你的⽬标 bean 可以实现 org.springframework.core.Ordered接⼝,或者如果你希望数组或列表中的项按特定顺序 排序,可以使⽤@Order 或标准的 @Priority 注解。
否则,它们的顺序遵循容器中相应⽬标 bean 定义的注册顺序。
你可以在⽬标类级别和 @Bean ⽅法上声明 @Order 注解,可能是通过单个 bean 定义(在使⽤同⼀bean类的多个定义的情况下)声明的。@Order可能会影响注⼊点的优先级,但请注意,它们不会影响单例启动顺序,这是⼀个由依赖关系和@DependsOn声明确定的正交问题。
注意,标准 javax.annotation.Priority 注解在 @Bean 级别不可⽤,因为它不能在⽅法上声明。它的语义可以通过
@Order values @Primary 在每种类型的单个 bean 上进⾏建模。
Map 实例也可以被注⼊,只要 key String 类型。 Map value 包括了所有的类型匹配的 Bean keys 是该 bean 的名字。⼊
下图所示:
public class MovieRecommender {

    private Map<String, MovieCatalog> movieCatalogs;

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

    // ...
}
如果注⼊点没有可以匹配的⽬标,那么⾃动注⼊会失败。如果是 array, collection 或者 map ,⾄少要有⼀个元素能匹配。
默认⾏为是将带注解的⽅法和字段视为指示所需依赖项。你可以如以下示例中所示更改此⾏为,使框架能够跳过不可满
⾜的注⼊点,⽅法是将其标记为⾮必需:
public class SimpleMovieLister {

    private MovieFinder movieFinder;

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

    // ...
}
如果⾮必需⽅法的依赖项(或多个参数的依赖项之⼀)不可⽤,则根本不会调⽤该⽅法。在这种情况下,不需要填写的
字段将不会被填充,从⽽保留其默认值。
注⼊的构造函数和⼯⼚⽅法参数是⼀种特殊情况,因为@Autowired上的“required”标志的含义有所不同,因为Spring的构造函数解析算法可能处理多个构造函数。默认情况下,构造函数和⼯⼚⽅法参数实际上是必需的,但在单个构造函数场景中有⼀些特殊规则,例如,如果没有匹配的bean可⽤,多元素注⼊点(数组、集合、映射)解析为空实例。这允许使⽤⼀个通⽤的实现模式,其中所有依赖项都可以在唯⼀的多参数构造函数中声明,例如声明为没有@Autowired注解 的单个公共构造函数。
每个类只能标记⼀个带required注解的构造函数,但可以标记多个⾮必需的构造函数。在这种情况下,每⼀个都是候选对象,Spring将使⽤最贪婪的可以满⾜最多依赖关系的构造函数,也就是说,拥有最多参数的构造函数。 构造函数解析算法与使⽤重载构造函数的未注解类相同,只是将候选对象缩⼩到带注解的构造函数。
建议使⽤ @Autowired 的'required'属性⽽不是使⽤setter⽅法上的@Required注解。“required”属性表示⾃动装载不需要该属性。如果⽆法⾃动装载,则忽略该属性。另⼀⽅⾯,@Required更强⼤,因为它强制通过容器⽀持的任何⽅式来设置属性。如果未定义任何值,则会引发相应的异常。
或者,你可以通过 Java 8 java.util.Optional 表示特定依赖项的⾮必需性质,如下示例显示:
public class SimpleMovieLister {

    @Autowired
    public void setMovieFinder(Optional<MovieFinder> movieFinder) {
        ...
    }
}
Spring Framework 5.0 中,你也可以使⽤ @Nullable 注解(任何包-中的任何类型,例如,JSR-305中的 javax.annotation.Nullable):
public class SimpleMovieLister {

    @Autowired
    public void setMovieFinder(@Nullable MovieFinder movieFinder) {
        ...
    }
你还可以使⽤ @Autowired 处理已知可解析依赖项的接⼝: BeanFactory ApplicationContext Environment
ResourceLoader ApplicationEventPublisher MessageSource 。这些接⼝及其扩展接⼝(如
ConfigurableApplicationContext ResourcePatternResolver )将⾃动解析,⽆需特殊设置。以下示例⾃动装载
ApplicationContext 对象:
public class MovieRecommender {

    @Autowired
    private ApplicationContext context;

    public MovieRecommender() {
    }

    // ...
}
@Autowired, @Inject, @Value, @Resource 注解是在 Spring BeanPostProcessor 中处理的,这意味着你不能将这
些注解⽤在你⾃⼰的 BeanPostProcessor BeanFactoryPostProcessor 类型。这些类型必须使⽤ XML 或者 @Bean ⽅法
来显示指定。

1.9.3. Fine-tuning Annotation-based Autowiring with @Primary

由于按类型⾃动装载可能会导致多个候选者,因此通常需要对选择过程进⾏更多的控制。实现这⼀点的⼀种⽅法是使⽤ Spring的@Primary注解。@Primary表示当多个 bean 是要⾃动连接到单值依赖项的候选对象时,应该优先考虑特定 bean。如果候选对象中只存在⼀个主bean,则它将成为⾃动装载的值。 考虑将 firstMovieCatalog 定义为 Primary MovieCatalog的以下配置:
@Configuration
public class MovieConfiguration {

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

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

    // ...
}
下⾯的 MovieRecommender ⾃动注⼊了 firstMovieCatalog:
public class MovieRecommender {

    @Autowired
    private MovieCatalog movieCatalog;

    // ...
}
相应的 Bean 定义如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <context:annotation-config/>

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

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

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

</beans>

1.9.4. Fine-tuning Annotation-based Autowiring with Qualifiers

当可以确定⼀个主要候选对象时,@Primary是⼀种在多个实例中按类型使⽤⾃动装载的有效⽅法。当你需要对选择过程进⾏更多控制时,可以使⽤Spring的@Qualifier注解。你可以将限定符值与特定参数相关联,缩⼩类型匹配集的范围,以便为每个参数选择特定的bean。在最简单的情况下,这可以是⼀个简单的描述性值,如下⾯的示例所示:

public class MovieRecommender {

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

    // ...
}
你还可以在单个构造函数参数或⽅法参数上指定 @Qualifier 注解,如下例所示:
public class MovieRecommender {

    private MovieCatalog movieCatalog;

    private CustomerPreferenceDao customerPreferenceDao;

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

    // ...
}
下⾯是相应的 bean 定义:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <context:annotation-config/>

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

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

带有main限定符值的bean与构造函数参数连接,构造函数参数使用相同的值进行限定。

带有action限定符值的bean与构造函数参数连接,构造函数参数使用相同的值进行限定。

对于回退匹配, bean 名称被视为默认限定符值。因此,可以使⽤ id main ⽽不是嵌套限定符元素来定义 bean ,从⽽得
到相同的匹配结果。

然⽽,尽管你可以使⽤此约定按名称引⽤特定的bean@Autowired是关于带有可选语义限定符的类型驱动注⼊的根本。这意味着,即使使⽤bean名称回退,限定符值在类型匹配集内也始终具有收缩语义。它们不会在语义上表示对唯⼀ bean id的引⽤。好的限定符值是mainEMEApersistent,表示独⽴于bean id的特定组件的特征,在匿名bean定义 (如前⾯示例中的定义)的情况下,可以⾃动⽣成这些特征。

限定符也适⽤于类型化集合,如前⾯讨论的-例如,设置。在这种情况下,根据声明的限定符,所有匹配的bean都作为集合注⼊。这意味着限定符不必是唯⼀的。相反,它们构成了过滤标准。例如,可以定义多个具有相同限定符值 “action”的MovieCatalog bean,所有这些限定符值都被注⼊到⼀个⽤@qualifier(“action”)注解的集合中。

让限定符值根据⽬标 bean 名称在类型匹配候选中进⾏选择,不需要在注⼊点处使⽤ @Qualifier 注解。如果没有其他分辨标记(例如限定符或主标记),对于⾮唯⼀依赖关系情况,Spring将针对⽬标bean 名称匹配注⼊点名称(即字段名称或参数名称),来选择相同的命名候选对象(如果有)。
也就是说,如果你打算⽤名称表示注解驱动的注⼊,那么不要主要使⽤ @Autowired ,即使它能够在类型匹配的候选对象中通过bean 名称进⾏选择。相反,使⽤ JSR-250 @Resource注解,该注解在语义上定义为通过其唯⼀名称标识特定的⽬标组件,声明的类型与匹配过程⽆关。@Autowired有相当不同的语义:在按类型选择候选bean之后,指定的字符串限定符值只在那些类型选择的候选对象中考虑(例如,将account 限定符与标记有相同限定符标签的bean 匹配)。
对于本身定义为集合、映射或数组类型的 bean @Resource 是⼀个很好的解决⽅案,它通过唯⼀的名称引⽤特定的集合或数组bean。也就是说,从4.3版的collection 开始,只要元素类型信息保存在 @Bean 返回类型签名或集合继承层次结构中,就可以通过spring @Autowired 类型匹配算法来匹配 map 和数组类型。在这种情况下,可以使⽤限定符值在相同类型的集合中进⾏选择,如⼀段所述。
4.3 开始, @Autowired 还考虑了注⼊的⾃引⽤(即,返回当前注⼊的 bean 的引⽤)。请注意,⾃注⼊是⼀种回退。对其他组件的常规依赖始终具有优先权。从这个意义上说,⾃我推荐⼈不参与定期的候选⼈选择,相反,它们总是以最低优先级结束。在实践中,你应该只使⽤⾃引⽤作为最后的⼿段(例如,通过bean的事务代理在同⼀实例上调⽤其他⽅法)。在这种情况下,考虑将受影响的⽅法分解为单独的委托bean。或者,你可以使⽤@Resource,它可以通过其唯⼀的名称将代理返回到当前bean。
@Autowired 应⽤于字段、构造函数和多参数⽅法,允许在参数级别缩⼩限定符注解的范围。相反,只有具有单个参数的字段和bean 属性 setter ⽅法才⽀持 @Resource 。因此,如果注⼊⽬标是⼀个构造函数或多参数⽅法,那么应该坚持使⽤限定符。
 
你可以创建⾃⼰的⾃定义限定符注解。为此,请定义⼀个注解并在定义中提供@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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <context:annotation-config/>

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

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

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

</beans>

 

在类路径扫描和托管组件中,可以看到基于注解的替代⽅案,以XML形式提供限定符元数据。具体来说,请参⻅为限定符元数据提供注解。

 

在某些情况下,使⽤不带值的注解可能就⾜够了。当注解具有更⼀般的⽤途并且可以应⽤于多个不同类型的依赖项时,这⼀点⾮常有⽤。例如,你可以提供⼀个脱机⽬录,当没有可⽤的Internet连接时可以搜索该⽬录。⾸先,定义简单注解,如下示例所示:

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

}
然后将注解添加到要⾃动装载的字段或属性中,如下⾯的示例所示:
public class MovieRecommender {

    @Autowired
    @Offline 
    private MovieCatalog offlineCatalog;

    // ...
}


This line adds the @Offline annotation.

现在 bean 定义只需要⼀个qualifier type就够了:
<bean class="example.SimpleMovieCatalog">
    <qualifier type="Offline"/> 
    <!-- inject any dependencies required by this bean -->
</bean>


This element specifies the qualifier.

你还可以定义⾃定义限定符注解,这些注解接受除简单值属性之外的命名属性,或者不接受简单值属性。如果在要⾃动
注⼊的字段或参数上指定多个属性值,则 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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <context:annotation-config/>

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

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

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

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

</beans>

1.9.5. Using Generics as Autowiring Qualifiers

除了 @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
通⽤限定符也适⽤于⾃动装载列表、映射实例和数组。以下示例⾃动装载通⽤列表:
// 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. Using CustomAutowireConfigurer

CustomAutoWireConfigurer 是⼀个BeanFactoryPostProcessor,它允许你注册⾃⼰的⾃定义限定符注解类型,即使它们没有⽤Spring @Qualifier 进⾏注解。下⾯的示例演示如何使⽤ CustomAutoWireConfigurer
<bean id="customAutowireConfigurer"
        class="org.springframework.beans.factory.annotation.CustomAutowireConfigurer">
    <property name="customQualifierTypes">
        <set>
            <value>example.CustomQualifier</value>
        </set>
    </property>
</bean>
AutowireCandidateResolver 通过下⾯⼏种⽅式来确定⾃动注⼊的候选⼈:
bean 中定义的 autowire-candidate
<beans/> default-autowire-candidates模式
@Qualifier 注解和任何⾃定义的注册到 CustomAutowireConfigurer 的注解
当多个 bean 符合 autowire 候选条件时,“primary”的确定如下:如果候选对象中只有⼀个bean定义的primary属性设置为true,则选择它。

1.9.7. Injection with @Resource

Spring还⽀持通过在字段或bean属性setter⽅法上使⽤jsr-250 @Resource注解(javax.annotation.Resource)进⾏注⼊。这是JavaEE中常⻅的模式:例如,在JSF托管beanJAX-WS端点中。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;
    }
}
注解提供的名称由 CommonAnnotationBeanPostProcessor 知道的 ApplicationContext 解析为 bean 名称。如果显式
配置 Spring SimpleJndiBeanFactory ,则可以通过 JNDI 解析名称。但是,我们建议你依赖默认⾏为,并使⽤
Spring JNDI 查找功能来保持间接寻址的级别。
@Resource ⽤法中,如果没有指定显式名称,并且类似于 @Autowired @Resource 会找到⼀个主类型匹配,⽽不是指定的bean,并解析已知的可解析依赖项:BeanFactory ApplicationContext ResourceLoader 、ApplicationEventPublisher,和 MessageSource 接⼝。
因此,在下⾯的示例中, customerPreferenceDAO 字段⾸先查找名为“customerPreferenceDAO”的bean,然后返回到与customerPreferenceDAO类型匹配的主类型:
public class MovieRecommender {

    @Resource
    private CustomerPreferenceDao customerPreferenceDao;

    @Resource
    private ApplicationContext context; 

    public MovieRecommender() {
    }

    // ...
}

1.9.8. Using @Value

@Value 通常用于注入外部属性:

@Component
public class MovieRecommender {

    private final String catalog;

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

使用以下配置:

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

和以下application.properties文件:

catalog.name=MovieCatalog

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

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

@Configuration
public class AppConfig {

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

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

如果${} 无法解析任何占位符,则使用上述配置可确保Spring初始化失败。也可以使用方法一样 setPlaceholderPrefixsetPlaceholderSuffixsetValueSeparator自定义的占位符。


Spring Boot默认配置一个PropertySourcesPlaceholderConfigurerbean,它将从application.propertiesapplication.yml文件中获取属性。

Spring提供的内置转换器支持允许自动处理简单的类型转换(例如转换为Integer 或int)。多个逗号分隔的值可以自动转换为String数组,而无需付出额外的努力。

可以提供如下默认值:

@Component
public class MovieRecommender {

    private final String catalog;

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

Spring BeanPostProcessor使用ConversionService幕后处理将String值转换@Value为目标类型的过程。如果要为自己的自定义类型提供转换支持,则可以提供自己的 ConversionServicebean实例,如以下示例所示:

@Configuration
public class AppConfig {

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

 

@Value包含SpEL表达式时,该值将在运行时动态计算,如以下示例所示:

@Component
public class MovieRecommender {

    private final String catalog;

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

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

@Component
public class MovieRecommender {

    private final Map<String, Integer> countOfMoviesPerCatalog;

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

1.9.9. Using @PostConstruct and @PreDestroy

CommonAnnotationBeanPostProcessor不仅识别@Resource注解,还识别JSR-250⽣命周期注解:javax.annotation.PostConstruct和javax.annotation.PreDestroy。在Spring2.5中引⼊了对这些注解的⽀持,它提供了⽣命周期回调机制的替代⽅案,如初始化回调和销毁回调中所述。如果CommonAnnotationBeanPostProcessor注册在Spring ApplicationContext中,则在⽣命周期中与相应的Spring Lifecycle Interface⽅法或显式声明的回调⽅法相同的点调⽤包含这些注解之⼀的⽅法。在以下示例中,缓存在初始化时预填充,在销毁时清除:

public class CachingMovieLister {

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

    @PreDestroy
    public void clearMovieCache() {
        // clears the movie cache upon destruction...
    }
}
有关组合各种⽣命周期机制的效果的详细信息,请参⻅组合⽣命周期机制。
@Resource⼀样,@PostConstruct和 @PreDestroy 注解类型是 JDK 6 8 标准 Java 库的⼀部分。然⽽,整个javax.annotation包与JDK 9中的核⼼ Java 模块分离,并最终在 JDK 11 中被删除。如果需要, javax.annotation-api ⼯件现在需要通过maven central获得,只需像其他库⼀样添加到应⽤程序的类路径中即可。
 

 

1.10. Classpath Scanning and Managed Components

本章中的⼤多数示例使⽤ XML 来指定在 Spring 容器中⽣成每个 BeanDefinition 的配置元数据。上⼀节(基于注解的容器配置)演示了如何通过源代码级注解提供⼤量的配置元数据。然⽽,即使在这些示例中,XML⽂件中也显式定义了“基本”bean定义,⽽注解只驱动依赖注⼊。
本节介绍通过扫描类路径隐式检测候选组件的选项。候选组件是与筛选条件相匹配的类,并在容器中注册了相应的 bean
定义。这样就不需要使⽤ XML 来执⾏ bean 注册。相反,你可以使⽤注解(例如 @Component )、 AspectJ 类型表达式或
你⾃⼰的⾃定义筛选条件来选择哪些类具有向容器注册的 bean 定义。
Spring3.0 开始, Spring JavaConfig 项⽬提供的许多特性都是核⼼ Spring 框架的
部分。这允许你使⽤ Java
bean ,⽽不是使⽤传统的 XML ⽂件。查看 @Configuration @Bean @Import @DependsOn 注解,了解如
何使⽤这些新功能的示例。
 

1.10.1. @Component and Further Stereotype Annotations

@Repository 注解是⼀种类的⼀个标记,这些类实现了存储库的⻆⾊或原型(也称为数据访问对象或DAO)。
 
Spring 提供了更多的原型注解:
 
@Component @Service @Controller @Component 是任何 Spring 管理的组件的通
⽤原型。 @Repository @Service @Controller @Component 针对更具体的⽤例(分别在持久性、服务和表示层
中)的特殊注解。因此,你可以⽤ @Component 注解组件类,但是,通过⽤ @Repository @Service @Controller
解它们,你的类将更适合通过⼯具处理或与 aspects 关联。
 
例如,这些构造型注解是切⼊点的理想⽬标。 @Repository @Service @Controller 也可以在 Spring 框架的未来版本
中附加语义。因此,如果你选择在服务层中使⽤ @Component @Service @Service 显然是更好的选择。同样,正如
前⾯所述, @Repository 已经被⽀持作为持久性层中⾃动异常转换的标记。
 

1.10.2. Using Meta-annotations and Composed Annotations

⼤多数 Spring 的注解都可以作为元注解⽤在你⾃定的注解中。所谓元注解就是可以⽤在其他注解中的注解。 像之前提到
@Component 就是 @Service 的元注解。如下:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component 
public @interface Service {

    // ...
}

The Component causes @Service to be treated in the same way as @Component.

你也可以合并使⽤元注解。⽐如:在 Spring MVC 中的 @RestController 就是由 @Controller @ResponseBody 组成的。
此外,组合注解还可以选择从元注解中重新声明属性以允许⾃定义。当你只想公开元注解属性的⼀个⼦集时,这尤其有⽤。例如,Spring的@SessionScope 注解将作⽤域名称硬编码为 session ,但仍允许⾃定义 proxyMode 。以下列表显示 了sessionScope 注解的定义:
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Scope(WebApplicationContext.SCOPE_SESSION)
public @interface SessionScope {

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

}
你可以不定义 @SessionScope ⾥⾯的 proxyMode , 如下:
@Service
@SessionScope
public class SessionScopedService {
    // ...
}
你也可以重写 proxyMode ,如下:
@Service
@SessionScope(proxyMode = ScopedProxyMode.INTERFACES)
public class SessionScopedUserService implements UserService {
    // ...
}

For further details, see the Spring Annotation Programming Model wiki page.

1.10.3. Automatically Detecting Classes and Registering Bean Definitions

Spring 可以⾃动检测构造型类,并⽤ ApplicationContext 注册相应的 BeanDefinition 实例。例如,以下两个类可⽤于此类
⾃动检测:
@Service
public class SimpleMovieLister {

    private MovieFinder movieFinder;

    public SimpleMovieLister(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }
}
@Repository
public class JpaMovieFinder implements MovieFinder {
    // implementation elided for clarity
}
要⾃动检测这些类并注册相应的 bean ,需要将 @ComponentScan 添加到 @Configuration 类中,其中 basePackages 属性
是这两个类的通⽤⽗包。(或者,可以指定包含每个类的⽗包的逗号、分号或空格分隔列表。)
@Configuration
@ComponentScan(basePackages = "org.example")
public class AppConfig  {
    // ...
}
为了简洁起⻅,前⾯的示例可能使⽤了注解的 value 属性(即 @ComponentScan (“org.example”))。
 
下⾯是使⽤ XML 的例⼦:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

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

</beans>
<context:component-scan> 隐式的开启了<context:annotation-config>的功能。所以当使⽤<context:component-scan>的时候就不需要⽤<context:annotation-config>。
扫描 classpath 包需要在 classpath 中存在相应的⽬录条⽬。当你使⽤ Ant 构建 JAR 时,请确保你没有激活 JAR 任务
的仅限⽂件的开关。此外,在某些环境中,根据安全策略,类路径⽬录可能不会公开.
例如, JDK 1.7.0_45 及更⾼版本上的独⽴应⽤程序(这需要在清单中设置“可信库”)请参⻅
https://stackoverflow.com/questions/19394570/java-jre-7u45-breaks-classloader-getresources)。
JDK9 的模块路径( Jigsaw )上, Spring 的类路径扫描通常按预期⼯作。但是,请确保组件类暴露在 module-info描述符中。如果希望Spring 调⽤类的⾮公共成员,请确保它们是“打开的”(即,它们使⽤opens声明⽽不是 module-info描述符中的exports声明)。
 
此外,在使⽤ component-scan元素时,AutowiredAnnotationBeanPostProcessor 和CommonAnnotationBeanPostProcessor都是隐式被使⽤的。这意味着这两个组件都是⾃动检测并连接在⼀起的-都没有XML中提供任何bean配置元数据。
 
通过将 annotation-config属性的值设置为false ,可以禁⽤ AutoWiredAnnotationBeanPostProcessor
CommonAnnotationBeanPostProcessor 的注册。
 

1.10.4. Using Filters to Customize Scanning

默认情况下,⽤ @Component @Repository @Service @Controller 注解的类或⽤ @Component 注解的⾃定义注解
是唯⼀检测到的候选组件。但是,可以通过应⽤⾃定义筛选器来修改和扩展此⾏为。将它们作为@Component scan注
解的 includefilters excludefilters 参数添加(或作为 component scan 元素的 include filter exclude filter ⼦元素添加)。
每个筛选器元素都需要类型和表达式属性。下表介绍了筛选选项:
下⾯例⼦展示了忽略 @Repository 并且使⽤“stub”repositories 的注解:
@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>
你也可以禁⽤默认的 filters ,通过设置 useDefaultFilters=false在注解中或者设置<component-scan/> 中use-default-filters="false"的属性。 这样就会禁⽤@Component, @Repository, @Service, @Controller, 和 @Configuration 注解.
 

1.10.5. Defining Bean Metadata within Components

Spring 组件还可以为容器提供 bean 定义元数据。你可以使⽤⽤于在 @Configuration annotated 类中定义 bean 元数据的相
@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定义,该 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);
    }
}
该示例⾃动将字符串⽅法参数 country 注⼊到另⼀个名为privateInstance的 bean 上的 age 属性的值。 Spring 表达式语⾔元素通过符号#{ <expression> } 定义属性的值。对于 @Value 注解,表达式解析器被预先配置为在解析表达式⽂本时查找 bean名称。

 

SpringFramework4.3 开始,还可以声明 InjectionPoint 类型的⼯⼚⽅法参数(或其更具体的⼦类: DependencyDescriptor)以访问触发当前 bean 创建的请求注⼊点。
 
注意,这只适⽤于 bean 实例的实际创建,⽽不适⽤于现有实例的注⼊。因此,这个特性对于原型范围的 bean 最有意
义。对于其他作⽤域, factory ⽅法只会看到触发在给定作⽤域中创建新 bean 实例的注⼊点(例如,触发惰性单例 bean
创建的依赖项)。在这种情况下,你可以使⽤提供的注⼊点元数据和语义关注。下⾯的示例演示如何使⽤
InjectionPoint
@Component
public class FactoryMethodComponent {

    @Bean @Scope("prototype")
    public TestBean prototypeInstance(InjectionPoint injectionPoint) {
        return new TestBean("prototypeInstance for " + injectionPoint.getMember());
    }
}
常规 Spring 组件中的 @Bean ⽅法的处理⽅式与 Spring@Configuration 类中的对应⽅法不同。不同的是, @Component
类没有⽤ cglib 来增强以截获⽅法和字段的调⽤。 cglib 代理是调⽤ @Configuration classes 中通过 @Bean methods 内的⽅
法或字段创建对协作对象的 bean 元数据引⽤的⽅法。
 
即使通过引⽤对 @Bean ⽅法的编程调⽤来引⽤其他 bean ,这也不是普通的 Java 语义调⽤,⽽是通过容器来提供通常的⽣命周期管理和Spring bean 的代理,相反,在普通的 @Component 类中调⽤“bean”⽅法中具有标准Java语义的⽅法或字段,没有特殊的CGLIB处理或其他约束应⽤。
 
你可以将 @Bean ⽅法声明为静态⽅法,允许在不将其包含的配置类创建为实例的情况下调⽤它们。在定义 post-processor bean(例如,BeanFactoryPostProcessor BeanPostProcessor 类型)时,这是特别有意义的,因为这样的bean 在容器⽣命周期的早期就被初始化,应该避免在此时触发配置的其他部分。
由于技术限制,对 static@Bean ⽅法的调⽤永远不会被容器截获,即使是在 @Configuration 类(如本节前⾯所述)中也是如此:cglib ⼦类只能重写⾮静态⽅法。因此,直接调⽤另⼀个@Bean⽅法具有标准 Java 语义,导致从⼯⼚⽅法本身直接返回⼀个独⽴的实例。
“@Bean”⽅法的Java语⾔可⻅性不会对Spring 容器中的 bean 定义产⽣直接影响。你可以⾃由地声明你认为适合⾮ @Configuration类以及任何静态⽅法的⼯⼚⽅法。但是, @Configuration 类中的常规 @Bean ⽅法必须是可重写的,也就是说,它们不能声明为私有或最终的。
“@Bean”⽅法也在给定组件或配置类的基类上发现,以及在由组件或配置类实现的接⼝中声明的Java 8默认⽅法上。这允许在组成复杂的配置安排⽅⾯有很⼤的灵活性,即使是多个继承也可以通过Java 8默认⽅法(如 Spring 4.2)来实现。
最后,⼀个类可以为同⼀个bean保存多个@Bean⽅法,作为多个⼯⼚⽅法的安排,这取决于运⾏时可⽤的依赖项。这与在其他配置⽅案中选择“最贪婪”的构造函数或⼯⼚⽅法的算法相同:在构建时选择具有最多可满⾜依赖项的变量,类似于容器如何在多个@Autowired构造函数之间选择。
 

1.10.6. Naming Autodetected Components

当⼀个组件作为扫描过程的⼀部分被⾃动检测时,它的bean名称由该扫描仪已知的BeanNameGenerator策略⽣成。默 认情况下,任何包含value Spring 原型注解( @Component @Repository @Service @Controller )都会将该名称 提供给相应的bean 定义。
如果这样的注解不包含 value 或任何其他检测到的组件(例如那些由⾃定义过滤器发现的组件),则默认 bean 名称⽣成
器将返回⼩写的⾮限定类名。例如,如果检测到以下组件类,则名称为 myMovieLister movieFinderImpl
@Service("myMovieLister")
public class SimpleMovieLister {
    // ...
}
@Repository
public class MovieFinderImpl implements MovieFinder {
    // ...
}
如果你不想依赖默认的 bean 命名策略,可以提供⼀个⾃定义的bean命名策略。⾸先,实现 BeanNameGenerator接⼝,并确保包含⼀个默认的⽆参数构造函数。然后,在配置扫描器时提供完全限定的类名,如下⾯的示例注解和bean定义所示:
@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. Providing a Scope for Autodetected Components

与⼀般的Spring管理组件⼀样,⾃动检测组件的默认和最常⻅的作⽤域是singleton。但是,有时你需⼀个可以由 @Scope注解指定的不同范围。可以在注解中提供作⽤域的名称,如下示例所示:
@Scope("prototype")
@Repository
public class MovieFinderImpl implements MovieFinder {
    // ...
}
@Scope 注解只在具体的 bean 类(对于带注解的组件)或⼯⼚⽅法(对于 @Bean ⽅法)上进⾏内省。与 XML
bean 定义不同,没有 bean 定义继承的概念,并且类级别的继承层次结构与元数据⽆关。
 
有关特定于 Web 的范围(如 Spring 上下⽂中的“请求”或“会话”)的详细信息,请参阅请求、会话、应⽤程序和 WebSocket范围。与这些作⽤域的预构建注解⼀样,你也可以使⽤Spring的元注解⽅法编写⾃⼰的作⽤域注解:例如, 使⽤@Scope(“prototype”)注解的⾃定义注解元,也可能声明⾃定义作⽤域代理模式。
 
要为范围解析提供⾃定义策略,⽽不是依赖基于注解的⽅法,可以实现 ScopeMetadataResolver 接⼝。请确保包
含默认的⽆参数构造函数。然后,你可以在配置扫描器时提供完全限定的类名,正如下⾯的注解和 bean 定义示例
所示:
@Configuration
@ComponentScan(basePackages = "org.example", scopeResolver = MyScopeResolver.class)
public class AppConfig {
    // ...
}
<beans>
    <context:component-scan base-package="org.example" scope-resolver="org.example.MyScopeResolver"/>
</beans>
当使⽤某些⾮单例作⽤域时,可能需要为作⽤域对象⽣成代理。为此,组件扫描元素上可以有⼀个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. Providing Qualifier Metadata with Annotations

@Qualifier注解将在基于微调注解的带有限定符的⾃动注⼊中讨论。该部分中的示例演示了在解析autowire候选时使⽤@Qualifier注解和⾃定义限定符注解来提供细粒度控制。因为这些示例是基于XML bean定义的,所以通过使⽤XML中 bean元素的限定符或元⼦元素,在候选bean定义上提供了限定符元数据。当依赖类路径扫描⾃动检测组件时,可以在候选类上为限定符元数据提供类型级注解。以下三个示例演示了此技术:

@Component
@Qualifier("Action")
public class ActionMovieCatalog implements MovieCatalog {
    // ...
}
@Component
@Genre("Action")
public class ActionMovieCatalog implements MovieCatalog {
    // ...
}
@Component
@Offline
public class CachingMovieCatalog implements MovieCatalog {
    // ...
}
与⼤多数基于注解的替代⽅法⼀样,请记住注解元数据绑定到类定义本身,⽽XML的使⽤允许同⼀类型的多个bean可以有多个不同的限定符,因为元数据是按实例⽽不是按class提供的。
 

1.10.9. Generating an Index of Candidate Components

虽然类路径扫描速度⾮常快,但是可以通过在编译时创建⼀个静态候选列表来提⾼⼤型应⽤程序的启动性能。在此模式下,作为组件扫描⽬标的所有模块都必须使⽤此机制。
你现有的 @ComponentScan <context:component-scan>指令必须保持原样,以请求上下⽂扫描某些包中的候选项。当ApplicationContext检测到这样的索引时,它会⾃动使⽤它,⽽不是扫描类路径。
要⽣成索引,请向每个模块添加⼀个附加依赖项,该模块包含作为组件扫描指令⽬标的组件。
下⾯的示例说明如何使⽤Maven:
<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context-indexer</artifactId>
        <version>5.2.3.BUILD-SNAPSHOT</version>
        <optional>true</optional>
    </dependency>
</dependencies>
Gradle 4.5 或者更早版本,依赖必须配置在 compileOnly 中,如下:
dependencies {
    compileOnly "org.springframework:spring-context-indexer:5.2.3.BUILD-SNAPSHOT"
}
Gradle 4.6 或者以后版本,依赖必须配置在 annotationProcessor 中,如下:
dependencies {
    annotationProcessor "org.springframework:spring-context-indexer:{spring-version}"
}
这个过程⽣成⼀个包含在JAR⽂件中的 META-INF/spring.components⽂件。
在你的 IDE 中使⽤此模式时, spring-context-indexer必须注册为注解处理器,以确保在更新候选组件时索引是最 新的。
当在类路径上找到META-INF/Spring.components时,索引将⾃动启⽤。如果某个索引部分可⽤于某些库(或⽤例),但⽆法为整个应⽤程序⽣成,则可以通过将spring.index.ignore设置为true(作为系统属性或类路径根⽬录 下的spring.properties⽂件)来回滚到常规类路径安排(就像根本没有索引⼀样)。
 
 

 

1.11. Using JSR 330 Standard Annotations

 

Spring3.0之后,Spring提供了JSR-330标志注解的⽀持。这些注解和Spring注解⼀样被进⾏扫描,如果想使⽤它们, 需要包含下⾯的jar包:

   如果你使⽤ maven ,那么 javax.inject ⼯件可以在标准 maven 存储库中使⽤
https://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. Dependency Injection with @Inject and @Named

你可以使⽤ @javax.inject.Inject来替代@Autowired

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。此外,你可以将注⼊点声明为 Provider,允许通过 Provider.get() 调⽤按需访问较短作⽤域的 bean 或延迟访问其他 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⼀起使⽤。这在这⾥更适⽤,因为@Jnject没有 required的属性。以下两个示例演示如何使⽤ @Inject @Nullable
public class SimpleMovieLister {

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

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

 

1.11.2. @Named and @ManagedBean: Standard Equivalents to the @Component Annotation

    除了使⽤ @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 ,你可以同样使⽤组件扫描:
@Configuration
@ComponentScan(basePackages = "org.example")
public class AppConfig  {
    // ...
}
@Component 不同 ,JSR-330的@Named JSR-250的@ManagedBean 是不可组合的。你应该使⽤ Spring 的构造型模型来构建⾃定义组件注解。
 

1.11.3. Limitations of JSR-330 Standard Annotations

当使⽤标准注解时,你应该知道⼀些重要功能不可⽤,如下表所示:

 

 


 

1.12. Java-based Container Configuration

这节包括了如何在 Java 代码⾥⾯使⽤注解来配置 Spring 容器。

1.12.1. Basic Concepts: @Bean and @Configuration

Spring新的Java配置⽀持中的中⼼构件是@Configuration@Bean

@Bean注解⽤于指示⽅法实例化、配置和初始化要由Spring IOC容器管理的新对象。对于熟悉Spring<beans/>XML 配置的⽤户,@Bean注解与<bean/>元素具有相同的作⽤。你可以将@Bean注解的⽅法与任何spring @Component⼀起使⽤。但是,它们最常⽤于@Configuration bean。

@Configuration注解类表明它的主要⽤途是作为bean定义的源。此外,@Configuration classes允许通过调⽤同⼀类中的其他@Bean⽅法来定义bean间的依赖关系。最简单的@Configuration类如下:

@Configuration
public class AppConfig {

    @Bean
    public MyService myService() {
        return new MyServiceImpl();
    }
}
上⾯的 AppConfig 类和下⾯的 XML 配置相同:
<beans>
    <bean id="myService" class="com.acme.services.MyServiceImpl"/>
</beans>
完整的 @Configuration 与“lite”@Bean模式?
 
@Bean ⽅法在没有⽤ @Configuration 注解的类中声明时,它们被称为在“lite”模式下处理。在@Component甚⾄是普通⽼类中声明的bean⽅法被认为是“lite”,因为包含类的主要⽬的不同,@Bean⽅法在这⾥可以看做⼀种奖励。例如,服务组件可以通过在每个适⽤的组件类上附加的@Bean⽅法向容器公开管理视图。在这种情况下,@Bean⽅法是⼀种通⽤的⼯⼚⽅法机制。
 
与完整的 @Configuration 不同, lite@Bean ⽅法不能声明 bean 之间的依赖关系。相反,它们对其包含组件的内部状态进⾏操作,也可以选择对可能声明的参数进⾏操作。因此,这样的@Bean ⽅法不应调⽤其他 @Bean ⽅法。 每个这样的⽅法实际上只是特定bean 引⽤的⼯⼚⽅法,没有任何特殊的运⾏时语义。这⾥的积极副作⽤是不需要在运⾏时应⽤cglib ⼦类化,所以在类设计⽅⾯没有限制(也就是说,包含类可能是最终的等等)。
 
在常⻅的场景中, @Bean ⽅法将在 @Configuration 类中声明,确保始终使⽤“full”模式,因此跨⽅法引⽤将被重定向到容器的⽣命周期管理。这防⽌了通过常规Java调⽤意外调⽤相同的bean⽅法,这有助于减少在“Lite”模式下操作时难以跟踪的细微错误。
 
下⾯的章节将深⼊讨论 @Bean @Configuration 注解。但是,⾸先,我们介绍了使⽤基于 Java 的配置创建 Spring 容器
的各种⽅法。
 

1.12.2. Instantiating the Spring Container by Using AnnotationConfigApplicationContext

下⾯是SpringAnnotationConfigApplicationContext的介绍,他是在Spring 3.0被引⼊的。

他是 ApplicationContext 的⼀个实现,他可以接收@Configuration, @Component ,和使⽤ JSR-330注解的类作为输⼊。

 

@Configuration 作为输⼊时, @Configuration 类本身被注册成为⼀个bean,并且他⾥⾯包含的所有 @Bean ⽅法,都会被注册成Bean
 
@Component JSR-330类作为输⼊时,它们被注册为bean 定义,并且假设在必要时在这些类中使⽤诸如@Autowired或 @Inject 之类的 DI 元数据。
 

Simple Construction

ClassPathXmlApplicationContext需要XML⽂件作为输⼊⼀样,AnnotationConfigApplicationContext需要 @Configuration 作为实例化参数,这样可以完全不使⽤XML⽂件:

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 annotated class 。 如下所示:

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.
 

Building the Container Programmatically by Using register(Class<?>…​)

你可以使⽤⽆参数构造函数来实例化 AnnotationConfigApplicationContext ,然后通过 register() 拉配置他。这个在程序的
⽅式中⽐较有⽤:
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();
}

Enabling Component Scanning with scan(String…​)

想使⽤组件扫描,可以通过如下⽅式:
@Configuration
@ComponentScan(basePackages = "com.acme") 
public class AppConfig  {
    ...
}

有经验的Spring⽤户可能很熟悉下⾯的XML配置⽤法:

<beans>
    <context:component-scan base-package="com.acme"/>
</beans>
上⾯的例⼦中, com.acme 包被扫描任何 @Component 注解的类,这些类被注⼊到 Spring 容器中。AnnotationConfigApplicationContext 提供了 scan(String…)⽅法和component-scan⼀样的功能。如下
public static void main(String[] args) {
    AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
    ctx.scan("com.acme");
    ctx.refresh();
    MyService myService = ctx.getBean(MyService.class);
}
记住, @Configuration 类是⽤ @Component 元注解的,因此它们是组件扫描的候选者。在前⾯的示例中,假设
AppConfig 是在 com.acme 包(或下⾯的任何包)中声明的,则会在调⽤ scan ()期间取到他。在 refresh ()之
后,它的所有 @Bean ⽅法都被处理并注册为容器中的 bean 定义。
 

Support for Web Applications with AnnotationConfigWebApplicationContext

AnnotationConfigApplicationContext 的⼀个WebApplicationContext变种是 AnnotationConfigWebApplicationContext 。 当你配置Spring ContextLoaderListener ,或者 Spring MVC DispatcherServlet 时,可以使⽤他。下⾯的 web.xml 配置了 ⼀个标准的Spring MVC web应⽤:
<web-app>
    <!-- Configure ContextLoaderListener to use AnnotationConfigWebApplicationContext
        instead of the default XmlWebApplicationContext -->
    <context-param>
        <param-name>contextClass</param-name>
        <param-value>
            org.springframework.web.context.support.AnnotationConfigWebApplicationContext
        </param-value>
    </context-param>

    <!-- Configuration locations must consist of one or more comma- or space-delimited
        fully-qualified @Configuration classes. Fully-qualified packages may also be
        specified for component-scanning -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>com.acme.AppConfig</param-value>
    </context-param>

    <!-- Bootstrap the root application context as usual using ContextLoaderListener -->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <!-- Declare a Spring MVC DispatcherServlet as usual -->
    <servlet>
        <servlet-name>dispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!-- Configure DispatcherServlet to use AnnotationConfigWebApplicationContext
            instead of the default XmlWebApplicationContext -->
        <init-param>
            <param-name>contextClass</param-name>
            <param-value>
                org.springframework.web.context.support.AnnotationConfigWebApplicationContext
            </param-value>
        </init-param>
        <!-- Again, config locations must consist of one or more comma- or space-delimited
            and fully-qualified @Configuration classes -->
        <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. Using the @Bean Annotation

@Bean 是⽅法级的注解,和 XML 中的 <bean/> 元素有同样的作⽤。注解⽀持⼀些属性如: init- method destroy-method autowiring
你可以在 @Configuration 或者 @Component 注解中使⽤ @Bean
 

Declaring a Bean

要声明 bean ,可以使⽤ @Bean 注解对⽅法进⾏注解。你可以使⽤此⽅法在指定为⽅法返回值的类型的
ApplicationContext 中注册 bean 定义。默认情况下, bean 名称与⽅法名称相同。下⾯的示例显示了 @Bean ⽅法声明:
@Configuration
public class AppConfig {

    @Bean
    public TransferServiceImpl transferService() {
        return new TransferServiceImpl();
    }
}
上⾯例⼦和下⾯相同:
<beans>
    <bean id="transferService" class="com.acme.TransferServiceImpl"/>
</beans>
两种声明都可以在 ApplicationContext 定义 transferService 。并将 transferService 和⼀个TransferServiceImpl的实例对象 绑定起来:
transferService	->	com.acme.TransferServiceImpl
你也可以将 @Bean 定义在⼀个返回接⼝的⽅法:
@Configuration
public class AppConfig {

    @Bean
    public TransferService transferService() {
        return new TransferServiceImpl();
    }
}
但是,这会将⾼级类型预测的可⻅性限制为指定的接⼝类型( TransferService )。然后,在容器只知道⼀次完整类型 (transferserviceimpl)的情况下,受影响的单例bean 已经被实例化。⾮懒惰的单例 bean根据其声明顺序进⾏实例化,因此你可能会看到不同的类型匹配结果,具体取决于另⼀个组件尝试通过⾮声明类型进⾏匹配的时间(例如
@Autowired TransferServiceImpl,⼀旦transferService bean被实例化时,它就会被解析)。
 
如果通过已声明的服务接⼝⼀致地引⽤你的类型,则@Bean返回类型可以安全地加⼊该设计决策。但是,对于实现多个接⼝的组件或可能由它们的实现类型引⽤的组件,声明更加具体的返回类型可能会更安全(⾄少在引⽤bean的注⼊点所要求的特定类型)

 

Bean Dependencies

@Bean 注解的⽅法可以有多个参数,如下所示,在实例化是 TransferService 需要⼀个AccountRepository,我们使⽤⽅法参数来实现这个依赖,如下:
@Configuration
public class AppConfig {

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

 

解析机制与基于构造函数的依赖注⼊⾮常相似。有关详细信息,请参阅相关部分。
 

Receiving Lifecycle Callbacks

@Bean 注解定义的任何类都⽀持常规的⽣命周期回调,并且可以使⽤ jsr-250中的@PostConstruct @PreDestroy 注解。更多详细信息,请参⻅JSR-250注解。
 
还完全⽀持常规的 Spring ⽣命周期回调。如果 bean 实现了 InitialingBean DisposableBean Lifecycle ,那么容器将调
⽤它们各⾃的⽅法。
 
还完全⽀持 *Aware 接⼝的标准集(如BeanFactoryAware BeannameAware MessageSourceAware
ApplicationContextAware 等)。
 
@Bean 注解⽀持指定任意的初始化和销毁回调⽅法,很像 spring xml init ⽅法和 bean 元素上的 destroy ⽅法属性,如下
例所示:
public class BeanOne {

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

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

 


@Configuration
public class AppConfig {

    @Bean(initMethod = "init")
    public BeanOne beanOne() {
        return new BeanOne();
    }

    @Bean(destroyMethod = "cleanup")
    public BeanTwo beanTwo() {
        return new BeanTwo();
    }
}

 

默认情况下,使⽤具有公共 close shutdown ⽅法的 Java 配置定义的 bean 将⾃动加⼊销毁回调。如果你有⼀个公 共close或shutdown ⽅法,并且不希望在容器关闭时调⽤它,则可以将 @Bean destroyMethod=“”)添加到bean 定义中,以禁⽤默认(inferred)模式。
默认情况下,你可能希望为使⽤ JNDI 获取的资源执⾏此操作,因为它的⽣命周期是在应⽤程序外部管理的。特别
是,确保对 DataSource 总是这样做,因为它在 JavaEE 应⽤服务器上是有问题的。
下⾯的示例演示如何防⽌ DataSource 的⾃动销毁回调:
@Bean(destroyMethod="")
public DataSource dataSource() throws NamingException {
    return (DataSource) jndiTemplate.lookup("MyDS");
}
另外,对于 @Bean ⽅法,你通常使⽤编程的 JNDI 查找,或者使⽤ Spring JNDITemplate JNDIlocatedElegate 帮助
器,或者直接使⽤ JNDI InitialContext ,但不使⽤ JNDIObjectFactoryBean 变量(这将强制你将返回类型声明为
factoryBean 类型,⽽不是实际的⽬标类型,从⽽使它更难⽤于那些打算引⽤所提供的资源的 @Bean ⽅法中的交叉引⽤
调⽤)。
 
对于上述示例中的 beanone ,在构造期间直接调⽤ init ()⽅法同样有效,如下示例所示:
@Configuration
public class AppConfig {

    @Bean
    public BeanOne beanOne() {
        BeanOne beanOne = new BeanOne();
        beanOne.init();
        return beanOne;
    }

    // ...
}
当你直接在 Java 中⼯作时,你可以⽤你的对象做任何事情,并不总是需要依赖于容器的⽣命周期。
 

Specifying Bean Scope

使⽤ @Scope 来声明作⽤域
在使⽤ @Bean 注解时候,你可以使⽤在 Bean Scopes 章节中讲到的任何定义 scopes 的⽅法。
默认的作⽤域是 singleton , 但是你可以使⽤ @Scope 来重新,如下:
@Configuration
public class MyConfiguration {

    @Bean
    @Scope("prototype")
    public Encryptor encryptor() {
        // ...
    }
}

@Scope and scoped-proxy

<aop:scoped-proxy/>元素。Java 配置 bean @Scope 注解的 proxyMode 属性,可以提供他等价的⽀持。默认值为 no proxy( ScopedProxyMode.NO ),但可以指定 ScopedProxyMode.TARGET_CLASS 或ScopedProxyMode.INTERFACES。
 
如果将具有范围的代理示例从 XML 引⽤⽂档(参⻅范围代理)导⼊到使⽤ Java 的“bean”,则类似于以下内容:
// 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;
}

Customizing Bean Naming

默认情况下,配置类使⽤ @Bean ⽅法的名称作为结果 bean 的名称。但是,可以使⽤ name 属性覆盖此功能,如下示例所
示:
@Configuration
public class AppConfig {

    @Bean(name = "myThing")
    public Thing thing() {
        return new Thing();
    }
}

 

Bean Aliasing(别名)

正如在命名 bean 中所讨论的,有时需要为单个 bean 指定多个名称,也称为 bean 别名。 @Bean 注解的 name 属性为此接受⼀个字符串数组。下⾯的示例演示如何为bean设置多个别名:
@Configuration
public class AppConfig {

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

Bean Description

有时,提供⼀个更详细的bean⽂本描述会有所帮助。当 bean 暴露(可能通过 jmx )⽤于监视时,这尤其有⽤。 要向 @Bean添加描述,可以使⽤ @description 注解,如下示例所示:
@Configuration
public class AppConfig {

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

 

1.12.4. Using the @Configuration annotation

@Configuration是⼀个类级注解,指示对象是bean定义的源。@Configuration类通过public @Bean注解⽅法声明 bean。

@Configuration classes上的@Bean⽅法的调⽤也可⽤于定义Bean之间的依赖关系。请参⻅基本概念: @Bean@Configuration以获取⼀般介绍。

Injecting Inter-bean Dependencies

bean 相互依赖时,表示这种依赖就如同让⼀个bean⽅法调⽤另⼀个bean⽅法⼀样简单,如下示例所示:
@Configuration
public class AppConfig {

    @Bean
    public BeanOne beanOne() {
        return new BeanOne(beanTwo());
    }

    @Bean
    public BeanTwo beanTwo() {
        return new BeanTwo();
    }
}
在上⾯例⼦中, beanOnes 通过构造函数注⼊,引⼊ beanTwo
只有当 @Bean ⽅法在 @Configuration 类中声明时,此声明 bean 间依赖关系的⽅法才有效。不能使⽤普通的
@Component 类声明 bean 之间的依赖关系。
 

Lookup Method Injection

如前所述,查找⽅法注⼊是⼀个⾼级特性,你应该很少使⽤。它在单例作⽤域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 where is the implementation of this method?
    protected abstract Command createCommand();
}
通过使⽤ Java 配置,可以创建 CommandManager 的⼦类,并重写抽象的 createCommand ()⽅法,从⽽查找新的
prototype )命令对象。以下示例显示了如何执⾏此操作:
@Bean
@Scope("prototype")
public AsyncCommand asyncCommand() {
    AsyncCommand command = new AsyncCommand();
    // inject dependencies here as required
    return command;
}

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

Further Information About How Java-based Configuration Works Internally

下⾯展示了 @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() 中都被调⽤了⼀次。 因为 clientDao()创建了⼀个ClientDaoImpl实例,你可能认为创建了两个ClientDaoImpl 实例, 但是在 Spring 中, bean 实例通常拥有 singleton 作⽤域范围。
这是问题的关键:所有的 @Configuration 类都会在启动的时候被 CGLIB 继承,在⼦类中,它会去检测所有的缓存(作⽤
域范围)的 bean ,然后才回去调⽤⽗类的⽅法去创建实例。
当然,根据 Scope 的不同情况可能不同,这⾥我们只讨论 singleton
Spring3.2 中,不在需要将 CGLIB 添加到你的 classpath 中了,因为 CGLIB 包已经被包括在了org.springframework.cglib,也就是spring-core JAR中。
使⽤ CGLIB 在启动时动态添加功能是有限制的。通常来说配置类必须不是 final 的。但是在 4.3 中, configuration
允许添加任何构造函数,包括 @Autowired 或者单独的⾮默认的构造函数。
如果你希望避免任何 cglib 强加的限制,请考虑在⾮ @Configuration 类(例如,在普通的 @Component 类上)上声
@Bean ⽅法。然后, @Bean ⽅法之间的跨⽅法调⽤不会被拦截,因此你必须完全依赖于构造函数或⽅法级别
的依赖注⼊。
 

1.12.5. Composing Java-based Configurations

Spring 基于 Java 的配置特性允许你组合注解,这可以减少配置的复杂性。
 
使⽤@Import注解
与在 Spring XML ⽂件中使⽤ <import/> 元素来帮助模块化配置⼀样,@Import注解允许从另⼀个配置类加载@Bean定义,如下例所示:
@Configuration
public class ConfigA {

    @Bean
    public A a() {
        return new A();
    }
}

@Configuration
@Import(ConfigA.class)
public class ConfigB {

    @Bean
    public B b() {
        return new B();
    }
}

现在在实例化容器时,不需要显示的引⼊ConfigA.class ConfigB.class,只要ConfigB就够了:

public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigB.class);

    // now both beans A and B will be available...
    A a = ctx.getBean(A.class);
    B b = ctx.getBean(B.class);
}
这种⽅法简化了容器的实例化,因为只需要处理⼀个类,⽽不需要在构造期间记住⼤量@Configuration类。
SpringFramework4.2 开始, @Import 还⽀持引⽤常规组件类,类似于 AnnotationConfigApplicationContext.register⽅法。
如果你希望避免组件扫描,那么使⽤⼀些配置类作为⼊⼝点来显式定义所有组件,这尤其有⽤。

 

Imported @Bean上的注⼊依赖
 
前⾯的例⼦是可⾏的,但很简单。在⼤多数实际场景中, bean 在配置类之间相互依赖。使⽤ XML 时,这不是⼀个问题,因为不涉及编译器,你可以声明ref=“somebean”,并信任spring在容器初始化期间解决它。当使⽤@Configuration 类时,Java编译器将约束放置在配置模型上,因为对其他bean的引⽤必须是有效的Java 语法。
 
幸运的是,解决这个问题很简单。正如我们已经讨论过的那样, @Bean ⽅法可以有任意数量的参数来描述 bean 依赖
性。考虑以下更真实的场景,其中有⼏个 @Configuration 类,每个类取决于其他类中声明的 bean
@Configuration
public class ServiceConfig {

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

@Configuration
public class RepositoryConfig {

    @Bean
    public AccountRepository accountRepository(DataSource dataSource) {
        return new JdbcAccountRepository(dataSource);
    }
}

@Configuration
@Import({ServiceConfig.class, RepositoryConfig.class})
public class SystemTestConfig {

    @Bean
    public DataSource dataSource() {
        // return new DataSource
    }
}

public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class);
    // everything wires up across configuration classes...
    TransferService transferService = ctx.getBean(TransferService.class);
    transferService.transfer(100.00, "A123", "C456");
}
还有另⼀种⽅法可以达到同样的结果。记住,@Configuration类最终只是容器中的另⼀个bean:这意味着它们可以利⽤@Autowired和 @Value 注⼊以及与任何其他 bean 相同的其他特性。
 
确保以这种⽅式注⼊的依赖项是最简单的类型。 @Configuration 类在上下⽂的初始化过程中被提前处理,强制以
这种⽅式注⼊依赖项可能会导致意外的早期初始化。尽可能采⽤基于参数的注⼊,如前⾯的示例所示。
 
另外,通过 @Bean 特别是 BeanPostProcessor BeanFactoryPostProcessor 定义。这些⽅法通常应声明为 static
@Bean ⽅法,⽽不是触发其包含的配置类的实例化。否则, @Autowired @Value 不能在 configuration 类本身上
⼯作,因为它被创建为 bean 实例太早了。
 
下⾯的示例显示如何将⼀个bean⾃动连接到另⼀个bean:
@Configuration
public class ServiceConfig {

    @Autowired
    private AccountRepository accountRepository;

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

@Configuration
public class RepositoryConfig {

    private final DataSource dataSource;

    public RepositoryConfig(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    @Bean
    public AccountRepository accountRepository() {
        return new JdbcAccountRepository(dataSource);
    }
}

@Configuration
@Import({ServiceConfig.class, RepositoryConfig.class})
public class SystemTestConfig {

    @Bean
    public DataSource dataSource() {
        // return new DataSource
    }
}

public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class);
    // everything wires up across configuration classes...
    TransferService transferService = ctx.getBean(TransferService.class);
    transferService.transfer(100.00, "A123", "C456");
}
@Configuration 类中的构造函数注⼊仅在 Spring Framework 4.3 之后受⽀持。还要注意,如果⽬标 bean 只定义
⼀个构造函数,则不需要指定@Autowired 。在前⾯的示例中, RepositoryConfig 构造函数上不需要 @Autowired

 

  完全符合条件的imported beans,便于导航  

 

在前⾯的场景中,使⽤ @Autowired 可以很好地⼯作并提供所需的模块性,但是确定在哪⾥声明 autowired bean 定义仍然有点含糊不清。例如,作为查看serviceConfig 的开发⼈员,你如何确切知道 @Autowired AccountRepositor bean 的声明位置?它在代码中不是明确的,这可能很好。请记住,Spring ⼯具套件提供了⼀种⼯具,它可以图形化的呈现显示所有东⻄是如何连接的,这可能是你所需要的全部。此外,你的Java IDE可以很容易地找AccountRepository 类型的所有声明和⽤法,并快速显示返回该类型的@Bean ⽅法的位置。 如果这种模糊性是不可接受的,并且你希望在你的 IDE 中从⼀个@Configuration类直接导航到另⼀个@Configuration类,请考虑⾃动连接配置类本身。以下示例显示了如何执⾏此操作:
@Configuration
public class ServiceConfig {

    @Autowired
    private RepositoryConfig repositoryConfig;

    @Bean
    public TransferService transferService() {
        // navigate 'through' the config class to the @Bean method!
        return new TransferServiceImpl(repositoryConfig.accountRepository());
    }
}
在前⾯的情况下,定义 AccountRepository 是完全明确的。但是, serviceConfig 现在与 RepositoryConfig 紧密耦合。这就
是权衡。通过使⽤基于接⼝或基于抽象类的 @Configuration 类,可以稍微减轻这种紧密耦合。请考虑以下示例:
@Configuration
public class ServiceConfig {

    @Autowired
    private RepositoryConfig repositoryConfig;

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

@Configuration
public interface RepositoryConfig {

    @Bean
    AccountRepository accountRepository();
}

@Configuration
public class DefaultRepositoryConfig implements RepositoryConfig {

    @Bean
    public AccountRepository accountRepository() {
        return new JdbcAccountRepository(...);
    }
}

@Configuration
@Import({ServiceConfig.class, DefaultRepositoryConfig.class})  // import the concrete config!
public class SystemTestConfig {

    @Bean
    public DataSource dataSource() {
        // return DataSource
    }

}

public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class);
    TransferService transferService = ctx.getBean(TransferService.class);
    transferService.transfer(100.00, "A123", "C456");
}
现在 ServiceConfig 与具体的 DefaultRepositoryConfig 松散地耦合在⼀起,⽽内置的IDE⼯具仍然很有⽤:你可以很容易地获得RepositoryConfig 实现的类型层次结构。通过这种⽅式,导航 @Configuration 类及其依赖项与导航基于接⼝的代码的常规过程没有什么不同。
如果要影响某些bean的启动创建顺序,请考虑将它们中的⼀些声明为@lazy(⽤于在第⼀次访问时创建⽽不是在启动时创建)或@dependson某些其他bean(确保在当前bean之前创建特定的其他bean,这超出了后者的直接依赖性的含 义)。
 
条件的引⼊@Configuration和@Bean
 
根据某些任意的系统状态,有条件地启⽤或禁⽤完整的 @Configuration 类,甚⾄单个的 @Bean ⽅法,通常很有⽤。其中⼀个常⻅的例⼦是,只有在Spring环境中启⽤了特定的概要⽂件时,才使⽤@Profile 注解来激活 bean (有关详细信息,请参⻅bean 定义概要⽂件)。
 
@Profile 注解实际上是通过使⽤⼀个更灵活的名为@Conditional的注解来实现的。 @Conditional annotation 指示在注册@Bean之前应该咨询的特定 org.springframework.context.annotation.Condition实现。

 

Condition 接⼝的实现提供了⼀个返回true或 false 的匹配(…)⽅法。例如,下⾯的列表显示了⽤于@Profile的实际条件实现:
 
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
    // Read the @Profile annotation attributes
    MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());
    if (attrs != null) {
        for (Object value : attrs.get("value")) {
            if (context.getEnvironment().acceptsProfiles(((String[]) value))) {
                return true;
            }
        }
        return false;
    }
    return true;
}

组合JavaXML配置

Spring @Configuration 类⽀持并不打算完全替代 Spring XML 。⼀些⼯具(如SpringXML名称空间)仍然是配置容器的 理想⽅法。在XML ⽅便或必要的情况下,你可以选择:以“以XML为中⼼”的⽅式实例化容器,例如使⽤ ClassPathXmlApplicationContext,或者使⽤“AnnotationConfigApplicationContext”和“@ImportResource”按需导⼊XML,实例化容器。
 
XML为中⼼使⽤@Configuration
 
最好从 XML 引导 Spring 容器,并以特别的⽅式包含 @Configuration 类。例如,在使⽤ SpringXML 的⼤型现有代码库中,根据需要创建@Configuration 类并从现有 XML ⽂件中包含它们更容易。在本节后⾯,我们将介绍在这种“以XML为中⼼” 的情况下使⽤@Configuration类的选项。
 
将@Configuration classes声明为plain spring <bean/>元素
 
记住, @Configuration 类最终是容器中的 bean 定义。在本系列示例中,我们创建了⼀个名为appconfig的 @Configuration类,并将其作为 <bean/> 定义包含在 system-test-config.xml中。由于打开了<context:annotation-config/>,容器识别@Configuration注解并正确处理appconfig中声明的 @Bean ⽅法。 下⾯的示例展示了 Java 中的⼀个普通配置类:
@Configuration
public class AppConfig {

    @Autowired
    private DataSource dataSource;

    @Bean
    public AccountRepository accountRepository() {
        return new JdbcAccountRepository(dataSource);
    }

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

 

 

下⾯是 system-test-config.xml file⽂件
<beans>
    <!-- enable processing of annotations such as @Autowired and @Configuration -->
    <context:annotation-config/>
    <context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>

    <bean class="com.acme.AppConfig"/>

    <bean class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
</beans>

 

jdbc.properties ⽂件:
jdbc.url=jdbc:hsqldb:hsql://localhost/xdb
jdbc.username=sa
jdbc.password=
public static void main(String[] args) {
    ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:/com/acme/system-test-config.xml");
    TransferService transferService = ctx.getBean(TransferService.class);
    // ...
}

 

system-test-config.xml⽂件中,appconfig <bean/>不声明id元素。虽然这样做是可以接受的,但这是不必要的,因为没有其他bean 引⽤过它,并且不太可能按名称从容器中显式地提取它。类似地,数据源 bean 也只是按类型⾃动连接的,因此不严格要求显式bean id
 
使⽤<context:component-scan/>来查找@Configuration

 

因为 @Configuration 是⽤ @Component 元注解的, @Configuration 注解类⾃动成为组件扫描的候选者。使⽤前⾯示例中
描述的相同场景,我们可以重新定义 system-test-config.xml以利⽤组件扫描。注意,在这种情况下,我们不需要显式声
<context:annotation-config/>,因为<context:component-scan/>启⽤了相同的功能。 以下示例显示修改后的systemtest-config.xml⽂件:
 
<beans>
    <!-- picks up and registers AppConfig as a bean definition -->
    <context:component-scan base-package="com.acme"/>
    <context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>

    <bean class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
</beans>
@Configuration类为中⼼使⽤XML@ImportResource
 
在@Configuration classes是配置容器的主要机制的应⽤程序中,⾄少需要使⽤ ⼀些XML。在这些场景中,你可以使⽤ @ImportResource,并且只定义所需的 XML 。这样做实现了“以Java为中⼼”的⽅法来配置容器,并将XML保持在最低限度。下⾯的示例(包括配置类、定义Bean XML ⽂件、属性⽂件和主类)显示了如何使⽤ @ImportResource 注解来实现“以Java为中⼼”的配置,根据需要使⽤XML:
 
@Configuration
@ImportResource("classpath:/com/acme/properties-config.xml")
public class AppConfig {

    @Value("${jdbc.url}")
    private String url;

    @Value("${jdbc.username}")
    private String username;

    @Value("${jdbc.password}")
    private String password;

    @Bean
    public DataSource dataSource() {
        return new DriverManagerDataSource(url, username, password);
    }
}

properties-config.xml

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

 

jdbc.properties
jdbc.url=jdbc:hsqldb:hsql://localhost/xdb
jdbc.username=sa
jdbc.password=
public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
    TransferService transferService = ctx.getBean(TransferService.class);
    // ...
}

 


 

 

1.13. Environment Abstraction

Environment 接⼝是集成在容器中的抽象,它为应⽤程序环境的两个关键⽅⾯建模:
profiles properties
 
profile 是⼀个命名的逻辑bean定义组,只有在给定的配置⽂件处于活动状态时,才会在容器中注册。 bean 可以分配给⼀个配置⽂件,⽆论是⽤XML定义的还是⽤注解定义的。与配置⽂件相关的环境对象的⻆⾊是确定哪些配置⽂件(如果有)当前处于活动状态,以及默认情况下哪些配置⽂件(如果有)应处于活动状态。
 
Properties 在⼏乎所有的应⽤程序中都扮演着重要的⻆⾊,可能源于各种源:属性⽂件、 JVM 系统属性、系统环境变量、JNDI servlet 上下⽂参数、特殊属性对象、映射对象等。环境对象与属性相关的作⽤是为⽤户提供⼀个⽅便的服务接⼝,⽤于配置属性源并从中解析属性。
 
1.13.1 Bean定义Profiles
 

bean定义Profiles在核⼼容器中提供了⼀种机制,允许在不同环境中注册不同的bean。“环境”这个词对不同的⽤户来说 可能意味着不同的东⻄,并且这个特性可以帮助处理许多⽤例,包括:

    在开发中处理内存中的数据源,⽽不是在 QA 或⽣产中从 JNDI 中查找相同的数据源。
    仅在将应⽤程序部署到性能环境中时注册监控基础结构。
    为客户 A 和客户 B 部署注册定制的 bean 实现。

 

在需要 DataSource 的实际应⽤程序中考虑第⼀个⽤例。在测试环境中,配置可能类似于以下内容:
@Bean
public DataSource dataSource() {
    return new EmbeddedDatabaseBuilder()
        .setType(EmbeddedDatabaseType.HSQL)
        .addScript("my-schema.sql")
        .addScript("my-test-data.sql")
        .build();
}
现在考虑如何将此应⽤程序部署到 QA 或⽣产环境中,假设应⽤程序的数据源已注册到⽣产应⽤程序服务器的 JNDI ⽬录
中。我们的数据源 bean 现在看起来如下所示:
@Bean(destroyMethod="")
public DataSource dataSource() throws Exception {
    Context ctx = new InitialContext();
    return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
}
问题是如何根据当前环境在使⽤这两种变体之间进⾏切换。随着时间的推移, Spring ⽤户设计了许多实现这⼀点的⽅法,通常依赖于系统环境变量和包含$placeholder标记的XML<import/> 语句的组合,这些标记根据环境变量的值解析为正确的配置⽂件路径。bean 定义概要⽂件是⼀个核⼼容器特性,它为这个问题提供了解决⽅案。
 
如果我们概括前⾯环境特定 bean 定义示例中所示的⽤例,那么最终需要在特定上下⽂中注册特定 bean 定义,⽽不是在其他上下⽂中注册特定bean 定义。可以说,你希望在情况 A 中注册 bean 定义的某个概要⽂件,在情况 B 中注册另⼀个概要⽂件。我们⾸先更新配置以反映这种需求。
 
使⽤@Profile

 

@Profile 注解允许你指示⼀个组件在⼀个或多个指定的配置⽂件处于活动状态时可以注册。使⽤前⾯的示例,我们可以
如下重写数据源配置:
 
@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");
    }
}

 

如上所示,在 @Bean 中,你显示的使⽤编程式 JNDI 查找,通过使⽤ Spring 的 JndiTemplate/JndiLocatorDelegate,或者 JNDI InitialContext , ⽽不是 JndiObjectFactoryBean ,**这会让你的返回值变成FactoryBean。
 
配置⽂件字符串可以包含简单的配置⽂件名(例如, production )或配置⽂件表达式。配置⽂件表达式允许表达更复杂的配置⽂件逻辑(例如,production & us-east)。配置⽂件表达式中⽀持以下运算符:
 
! 逻辑⾮
& 逻辑与
| 逻辑或
 
你不能不加括号来混合使⽤ & | ,如 production & us-east | eu-central 是错误的,应该production & (us-east | eu-central)。
可以将 @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”,否则不会注册或处理该类。如果给定配置⽂件的前缀是not运算符 (!),仅当配置⽂件未处于活动状态时,才会注册带注解的元素。例如,给定@Profile(“p1”,“!p2“”),如 果配置⽂件“p1”处于活动状态或配置⽂件“p2”未处于活动状态,则会发⽣注册.
 
@Profile 也可以⽤在⽅法级别⽤来包含⼀个特殊的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");
    }
}
The standaloneDataSource method is available only in the development profile.
 The jndiDataSource method is available only in the production profile.
使⽤ @Profile @Bean ⽅法中,可以使⽤特殊场景:在相同 Java ⽅法名称(类似于构造函数重载)的重载 @Bean⽅法的情况下,需要在所有重载⽅法上⼀致地声明@Profile条件。如果条件不⼀致,那么只有重载⽅法中 第⼀个声明的条件才重要。因此,@Profile不能⽤于选择具有特定参数签名的重载⽅法。同⼀bean的所有⼯⼚⽅法之间的解析在创建时遵循Spring的构造函数解析算法。
 
如果要定义具有不同配置条件的替代 bean ,请使⽤不同的 Java ⽅法名称,使⽤前⾯的示例所示,使⽤“bean名称” 属性指向相同的bean名称。如果参数签名都是相同的(例如,所有的变体都没有带参的⼯⼚⽅法),这是在⼀个 有效的Java类中表示这种安排的唯⼀⽅式(因为只有⼀个特定名称和参数签名的⽅法)。
 
ProfilesXML中使⽤
 
可以在 XML 中使⽤ profile 属性,如下所示:
<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>
同样可以将两个 XML 合并成⼀个:
<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-bean.xsd已经被约束为只允许作为⽂件中的最后⼀个元素。这有助于提供灵活性,⽽不会在XML⽂件中引起混 乱。
XML 对应项不⽀持前⾯描述的配置⽂件表达式。但是,可以使⽤否定配置⽂件!操作元。也可以通过嵌套配置⽂件来应⽤逻辑“and”,如下示例所示:
<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="production">
        <beans profile="us-east">
            <jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/>
        </beans>
    </beans>
</beans>
上⾯的例⼦中, dataSource 被⽤在 production us-east profiles。
 

激活Profile

既然我们已经更新了配置,我们仍然需要指示 Spring 哪个配置⽂件是活动的。如果我们现在启动示例应⽤程序,我们会
看到⼀个NoSuchBeanDefinitionException被抛出,因为容器找不到名为 datasource SpringBean.
 
激活⼀个概要⽂件可以⽤⼏种⽅法完成,但最简单的⽅法是通过应⽤程序上下⽂提供的环境API以编程⽅式完成。以下示例显示了如何执⾏此操作:
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.getEnvironment().setActiveProfiles("development");
ctx.register(SomeConfig.class, StandaloneDataConfig.class, JndiDataConfig.class);
ctx.refresh();
此外,还可以通过 spring.profiles.active属性声明性地激活概要⽂件,该属性可以通过系统环境变量、jvm 系统属性、
web.xml 中的 servlet 上下⽂参数指定,甚⾄可以作为 JNDI 中的条⽬指定(请参⻅ PropertySource 抽象)。在集成测试
中,可以使⽤ Spring 测试模块中的 @Active profiles 注解声明活动配置⽂件(请参⻅环境配置⽂件的上下⽂配置)。
 
请注意,配置⽂件不是“要么或”命题。你可以⼀次激活多个配置⽂件。通过编程,你可以为setActiveProfiles()⽅法提供多个配置⽂件名,该⽅法接受String… varargs。以下示例激活多个配置⽂件:
 
ctx.getEnvironment().setActiveProfiles("profile1",	"profile2");
在声明中, spring.profiles.active可以接受由逗号分隔的配置⽂件名列表,如下例所示:
   -Dspring.profiles.active="profile1,profile2"

 

默认Profile
默认的 Profile 表示该 Profile 默认被激活,如下所示:
@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();
    }
}
如果没有 Profile 被激活,那么 dataSource 就会被创建,你可以看成创建 bean 的默认⽅式。如果其他的 Profile 被激活了,
那么默认的 Profile 就不会被使⽤。
你可以在环境中使⽤ SetDefaultProfiles ()更改默认 profile 的名称,或者声明性地使⽤ spring.profiles.default属性更改默认概要⽂件的名称。
 

1.13.2. PropertySource Abstraction

SpringEnvironment抽象在property sources的可配置层次结构上提供搜索操作。考虑以下列表:

ApplicationContext ctx = new GenericApplicationContext();
Environment env = ctx.getEnvironment();
boolean containsMyProperty = env.containsProperty("my-property");
System.out.println("Does my environment contain the 'my-property' property? " + containsMyProperty);
在前⾯的⽚段中,我们看到了⼀种⾼级⽅法,询问Spring是否为当前环境定义了 my-property属性。为了回答这个问题, Environment对象对⼀组PropertySource对象执⾏搜索。PropertySource 是对任何键值对源的简单抽象, Spring 的 StandardEnvironment配置了两个 PropertySource 对象:⼀个表示⼀组JVM系统属性(System.GetProperties()), 另⼀个表示⼀组系统环境变量。es(system.getenv())。
 
这些默认属性源使⽤ StandardEnvironment ,可在独⽴应⽤程序中使⽤。 StandardServletEnvironment 使⽤其他
默认属性源(包括 servlet config servlet 上下⽂参数)填充。它可以选择启⽤ JndiPropertySource 。有关详细信
息,请参阅 JavaDoc
具体来说,在使⽤ StandardEnvironment 时,如果运⾏时存在 my-property系统属性或my-property环境变量,则对 env.containsProperty(“my-property”)的调⽤将返回true。
执⾏的搜索是分层的。默认情况下,系统属性优先于环境变量。因此,如果在调⽤ env.getproperty(“my-property”)的过程中,在这两个位置都设置了my-property属性,则系统属性值“wins”并返回。请注意,属性值不会合并,⽽是完全被前⾯的条⽬覆盖。
对于常⻅的 StandardServletenvironment ,完整的层次结构如下,最⾼优先级条⽬位于顶部:
ServletConfig 参数(如果适⽤-例如,对于DispatcherServlet 上下⽂)
ServletContext参数( web.xml context-param 项)
JNDI 环境变量( Java:COMP/Env/ 条⽬)
JVM 系统属性(-d命令⾏参数)
JVM 系统环境(操作系统环境变量)

 

最重要的是,整个机制是可配置的。也许你有⼀个⾃定义的属性源,希望将其集成到这个搜索中。为此,请实现并实例化⾃⼰的PropertySource,并将其添加到当前环境的PropertySource集合中。以下示例显示了如何执⾏此操作:

ConfigurableApplicationContext ctx = new GenericApplicationContext();
MutablePropertySources sources = ctx.getEnvironment().getPropertySources();
sources.addFirst(new MyPropertySource());
在前⾯的代码中, MyPropertySource 已在搜索中以最⾼优先级添加。如果它包含“我的属性”属性,则检测并返回该属 性,⽽不是任何其他属性源中的“我的属性”属性。MutablePropertySources API公开了许多允许对属性源集进⾏精确操作的⽅法。

 

1.13.3. Using @PropertySource

@PropertySource 注解提供了⽅便和声明式的机制为 Spring 的添加 PropertySource
假如⼀个app.properties包括key-value值,例如:testbean.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. Placeholder Resolution in Statements

历史上,元素中占位符的值只能根据 JVM 系统属性或环境变量来解析。情况不再是这样了。因为环境抽象集成在整个容
器中,所以很容易通过它来路由占位符的解析。这意味着你可以以任何你喜欢的⽅式配置解析过程。你可以更改搜索系
统属性和环境变量的优先级,或者完全删除它们。你还可以根据需要将⾃⼰的属性源添加到组合中。
具体来说,只要 customer property Environment 中可⽤,⽆论 customer property 在何处定义,以下陈述都有效:
<beans>
    <import resource="com/bank/service/${customer}-config.xml"/>
</beans>

 


 

1.14. Registering a LoadTimeWeaver

 


 

当类被加载进⼊ JVM 的时候, LoadTimeWeaver Spring ⽤来进⾏动态类转换。
开启 load-time weaving, 可以添加@EnableLoadTimeWeaving @Configuration 类,如下所示:
@Configuration
@EnableLoadTimeWeaving
public class AppConfig {
}
或者可以使⽤ XML context:load-time-weaver,如下:

 

<beans>
    <context:load-time-weaver/>
</beans>
⼀旦为ApplicationContext 配置好,该 ApplicationContext 中的任何 bean 都可以实现 LoadTimeWeaverAware ,从⽽接收
对加载时 weaver 实例的引⽤。这在结合 Spring JPA ⽀持时特别有⽤,因为在这种⽀持下, JPA 类转换可能需要负载时
间编织。有关详细信息,请参阅 LocalContainerEntityManagerFactoryBean JavaDoc 。有关 Aspectj 加载时编织的更多
信息,请参⻅ Spring 框架中的 Load Time Weaving with Aspectj
 
 
 

 

1.15. Additional Capabilities of the ApplicationContext

 

ApplicationContext 接⼝扩展了⼀个名为MessageSource的接⼝,因此提供国际化(“i18n”)功能。Spring还提供了层次化的消息源接⼝,可以对消息进⾏层次化的解析。这些接⼝共同提供了Spring效应消息解决的基础。在这些接⼝上定义的⽅法包括:
 
String getMessage(String code, Object[] args, String default, Locale loc): MessageSource 获取消息的基本⽅
法,如果在提供的 locale 中没有找到消息,那么将使⽤默认的消息。通过使⽤标准库提供的 MessageFormat 功能,
传⼊的任何参数都将成为替换值。
 
String getMessage(String code, Object[] args, Locale loc): 基本上和上⾯⽅法⼀样,但是有⼀个区别:没有默认⼩消息。如果消息没找到,那么NoSuchMessageException将抛出。
 
String getMessage(MessageSourceResolvable resolvable, Locale locale): 所有的之前⽅法的属性,都被封装在名
MessageSourceResolvable 的类中,你可以使⽤它的⽅法。
 
加载 ApplicationContext 时,它会⾃动搜索上下⽂中定义的 MessageSource bean MessageSource bean 必须具有名
称。如果找到这样的 bean ,那么对前⾯⽅法的所有调⽤都将委托给消息源。如果找不到消息源, ApplicationContext
尝试查找包含同名 bean 的⽗级。如果是这样,它将使⽤该 bean 作为消息源。如果 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>
该示例假定在类路径中定义了三个资源包,分别称为 format exceptions windows 。解析消息的任何请求都以通过
ResourceBundle 对象解析消息的 JDK 标准⽅式处理。在本例中,假设上述两个资源包⽂件的内容如下:
 
  # in format.properties
    message=Alligators rock!
    # in exceptions.properties
    argument.required=The {0} argument is required.

 

下⼀个例⼦显示了⼀个执⾏MessageSource功能的程序。记住,所有ApplicationContext实现也是 MessageSource 实现,因此可以强制转换到MessageSource 接⼝。
public static void main(String[] args) {
    MessageSource resources = new ClassPathXmlApplicationContext("beans.xml");
    String message = resources.getMessage("message", null, "Default", Locale.ENGLISH);
    System.out.println(message);
}
运⾏结果如下:
Alligators rock!

 

总⽽⾔之, MessageSource 是在⼀个名为beans.xml的⽂件中定义的,该⽂件位于类路径的根⽬录下。 MessageSource bean定义通过其 basename 属性引⽤许多资源束。在列表中传递给 basename 属性的三个⽂件作为⽂件存在于类路径的根⽬录中,分别称为format.properties、 exceptions.properties和 windows.properties。
 
下⼀个示例显示传递给消息查找的参数。这些参数被转换成字符串对象并插⼊到查找消息中的占位符中。
 
<beans>

    <!-- this MessageSource is being used in a web application -->
    <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
        <property name="basename" value="exceptions"/>
    </bean>

    <!-- lets inject the above MessageSource into this POJO -->
    <bean id="example" class="com.something.Example">
        <property name="messages" ref="messageSource"/>
    </bean>

</beans>
public class Example {

    private MessageSource messages;

    public void setMessages(MessageSource messages) {
        this.messages = messages;
    }

    public void execute() {
        String message = this.messages.getMessage("argument.required",
            new Object [] {"userDao"}, "Required", Locale.ENGLISH);
        System.out.println(message);
    }
}
execute() 的执⾏结果如下:
The userDao argument is required.
 
关于国际化(“i18n”),Spring的各种消息源实现遵循与标准JDK ResourceBundle相同的区域设置解析和回退规则。简⽽⾔之,继续前⾯定义的示例MessageSource ,如果要针对英国( en-GB)区域设置解析消息,则将分别创建名为 formaten-GB.properties、exceptionsen-GB.properties和windowsen-GB.properties的⽂件。
通常,区域设置解析由应⽤程序的周围环境管理。在下⾯的示例中,将⼿动指定解析(英国)消息所依据的区域设置。
 
 
# in exceptions_en_GB.properties
argument.required=Ebagum lad, the {0} argument is required, I say, required.
public static void main(final String[] args) {
    MessageSource resources = new ClassPathXmlApplicationContext("beans.xml");
    String message = resources.getMessage("argument.required",
        new Object [] {"userDao"}, "Required", Locale.UK);
    System.out.println(message);
}
运⾏结果如下:
Ebagum lad, the 'userDao'argument is required, I say, required。
 
你还可以使⽤ MessageSourceAware 接⼝获取对已定义的任何 MessageSource 的引⽤。在 ApplicationContext 中定义的、实现MessageSourceAware 接⼝的任何 bean 在创建和配置 bean 时都会与应⽤程序上下⽂的 MessageSource⼀起注⼊。
 
作为 ResourceBundleMessageSource 的替代⽅案, Spring 提供了⼀个 ReloadableResourceBundleMessageSource类。此变体⽀持相同的包⽂件格式,但⽐基于JDK的标准 ResourceBundleMessageSource实现更灵活。特别是,它允许从任何 Spring 资源位置(不仅是从类路径)读取 ⽂件,并⽀持捆绑属性⽂件的热重新加载(同时有效地在两者之间缓存它们)。有关详细信息,请参阅 ReloadableResourceBundleMessageSource javadoc。

 

1.15.2. Standard and Custom Events

ApplicationContext 中的事件处理通过 ApplicationEvent 类和 ApplicationListener 接⼝提供。如果实现 ApplicationListener
接⼝的 bean 部署到上下⽂中,则每次 ApplicationEvent 发布到 ApplicationContext 时,都会通知该 bean 。本质上,这是标
准的观测者设计模式。
Spring4.2 开始,事件基础结构得到了显著的改进,提供了⼀个基于注解的模型以及发布任意事件的能⼒(也就 是说,不⼀定从ApplicationEvent扩展的对象)。当这样⼀个对象被发布时,我们将它包装在⼀个事件中。
下表描述了 Spring 提供的标准事件描述:
 

你也可以创建⾃⼰的事件。下⾯显示了继承 ApplicationEvent 的例⼦:
public class BlackListEvent extends ApplicationEvent {

    private final String address;
    private final String content;

    public BlackListEvent(Object source, String address, String content) {
        super(source);
        this.address = address;
        this.content = content;
    }

    // accessor and other methods...
}
若要发布⾃定义 ApplicationEvent ,请在 ApplicationEventPublisher 上调⽤ PublishEvent ()⽅法。通常,这是通过创建⼀个实现ApplicationEventPublisherAware的类并将其注册为 SpringBean 来完成的。下⾯的示例显示了这样的类:
public class EmailService implements ApplicationEventPublisherAware {

    private List<String> blackList;
    private ApplicationEventPublisher publisher;

    public void setBlackList(List<String> blackList) {
        this.blackList = blackList;
    }

    public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
        this.publisher = publisher;
    }

    public void sendEmail(String address, String content) {
        if (blackList.contains(address)) {
            publisher.publishEvent(new BlackListEvent(this, address, content));
            return;
        }
        // send email...
    }
}
在配置时, Spring 容器检测到 EmailService 实现了 ApplicationEventPublisherAware ,并⾃动调⽤
setApplicationEventPublisher ()。实际上,传⼊的参数是 Spring 容器本身。你正在通过其 applicationEventPublisher
接⼝与应⽤程序上下⽂进⾏交互。
要接收定制的 applicationEvent ,可以创建⼀个实现applicationListener的类,并将其注册为 SpringBean 。下⾯的示例显
示了这样的类:
public class BlackListNotifier implements ApplicationListener<BlackListEvent> {

    private String notificationAddress;

    public void setNotificationAddress(String notificationAddress) {
        this.notificationAddress = notificationAddress;
    }

    public void onApplicationEvent(BlackListEvent event) {
        // notify appropriate parties via notificationAddress...
    }
}
注意, applicationListener 通常是⽤⾃定义事件的类型(前⾯的示例中是 blacklistevent )参数化的。这意味着 onApplicationEvent()⽅法可以保持类型安全,避免任何向下强制转换的需要。你可以注册任意多个事件侦听器,但请注意,默认情况下,事件侦听器同步接收事件。这意味着publishEvent ()⽅法将⼀直阻塞,直到所有侦听器完成对事件的处理。这种同步和单线程⽅法的⼀个优点是,当侦听器接收到事件时,如果事务上下⽂可⽤,它将在发布服务器的事务上下⽂中操作。如果需要事件发布的另⼀个策略,请参阅JavaDoc for Spring的ApplicationEventMulticaster接⼝。
 
下⾯的示例显示了⽤于注册和配置上述每个类的 bean 定义:
<bean id="emailService" class="example.EmailService">
    <property name="blackList">
        <list>
            <value>known.spammer@example.org</value>
            <value>known.hacker@example.org</value>
            <value>john.doe@example.org</value>
        </list>
    </property>
</bean>

<bean id="blackListNotifier" class="example.BlackListNotifier">
    <property name="notificationAddress" value="blacklist@example.org"/>
</bean>
综上所述,当调⽤ emailService bean sendEmail ()⽅法时,如果有任何电⼦邮件应被列⼊⿊名单,则会发布⼀个blacklistevent类型的⾃定义事件。⿊名单通知bean注册为 ApplicationListener 并接收⿊名单事件,此时它可以通知相应
的⽅法。
Spring 的事件机制是为同⼀应⽤程序上下⽂中SpringBean之间的简单通信⽽设计的。然⽽,对于更复杂的企业集成需求,单独维护的Spring Integration 为构建基于众所周知的 Spring 编程模型的轻量级、⾯向模式、事件驱动的 体系结构提供了完整的⽀持。
基于注解的Event Listeners

 

Spring4.2 开始,你可以使⽤ EventListener 注解在托管 bean 的任何公共⽅法上注册事件侦听器。 BlackListNotifier 程序
可以改写如下:
public class BlackListNotifier {

    private String notificationAddress;

    public void setNotificationAddress(String notificationAddress) {
        this.notificationAddress = notificationAddress;
    }

    @EventListener
    public void processBlackListEvent(BlackListEvent event) {
        // notify appropriate parties via notificationAddress...
    }
}
⽅法签名再次声明它要侦听的事件类型,但这次使⽤灵活的名称,⽽不实现特定的侦听器接⼝。只要实际事件类型在其
实现层次结构中解析泛型参数,就可以通过泛型缩⼩事件类型。
 
如果你的⽅法应该监听多个事件,或者你想要定义它⽽不使⽤任何参数,那么也可以在注解本身上指定事件类型。以下
示例显示了如何执⾏此操作:
@EventListener({ContextStartedEvent.class, ContextRefreshedEvent.class})
public void handleContextStart() {
    // ...
}
还可以使⽤定义 spEL 表达式的注解的条件属性添加其他运⾏时筛选,该表达式应与实际调⽤特定事件的⽅法相匹配。
以下示例显示了如何重写通知程序,以便仅在事件的内容属性等于我的事件时调⽤:
@EventListener(condition = "#blEvent.content == 'my-event'")
public void processBlackListEvent(BlackListEvent blEvent) {
    // notify appropriate parties via notificationAddress...
}
每个 spEL 表达式都根据专⽤上下⽂进⾏计算。下表列出了上下⽂可⽤的项,以便你可以将它们⽤于条件事件处理:

 

请注意, root.event 允许你访问基础事件,即使你的⽅法签名实际上引⽤了已发布的任意对象。
如果在处理另⼀个事件时需要发布事件,可以更改⽅法签名以返回应发布的事件,如下例所示:
@EventListener
public ListUpdateEvent handleBlackListEvent(BlackListEvent event) {
    // notify appropriate parties via notificationAddress and
    // then publish a ListUpdateEvent...
}

异步Listener不⽀持整个特性

此新⽅法为上述⽅法处理的每个 BlackListEvent 发布⼀个新的ListUpdateEvent。如果需要发布多个事件,可以返回事件集合。

 

异步侦听器

 

如果希望特定的侦听器异步处理事件,可以重⽤常规的 @Async ⽀持。以下示例显示了如何执⾏此操作:

 

@EventListener
@Async
public void processBlackListEvent(BlackListEvent event) {
    // BlackListEvent is processed in a separate thread
}
注意下⾯的异步事件限制:
如果事件 listener 抛出 Exception ,异常并不会传递给调⽤者。参考 AsyncUncaughtExceptionHandler
这样的事件 listener 不能发送回复。作为 listener 的结果,如果你想发送另外的事件,调⽤ApplicationEventPublisher来⼿动发送。
 
 
Listeners排序
 
如果需要先调⽤⼀个监听器,然后再调⽤另⼀个监听器,则可以将@order注解添加到⽅法声明中,如下例所示:
 
@EventListener
@Order(42)
public void processBlackListEvent(BlackListEvent event) {
    // notify appropriate parties via notificationAddress...
}
⼀般事件
 
你还可以使⽤泛型进⼀步定义事件的结构。考虑使⽤EntityCreatedEvent ,其中 t 是创建的实际实体的类型。例如,你可
以创建以下侦听器定义以仅接收某个⼈的 EntityCreatedEvent
@EventListener
public void onPersonCreated(EntityCreatedEvent<Person> event) {
    // ...
}
由于类型擦除,只有在激发的事件解析事件侦听器筛选的通⽤参数(⽐如类 PersonCreatedEvent 扩展EntityCreatedEvent…)时,此操作才有效。
在某些情况下,如果所有事件都遵循相同的结构(如前⼀个示例中的事件⼀样),则这可能会变得相当乏味。在这种情况下,你可以实现ResolvableTypeProvider程序,以指导框架超越运⾏时环境提供的内容。以下事件显示如何执⾏此操作:
public class EntityCreatedEvent<T> extends ApplicationEvent implements ResolvableTypeProvider {

    public EntityCreatedEvent(T entity) {
        super(entity);
    }

    @Override
    public ResolvableType getResolvableType() {
        return ResolvableType.forClassWithGenerics(getClass(), ResolvableType.forInstance(getSource()));
    }
}
这不仅适⽤于 ApplicationEvent ,还适⽤于作为事件发送的任意对象。
 
 

1.15.3. Convenient Access to Low-level Resources

为了优化应⽤程序上下⽂的使⽤和理解,你应该熟悉Spring的资源抽象,如参考资料中所述。

应⽤程序上下⽂是可⽤于加载资源对象的ResourceLoader。资源本质上是JDK java.net.URL类的功能更丰富的版本。实际上,资源的实现在适当的情况下包装了java.net.url的实例。Resource可以以透明的⽅式从⼏乎任何位置获取低级资源,包括从类路径、⽂件系统位置、使⽤标准URL可描述的任何位置以及其他⼀些变体。如果资源位置字符串是没有任何特殊前缀的简单路径,那么这些资源的来源是特定的,并且适合于实际的应⽤程序上下⽂类型。

 

可以将部署到应⽤程序上下⽂中的bean配置为实现特殊的回调接⼝ResourceLoaderAware,以便在初始化时⾃动回调,并将应⽤程序上下⽂本身作为ResourceLoader传⼊。你还可以公开资源类型的属性,以⽤于访问静态资源。它们像其他任何性质⼀样被注⼊其中。你可以将这些资源属性指定为简单的字符串路径,并在部署bean时依赖于从这些⽂本字符串到实际资源对象的⾃动转换。

提供给ApplicationContext构造函数的⼀个或多个位置路径实际上是资源字符串,并且以简单的形式根据特定的上下⽂实现进⾏适当的处理。例如,ClassPathXMLApplicationContext将简单位置路径视为类路径位置。也可以使⽤带有特殊前缀的位置路径(资源字符串)强制从类路径或URL加载定义,⽽不管实际的上下⽂类型如何

 

1.15.4. Convenient ApplicationContext Instantiation for Web Applications

可以通过使⽤ ContextLoader 等声明性地创建 ApplicationContext 实例。当然,你也可以使⽤ ApplicationContext 实现之⼀ 以编程⽅式创建applicationContext实例。
可以使⽤ ContextLoaderListener 注册 ApplicationContext ,如下示例所示:
<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>/WEB-INF/daoContext.xml /WEB-INF/applicationContext.xml</param-value>
</context-param>

<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
侦听器检查 ContextConfigLocation 参数。如果参数不存在,侦听器将使⽤ /WEB-INF/ApplicationContext.xml作为默认值。当参数确实存在时,侦听器使⽤预定义的分隔符(逗号、分号和空格)分隔字符串,并将这些值⽤作搜索应⽤程序上下⽂的位置。还⽀持Ant-style 的路径模式。例如/WEB-INF/_context.xml(对于名称以context.xml结尾并位于WEB-INF⽬录中的所有⽂件)和/WEB-INF/**/_context.xml(对于WEB-INF的任何⼦⽬录中的所有此类⽂件)。

 

1.15.5. Deploying a Spring ApplicationContext as a Java EE RAR File

可以将 Spring ApplicationContext 作为 RAR ⽂件部署,在 JavaEE RAR 部署单元中封装上下⽂及其所有所需的 bean 类和库JAR 。这相当于引导⼀个独⽴的ApplicationContext(仅托管在 JavaEE 环境中)能够访问 JavaEE 服务器设施。 RAR 部 署是⼀种更为⾃然的替代⽅案,⽤于部署⼀个headless WAR⽂件——实际上,⼀个没有任何HTTP⼊⼝点的WAR⽂件,它仅⽤于在JavaEE环境中引导Spring ApplicationContext。
RAR 部署⾮常适合不需要 HTTP ⼊⼝点,⽽是只包含消息端点和计划作业的应⽤程序上下⽂。在这种上下⽂中, bean 可以使⽤应⽤服务器资源,如jta 事务管理器和 jndi 绑定的 jdbc DataSource 实例和 jms ConnectionFactory 实例,还可以通过Spring的标准事务管理和 jndi jmx ⽀持设施在平台的 jmx 服务器-注册。应⽤程序组件还可以通过Spring的 TaskExecutor抽象与应⽤服务器的JCA WorkManager 交互。
 
请参阅 SpringContextResourceAdapter 类的 JavaDoc ,了解 RAR 部署中涉及的配置详细信息。
对于 Spring ApplicationContext 作为 JavaEE RAR ⽂件的简单部署:
1. 将所有应⽤程序类打包成⼀个rar⽂件(这是⼀个具有不同⽂件扩展名的标准jar⽂件)。将所有必需的库 jar 添加到 rar存档的根⽬录中。.添加⼀个META-INF/ra.xml部署描述符(如SpringContextResourceAdapter的JavaDoc中所示)和相应的SpringXML bean 定义⽂件(通常为'META-INF/applicationContext.xml)。
2. 将⽣成的 rar ⽂件放到应⽤程序服务器的部署⽬录中。
这种 RAR 部署单元通常是独⽴的。它们不向外部世界公开组件,甚⾄不向同⼀应⽤程序的其他模块公开组件。与 基于rar的ApplicationContext 的交互通常通过与其他模块共享的 JMS ⽬标进⾏。例如,基于 rar 的 ApplicationContext还可以调度⼀些作业或对⽂件系统(或类似系统)中的新⽂件做出反应。如果需要允许从外部 进⾏同步访问,它可以(例如)导出RMI端点,该端点可以由同⼀台计算机上的其他应⽤程序模块使⽤。

 


 

1.16. The BeanFactory

 

BeanFactory APISpringIOC功能提供了基础。其特定的契约主要⽤于与Spring和相关第三⽅框架的其他部分集成,

其默认的 ListableBeanFactory 实现是更⾼级别 GenericApplicationContext 容器中的关键委托。
 
BeanFactory 和相关接⼝(如 BeanFactoryAware InitializingBean DisposableBean )是其他框架组件的重要集成点。
通过不需要任何注解甚⾄反射,它们允许容器及其组件之间⾮常有效的交互。应⽤程序级 bean 可以使⽤相同的回调接
⼝,但通常更倾向于通过注解或编程配置进⾏声明性依赖项注⼊。
 
BeanFactory 和相关接⼝(如 BeanFactoryAware InitializingBean DisposableBean )是其他框架组件的重要集成点。
通过不需要任何注解甚⾄反射,它们允许容器及其组件之间⾮常有效的交互。应⽤程序级 bean 可以使⽤相同的回调接
⼝,但通常更倾向于通过注解或编程配置进⾏声明性依赖项注⼊。
 

1.16.1 BeanFactory 还是 ApplicationContext

本节解释BeanFactoryApplicationContext容器级别之间的差异以及引导的含义。

除⾮有充分的理由不这样做,否则应该使⽤ ApplicationContext ,将 GenericApplicationContext 及其⼦类
AnnotationConfigApplicationContext 作为⾃定义引导的常⻅实现。这些是 Spring 核⼼容器的主要⼊⼝点,⽤于所有常⻅
⽤途:加载配置⽂件、触发类路径扫描、以编程⽅式注册 bean 定义和带注解的类,以及(从 5.0 开始)注册功能性 bean
定义。
 
由于 ApplicationContext 包含 BeanFactory 的所有功能,因此通常建议将其置于普通 BeanFactory 之上,但需要完全控制
Bean 处理的情况除外。在 ApplicationContext (例如 GenericapplicationContext 实现)中,通过约定(即,通过 bean
称或 bean 类型-特别是后处理器)检测到⼏种bean,⽽普通的 DefaultListableBeanFactory 对于任何特殊 bean 都是不可
知的。
 
对于许多扩展容器特性,如注解处理和 AOP 代理, BeanPostProcessor 扩展点是必不可少的。如果只使⽤普通的
DefaultListableBeanFactory ,则默认情况下不会检测到此类后处理器并激活它们。这种情况可能会令⼈困惑,因为
bean 配置实际上没有任何问题。相反,在这种情况下,需要通过额外的设置完全引导容器。
 
下表列出了 BeanFactory ApplicationContext 接⼝和实现提供的功能。
 
显示使⽤ DefaultListableBeanFactory 注册 bean post-processor, 你需要在程序中调⽤addBeanPostProcessor ,如下:
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
// populate the factory with bean definitions

// now register any needed BeanPostProcessor instances
factory.addBeanPostProcessor(new AutowiredAnnotationBeanPostProcessor());
factory.addBeanPostProcessor(new MyBeanPostProcessor());

// now start using the factory

 

要应⽤ BeanFactoryPostProcessor 到普通的 DefaultListableBeanFactory ,你需要调⽤ postProcessBeanFactory , 如
下:
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
reader.loadBeanDefinitions(new FileSystemResource("beans.xml"));

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

// now actually do the replacement
cfg.postProcessBeanFactory(factory);
在这两种情况下,显式注册步骤都不⽅便,这就是为什么在⽀持 Spring 的应⽤程序中,各种 ApplicationContext 变量⽐普
通的 defaultListableBeanFactory 更受欢迎,特别是在典型企业应⽤中,依赖 BeanFactoryPostProcessor
BeanPostProcessor 实例实现扩展容器功能时。
 
AnnotationConfigApplicationContext 注册了所有常⻅的注解后处理程序,并可以通过配置注解(如
@EnableTransactionManagement )将其他处理程序置于覆盖范围内。在 Spring 基于注解的配置模型的抽象级别
上, bean 后处理器的概念变成了⼀个更内部的容器细节。

 

 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值