Spring 核心(第二部分)

1.5 Bean作用域

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

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

下表描述了支持的范围:

Scope Description

singleton

(默认)为每个Spring IoC容器将单个bean定义作用于单个对象实例。

 

prototype

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

request

将单个bean定义的范围限定为单个HTTP请求的生命周期。也就是说,每个HTTP请求都有自己的bean实例,这些实例是在单个bean定义的基础上创建的。仅在可感知web的Spring应用程序上下文中有效。

session

将单个bean定义的范围限定为HTTP会话的生命周期。仅在可感知web的Spring应用程序上下文中有效。

application

将单个bean定义作用于ServletContext的生命周期。仅在可感知web的Spring应用程序上下文中有效。

websocket

将单个bean定义作用于WebSocket的生命周期。仅在可感知web的Spring应用程序上下文中有效。

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

1.5.1 单例的作用域

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

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

Spring 核心(第二部分)_第1张图片

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




1.5.2 原型作用域

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

Spring 核心(第二部分)_第2张图片

通常不将数据访问对象配置为原型,因为典型的DAO不包含任何会话状态。我们更容易重用单例图的核心。)
下面的例子在XML中将bean定义为原型:

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

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

1.5.3 具有原型bean依赖项的单例bean

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

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

1.5.4 Request, Session, Application, and WebSocket作用域

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

初始化的Web配置

为了在请求、会话、应用程序和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文件中:


    ...
    
        
            org.springframework.web.context.request.RequestContextListener
        
    
    ...

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


    ...
    
        requestContextFilter
        org.springframework.web.filter.RequestContextFilter
    
    
        requestContextFilter
        /*
    
    ...

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

Request作用域

请考虑以下bean定义的XML配置:

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

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

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

Session作用域

请考虑以下bean定义的XML配置:

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作用域

请考虑一下bean定义的以下XML配置:

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

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

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

将限定作用域的bean作为依赖项

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

注意点:

你也可以在定义为单例的bean之间使用,然后引用通过一个可序列化的中间代理,因此能够在反序列化时重新获得目标单例bean。

当对范围原型的bean声明时,共享代理上的每个方法调用都会导致创建一个新的目标实例,然后将调用转发给该实例。

而且,范围代理不是以生命周期安全的方式从较短范围访问bean的惟一方法。你也可以声明你的注射点(也就是构造函数或setter参数或autowired的字段)作为ObjectFactory < MyTargetBean >,允许getObject()调用来检索当前实例对需求每次需要——没有分别持有实例或存储它。

作为扩展的变体,您可以声明ObjectProvider,它提供了几个额外的访问变体,包括getIfAvailable和getIfUnique。

该方法的JSR-330变体称为Provider,并与Provider声明和对应的get()调用一起用于每次检索尝试。有关JSR-330的更多细节,请参见这里。

下面例子中的配置只有一行,但是理解它背后的“为什么”和“如何”是很重要的:




    
    
        
         //1
    

    
    
        
        
    

//1 定义代理的行。

要创建这样的代理,需要将一个子元素插入到有作用域的bean定义中(参见选择要创建的代理类型和基于XML模式的配置)。为什么在请求、会话和自定义范围级别定义作用域的bean需要元素?考虑一下下面的单例bean定义,并将它与您需要为前面提到的作用域定义的内容进行对比(请注意,下面的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注入到协作对象中时,需要以下(正确和完整的)配置,如下面的示例所示:


    



    

选择要创建的代理的类型

默认情况下,当Spring容器为用元素标记的bean创建代理时,将创建一个基于cglib的类代理。

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

或者,您可以配置Spring容器,为这种作用域bean创建标准的基于JDK接口的代理,方法是为元素的代理目标类属性的值指定false。使用基于JDK接口的代理意味着在应用程序类路径中不需要额外的库来影响这种代理。但是,这也意味着作用域bean的类必须实现至少一个接口,并且所有注入作用域bean的合作者必须通过它的一个接口引用bean。下面的例子展示了一个基于接口的代理:



    



    

有关选择基于类或基于接口的代理的更详细信息,请参见 [aop-proxying]。

1.5.5 自定义作用域

bean作用域机制是可扩展的。您可以定义自己的作用域,甚至可以重新定义现有的作用域,尽管后者被认为是不好的实践,并且您不能覆盖内置的单例和原型作用域。

创建自定义范围

要将您的自定义范围集成到Spring容器中,您需要实现org.springframe .bean .factory.config.Scope接口,将在本节中描述。要了解如何实现您自己的作用域,请参阅Spring框架本身提供的作用域实现和作用域javadoc,这将更详细地解释您需要实现的方法。

作用域接口有四种方法来从作用域获取对象,从作用域删除对象,然后销毁对象。
例如,会话范围实现将返回会话范围的bean(如果它不存在,则该方法将在将其绑定到会话以供将来引用后返回bean的新实例)。下面的方法从底层范围返回对象:

Object get(String name, ObjectFactory objectFactory)

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

Object remove(String name)

以下方法注册当范围被销毁或范围内的指定对象被销毁时应该执行的回调:

void registerDestructionCallback(String name, Runnable destructionCallback)

有关销毁回调的更多信息,请参阅javadoc或Spring作用域实现。
下面的方法获取底层作用域的对话标识符:

String getConversationId()

这个标识符对于每个范围都是不同的。对于会话范围的实现,此标识符可以是会话标识符。

使用自定义作用域

在编写和测试一个或多个自定义范围实现之后,您需要让Spring容器知道您的新范围。下面的方法是向Spring容器注册新范围的中心方法:

void registerScope(String scopeName, Scope scope);

此方法在ConfigurableBeanFactory接口上声明,该接口可通过Spring附带的大多数具体ApplicationContext实现上的BeanFactory属性获得。
registerScope(..)方法的第一个参数是与范围关联的惟一名称。Spring容器中此类名称的例子有singleton和prototype。registerScope(..)方法的第二个参数是您希望注册和使用的自定义范围实现的实际实例。

假设您编写了自定义范围实现,然后在下一个示例中注册它。

注意:下一个示例使用SimpleThreadScope,它包含在Spring中,但默认情况下不注册。对于您自己的自定义范围实现,说明是相同的。

Scope threadScope = new SimpleThreadScope();
beanFactory.registerScope("thread", threadScope);
然后,您可以创建遵循自定义范围的范围规则的bean定义,如下所示:

使用自定义范围实现,您不局限于范围的程序性注册。您还可以通过使用CustomScopeConfigurer类声明性地进行范围注册,如下面的示例所示:




    
        
            
                
                    
                
            
        
    

    
        
        
    

    
        
    

注意:在FactoryBean实现中放置时,受作用域限制的是工厂bean本身,而不是从getObject()返回的对象。

1.6 自定义Bean的性质

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

  • Lifecycle Callbacks

  • ApplicationContextAware and BeanNameAware

  • Other Aware Interfaces

1.6.1. Lifecycle Callbacks

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

注意:

JSR-250 @PostConstruct和@PreDestroy注释通常被认为是在现代Spring应用程序中接收生命周期回调的最佳实践。使用这些注释意味着您的bean没有耦合到特定于spring的接口。有关详细信息,请参见[beans-post - construct-and- pre- annotations]。
如果您不希望使用JSR-250注释,但仍然希望消除耦合,那么可以考虑init-method和destroy-method bean定义元数据。

在内部,Spring框架使用BeanPostProcessor实现来处理它能找到并调用适当方法的任何回调接口。如果您需要自定义特性或Spring默认不提供的其他生命周期行为,您可以自己实现BeanPostProcessor。有关更多信息,请参见容器扩展点。

除了初始化和销毁回调之外,spring管理的对象还可以实现生命周期接口,以便这些对象可以参与启动和关闭过程,这是由容器自身的生命周期驱动的。
生命周期回调接口将在本节中描述。

Initialization Callbacks

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

void afterPropertiesSet() throws Exception;

我们建议您不要使用InitializingBean接口,因为它不必要地将代码耦合到Spring。另外,我们建议使用@PostConstruct注释或指定POJO初始化方法。对于基于xml的配置元数据,可以使用init-method属性指定具有void无参数签名的方法的名称。使用Java配置,您可以使用@Bean的initMethod属性。看到[beans-java-lifecycle-callbacks]。考虑下面的例子:

public class ExampleBean {

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

上面的例子与下面的例子(包含两个清单)的效果几乎完全相同:

public class AnotherExampleBean implements InitializingBean {

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

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

Destruction Callbacks

org.springframework.beans.factory.DisposableBean 当包含它的容器被销毁时,可处置bean接口让bean获得回调。可处置bean接口指定了一个方法:

void destroy() throws Exception;

我们建议您不要使用DisposableBean回调接口,因为它不必要地将代码与Spring绑定在一起。另外,我们建议使用@PreDestroy注释或指定bean定义支持的泛型方法。使用基于xml的配置元数据,您可以在上使用destroy-method属性。通过Java配置,您可以使用@Bean的destroyMethod属性。看到[beans-java-lifecycle-callbacks]。考虑以下定义:

public class ExampleBean {

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

以上定义与以下定义的效果几乎完全相同:

public class AnotherExampleBean implements DisposableBean {

    @Override
    public void destroy() {
        // do some destruction work (like releasing pooled connections)
    }
}

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

注意:您可以为元素的destroy-method属性指定一个特殊的(推断)值,该值指示Spring自动检测特定bean类上的公共关闭或关闭方法。(任何实现java.lang的类。AutoCloseable或io。因此,可关闭将匹配。)您还可以在元素的Default - Destroy -method属性上设置这个特殊的(推断出来的)值,以便将此行为应用于整个bean集(请参阅缺省初始化和销毁方法)。注意,这是Java配置的默认行为。

默认初始化和销毁方法

当您编写不使用特定于spring的InitializingBean和DisposableBean回调接口的初始化和销毁方法回调时,您通常会编写具有init()、initialize()、dispose()等名称的方法。理想情况下,这样的生命周期回调方法的名称在整个项目中是标准化的,这样所有开发人员都可以使用相同的方法名称并确保一致性。

您可以将Spring容器配置为“look”指定的初始化并销毁每个bean上的回调方法名。这意味着,作为应用程序开发人员,您可以编写应用程序类并使用名为init()的初始化回调,而不必为每个bean定义配置init-method="init"属性。在创建bean时,Spring IoC容器调用该方法(并根据前面描述的标准生命周期回调契约)。该特性还强制对初始化和销毁方法回调使用一致的命名约定。

假设您的初始化回调方法命名为init(),而销毁回调方法命名为destroy()。你的类类似于下面例子中的类:

public class DefaultBlogService implements BlogService {

    private BlogDao blogDao;

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

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

然后你可以在一个类似如下的bean中使用这个类:



    
        
    

顶级元素属性上的default-init-method属性导致Spring IoC容器将bean类上一个名为init的方法识别为初始化方法回调。在创建和组装bean时,如果bean类有这样的方法,则在适当的时候调用它。

您可以通过在顶级元素上使用default-destroy-method属性来配置destroy方法回调(即在XML中)。
现有的bean类已经有了根据约定命名的回调方法,您可以通过使用本身的init-method和destroy-method属性指定(在XML中)方法名来覆盖默认值。

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

Combining Lifecycle Mechanisms

从spring2.5开始,你有三个控制bean生命周期行为的选项:

  • InitializingBean和一次性bean回调接口
  • 自定义init()和destroy()方法
  • @PostConstruct和@PreDestroy注释。您可以组合这些机制来控制给定的bean。

注意:如果为bean配置了多个生命周期机制,并且每个机制都配置了不同的方法名,那么每个配置的方法将按照本说明后面列出的顺序执行。但是,如果为多个生命周期机制配置了相同的方法名(例如,初始化方法的init()),则该方法将执行一次,如前一节所述。

使用不同的初始化方法为同一个bean配置多个生命周期机制,调用方法如下:

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

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

  1. 方法注释@PreDestroy
  2. 销毁()是由抛出bean回调接口定义的
  3. 自定义配置的destroy()方法

启动和关闭回调

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

public interface Lifecycle {

    void start();

    void stop();

    boolean isRunning();
}

任何spring管理对象都可以实现生命周期接口。然后,当ApplicationContext本身接收到启动和停止信号(例如,运行时的停止/重启场景)时,它将这些调用级联到该上下文中定义的所有生命周期实现。它通过委托给一个LifecycleProcessor来做到这一点,如下面的清单所示:

public interface LifecycleProcessor extends Lifecycle {

    void onRefresh();

    void onClose();
}

注意,LifecycleProcessor本身就是生命周期接口的扩展。它还添加了另外两种方法来对刷新和关闭的上下文做出响应。

注意:

请注意,常规的org.springframe .context。生命周期接口是显式启动和停止通知的普通契约,并不意味着在上下文刷新时自动启动。对于特定bean的自动启动(包括启动阶段)的细粒度控制,请考虑实现org.springframe .context。SmartLifecycle代替。

另外,请注意停止通知不能保证在销毁之前发出。在常规关闭时,所有生命周期bean在传播常规销毁回调之前首先收到一个停止通知。但是,在上下文的生存期或中止的刷新尝试中进行热刷新时,只调用destroy方法。

启动和关闭调用的顺序可能很重要。如果任何两个对象之间存在“依赖关系”,依赖方在依赖后开始,在依赖前停止。然而,有时,直接依赖关系是未知的。您可能只知道某种类型的对象应该先于另一种类型的对象启动。在这些情况下,SmartLifecycle接口定义了另一个选项,即在其超级接口上定义的getPhase()方法。下面的清单显示了阶段接口的定义:

public interface Phased {

    int getPhase();
}

下面的清单显示了SmartLifecycle接口的定义:

public interface SmartLifecycle extends Lifecycle, Phased {

    boolean isAutoStartup();

    void stop(Runnable callback);
}

启动时,具有最低相位的对象首先启动。停止时,执行相反的顺序。因此,一个实现SmartLifecycle的对象,它的getPhase()方法返回Integer。MIN_VALUE将是第一个开始和最后一个停止的值。在频谱的另一端,相位值为整数。MAX_VALUE将指示该对象应该最后启动并首先停止(可能是因为它取决于要运行的其他进程)。在考虑phase值时,同样重要的是要知道,对于任何没有实现SmartLifecycle的“正常”生命周期对象,默认的phase是0。因此,任何负相位值都表示一个对象应该在那些标准组件之前启动(在它们之后停止)。对于任何正相位值,情况正好相反。

SmartLifecycle定义的stop方法接受回调。任何实现都必须在该实现的关闭过程完成后调用该回调的run()方法。这在必要时支持异步关闭,因为LifecycleProcessor接口的默认实现DefaultLifecycleProcessor会等待每个阶段中的对象组的超时值来调用回调。默认的每个阶段超时时间是30秒。通过在上下文中定义一个名为lifecycleProcessor的bean,可以覆盖默认的lifecycle processor实例。如果您只想修改超时,定义以下内容就足够了:


    
    

如前所述,LifecycleProcessor接口还定义了用于刷新和关闭上下文的回调方法。后者驱动关闭进程,就好像已经显式地调用了stop(),但它是在上下文关闭时发生的。另一方面,“refresh”回调启用了SmartLifecycle bean的另一个特性。当上下文被刷新时(在所有对象被实例化和初始化之后),回调被调用。此时,默认的生命周期处理器将检查每个SmartLifecycle对象的isAutoStartup()方法返回的布尔值。如果为真,则在该点启动该对象,而不是等待显式调用上下文的start()方法(与上下文刷新不同,对于标准上下文实现,上下文启动不会自动发生)。正如前面所述,阶段值和任何“依赖”关系决定启动顺序。

在非web应用程序中优雅地关闭Spring IoC容器

注意:

本节仅适用于非web应用程序。Spring的基于web的ApplicationContext实现已经有了适当的代码,可以在相关web应用程序关闭时优雅地关闭Spring IoC容器。

如果您在非web应用程序环境(例如,在富客户机桌面环境中)中使用Spring的IoC容器,请向JVM注册一个关闭挂钩。这样做可以确保优雅地关闭并调用单例bean上的相关销毁方法,以便释放所有资源。您仍然必须正确配置和实现这些销毁回调。
要注册一个关闭钩子,调用在ConfigurableApplicationContext接口上声明的registerShutdownHook()方法,如下面的示例所示:

import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public final class Boot {

    public static void main(final String[] args) throws Exception {
        ConfigurableApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");

        // add a shutdown hook for the above context...
        ctx.registerShutdownHook();

        // app runs here...

        // main method exits, hook is called prior to the app shutting down...
    }
}

1.6.2 ApplicationContextAware和BeanNameAware

当ApplicationContext创建一个实现org.springframe .context的对象实例时。ApplicationContext接口,实例提供了该ApplicationContext的引用。下面的清单显示了applicationcontext - ware接口的定义:

public interface ApplicationContextAware {

    void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
}

因此,bean可以通过编程方式操作创建它们的ApplicationContext,方法是通过ApplicationContext接口,或者通过将引用转换为该接口的一个已知子类(例如ConfigurableApplicationContext,它公开了额外的功能)。一种用途是通过编程检索其他bean。有时这种能力是有用的。但是,一般来说,您应该避免使用它,因为它将代码与Spring耦合在一起,并且不遵循控制反转的风格,即将协作者作为属性提供给bean。ApplicationContext的其他方法提供对文件资源的访问、发布应用程序事件和访问MessageSource。这些附加的特性在[上下文介绍]中进行了描述。

自动装配是获取ApplicationContext引用的另一种选择。传统的构造函数和byType自动装配模式(如自动装配协作者中所述)可以分别为构造函数参数或setter方法参数提供ApplicationContext类型的依赖项。为了获得更大的灵活性,包括自动装配字段和多个参数方法的能力,可以使用基于注释的自动装配特性。如果你这样做了,ApplicationContext就会被自动拖放到一个字段、构造函数参数或者方法参数中,如果这个字段、构造函数或者方法带有@Autowired注解,那么这个参数就会期望ApplicationContext类型。更多信息,请参见使用@Autowired。

当ApplicationContext创建一个实现org.springframe .bean .factory的类时。类的关联对象定义中定义的名称的引用。下面的清单显示了BeanNameAware接口的定义:

public interface BeanNameAware {

    void setBeanName(String name) throws BeansException;
}

回调是在填充普通bean属性之后,但在初始化回调(如InitializingBean、afterPropertiesSet或自定义init-method)之前调用的。

1.6.3 其他Aware接口

除了applicationcontext ware和BeanNameAware(前面讨论过)之外,Spring还提供了广泛的可识别回调接口,让bean向容器表明它们需要某种基础设施依赖。通常,名称表示依赖项类型。下表总结了最重要的Aware接口:

. Aware interfaces
Name Injected Dependency Explained in…​

ApplicationContextAware

申明ApplicationContext.

ApplicationContextAware and BeanNameAware

ApplicationEventPublisherAware

Event publisher of the enclosing ApplicationContext.

[context-introduction]

BeanClassLoaderAware

Class loader used to load the bean classes.

Instantiating Beans

BeanFactoryAware

Declaring BeanFactory.

ApplicationContextAware and BeanNameAware

BeanNameAware

Name of the declaring bean.

ApplicationContextAware and BeanNameAware

BootstrapContextAware

Resource adapter BootstrapContext the container runs in. Typically available only in JCA-aware ApplicationContextinstances.

JCA CCI

LoadTimeWeaverAware

Defined weaver for processing class definition at load time.

[aop-aj-ltw]

MessageSourceAware

Configured strategy for resolving messages (with support for parametrization and internationalization).

[context-introduction]

NotificationPublisherAware

Spring JMX notification publisher.

Notifications

ResourceLoaderAware

Configured loader for low-level access to resources.

[resources]

ServletConfigAware

Current ServletConfig the container runs in. Valid only in a web-aware Spring ApplicationContext.

Spring MVC

ServletContextAware

Current ServletContext the container runs in. Valid only in a web-aware Spring ApplicationContext.

Spring MVC

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

1.7 Bean定义继承

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

如果您以编程方式使用ApplicationContext接口,则子bean定义由ChildBeanDefinition类表示。大多数用户在这个级别上不使用它们。相反,它们在类(如ClassPathXmlApplicationContext)中声明式地配置bean定义。在使用基于xml的配置元数据时,可以通过使用父属性指定父bean作为该属性的值来指示子bean定义。下面的例子演示了如何做到这一点:


    
    


  //1
    
    

// 1 父属性。

如果没有指定,则子bean定义使用父定义中的bean类,但也可以覆盖它。在后一种情况下,子bean类必须与父类兼容(也就是说,它必须接受父类的属性值)。

子bean定义继承父bean的作用域、构造函数参数值、属性值和方法覆盖,并具有添加新值的选项。指定的任何范围、初始化方法、销毁方法或静态工厂方法设置将覆盖相应的父设置。

其余的设置总是取自子定义:依赖、自动装配模式、依赖项检查、单例和惰性初始化。

前面的示例通过使用抽象属性显式地将父bean定义标记为抽象。如果父定义没有指定类,则需要显式地将父bean定义标记为抽象,如下面的示例所示:


    
    



    
    

父bean不能单独实例化,因为它是不完整的,而且它也被显式地标记为抽象。当定义是抽象的时,它只能作为作为子定义的父定义的纯模板bean定义使用。尝试单独使用这样一个抽象的父bean,方法是将它引用为另一个bean的ref属性,或者使用父bean ID执行显式的getBean()调用,这会返回一个错误。类似地,容器的内部预实例化esingletons()方法忽略定义为抽象的bean定义。

注意:默认情况下,ApplicationContext预先实例化所有单例对象。因此,它是重要的(至少对单例bean),如果你有一个(父)bean定义你只打算使用作为模板,这个定义指定了一个类,您必须确保设置抽象属性为true,否则应用程序上下文会(试图)pre-instantiate抽象的bean。

1.8 容器扩展点

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

1.8.1 通过使用BeanPostProcessor定制bean

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

您可以配置多个BeanPostProcessor实例,并且可以通过设置order属性来控制这些BeanPostProcessor实例的执行顺序。只有在BeanPostProcessor实现有序接口时才能设置此属性。如果您编写自己的BeanPostProcessor,也应该考虑实现有序接口。有关详细信息,请参见BeanPostProcessor和有序接口的javadoc。参见BeanPostProcessor实例的程序性注册说明。

注意:BeanPostProcessor实例操作bean(或对象)实例。也就是说,Spring IoC容器实例化一个bean实例,然后BeanPostProcessor实例执行它们的工作。

BeanPostProcessor实例的作用域是每个容器。这仅在使用容器层次结构时才相关。如果您在一个容器中定义一个BeanPostProcessor,那么它只对该容器中的bean进行后处理。换句话说,在一个容器中定义的bean不会由在另一个容器中定义的BeanPostProcessor进行后处理,即使两个容器都是相同层次结构的一部分。

要更改实际的bean定义(即定义bean的蓝图),您需要使用BeanFactoryPostProcessor,如用BeanFactoryPostProcessor自定义配置元数据中所述。

org.springframework.beans.factory.config。BeanPostProcessor接口正好由两个回调方法组成。当这样一个类注册为后处理器的容器,每个容器创建bean实例,后处理器从容器之前得到一个回调容器初始化方法(如InitializingBean.afterPropertiesSet()或任何宣布init方法),任何bean初始化后回调。后处理器可以对bean实例采取任何操作,包括完全忽略回调。bean后处理器通常检查回调接口,或者使用代理包装bean。为了提供代理包装逻辑,一些Spring AOP基础设施类被实现为bean后处理器。

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

注意,当在配置类上使用@Bean工厂方法声明BeanPostProcessor时,工厂方法的返回类型应该是实现类本身,或者至少是org.springframe .bean .factory.config。BeanPostProcessor接口,清楚地指示该bean的后处理器特性。否则,ApplicationContext不能在完全创建它之前通过类型自动检测它。由于为了应用于上下文中其他bean的初始化,需要尽早实例化BeanPostProcessor,所以这种早期类型检测非常重要。

注意:

以编程方式注册BeanPostProcessor实例
虽然推荐的BeanPostProcessor注册方法是通过ApplicationContext自动检测(如前所述),但是您可以通过使用addBeanPostProcessor方法以编程方式针对ConfigurableBeanFactory注册它们。当您需要在注册之前计算条件逻辑,甚至在层次结构的上下文中复制bean post处理器时,这可能很有用。但是,请注意,以编程方式添加的BeanPostProcessor实例并不遵循有序接口。在这里,注册的顺序决定了执行的顺序。还要注意,以编程方式注册的BeanPostProcessor实例总是在通过自动检测注册的实例之前处理,而不考虑任何显式的顺序。

注意:

BeanPostProcessor实例和AOP自动代理
实现BeanPostProcessor接口的类是特殊的,容器以不同的方式对待它们。它们直接引用的所有BeanPostProcessor实例和bean在启动时实例化,作为ApplicationContext的特殊启动阶段的一部分。接下来,以排序的方式注册所有BeanPostProcessor实例,并将其应用于容器中所有后续的bean。因为AOP自动代理是作为一个BeanPostProcessor本身来实现的,所以无论是BeanPostProcessor实例还是它们直接引用的bean都不适合自动代理,因此,没有将方面编织到它们之中。

对于任何这样的bean,您应该看到一条信息日志消息:bean someBean不适合被所有BeanPostProcessor接口处理(例如:不适合自动代理)。

如果通过使用autowiring或@Resource(可能会回到autowiring)将bean连接到BeanPostProcessor中,Spring可能会在搜索类型匹配依赖项候选项时访问意外的bean,因此,使它们不适合自动代理或其他类型的bean后处理。例如,如果您有一个带有@Resource注释的依赖项,其中字段或setter名称与bean的声明名称不直接对应,并且没有使用name属性,那么Spring将访问其他bean,以便根据类型匹配它们。

下面的示例演示如何在ApplicationContext中编写、注册和使用BeanPostProcessor实例。

例子:Hello World, beanpostprocessor风格

第一个例子演示了基本用法。该示例显示了一个自定义BeanPostProcessor实现,该实现在容器创建每个bean时调用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:




    
        
    

    
    

注意,InstantiationTracingBeanPostProcessor是如何定义的。它甚至没有名称,因为它是一个bean,所以可以像其他bean一样依赖注入。(前面的配置还定义了一个由Groovy脚本支持的bean。有关Spring动态语言支持的详细信息,请参阅“ Dynamic Language Support”一章。

以下Java应用程序运行上述代码和配置:

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.scripting.Messenger;

public final class Boot {

    public static void main(final String[] args) throws Exception {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("scripting/beans.xml");
        Messenger messenger = 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 使用BeanFactoryPostProcessor自定义配置元数据

我们查看的下一个扩展点是org.springframe .bean .factory.config. beanfactorypostprocessor。此接口的语义与BeanPostProcessor的语义相似,但有一个主要区别:BeanFactoryPostProcessor操作bean配置元数据。也就是说,Spring IoC容器允许BeanFactoryPostProcessor读取配置元数据,并可能在容器实例化除BeanFactoryPostProcessor实例之外的任何bean之前更改它。

您可以配置多个BeanFactoryPostProcessor实例,并且可以通过设置order属性来控制这些BeanFactoryPostProcessor实例的运行顺序。但是,只有在BeanFactoryPostProcessor实现有序接口时才能设置此属性。如果您编写自己的BeanFactoryPostProcessor,也应该考虑实现有序接口。有关更多细节,请参见BeanFactoryPostProcessor和有序接口的javadoc。

注意:如果您想要更改实际的bean实例(即,从配置元数据创建的对象),那么您需要使用BeanPostProcessor(在前面通过使用BeanPostProcessor定制bean中描述过)。虽然在BeanFactoryPostProcessor中使用bean实例在技术上是可行的(例如,通过使用BeanFactory.getBean()),这样做会导致过早实例化bean,违反标准容器生命周期。这可能会导致负面的副作用,比如绕过bean的后处理。

而且,BeanFactoryPostProcessor实例的作用域是每个容器。这仅在使用容器层次结构时才相关。如果您在一个容器中定义一个BeanFactoryPostProcessor,那么它只应用于该容器中的bean定义。一个容器中的Bean定义不会由另一个容器中的BeanFactoryPostProcessor实例进行后处理,即使两个容器都是相同层次结构的一部分。

当在ApplicationContext中声明bean工厂后处理器时,它将自动执行,以便将更改应用于定义容器的配置元数据。Spring包含许多预定义的bean工厂后处理程序,如PropertyOverrideConfigurer和PropertySourcesPlaceholderConfigurer。您还可以使用自定义BeanFactoryPostProcessor—例如,注册自定义属性编辑器。

ApplicationContext自动检测部署到其中的实现BeanFactoryPostProcessor接口的任何bean。它在适当的时候使用这些bean作为bean factory的后处理器。您可以像部署任何其他bean一样部署这些后处理器bean。

与beanpostprocessor一样,您通常不希望将beanfactorypostprocessor配置为延迟初始化。如果没有其他bean引用bean(工厂)后处理器,则根本不会实例化后处理器。因此,将其标记为延迟初始化将被忽略,即使在元素的声明中将default-lazy-init属性设置为true, Bean(工厂)后处理器也将被急切地实例化。

例如:类名替换PropertySourcesPlaceholderConfigurer

通过使用标准的Java属性格式,您可以使用PropertySourcesPlaceholderConfigurer将属性值从bean定义外部化到一个单独的文件中。这样做使部署应用程序的人员能够自定义特定于环境的属性,如数据库url和密码,而不需要修改主XML定义文件或容器文件的复杂性或风险。

考虑以下基于xml的配置元数据片段,其中定义了具有占位符值的数据源:


    



    
    
    
    

该示例显示了从外部属性文件配置的属性。在运行时,PropertySourcesPlaceholderConfigurer应用于替换数据源的某些属性的元数据。要替换的值被指定为表单${property-name}的占位符,它遵循Ant和log4j以及JSP EL样式。

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

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

因此,$ {jdbc.username名}字符串在运行时被替换为值'sa',相同的情况也适用于其他与属性文件中的键匹配的占位符值。PropertySourcesPlaceholderConfigurer检查bean定义的大多数属性和属性中的占位符。此外,您可以自定义占位符前缀和后缀。

通过Spring 2.5中引入的上下文命名空间,您可以使用专用的配置元素来配置属性占位符。您可以在location属性中以逗号分隔的列表的形式提供一个或多个位置,如下面的示例所示:

PropertySourcesPlaceholderConfigurer不仅在您指定的属性文件中查找属性。默认情况下,如果在指定的属性文件中找不到属性,它会检查Spring环境属性和常规Java系统属性。

注意:您可以使用PropertySourcesPlaceholderConfigurer来替换类名,这在必须在运行时选择特定实现类时非常有用。下面的例子演示了如何做到这一点:


    
        classpath:com/something/strategy.properties
    
    
        custom.strategy.class=com.something.DefaultStrategy
    


如果在运行时不能将类解析为有效的类,则在即将创建bean时,即在非lazy-init bean的ApplicationContext的预实例化esingletons()阶段,bean的解析将失败。

例子:PropertyOverrideConfigurer

另一个bean工厂后处理器PropertyOverrideConfigurer类似于PropertySourcesPlaceholderConfigurer,但与后者不同,原始定义可以有缺省值,也可以没有bean属性的值。如果覆盖的属性文件没有某个bean属性的条目,则使用默认的上下文定义。

注意,bean定义不知道被覆盖,所以从XML定义文件中不能立即看出使用了覆盖配置程序。如果有多个PropertyOverrideConfigurer实例为同一个bean属性定义不同的值,由于覆盖机制,最后一个实例胜出。

属性文件配置行采用以下格式:

beanName.property=value

下面的清单显示了格式的一个例子:

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

此示例文件可与容器定义一起使用,容器定义包含一个名为dataSource的bean,该bean具有驱动程序和url属性。
也支持复合属性名,只要路径的每个组件(被覆盖的最终属性除外)都是非空的(可能是由构造函数初始化的)。在下面的例子中,tom bean的fred属性的bob属性的sammy属性被设置为标量值123:

tom.fred.bob.sammy=123

注意:指定的重写值总是文字值。它们不会被转换成bean引用。当XML bean定义中的原始值指定一个bean引用时,也适用这种约定。

通过在Spring 2.5中引入上下文命名空间,可以使用专用的配置元素配置属性覆盖,如下面的示例所示:

1.8.3 使用FactoryBean定制实例化逻辑

您可以实现org.springframe .bean .factory。FactoryBean接口用于本身就是工厂的对象。
FactoryBean接口是一个可插入Spring IoC容器实例化逻辑的点。如果您有复杂的初始化代码,用Java表示比(可能的)冗长的XML更好,那么您可以创建自己的FactoryBean,在该类中编写复杂的初始化,然后将定制的FactoryBean插入容器中。

FactoryBean接口提供了三种方法:

  • 对象getObject():返回工厂创建的对象的实例。实例可以共享,这取决于这个工厂返回的是单例还是原型。
  • 如果这个FactoryBean返回单例,则返回true;否则返回false。
  • 类getObjectType():返回getObject()方法返回的对象类型,如果类型事先不知道,则返回null。

FactoryBean概念和接口在Spring框架的许多地方都使用。FactoryBean接口的50多个实现都附带了Spring本身。

当您需要向容器请求实际的FactoryBean实例本身而不是它所生成的bean时,在调用ApplicationContext的getBean()方法时,在bean的id前面加上与符号(&)。因此,对于id为myBean的给定FactoryBean,在容器上调用getBean(“myBean”)将返回FactoryBean的产品,而调用getBean(“&myBean”)将返回FactoryBean实例本身。

1.9 基于注解的容器配置

对于配置Spring,注释是否优于XML ?

基于注释的配置的引入提出了一个问题,即这种方法是否比XML“更好”。简而言之,答案是“视情况而定”。长篇大论的回答是,每种方法都有其优缺点,通常由开发人员决定哪种策略更适合他们。由于它们的定义方式,注释在其声明中提供了大量上下文,从而使配置更短、更简洁。然而,XML擅长在不接触源代码或不重新编译它们的情况下连接组件。一些开发人员喜欢将连接放在接近源的地方,而另一些人则认为带注释的类不再是pojo,而且配置变得分散,更难控制。

无论选择什么,春天都可以同时容纳两种风格,甚至将它们混合在一起。值得指出的是,通过它的JavaConfig选项,Spring允许以一种非侵入性的方式使用注释,而不涉及目标组件源代码,并且在工具方面,Spring Tool Suite支持所有的配置风格。

XML设置的另一种替代方法是基于注释的配置,它依赖于将组件连接起来的字节码元数据,而不是尖括号声明。开发人员不使用XML来描述bean连接,而是通过使用相关类、方法或字段声明上的注释将配置移动到组件类本身。如示例中所述:RequiredAnnotationBeanPostProcessor,将BeanPostProcessor与注释结合使用是扩展Spring IoC容器的常见方法。例如,Spring 2.0引入了使用@Required注释强制执行所需属性的可能性。Spring 2.5使得采用相同的通用方法来驱动Spring的依赖项注入成为可能。本质上,@Autowired注解提供了与Autowiring合作者描述的相同的功能,但是更细粒度的控制和更广泛的适用性。Spring 2.5还增加了对JSR-250注释的支持,比如@PostConstruct和@PreDestroy。Spring 3.0增加了对javax中包含的JSR-330 (Java依赖注入)注释的支持。注入包,如@Inject和@Named。有关这些注释的详细信息可以在相关部分找到。

注意:注释注入在XML注入之前执行。因此,XML配置覆盖了通过这两种方法连接的属性的注释。

与往常一样,您可以将它们注册为单独的bean定义,但是也可以通过在基于xml的Spring配置中包含以下标记来隐式注册它们(请注意上下文名称空间的包含):




    

(隐式注册的后处理器包括AutowiredAnnotationBeanPostProcessor、CommonAnnotationBeanPostProcessor、PersistenceAnnotationBeanPostProcessor和前面提到的RequiredAnnotationBeanPostProcessor。)

注意:只在定义它的应用程序上下文中查找bean上的注释。这意味着,如果你把放在一个DispatcherServlet的WebApplicationContext中,它只会检查你的控制器中的@Autowired bean,而不会检查你的服务。有关更多信息,请参见DispatcherServlet。

1.9.1 @Required

@Required注释应用于bean属性设置器方法,如下面的示例所示:

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属性设置方法)。

1.9.2 使用@Autowired

注意:在本节的示例中,JSR 330的@Inject注释可以代替Spring的@Autowired注释。详情请看这里。

你可以将@Autowired注解应用到构造函数上,如下图所示:

public class MovieRecommender {

    private final CustomerPreferenceDao customerPreferenceDao;

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

    // ...
}

注意:从Spring Framework 4.3开始,如果目标bean只定义一个构造函数,那么就不再需要在这样的构造函数上使用@Autowired注解。但是,如果有多个构造函数可用,那么必须至少有一个用@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)是由用于@ autowire注释的注入点的类型一致声明的。否则,注入可能会由于运行时“没有找到类型匹配”错误而失败。

对于通过类路径扫描找到的xml定义的bean或组件类,容器通常预先知道具体的类型。但是,对于@Bean工厂方法,您需要确保声明的返回类型具有足够的表达能力。对于实现多个接口的组件或可能由其实现类型引用的组件,请考虑在工厂方法上声明最特定的返回类型(至少与引用bean的注入点所需的返回类型一样具体)。

您还可以指示Spring通过向需要该类型数组的字段或方法添加@Autowired注释来从ApplicationContext中提供特定类型的所有bean,如下例所示:

public class MovieRecommender {

    @Autowired
    private MovieCatalog[] movieCatalogs;

    // ...
}

类型化集合也是如此,如下例所示:

public class MovieRecommender {

    private Set movieCatalogs;

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

    // ...
}

注意:

如果希望数组或列表中的项按特定顺序排序,您的目标bean可以实现org.springframework.core.Ordered接口或可以使用@Order或标准的@Priority注释。否则,它们的顺序将遵循容器中相应的目标bean定义的注册顺序。

您可以在目标类级别和@Bean方法上声明@Order注释,可能是针对单个bean定义(如果多个定义使用相同的bean类)。@Order值可能会影响注入点的优先级,但是要注意它们不会影响单例启动顺序,这是由依赖关系和@DependsOn声明决定的正交关系。

注意,标准的jjavax.annotation.Priority在@Bean级别上不能使用Priority注释,因为它不能在方法上声明。它的语义可以通过@Order值与针对每种类型的单个bean上的@Primary组合来建模。

即使是类型化的Map实例,只要期望的键类型是String,也可以自动生成。映射值包含预期类型的所有bean,键包含相应的bean名称,如下例所示:

public class MovieRecommender {

    private Map movieCatalogs;

    @Autowired
    public void setMovieCatalogs(Map movieCatalogs) {
        this.movieCatalogs = movieCatalogs;
    }

    // ...
}

默认情况下,当给定注入点没有匹配的候选bean可用时,自动装配失败。对于声明的数组、集合或映射,至少需要一个匹配的元素。
默认行为是将带注释的方法和字段视为指示所需的依赖项。您可以改变这个行为,如下面的例子所示,使框架能够跳过一个不可满足的注入点,通过标记它为非必需的(例如,,通过设置@Autowired中的required属性为false):

public class SimpleMovieLister {

    private MovieFinder movieFinder;

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

    // ...
}

如果一个非必需的方法的依赖项(或者它的一个依赖项,在有多个参数的情况下)不可用,那么这个方法将不会被调用。在这种情况下,完全不会填充非必需字段,而是保留其默认值。

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

注意:任何给定bean类只有一个构造函数可以声明@Autowired,并将required属性设置为true,这表明构造函数在用作Spring bean时是自动装配的。此外,如果required属性被设置为true,那么只有一个构造函数可以被@Autowired注解。如果多个非必需的构造函数声明注释,它们将被视为自动装配的候选对象。将选择通过匹配Spring容器中的bean来满足最多依赖项的构造函数。如果没有一个候选者可以满足,那么将使用主/默认构造函数(如果存在)。如果一个类一开始只声明一个构造函数,那么它总是会被使用,即使没有注释。带注释的构造函数不一定是公共的。

在setter方法上,建议使用@Autowired的required属性,而不建议使用@Required注释。将required属性设置为false表示该属性对于自动装配目的不是必需的,如果不能自动装配,则忽略该属性。另一方面,@Required更强大,因为它强制通过容器支持的任何方式设置属性,如果没有定义值,则会引发相应的异常。

或者,您可以通过Java 8的Java .util来表示特定依赖项的非必需性质。可选,如下例所示:

public class SimpleMovieLister {

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

从Spring Framework 5.0开始,您还可以使用@Nullable注释(任何包中的任何类型的注释——例如,javax.annotation)。可空从JSR-305)或只是利用Kotlin内置的空安全支持:

public class SimpleMovieLister {

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

您还可以将@Autowired用于众所周知的可解析依赖项:BeanFactory、ApplicationContext、Environment、ResourceLoader、ApplicationEventPublisher和MessageSource。这些接口及其扩展接口(如ConfigurableApplicationContext或ResourcePatternResolver)将自动解析,不需要特殊的设置。下面的例子自动连线一个ApplicationContext对象:

public class MovieRecommender {

    @Autowired
    private ApplicationContext context;

    public MovieRecommender() {
    }

    // ...
}

注意:@Autowired、@Inject、@Value和@Resource注释由Spring BeanPostProcessor实现处理。这意味着您不能在自己的BeanPostProcessor或BeanFactoryPostProcessor类型(如果有的话)中应用这些注释。必须使用XML或Spring @Bean方法显式地“连接”这些类型。

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

由于按类型自动装配可能会产生多个候选对象,因此通常需要对选择过程有更多的控制。实现此目的的一种方法是使用Spring的@Primary注释。@Primary表示,当多个bean是要自动生成单值依赖项的候选bean时,应该优先考虑特定的bean。如果在候选bean中只存在一个主bean,那么它就成为autowired值。

考虑一下下面的配置,它将firstMovieCatalog定义为主MovieCatalog:

@Configuration
public class MovieConfiguration {

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

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

    // ...
}

在前面的配置中,下面的MovieRecommender是由firstMovieCatalog自动生成的:

public class MovieRecommender {

    @Autowired
    private MovieCatalog movieCatalog;

    // ...
}

相应的bean定义如下:




    

    
        
    

    
        
    

    

1.9.4 使用限定符微调基于注释的自动装配

@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定义。




    

    
          //1

        
    

    
         //2

        
    

    

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

对于回退匹配,bean名称被认为是默认的限定符值。因此,您可以使用main的id来定义bean,而不是使用嵌套的qualifier元素,从而得到相同的匹配结果。然而,尽管您可以使用这个约定来通过名称引用特定的bean,但是@Autowired基本上是关于带有可选语义限定符的类型驱动注入的。这意味着限定符值,即使使用了bean名称回退,在类型匹配集内也总是具有收缩语义。他们没有语义表达独特的bean的引用id。良好的限定符值主要EMEA或持久,表达某个特定组件的特点是独立于bean id,这可能是在匿名的情况下自动生成的bean定义等在前面的一个例子。

限定符也适用于类型化集合,如前所述—例如,设置。在这种情况下,根据声明的限定符,所有匹配的bean都作为集合注入。这意味着限定符不必是唯一的。相反,它们构成了过滤标准。例如,您可以使用相同的限定符值“action”定义多个MovieCatalog bean,所有这些都被注入到一个使用@Qualifier(“action”)注释的集合

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

也就是说,如果您打算通过名称来表示注释驱动的注入,那么不要主要使用@Autowired,即使它能够通过bean名称在类型匹配的候选项中进行选择。相反,使用JSR-250 @Resource注释,它的语义定义是通过特定的名称来标识特定的目标组件,声明的类型与匹配过程无关。@Autowired具有相当不同的语义:在根据类型选择候选bean之后,指定的字符串限定符值只在那些类型选择的候选者中被考虑(例如,将一个帐户限定符与标记有相同限定符标签的bean相匹配)。

对于本身定义为集合、映射或数组类型的bean, @Resource是一个很好的解决方案,它通过惟一名称引用特定的集合或数组bean。也就是说,从4.3开始,收集,您可以通过Spring的@Autowired类型匹配算法来匹配映射和数组类型,只要元素类型信息保存在@Bean返回类型签名或收集继承层次结构中。在这种情况下,可以使用限定符值在相同类型的集合中进行选择,如前一段所述。

在4.3中,@Autowired还考虑了注入的自引用(也就是说,回当前注入的bean的引用)。注意,自注入是一种退路。对其他组件的常规依赖始终具有优先级。从这个意义上说,自我推荐不参与常规的候选人选择,因此尤其不属于初选。相反,它们的优先级总是最低的。在实践中,应该只将self引用作为最后的手段(例如,通过bean的事务代理调用同一实例上的其他方法)。在这种情况下,考虑将受影响的方法分解到一个单独的委托bean中。或者,您可以使用@Resource,它可以通过惟一的名称将代理获取到当前bean。

尝试将来自@Bean方法的结果注入到相同的配置类中也是一种有效的自引用场景。要么在方法签名中实际需要的地方(与configuration类中的autowired字段相反)惰性地解析这些引用,要么将受影响的@Bean方法声明为静态的,将它们与包含它们的configuration类实例及其生命周期解耦。否则,只在回退阶段考虑这些bean,而选择其他配置类上的匹配bean作为主要候选(如果可用)。

@Autowired适用于字段、构造函数和多参数方法,允许在参数级别通过限定符注释来缩小范围。相反,@Resource只支持带有单个参数的字段和bean属性setter方法。因此,如果您的注入目标是一个构造函数或一个多参数方法,那么您应该坚持使用限定符。

您可以创建自己的自定义限定符注释。为此,定义一个注释并在定义中提供@Qualifier注释,如下面的示例所示:

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

    String value();
}

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

public class MovieRecommender {

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

    private MovieCatalog comedyCatalog;

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

    // ...
}

接下来,您可以提供候选bean定义的信息。您可以添加标记作为标记的子元素,然后指定类型和值来匹配您的自定义qualifier注释。类型与注释的完全限定类名匹配。另外,为了方便起见,如果不存在名称冲突的风险,可以使用简短的类名。下面的例子演示了这两种方法:




    

    
        
        
    

    
        
        
    

    

在[beans-classpath-scan]中,您可以看到一个基于注释的替代方法,它可以在XML中提供限定符元数据。具体地说,看到[beans-scanning-qualifiers]。
在某些情况下,使用没有值的注释可能就足够了。当注释服务于更一般的用途,并且可以跨多个不同类型的依赖项应用时,这可能很有用。例如,您可以提供一个离线目录,在没有Internet连接时可以搜索该目录。首先,定义简单的注释,如下例所示:

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

}

然后将注释添加到要自动装配的字段或属性中,如下例所示:

public class MovieRecommender {

    @Autowired
    @Offline //1
    private MovieCatalog offlineCatalog;

    // ...
}

//1 这一行添加了@Offline注释。
现在bean定义只需要一个限定符类型,如下面的例子所示:


     //1
    

//1 此元素指定限定符。
您还可以定义自定义限定符注释,除简单值属性外,它还接受命名属性。如果在要自动装配的字段或参数上指定了多个属性值,则bean定义必须匹配所有这些属性值,才能被视为自动装配候选。以下面的注释定义为例:

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

    String genre();

    Format format();
}

在这种情况下,格式是enum,定义如下:

public enum Format {
    VHS, DVD, BLURAY
}

要自动生成的字段使用自定义限定符进行注释,并包含两个属性的值:类型和格式,如下面的示例所示:

public class MovieRecommender {

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

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

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

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

    // ...
}

最后,bean定义应该包含匹配的限定符值。这个示例还演示了可以使用bean元属性代替元素。如果有的话,元素和它的属性是优先的,但是自动装配机制会返回到标签中提供的值,如果没有这样的限定符的话,就像下面例子中的最后两个bean定义一样:




    

    
        
            
            
        
        
    

    
        
            
            
        
        
    

    
        
        
        
    

    
        
        
        
    

===使用泛型作为自动装配限定符
除了@Qualifier注释之外,您还可以使用Java泛型类型作为隐式的限定形式。例如,假设你有以下配置:

@Configuration
public class MyConfiguration {

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

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

假设前面的bean实现了一个泛型接口(即存储和存储),您可以@Autowire存储接口并将泛型用作限定词,如下面的示例所示:

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

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

通用限定符也适用于自动装配列表、映射实例和数组。下面的例子自动装配一个通用列表:

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

使用CustomAutowireConfigurer = = =
customautowirefigurer是一个BeanFactoryPostProcessor,它允许您注册自己的自定义限定符注释类型,即使它们没有使用Spring的@Qualifier注释。下面的例子展示了如何使用customautowirefigurer:


    
        
            example.CustomQualifier
        
    

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

  • 每个bean定义的autowire-candidate值
  • 元素上可用的任何default-autowire-candidate模式
  • @Qualifier注解的存在,以及在customautowirefigurer中注册的任何自定义注解

当多个bean符合自动装配候选时,“主bean”的确定如下:如果候选bean中的一个bean定义的主属性设置为true,则选择它。

===注入@Resource
Spring还通过在字段或bean属性设置器方法上使用JSR-250 @Resource注释(javax.annotation.Resource)来支持注入。这是Java EE中的一种常见模式:例如,在jsf管理的bean和JAX-WS端点中。Spring也支持Spring管理对象的这种模式。
@Resource接受name属性。默认情况下,Spring将该值解释为要注入的bean名。换句话说,它遵循姓名语义,如下例所示:

public class SimpleMovieLister {

    private MovieFinder movieFinder;

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

//1 这一行注入一个@Resource。
如果没有显式指定名称,则默认名称派生自字段名或setter方法。对于字段,它采用字段名。对于setter方法,它采用bean属性名。下面的例子将把名为movieFinder的bean注入到它的setter方法中:

public class SimpleMovieLister {

    private MovieFinder movieFinder;

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

注意:

注释提供的名称由CommonAnnotationBeanPostProcessor所感知的ApplicationContext解析为bean名称。如果显式配置Spring的SimpleJndiBeanFactory,则可以通过JNDI解析这些名称。但是,我们建议您依赖缺省行为并使用Spring的JNDI查找功能来保持间接级别。

在没有指定显式名称且类似于@Autowired的@Resource使用情况下,@Resource会找到一个主类型匹配项,而不是一个特定的已命名bean,并解决众所周知的可解析依赖项:BeanFactory、ApplicationContext、ResourceLoader、ApplicationEventPublisher和MessageSource接口。

因此,在下面的示例中,customerPreferenceDao字段首先查找名为“customerPreferenceDao”的bean,然后返回到customerPreferenceDao类型的主类型匹配:

public class MovieRecommender {

    @Resource
    private CustomerPreferenceDao customerPreferenceDao;

    @Resource
    private ApplicationContext context; //1

    public MovieRecommender() {
    }

    // ...
}

//1上下文字段是基于已知的可解析依赖项类型:ApplicationContext注入的。
使用@ 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 { }

和以下应用程序。属性文件:

catalog.name=MovieCatalog

在这种情况下,catalog参数和字段将等于MovieCatalog值。
Spring提供了一个默认的宽松嵌入式值解析器。它将尝试解析属性值,如果不能解析,属性名(例如${catalog.name})将作为值注入。如果你想严格控制不存在的值,你应该声明一个PropertySourcesPlaceholderConfigurer bean,如下面的例子所示:

@Configuration
public class AppConfig {

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

注意:使用JavaConfig配置PropertySourcesPlaceholderConfigurer时,@Bean方法必须是静态的。

如果无法解析任何${}占位符,则使用上述配置可确保Spring初始化失败。也可以使用setPlaceholderPrefix、setPlaceholderSuffix或setValueSeparator等方法来定制占位符。

注意:默认情况下,Spring引导配置一个PropertySourcesPlaceholderConfigurer bean,它将从application.properties获取属性和application.yml文件。

Spring提供的内置转换器支持自动处理简单的类型转换(例如,转换为Integer或int)。多个逗号分隔的值可以自动转换为字符串数组,而不需要额外的工作。
可以提供一个默认值如下:

@Component
public class MovieRecommender {

    private final String catalog;

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

Spring BeanPostProcessor在后台使用一个ConversionService来处理将@Value中的字符串值转换为目标类型的过程。如果你想为自己的自定义类型提供转换支持,你可以提供自己的ConversionService bean实例,如下例所示:

@Configuration
public class AppConfig {

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

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

@Component
public class MovieRecommender {

    private final String catalog;

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

SpEL还支持使用更复杂的数据结构:

@Component
public class MovieRecommender {

    private final Map countOfMoviesPerCatalog;

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

===使用@PostConstruct和@PreDestroy
CommonAnnotationBeanPostProcessor不仅识别@Resource注释,还识别JSR-250生命周期注释:javax.annotation.PostConstruct 和javax.annotation.PreDestroy。在Spring 2.5中引入了对这些注释的支持,为初始化回调和销毁回调中描述的生命周期回调机制提供了另一种选择。如果CommonAnnotationBeanPostProcessor是在Spring ApplicationContext中注册的,那么在生命周期的同一点上,就会调用携带这些注释之一的方法,即对应的Spring生命周期接口方法或显式声明的回调方法。在下面的例子中,缓存在初始化时被预填充,在销毁时被清除:

public class CachingMovieLister {

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

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

有关组合各种生命周期机制的效果的详细信息,请参阅Combining Lifecycle Mechanisms。

与@Resource一样,@PostConstruct和@PreDestroy注释类型也是JDK 6到8的标准Java库的一部分。但是,整个javax。注释包在JDK 9中与核心Java模块分离,最终在JDK 11中被删除。如果需要,可以使用javax。现在需要通过Maven Central获得注释-api工件,只需将其像其他库一样添加到应用程序的类路径中即可。

==类路径扫描和托管组件
本章的大多数示例使用XML指定配置元数据,这些元数据在Spring容器中生成每个bean定义。上一节(基于注释的容器配置)演示了如何通过源代码级注释提供大量配置元数据。然而,即使在这些示例中,“基础”bean定义也是在XML文件中显式定义的,而注释只驱动依赖项注入。本节描述通过扫描类路径隐式检测候选组件的选项。候选组件是根据筛选条件匹配的类,并且具有注册到容器中的相应bean定义。这样就不需要使用XML来执行bean注册。相反,您可以使用注释(例如,@Component)、AspectJ类型表达式或您自己的自定义筛选条件来选择哪些类具有向容器注册的bean定义。

从Spring 3.0开始,Spring JavaConfig项目提供的许多特性都是Spring核心框架的一部分。这允许您使用Java而不是使用传统的XML文件定义bean。查看@Configuration、@Bean、@Import和@DependsOn注释,了解如何使用这些新特性。

=== @Component和更多的原型注释
@Repository注释是任何满足存储库角色或构造型(也称为数据访问对象或DAO)的类的标记。该标记的用途之一是异常的自动翻译,如异常翻译中所述。

Spring提供了进一步的构造型注解:@Component, @Service和@Controller。@Component是任何spring管理组件的通用构造型。@Repository、@Service和@Controller是@Component对更具体用例(分别在持久性、服务和表示层)的专门化。因此,您可以使用@Component来注释您的组件类,但是,通过使用@Repository、@Service或@Controller来注释它们,您的类更适合通过工具进行处理或与方面相关联。例如,这些原型注释是切入点的理想目标。在Spring框架的未来版本中,@Repository、@Service和@Controller还可以附加语义。因此,如果您在使用@Component或@Service作为服务层之间进行选择,那么@Service显然是更好的选择。类似地,如前所述,@Repository已经被支持作为持久层中自动异常转换的标记。

===使用元注释和复合注释
Spring提供的许多注释可以在您自己的代码中用作元注释。元注释是可以应用于其他注释的注释。例如,前面提到的@Service注释是使用@Component进行元注释的,如下例所示:

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

    // ...
}

//1 组件导致@Service与@Component以相同的方式处理。
您还可以组合元注释来创建“复合注释”。例如,Spring MVC中的@RestController注释由@Controller和@ResponseBody组成。

此外,复合注释可以选择性地从元注释中重新声明属性,以支持自定义。当您只想公开元注释属性的一个子集时,这一点特别有用。例如,Spring的@SessionScope注释将范围名称硬编码到会话中,但仍然允许定制proxyMode。下面的清单显示了SessionScope注释的定义:

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

    /**
     * Alias for {@link Scope#proxyMode}.
     * 

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

你可以使用@SessionScope而不用声明proxyMode如下:

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

你也可以覆盖proxyMode的值,如下面的例子所示:

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

有关详细信息,请参见Spring注释编程模型wiki页面 Spring Annotation Programming Model 。
===自动检测类并注册Bean定义
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  {
    // ...
}

注意:为了简单起见,前面的示例可以使用注释的值属性(即@ComponentScan(“org.example”))。

以下替代方法使用XML:




    

注意:的使用隐含地启用了的功能。当使用时,通常不需要包含元素。

扫描类路径包需要在类路径中存在相应的目录条目。当您使用Ant构建JAR时,请确保您没有激活JAR任务的只文件开关。而且,在某些环境中,类路径目录可能不会根据安全策略公开—例如,JDK 1.7.0_45和更高版本上的独立应用程序(这需要在清单中设置“可信库”---查看 https://stackoverflow.com/questions/19394570/java-jre-7u45-breaks-classloader-getresources)

在JDK 9的模块路径(Jigsaw)上,Spring的类路径扫描通常按预期工作。但是,请确保在模块信息描述符中导出了组件类。如果您希望Spring调用您的类的非公共成员,请确保它们是“打开的”(也就是说,它们在您的模块-信息描述符中使用了一个打开声明,而不是一个导出声明)。

此外,在使用组件扫描元素时,AutowiredAnnotationBeanPostProcessor和CommonAnnotationBeanPostProcessor都是隐式包含的。这意味着这两个组件是自动检测和连接在一起的——所有这些都不需要XML中提供的任何bean配置元数据。

注意:您可以通过包含带false值的annotation-config属性来禁用AutowiredAnnotationBeanPostProcessor和CommonAnnotationBeanPostProcessor的注册。

===使用过滤器自定义扫描
默认情况下,使用@Component、@Repository、@Service、@Controller、@Configuration注释的类,或者本身使用@Component注释的自定义注释是唯一检测到的候选组件。但是,您可以通过应用自定义过滤器来修改和扩展此行为。将它们添加为@ComponentScan注释的includeFilters或excludeFilters属性(或作为XML配置中的 元素的子元素)。每个筛选器元素都需要类型和表达式属性。下表描述了过滤选项:

Table 5. Filter Types
Filter Type Example Expression Description

annotation (default)

org.example.SomeAnnotation

在目标组件的类型级别上呈现或元呈现的注释。

assignable

org.example.SomeClass

目标组件可分配给(扩展或实现)的类(或接口)。

aspectj

org.example..*Service+

要由目标组件匹配的AspectJ类型表达式。

regex

org\.example\.Default.*

由目标组件的类名匹配的正则表达式。

custom

org.example.MyTypeFilter

自定义实现org.springframework.core.type.TypeFilter接口

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

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

下面的清单显示了等价的XML:


    
        
        
    

注意:您还可以通过在注释上设置useDefaultFilters=false或提供use-default-filters="false"作为元素的属性来禁用默认过滤器。这有效地禁用了对带有@Component、@Repository、@Service、@Controller、@RestController或@Configuration的类的自动检测。

===在组件中定义Bean元数据
Spring组件还可以向容器提供bean定义元数据。您可以使用@Configuration注释类中用于定义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定义,该定义有一个引用方法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 Language元素通过符号#{< Expression >}定义属性的值。对于@Value注释,表达式解析器预先配置为在解析表达式文本时查找bean名称。

从Spring Framework 4.3开始,您还可以声明类型为InjectionPoint(或其更具体的子类:DependencyDescriptor)的工厂方法参数来访问触发当前bean创建的请求注入点。注意,这只适用于bean实例的实际创建,而不适用于现有实例的注入。因此,该特性对于原型范围内的bean最有意义。对于其他作用域,工厂方法只看到在给定作用域中触发创建新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类中调用@Bean方法中的方法或字段来创建对协作对象的bean元数据引用的方法。这些方法不是用普通的Java语义调用的,而是通过容器来提供通常的Spring bean的生命周期管理和代理,甚至在通过对@Bean方法的编程调用引用其他bean时也是如此。相反,在普通@Component类中调用@Bean方法中的方法或字段具有标准的Java语义,没有特殊的CGLIB处理或应用其他约束。

您可以将@Bean方法声明为静态方法,这样就可以调用它们,而不需要创建包含它们的配置类作为实例。这在定义后处理器bean(例如,类型为BeanFactoryPostProcessor或BeanPostProcessor)时特别有意义,因为这样的bean在容器生命周期的早期初始化,并且应该避免在那时触发配置的其他部分。

由于技术限制:CGLIB子类只能覆盖非静态方法,所以对静态@Bean方法的调用不会被容器截获,甚至在@Configuration类中也不会(如本节前面所述)。因此,直接调用另一个@Bean方法具有标准的Java语义,从而直接从工厂方法本身返回独立的实例。

@Bean方法的Java语言可见性对Spring容器中的结果bean定义没有直接影响。您可以自由地声明您认为适合于非@ configuration类的工厂方法,也可以在任何地方声明静态方法。然而,@Configuration类中的常规@Bean方法需要被覆盖——也就是说,它们不能被声明为私有或final。

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

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

===命名自动检测到的组件
当一个组件作为扫描过程的一部分被自动检测时,它的bean名称由该扫描程序所知道的BeanNameGenerator策略生成。默认情况下,任何包含名称值的Spring构造型注释(@Component、@Repository、@Service和@Controller)都将该名称提供给相应的bean定义。

如果这样的注释不包含名称值或任何其他检测到的组件(例如由自定义过滤器发现的组件),则默认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 {
    // ...
}

    

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

与通常的spring管理组件一样,自动检测组件的默认和最常见范围是单例的。但是,有时您需要一个可以由@Scope注释指定的不同范围。您可以在注释中提供作用域的名称,如下面的示例所示:

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

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

有关Spring上下文中特定于web的作用域(如“请求”或“会话”)的详细信息,请参阅请求、会话、应用程序和WebSocket作用域。与为这些作用域预先构建的注释一样,您也可以通过使用Spring的元注释方法来编写自己的作用域注释:例如,使用@Scope(“prototype”)进行自定义注释元注释,也可以声明自定义作用域代理模式。

为了提供范围解析的自定义策略,而不是依赖于基于注释的方法,您可以实现ScopeMetadataResolver接口。确保包含一个默认的无参数构造函数。然后,您可以在配置扫描程序时提供完全限定的类名,如下面的注释和bean定义示例所示:

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

    

在使用某些非单例范围时,可能需要为范围对象生成代理。在作用域bean中,将推理描述为依赖项。为此,在组件扫描元素上提供了作用域代理属性。三个可能的值是:no、interface和targetClass。例如,标准JDK动态代理的配置如下:

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

    

===使用注释提供限定符元数据
在使用限定符微调基于注释的自动装配中讨论了@Qualifier注释。本节中的示例演示了如何使用@Qualifier注释和自定义qualifier注释在解析autowire候选对象时提供细粒度控制。因为这些示例是基于XML bean定义的,所以通过使用XML中bean元素的qualifier或meta子元素在候选bean定义上提供qualifier元数据。当依赖类路径扫描来自动检测组件时,您可以在候选类上提供带有类型级注释的限定符元数据。以下三个例子演示了这种技术:

@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在其限定符元数据中提供变体,因为该元数据是按实例而不是按类提供的。

===生成候选组件的索引
虽然类路径扫描非常快,但是可以通过在编译时创建静态候选列表来提高大型应用程序的启动性能。在这种模式下,所有作为组件扫描目标的模块都必须使用这种机制。

注意:现有的@ComponentScan或

要生成索引,需要向每个模块添加一个附加依赖项,其中包含的组件是组件扫描指令的目标。下面的示例展示了如何使用Maven实现这一点:


    
        org.springframework
        spring-context-indexer
        5.2.2.RELEASE
        true
    

在Gradle 4.5及更早版本中,依赖项应该在compileOnly配置中声明,如下例所示:

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

在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退回到常规的类路径安排(就好像根本没有索引一样)。忽略true,不管是作为系统属性还是在spring中。类路径根目录下的属性文件。

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

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


    javax.inject
    javax.inject
    1

===具有@Inject和@Named的依赖项注入
您可以使用@javax.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。此外,您可以将注入点声明为提供者,从而允许按需访问范围较短的bean,或者通过Provider.get()调用对其他bean进行延迟访问。下面的例子提供了前一个例子的一个变体:

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

public class SimpleMovieLister {

    private Provider movieFinder;

    @Inject
    public void setMovieFinder(Provider 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一起使用。可选或@Nullable。这在这里更加适用,因为@Inject没有required属性。下面两个例子展示了如何使用@Inject和@Nullable:

public class SimpleMovieLister {

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

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

=== @Named和@ManagedBean:与@Component注释等价的标准
您可以使用@javax.inject代替@Component。或javax.annotation命名。ManagedBean,如下例所示:

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

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

    private MovieFinder movieFinder;

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

    // ...
}

使用@Component而不指定组件的名称是很常见的。@Named可以以类似的方式使用,如下面的示例所示:

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

@Named
public class SimpleMovieLister {

    private MovieFinder movieFinder;

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

    // ...
}

使用@Named或@ManagedBean时,可以使用与使用Spring注释完全相同的组件扫描,如下面的示例所示:

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

注意:与@Component不同,JSR-330 @Named和JSR-250 ManagedBean注释不能组合。您应该使用Spring的原型模型来构建定制的组件注释。

=== JSR-330标准注释的限制
当您使用标准注释时,您应该知道一些重要的特性是不可用的,如下表所示:

Spring javax.inject.* javax.inject restrictions / comments

@Autowired

@Inject

@Inject没有“required”属性。可以与Java 8的Optional一起使用。

@Component

@Named / @ManagedBean

JSR-330不提供可组合模型,只提供了一种识别命名组件的方法。

@Scope("singleton")

@Singleton

JSR-330默认范围类似于Spring的原型。但是,为了使它与Spring的一般默认值保持一致,在Spring容器中声明的JSR-330 bean在默认情况下是单例的。为了使用单例之外的范围,您应该使用Spring的@Scope注释。javax。inject还提供了一个@Scope注释。尽管如此,本教程仅用于创建您自己的注释。

@Qualifier

@Qualifier / @Named

javax.inject.Qualifier is just a meta-annotation for building custom qualifiers. Concrete Stringqualifiers (like Spring’s @Qualifier with a value) can be associated through javax.inject.Named.

@Value

-

no equivalent

@Required

-

no equivalent

@Lazy

-

no equivalent

ObjectFactory

Provider

javax.inject.Provider是Spring的ObjectFactory的直接替代,只是使用了更短的get()方法名。它还可以与Spring的@Autowired结合使用,或者与非注释的构造函数和setter方法结合使用。

==基于java的容器配置
本节介绍如何在Java代码中使用注释来配置:

  • [beans-java-basic-concepts]

  • [beans-java-instantiating-container]

  • [beans-java-bean-annotation]

  • [beans-java-configuration-annotation]

  • [beans-java-composing-configuration-classes]

  • [beans-definition-profiles]

  • [beans-property-source-abstraction]

  • [beans-using-propertysource]

  • [beans-placeholder-resolution-in-statements]

===基本概念:@Bean和@Configuration
Spring新的java配置支持中的核心构件是@Configuration-annotated类和@Bean-annotated方法。

@Bean注释用于指示方法实例化、配置和初始化要由Spring IoC容器管理的新对象。对于熟悉Spring的 XML配置的人来说,@Bean注释的作用与元素相同。您可以对任何Spring @Component使用@ bean注释的方法。但是,它们最常与@Configuration bean一起使用。

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

@Configuration
public class AppConfig {

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

前面的AppConfig类等价于下面的Spring XML:


    

完整的@Configuration和“精简”的@Bean模式?
当@Bean方法在没有使用@Configuration注释的类中声明时,它们被称为在“lite”模式中处理。在@Component或普通旧类中声明的Bean方法被认为是“精简的”,包含类的主要用途不同,而@Bean方法在这里是一种附加功能。例如,服务组件可以通过每个适用组件类上的附加@Bean方法向容器公开管理视图。在这些场景中,@Bean方法是一种通用的工厂方法机制。

与完整的@Configuration不同,lite @Bean方法不能声明bean之间的依赖关系。相反,它们对包含它们的组件的内部状态以及可能声明的参数(可选)进行操作。因此,这样的@Bean方法不应该调用其他@Bean方法。每个这样的方法实际上只是一个特定bean引用的工厂方法,没有任何特殊的运行时语义。这里的积极的副作用是不需要在运行时应用CGLIB子类,所以在类设计方面没有限制(也就是说,包含的类可能是final,等等)。

在常见的场景中,@Bean方法是在@Configuration类中声明的,以确保总是使用“full”模式,从而将交叉方法引用重定向到容器的生命周期管理。这可以防止通过常规Java调用意外调用相同的@Bean方法,这有助于减少在“lite”模式下操作时难以跟踪的细微bug。

下面几节将深入讨论@Bean和@Configuration注释。但是,我们首先介绍了通过基于java的配置创建spring容器的各种方法。

===使用注释configapplicationcontext实例化Spring容器
下面几节将介绍Spring 3.0中引入的注释configapplicationcontext。这个通用的ApplicationContext实现不仅可以接受@Configuration类作为输入,还可以接受普通的@Component类和使用JSR-330元数据注释的类。

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

当提供@Component和JSR-330类时,它们被注册为bean定义,并假设DI元数据(如@Autowired或@Inject)在这些类中使用。

= = = =结构简单
与在实例化ClassPathXmlApplicationContext时将Spring XML文件用作输入非常相似,您可以在实例化注释configapplicationcontext时将@Configuration类用作输入。这使得Spring容器可以完全不使用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注释类都可以作为输入提供给构造函数,如下例所示:

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。
===使用register(Class…)以编程方式构建容器

您可以使用一个无参数的构造函数来实例化一个带注释的configapplicationcontext,然后使用register()方法来配置它。当以编程方式构建一个带注释的configapplicationcontext时,这种方法特别有用。下面的例子演示了如何做到这一点:

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

===启用扫描组件(字符串…)
要启用组件扫描,您可以将@Configuration类注释如下:

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

//1 该注释支持组件扫描。

注意:有经验的Spring用户可能熟悉与Spring上下文等价的XML声明:namespace,如下例所示:


    

在前面的例子中,com。扫描acme包以查找任何@Component-annotated类,这些类在容器中注册为Spring bean定义。AnnotationConfigApplicationContext公开了scan(String…)方法,允许使用相同的组件扫描功能,如下面的示例所示:

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

注意:请记住@Configuration类是用@Component进行元注释的,因此它们是组件扫描的候选对象。在前面的例子中,假设AppConfig是在com中声明的。acme包(或下面的任何包),它在调用scan()期间被获取。在refresh()中,它的所有@Bean方法都将作为bean定义在容器中进行处理和注册。

===支持带有注释configwebapplicationcontext的Web应用程序
注释configapplicationcontext的WebApplicationContext变体可以与注释configwebapplicationcontext一起使用。您可以在配置Spring ContextLoaderListener servlet侦听器、Spring MVC DispatcherServlet等时使用这个实现。下面的web.xml片段配置了一个典型的Spring MVC web应用程序(注意上下文-param和init-param的使用):


    
    
        contextClass
        
            org.springframework.web.context.support.AnnotationConfigWebApplicationContext
        
    

    
    
        contextConfigLocation
        com.acme.AppConfig
    

    
    
        org.springframework.web.context.ContextLoaderListener
    

    
    
        dispatcher
        org.springframework.web.servlet.DispatcherServlet
        
        
            contextClass
            
                org.springframework.web.context.support.AnnotationConfigWebApplicationContext
            
        
        
        
            contextConfigLocation
            com.acme.web.MvcConfig
        
    

    
    
        dispatcher
        /app/*
    

===使用@Bean注释
@Bean是方法级别的注释,是XML 元素的直接模拟。注释支持提供的一些属性,比如:* init-method * destroy-method * autowiring * name。
您可以在@Configuration-annotated类或@Component-annotated类中使用@Bean注释。
===声明一个Bean

要声明一个bean,您可以使用@Bean注释来注释一个方法。您可以使用此方法在作为方法返回值指定的类型的ApplicationContext中注册bean定义。默认情况下,bean名与方法名相同。下面的例子显示了一个@Bean方法声明:

@Configuration
public class AppConfig {

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

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


    

这两个声明都使一个名为transferService的bean在ApplicationContext中可用,绑定到类型为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的依赖项
@ bean注释的方法可以有任意数量的参数来描述构建该bean所需的依赖关系。例如,如果我们的TransferService需要一个AccountRepository,我们可以用一个方法参数来实现这个依赖,如下面的例子所示:

@Configuration
public class AppConfig {

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

解析机制与基于构造的依赖注入非常相似。有关详细信息,请参阅相关部分。
===接收生命周期回调
使用@Bean注释定义的任何类都支持常规的生命周期回调,并且可以使用JSR-250中的@PostConstruct和@PreDestroy注释。请参阅JSR-250 annotations 了解更多细节。

也完全支持常规的Spring生命周期回调。如果一个bean实现了InitializingBean、DisposableBean或Lifecycle,容器将调用它们各自的方法。
标准的感知接口集(如BeanFactoryAware、BeanNameAware、MessageSourceAware、applicationcontext ware等)也得到了完全支持。
@Bean注释支持指定任意初始化和销毁回调方法,很像Spring XML在bean元素上的init-method和destroy-method属性,如下面的示例所示:

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

注意:默认情况下,使用具有公共关闭或关闭方法的Java配置定义的bean将被自动登记到销毁回调中。如果您有一个公共关闭或关闭方法,并且您不希望在容器关闭时调用它,那么您可以向bean定义中添加@Bean(destroyMethod="")来禁用默认(推断)模式。

在缺省情况下,您可能希望对使用JNDI获得的资源这样做,因为它的生命周期是在应用程序之外管理的。特别要注意的是,一定要始终对一个数据源这样做,因为在Java EE应用服务器上这样做是有问题的。
下面的例子展示了如何防止一个数据源的自动销毁回调:

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

@ bean方法,您通常使用程序化的JNDI查找,通过使用Spring的JndiTemplate JndiLocatorDelegate助手或直接使用JNDI InitialContext但不是JndiObjectFactoryBean变体(这将迫使你声明返回类型作为FactoryBean类型,而不是实际的目标类型,因此很难使用交叉引用调用@ bean方法,打算在其他参考所提供的资源)。

对于上例中的BeanOne,在构造期间直接调用init()方法同样有效,如下例所示:

@Configuration
public class AppConfig {

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

    // ...
}

注意:当您直接在Java中工作时,您可以对对象做任何您喜欢的事情,而不总是需要依赖容器生命周期。

===指定Bean作用域
Spring包含@Scope注释,因此您可以指定bean的范围。
====使用@Scope注释
您可以指定使用@Bean注释定义的bean应该具有特定的范围。您可以使用Bean作用域部分中指定的任何标准作用域。
默认的范围是单例的,但是你可以用@Scope注释来覆盖它,如下面的例子所示:

@Configuration
public class MyConfiguration {

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

==== @Scope和scoped-proxy
Spring通过作用域代理提供了处理作用域依赖项的方便方法。使用XML配置时创建这样一个代理的最简单方法是元素。用@Scope注释在Java中配置bean可以提供与proxyMode属性相同的支持。默认值是no proxy (ScopedProxyMode. no),但是您可以指定ScopedProxyMode。TARGET_CLASS或ScopedProxyMode.INTERFACES。

如果您使用Java将XML参考文档中的作用域代理示例(参见作用域代理)移植到我们的@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;
}

====自定义Bean命名
默认情况下,配置类使用@Bean方法的名称作为结果bean的名称。但是,可以使用name属性覆盖此功能,如下面的示例所示:

@Configuration
public class AppConfig {

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

= = = =Bean混叠
正如在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描述
有时,提供bean的更详细的文本描述是有帮助的。当出于监控目的而公开bean(可能通过JMX)时,这一点特别有用。
要向@Bean添加描述,可以使用@Description注释,如下面的示例所示:

@Configuration
public class AppConfig {

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

===使用@Configuration注释
@Configuration是一个类级注释,指示对象是bean定义的源。@Configuration类通过公共的@Bean注释方法声明bean。在@Configuration类上调用@Bean方法也可以用来定义bean之间的依赖关系。有关一般介绍,请参见[bean -java-基本概念]。

===注入bean间的依赖关系
当bean相互依赖时,表达这种依赖关系就像让一个bean方法调用另一个bean方法一样简单,如下面的例子所示:

@Configuration
public class AppConfig {

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

    @Bean
    public BeanTwo beanTwo() {
        return new BeanTwo();
    }
}

在前面的示例中,beanOne通过构造函数注入接收对beanTwo的引用。

注意:只有在@Configuration类中声明@Bean方法时,这种声明bean间依赖关系的方法才有效。您不能通过使用普通的@Component类来声明bean之间的依赖关系。

====查找方法注入
如前所述,查找方法注入是一个高级特性,应该很少使用。它在单作用域bean依赖于原型作用域bean的情况下非常有用。使用Java进行这种类型的配置为实现这种模式提供了一种自然的方法。下面的例子展示了如何使用查找方法注入:

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

    // okay... but where is the implementation of this method?
    protected abstract Command createCommand();
}

通过使用Java配置,您可以创建CommandManager的子类,其中以查找新的(原型)命令对象的方式覆盖了抽象的createCommand()方法。下面的例子演示了如何做到这一点:

@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();
        }
    }
}

===关于基于java的配置如何在内部工作的更多信息
考虑下面的例子,它显示了一个@Bean注释的方法被调用两次:

@Configuration
public class AppConfig {

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

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

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

在clientService1()中调用一次clientDao(),在clientService2()中调用一次。因为这个方法创建了一个新的ClientDaoImpl实例并返回它,所以您通常会希望有两个实例(每个服务一个)。这肯定会有问题:在Spring中,实例化的bean默认有一个单例范围。这就是神奇之处:所有@Configuration类都是在启动时用CGLIB子类化的。在子类中,子方法在调用父方法并创建新实例之前,首先检查容器是否有缓存的(作用域限定的)bean

注意:根据bean的范围,行为可能会有所不同。我们这里说的是单例。

从Spring 3.2开始,不再需要将CGLIB添加到类路径中,因为CGLIB类已经在org.springframework下重新打包了。cglib并直接包含在spring-core JAR中。

由于CGLIB在启动时动态添加特性,所以有一些限制。特别是,配置类不能是final。但是,从4.3开始,配置类上可以使用任何构造函数,包括使用@Autowired或单个非默认构造函数声明进行默认注入。

如果希望避免cglib强加的限制,可以考虑在非@ @配置类上声明@Bean方法(例如,在普通的@Component类上声明)。然后不会拦截@Bean方法之间的交叉方法调用,因此必须完全依赖于构造函数或方法级别的依赖注入。

===组合基于java的配置
Spring的基于java的配置特性允许您编写注释,这可以减少配置的复杂性。
====使用@Import注释
与在Spring XML文件中使用元素来帮助模块化配置一样,@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类。

注意:从Spring Framework 4.2开始,@Import也支持对常规组件类的引用,类似于注释configapplicationcontext。注册方法。如果您想避免组件扫描,这是特别有用的,通过使用一些配置类作为入口点来显式定义所有组件。

====注入导入的@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。这些方法通常应该声明为静态@Bean方法,而不是触发其包含的配置类的实例化。否则,@Autowired和@Value不会在配置类本身上工作,因为它是作为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");
}

注意:只有在Spring Framework 4.3中才支持@Configuration类中的构造函数注入。还请注意,如果目标bean只定义一个构造函数,则不需要指定@Autowired。

Fully-qualifying imported beans for ease of navigation

在前面的场景中,使用@Autowired可以很好地工作,并提供了所需的模块化,但是准确地确定在哪声明了autowired bean的定义仍然是有些模棱两可的。例如,作为一名关注ServiceConfig的开发人员,您如何知道@Autowired AccountRepository 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类及其依赖项与导航基于接口的代码的通常过程没有什么不同。

注意:如果你想影响某些豆类的启动创建订单,考虑将其中一些声明为@Lazy(用于创建在第一次访问,而不是在启动时)或@DependsOn某些其他bean(确保特定的其他bean创建当前bean之前,超出后者的直接依赖关系暗示)。

====有条件地包含@Configuration类或@Bean方法
根据任意的系统状态,有条件地启用或禁用一个完整的@Configuration类,甚至单个的@Bean方法,这通常很有用。一个常见的例子是,只有在Spring环境中启用了特定的配置文件时,才使用@Profile注释来激活bean(有关详细信息,请参阅[beans-definition-profiles])。

@Profile注释实际上是通过使用一个灵活得多的名为@Conditional的注释实现的。@条件注释表示特定的org.springframework.context.annotation。在注册@Bean之前应该咨询的条件实现。
条件接口的实现提供一个返回true或false的matches(…)方法。例如,下面的清单显示了用于@Profile的实际条件实现:

@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
    // Read the @Profile annotation attributes
    MultiValueMap 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;
}

有关更多细节,请参见@Conditional javadoc。
===组合Java和XML配置

Spring的@Configuration类支持的目标不是100%完全替代Spring XML。一些工具(如Spring XML namespaces)仍然是配置容器的理想方法。在XML方便或必要的情况下,你有一个选择:要么在容器实例化在一个“以XML为中心”的方式使用,例如,ClassPathXmlApplicationContext或实例化它“以java为中心”的方式通过使用所和@ImportResource注释导入XML。

以xml为中心使用@Configuration类
最好是从XML引导Spring容器,并以特别的方式包含@Configuration类。例如,在使用Spring XML的大型现有代码库中,更容易根据需要创建@Configuration类,并从现有XML文件中包含它们。在本节的后面,我们将讨论在这种“以xml为中心”的情况下使用@Configuration类的选项。

将@Configuration类声明为普通Spring 元素
请记住,@Configuration类最终是容器中的bean定义。在本系列的示例中,我们创建了一个名为AppConfig的@Configuration类,并将其作为定义包含在system-test-config.xml中。因为打开了,容器识别@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());
    }
}

下面的示例显示了示例系统-test-config.xml文件的一部分:


    
    
    

    

    
        
        
        
    

下面的示例显示了一种可能的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 没有声明id元素。虽然这样做是可以接受的,但这是不必要的,因为没有其他bean引用它,而且不太可能通过名称显式地从容器中获取它。类似地,DataSource bean只根据类型进行自动生成,因此并不严格要求显式bean id。

使用获取@Configuration类

因为@Configuration使用@Component进行元注释,所以带有@Configuration注释的类自动成为组件扫描的候选类。使用与前面示例相同的场景,我们可以重新定义system-test-config.xml,以利用组件扫描。注意,在本例中,我们不需要显式地声明,因为启用了相同的功能。
下面的示例显示了修改后的system-test-config.xml文件:


    
    
    

    
        
        
        
    

以配置类为中心使用XML和@ImportResource
在@Configuration类是配置容器的主要机制的应用程序中,仍然可能需要至少使用一些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
properties-config.xml

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

= =环境抽象
环境接口是集成在容器中的抽象,它对应用程序环境的两个关键方面建模:概要文件和属性。
配置文件是一组命名的逻辑bean定义,只有在给定的配置文件处于活动状态时才向容器注册。可以将bean分配给配置文件,不管它是用XML定义的还是用注释定义的。与概要文件相关的环境对象的作用是确定哪些概要文件(如果有的话)当前是活动的,以及哪些概要文件(如果有的话)在默认情况下应该是活动的。

属性在几乎所有的应用程序中都扮演着重要的角色,它们可能来自各种各样的来源:属性文件、JVM系统属性、系统环境变量、JNDI、servlet上下文参数、特别属性对象、映射对象等等。与属性相关的环境对象的作用是为用户提供一个方便的服务接口,用于配置属性源并从中解析属性。

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

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

考虑实际应用程序中需要数据源的第一个用例。在测试环境中,配置可能类似于以下内容:

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

现在考虑如何将此应用程序部署到QA或生产环境中,假设应用程序的数据源是在生产应用程序服务器的JNDI目录中注册的。我们的数据源bean现在看起来像下面的清单:

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

问题是如何根据当前环境在使用这两种变体之间进行切换。随着时间的推移,Spring用户已经设计了许多方法来实现这一点,通常依赖于系统环境变量和包含${占位符}标记的XML 语句的组合,这些标记根据环境变量的值解析为正确的配置文件路径。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 & us-east)。配置文件表达式支持以下操作符:

  • !:一个合乎逻辑的“不是”的配置文件
  • &:一个符合逻辑的“和”的配置文件
  • |:配置文件的逻辑“或”

注意:如果不使用括号,就不能混合使用&和|操作符。例如,production & us-east | eu-central不是有效的表达式。它必须表示为生产& (us-east | eu-central)。

您可以使用@Profile作为 meta-annotation,以创建自定义组合注释。下面的例子定义了一个自定义的@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(例如,一个特定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");
    }
}

=== XML Bean定义配置文件
XML对应项是元素的配置文件属性。我们前面的示例配置可以在两个XML文件中重写,如下所示:



    
        
        
    


    

也可以避免在同一个文件中分割和嵌套元素,如下面的例子所示:



    

    
        
            
            
        
    

    
        
    

spring-bean.xsd被限制为只允许文件中的最后一个元素。这应该有助于提供灵活性,而不会在XML文件中引起混乱。

XML对等物不支持前面描述的概要表达式。但是,可以使用!操作符。也可以通过嵌套配置文件来应用逻辑“和”,如下面的例子所示:



    

    
        
            
        
    

在前面的示例中,如果生产和us-east配置文件都处于活动状态,则公开数据源bean。

===激活Profile
现在我们已经更新了配置,我们仍然需要告诉Spring哪个配置文件是活动的。如果我们现在启动我们的示例应用程序,我们将看到抛出一个NoSuchBeanDefinitionException,因为容器无法找到名为dataSource的Spring bean。

可以通过几种方式激活配置文件,但最直接的方式是通过ApplicationContext提供的环境API以编程方式激活配置文件。下面的例子演示了如何做到这一点:

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

此外,您还可以通过spring.profiles声明性地激活配置文件。活动属性,可以通过系统环境变量、JVM系统属性、web中的servlet上下文参数来指定。或者甚至作为JNDI中的一个条目(参见 [beans-property-source-abstraction])。在集成测试中,可以通过使用spring-test模块中的@ActiveProfiles注释来声明活动概要文件(参见context configuration with environment profiles)。

请注意,配置文件不是一个“非此即彼”的命题。您可以同时激活多个配置文件。通过编程,您可以向setActiveProfiles()方法提供多个配置文件名称,该方法接受String…下面的例子激活多个配置文件:

ctx.getEnvironment().setActiveProfiles("profile1", "profile2");

声明,spring.profiles.active可以接受逗号分隔的配置文件名称列表,如下例所示:
-Dspring.profiles.active = " profile1 profile2”
= = = =默认的配置
默认配置文件表示默认启用的配置文件。考虑下面的例子:

@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();
    }
}

如果没有激活配置文件,则创建数据源。您可以将此视为为一个或多个bean提供默认定义的一种方式。如果启用了任何配置文件,则不应用默认配置文件。
您可以通过在环境中使用setDefaultProfiles()来更改默认配置文件的名称,或者通过声明的方式,通过使用spring.profiles.default属性来更改名称。

= = = PropertySource抽象
Spring的环境抽象在可配置的属性源层次结构上提供搜索操作。考虑以下清单:

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属性。为了回答这个问题,环境对象对一组PropertySource对象执行搜索。PropertySource是对任何键值对源的简单抽象,Spring的标准环境配置了两个PropertySource对象——一个表示JVM系统属性集(system . getproperties()),另一个表示系统环境变量集(system .getenv())。

使用@PropertySource = = =
@PropertySource注释为向Spring的环境添加PropertySource提供了一种方便的声明性机制。
给定一个名为app.properties的文件,其中包含键-值对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资源位置中出现的任何${…}占位符都将根据已经在环境中注册的属性源集进行解析,如下面的示例所示:

假设我的。占位符出现在已经注册的一个属性源中(例如,系统属性或环境变量),占位符被解析为相应的值。如果没有,则使用default/path作为默认值。如果没有指定默认值,并且无法解析属性,则抛出IllegalArgumentException。

注意:根据Java 8的约定,@PropertySource注释是可重复的。但是,所有这些@PropertySource注释都需要在相同的级别上声明,可以直接在configuration类上声明,也可以作为相同自定义注释中的元注释声明。不建议混合使用直接注释和元注释,因为直接注释可以有效地覆盖元注释。

===语句中的占位符解析
从历史上看,元素中占位符的值只能根据JVM系统属性或环境变量来解析。现在情况已经不同了。因为环境抽象是在整个容器中集成的,所以很容易通过它来路由占位符的解析。这意味着您可以以任何您喜欢的方式配置解析过程。您可以更改通过系统属性和环境变量进行搜索的优先级,或者完全删除它们。您还可以适当地将自己的属性源添加到组合中。
具体来说,下面的语句不管在哪里定义客户属性,只要它在环境中可用:


    

==注册LoadTimeWeaver
Spring使用LoadTimeWeaver在类加载到Java虚拟机(JVM)时动态地转换它们。
要启用加载时编织,可以将@ enableloadtime织造添加到@Configuration类之一,如下面的示例所示:

@Configuration
@EnableLoadTimeWeaving
public class AppConfig {
}

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


    

一旦为ApplicationContext配置好,该ApplicationContext中的任何bean都可以实现LoadTimeWeaverAware,从而接收对加载时编织器实例的引用。这在与 Spring’s JPA support相结合时特别有用,因为在JPA类转换中加载时编织可能是必需的。有关更多细节,请咨询LocalContainerEntityManagerFactoryBean javadoc。有关AspectJ加载时编织的更多信息,请参见[aop- j-ltw]。

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

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

  • 通过MessageSource接口访问i18n风格的消息。
  • 通过ResourceLoader接口访问资源,例如url和文件。
  • 事件发布,即通过使用ApplicationEventPublisher接口实现ApplicationListener接口的bean。
  • 通过层次结构加载多个(层次结构)上下文,使每个上下文都集中于一个特定的层,例如应用程序的web层

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

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

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

Spring提供了两种MessageSource实现,ResourceBundleMessageSource和StaticMessageSource。两者都实现了层次化的messagesource以实现嵌套消息传递。StaticMessageSource很少使用,但提供了向源添加消息的编程方法。下面的例子显示了ResourceBundleMessageSource:


    
        
            
                format
                exceptions
                windows
            
        
    

下一个示例显示了一个执行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);
}

下一个示例显示传递给消息查找的参数。这些参数被转换成字符串对象并插入到查找消息中的占位符中。



    
    
        
    

    
    
        
    

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的各种MessageSource实现遵循与标准JDK ResourceBundle相同的地区解析和回退规则。简而言之,继续前面定义的示例messageSource,如果您希望根据英国(en-GB)地区解析消息,您将创建名为format_en_GB.propertiesexceptions_en_GB.properties, 和windows_en_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的引用。在创建和配置bean时,在实现MessageSourceAware接口的ApplicationContext中定义的任何bean都将被注入应用程序上下文的MessageSource。

注意:作为ResourceBundleMessageSource的替代,Spring提供了一个ReloadableResourceBundleMessageSource类。此变体支持相同的包文件格式,但比基于标准JDK的ResourceBundleMessageSource实现更灵活。特别是,它允许从任何Spring资源位置读取文件(不仅仅是从类路径),并支持热重载bundle属性文件(同时有效地在中间缓存它们)。有关详细信息,请参见ReloadableResourceBundleMessageSource javadoc。

===标准和自定义事件
ApplicationContext中的事件处理是通过ApplicationEvent类和ApplicationListener接口提供的。如果将实现ApplicationListener接口的bean部署到上下文中,每当将ApplicationEvent发布到ApplicationContext时,都会通知该bean。本质上,这是标准的观察者设计模式。

注意:从Spring 4.2开始,事件基础设施得到了显著的改进,并提供了基于注释的模型以及发布任意事件的能力(即不需要从ApplicationEvent扩展的对象)。当这样一个对象被发布时,我们将它包装成一个事件。

下表描述了Spring提供的标准事件:

Table 7. Built-in Events
Event Explanation

ContextRefreshedEvent

在初始化或刷新ApplicationContext时发布(例如,通过使用ConfigurableApplicationContext接口上的refresh()方法)。在这里,“初始化”意味着加载所有bean,检测并激活后处理器bean,预实例化单例,以及ApplicationContext对象已准备好使用。只要上下文没有关闭,就可以多次触发刷新,前提是所选的ApplicationContext实际上支持这样的“热”刷新。例如,XmlWebApplicationContext支持热刷新,但是GenericApplicationContext不支持。

ContextStartedEvent

通过在ConfigurableApplicationContext接口上使用start()方法启动ApplicationContext时发布。这里,“启动”意味着所有生命周期bean都接收一个显式的启动信号。通常,此信号用于在显式停止之后重新启动bean,但也可以用于启动尚未配置为自动启动的组件(例如,尚未在初始化时启动的组件)。

ContextStoppedEvent

通过在ConfigurableApplicationContext接口上使用stop()方法停止ApplicationContext时发布。这里,“停止”意味着所有生命周期bean都接收一个显式的停止信号。可以通过start()调用重新启动已停止的上下文。

ContextClosedEvent

通过在ConfigurableApplicationContext接口上使用close()方法或通过JVM关闭挂钩关闭ApplicationContext时发布。这里,“关闭”意味着所有的单例bean都将被销毁。一旦上下文关闭,它就会到达生命的终点,无法刷新或重新启动。

RequestHandledEvent

一个特定于web的事件,通知所有bean HTTP请求已得到服务。此事件在请求完成后发布。此事件仅适用于使用Spring的DispatcherServlet的web应用程序。

ServletRequestHandledEvent

RequestHandledEvent的子类,用于添加特定于servlet的上下文信息。

您还可以创建和发布自己的自定义事件。下面的例子展示了一个简单的类,它扩展了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的类并将其注册为Spring bean来实现的。下面的例子展示了这样一个类:

public class EmailService implements ApplicationEventPublisherAware {

    private List blackList;
    private ApplicationEventPublisher publisher;

    public void setBlackList(List 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的类,并将其注册为一个Spring bean。下面的例子展示了这样一个类:

public class BlackListNotifier implements ApplicationListener {

    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以获得Spring的ApplicationEventMulticaster接口和配置选项的SimpleApplicationEventMulticaster实现。

下面的例子展示了用于注册和配置上述每个类的bean定义:


    
        
            [email protected]
            [email protected]
            [email protected]
        
    



    

总之,当调用emailService bean的sendEmail()方法时,如果有任何应该列入黑名单的电子邮件消息,则会发布BlackListEvent类型的自定义事件。blackListNotifier bean注册为ApplicationListener并接收BlackListEvent,此时它可以通知相关方。

注意:Spring的事件机制是为同一应用程序上下文中Spring bean之间的简单通信而设计的。然而,对于更复杂的企业集成需求,单独维护的Spring integration项目提供了对构建轻量级、面向模式、事件驱动的体系结构的完整支持,这些体系结构构建于著名的Spring编程模型之上。

===基于注释的事件监听器
从Spring 4.2开始,您可以使用@EventListener注释在托管bean的任何公共方法上注册事件监听器。黑名单通知可以重写如下:

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 expression 的注释的条件属性来添加额外的运行时筛选,该注释属性应该与实际调用特定事件的方法相匹配。
下面的例子展示了如何重写我们的通知程序,只有在事件的内容属性等于my-event时才能被调用:

@EventListener(condition = "#blEvent.content == 'my-event'")
public void processBlackListEvent(BlackListEvent blEvent) {
    // notify appropriate parties via notificationAddress...
}

每个SpEL表达式根据专用上下文计算。下表列出了对上下文可用的项,以便您可以将它们用于条件事件处理:

Name Location Description Example

Event

root object

The actual ApplicationEvent.

#root.event or event

Arguments array

root object

The arguments (as an object array) used to invoke the method.

#root.args or argsargs[0] to access the first argument, etc.

Argument name

evaluation context

The name of any of the method arguments. If, for some reason, the names are not available (for example, because there is no debug information in the compiled byte code), individual arguments are also available using the #a<#arg> syntax where <#arg> stands for the argument index (starting from 0).

#blEvent or #a0 (you can also use #p0 or #p<#arg> parameter notation as an alias)

= = = =异步监听
如果希望特定的侦听器异步处理事件,可以重用常规的@Async支持。下面的例子演示了如何做到这一点:

@EventListener
@Async
public void processBlackListEvent(BlackListEvent event) {
    // BlackListEvent is processed in a separate thread
}

在使用异步事件时要注意以下限制:

  • 如果异步事件侦听器抛出异常,则不会将其传播到调用者。有关更多细节,请参见AsyncUncaughtExceptionHandler。
  • 异步事件监听器方法不能通过返回值来发布后续事件。如果您需要作为处理的结果发布另一个事件,请注入ApplicationEventPublisher来手动发布事件。

= = = =Ordering Listeners
如果需要在调用另一个侦听器之前调用一个侦听器,可以在方法声明中添加@Order注释,如下面的示例所示:

@EventListener
@Order(42)
public void processBlackListEvent(BlackListEvent event) {
    // notify appropriate parties via notificationAddress...
}

= = = =一般事件
还可以使用泛型进一步定义事件的结构。考虑使用EntityCreatedEvent,其中T是创建的实际实体的类型。例如,您可以创建以下侦听器定义来仅接收一个人的EntityCreatedEvent:

@EventListener
public void onPersonCreated(EntityCreatedEvent event) {
    // ...
}

由于类型擦除,只有在触发的事件解析事件侦听器筛选的通用参数时才有效(即,类似于类PersonCreatedEvent扩展EntityCreatedEvent{…})。

在某些情况下,如果所有事件都遵循相同的结构,这可能会变得非常繁琐(上例中的事件应该如此)。在这种情况下,您可以实现ResolvableTypeProvider来指导框架超越运行时环境所提供的功能。以下事件演示了如何做到这一点:

public class EntityCreatedEvent extends ApplicationEvent implements ResolvableTypeProvider {

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

    @Override
    public ResolvableType getResolvableType() {
        return ResolvableType.forClassWithGenerics(getClass(), ResolvableType.forInstance(getSource()));
    }
}

注意:这不仅适用于ApplicationEvent,也适用于作为事件发送的任意对象。

===方便地访问底层资源
为了最佳地使用和理解应用程序上下文,您应该熟悉Spring的资源抽象,如 [resources]中所述。
应用程序上下文是ResourceLoader,可用于加载资源对象。资源本质上是JDK java.net.URL类的一个功能更丰富的版本。实际上,资源的实现在适当的地方封装了java.net.URL的一个实例。资源可以以透明的方式从几乎任何位置获取底层资源,包括类路径、文件系统位置、任何可以用标准URL描述的位置,以及其他一些变体。如果资源位置字符串是没有任何特殊前缀的简单路径,那么这些资源的来源是特定的,并且适合于实际的应用程序上下文类型。

您可以配置部署到应用程序上下文中的bean,以实现特殊的回调接口ResourceLoaderAware,以便在初始化时自动回调,并将应用程序上下文本身作为ResourceLoader传递进来。您还可以公开Resource类型的属性,用于访问静态资源。它们像其他属性一样被注入其中。您可以将这些资源属性指定为简单的字符串路径,并依赖于部署bean时从这些文本字符串到实际资源对象的自动转换。

提供给ApplicationContext构造函数的位置路径实际上是资源字符串,并且以简单的形式根据特定的上下文实现进行适当的处理。例如,ClassPathXmlApplicationContext将简单的位置路径作为classpath位置。您还可以使用带有特殊前缀的位置路径(资源字符串)来强制从类路径或URL加载定义,而不管实际的上下文类型。

===方便的Web应用程序的ApplicationContext实例化
您可以通过使用(例如)ContextLoader来声明性地创建ApplicationContext实例。当然,您也可以通过使用ApplicationContext实现之一以编程方式创建ApplicationContext实例。

你可以使用ContextLoaderListener注册一个ApplicationContext,如下面的例子所示:


    contextConfigLocation
    /WEB-INF/daoContext.xml /WEB-INF/applicationContext.xml



    org.springframework.web.context.ContextLoaderListener

侦听器检查contextConfigLocation参数。如果该参数不存在,侦听器将使用/WEB-INF/applicationContext.xml作为默认值。当参数存在时,侦听器使用预定义的分隔符(逗号、分号和空格)分隔字符串,并将这些值用作搜索应用程序上下文的位置。还支持ant样式的路径模式。例如/WEB-INF/*Context.xml(用于所有名称以Context.xml结尾并驻留在WEB-INF目录中的文件)和/WEB-INF/**/*Context.xml(对于WEB-INF的任何子目录中的所有此类文件)

将Spring ApplicationContext部署为Java EE RAR文件
可以将Spring ApplicationContext部署为RAR文件,将上下文及其所需的所有bean类和库jar封装在Java EE RAR部署单元中。这相当于引导一个独立的ApplicationContext(仅在Java EE环境中托管)来访问Java EE服务器设施。RAR部署是部署无头WAR文件的一种更自然的替代方案—实际上,没有任何HTTP入口点的WAR文件仅用于在Java EE环境中引导Spring ApplicationContext。

RAR部署非常适合不需要HTTP入口点,而只由消息端点和计划的作业组成的应用程序上下文。在这种上下文中,bean可以使用应用服务器资源,如JTA事务管理器和JNDI绑定的JDBC数据源实例和JMS ConnectionFactory实例,还可以向平台的JMX服务器注册——所有这些都是通过Spring的标准事务管理和JNDI和JMX支持设施实现的。应用程序组件还可以通过Spring的TaskExecutor抽象与应用服务器的JCA WorkManager交互。

有关RAR部署中涉及的配置细节,请参阅SpringContextResourceAdapter类的javadoc。
对于一个简单的部署Spring ApplicationContext作为一个Java EE RAR文件:

1.所有应用程序的类打包成一个RAR文件(这是一个标准的JAR文件与不同的文件扩展名)。阀门所有必需的库JAR RAR存档的根源。阀门一个meta - inf / ra.xml部署描述符(SpringContextResourceAdapter javadoc所示)和相应的Spring XML bean定义文件(s)(通常是meta - inf /中)。
2。将结果RAR文件放入应用服务器的部署目录。

注意:这种RAR部署单元通常是自包含的。它们不向外界公开组件,甚至不向同一应用程序的其他模块公开。与基于rar的ApplicationContext的交互通常通过与其他模块共享的JMS目的地进行。例如,基于rar的ApplicationContext还可以调度一些作业或对文件系统中的新文件(或类似的东西)做出响应。如果它需要允许来自外部的同步访问,它可以(例如)导出RMI端点,这些端点可能被同一机器上的其他应用程序模块使用。

 

 

 

你可能感兴趣的:(Spring,文档,Spring,Ioc)