Spring框架5.3.27官方文档(中文翻译)—— 核心技术-IoC容器(二)

1,348 阅读44分钟

1.IoC容器

最近发现了一个Spring的中文网,文档翻译的很全,故后续不再继续翻译文档。

文章列表

原文:docs.spring.io/spring-fram…

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

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

1.6.1. 生命周期回调(Lifecycle Callbacks)

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

JSR-250 @PostConstruct@PreDestroy注解通常被认为是在现代Spring应用程序中接收生命周期回调的最佳实践。使用这些注解意味着您的bean没有耦合到特定于spring的接口。详情请参见使用@PostConstruct@PreDestroy。如果不希望使用JSR-250注解,但仍希望消除耦合,请考虑初始化方法和销毁方法bean定义元数据。

补充

实现bean的生命周期管理有三种方式:

  • 实现InitializingBeanDisposableBean,存在和Spring接口耦合,不推荐使用
  • 使用JSR-250的@PostConstruct@PreDestroy,将bean和特定接口解耦
  • 使用XML的init-methoddestroy-method

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

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

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

初始化回调(Initialization Callbacks)

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

public interface InitializingBean {
	void afterPropertiesSet() throws Exception;
}

我们建议不要使用InitializingBean接口,因为它不必要地将代码耦合到Spring。另外,我们建议使用@PostConstruct注解或指定POJO初始化方法。对于基于XML的配置元数据,可以使用init-method属性指定具有无效无参数签名的方法的名称。在Java配置中,您可以使用@BeaninitMethod属性。参见接收生命周期回调(Receiving Lifecycle Callbacks)。考虑下面的例子:

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

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

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

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

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

但是,前面两个示例中的第一个示例没有将代码与Spring耦合

销毁回调(Destruction Callbacks)

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

void destroy() throws Exception;

我们建议您不要使用DisposableBean回调接口,因为它不必要地将代码耦合到Spring。另外,我们建议使用@PreDestroy注解或指定bean定义支持的泛型方法。对于基于XML的配置元数据,您可以在<bean/>上使用destroy-method属性。在Java配置中,您可以使用@BeandestroyMethod属性。参见接收生命周期回调(Receiving Lifecycle Callbacks)。考虑以下定义:

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

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

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

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

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

但是,前面两个定义中的第一个没有将代码与Spring耦合

你可以为一个<bea>元素的属性destroy-method设置一个具有一个特殊的(推断的)值,该值指示Spring自动检测特定bean类上的公共closeshutdown方法。(因此,任何实现java.lang.AutoCloseablejava.io.Closeable的类都将匹配。)您还可以在<beans>default-destroy-method属性上设置这个特殊(推断的)值,用于将此行为应用于整个bean集和(请参阅默认初始化和销毁方法(Default Initialization and Destroy Methods))。注意,这是Java配置的默认行为。

默认的初始化和销毁方法(Default Initialization and Destroy Methods)

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

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

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

public class DefaultBlogService implements BlogService {

    private BlogDao blogDao;

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

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

然后你可以在bean中使用这个类,如下所示:

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

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

</beans>

<beans>元素配置了default-init-method属性后,Spring IoC容器会识别bean类上名称为init的方法为初始化回调方法。在创建和组装bean时,如果bean类有这样的方法,将在适当的时候调用它。

通过在顶层<beans>元素上使用default-destroy-method属性,可以类似地配置销毁方法回调(即在XML中)。

如果现有的bean类已经具有与约定不一致的回调方法,则可以通过使用<bean>init-methoddestroy-method属性指定(在XML中)方法名来覆盖默认值。

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

组合生命周期机制(Combining Lifecycle Mechanisms)

从Spring 2.5开始,你有三个选项来控制bean的生命周期行为:

您可以组合这些机制,用于控制给定的Bean。

提示

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

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

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

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

  1. @PreDestroy注解的方法
  2. DisposableBean回调接口定义的destroy()
  3. 一个自定义配置的destroy()方法

启动和关闭的回调(Startup and Shutdown Callbacks)

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

public interface Lifecycle {

    void start();

    void stop();

    boolean isRunning();
}

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

public interface LifecycleProcessor extends Lifecycle {

    void onRefresh();

    void onClose();
}

注意,LifecycleProcessor本身是Lifecycle接口的扩展。它还添加了另外两个方法,用于对正在刷新正在关闭的上下文作出反应。

请注意,常规的org.springframework.context.Lifecycle接口是用于显式启动停止通知的普通约定,并不意味着在上下文刷新时自动启动。要对特定bean的自动启动(包括启动阶段)进行细粒度控制,可以考虑实现org.springframework.context.SmartLifecycle

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

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

public interface Phased {
    int getPhase();
}

SmartLifecycle接口的定义如下所示:

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

启动时,阶段值最低的对象首先启动。当停止时遵循相反的顺序。因此,一个实现SmartLifecycle的对象,其getPhase()方法返回Integer.MIN_VALUE将是第一个启动最后一个停止的。相反,阶段值为IntegerMAX_VALUE表示对象应该最后启动,首先停止(可能是因为它依赖于正在运行的其他进程)。在考虑阶段值时,同样重要的是要知道,任何没有实现SmartLifecycle的“正常”生命周期对象的默认阶段都是0。因此,任何负阶段值都表示一个对象应该在这些标准组件之前启动(并在它们之后停止)。相反,对于任何正阶段值都是正确的。

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

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

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

在非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");

        // 为上下文添加一个钩子
        ctx.registerShutdownHook();

        // 运行程序

        // main方法关闭,钩子在程序关闭前被调用
    }
}

1.6.2. AplicationContextAwareBeanNameAware

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

public interface ApplicationContextAware {

    void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
}

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

自动装配是获取对ApplicationContext引用的另一种替代方法。传统的构造函数和byType自动装配模式(如自动装配协作器(Autowiring Collaborators)中所述)可以分别为构造函数参数setter方法参数提供ApplicationContext类型的依赖。要获得更大的灵活性,包括自动装配字段和多参数方法的能力,请使用基于注解的自动装配特性。如果你这样做了,ApplicationContext被自动装配到一个字段、构造函数参数或方法参数中,如果有问题的字段、构造函数或方法带有@Autowired注解,则该字段、构造函数参数或方法参数需要ApplicationContext类型。有关更多信息,请参见使用@Autowired(Using @Autowired

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

public interface BeanNameAware {

    void setBeanName(String name) throws BeansException;
}

回调在填充普通bean属性之后调用,但在初始化回调(如InitializingBean.afterPropertiesSet())或自定义初始化方法之前调用。

1.6.3. 其它Aware接口

除了ApplicationContextAwareBeanNameAware(前面讨论过)之外,Spring还提供了广泛的Aware回调接口,让bean向容器表明它们需要特定的基础设施依赖。作为一般规则,名称指示依赖类型。下表总结了最重要的Aware接口:

名称注入的依赖解释
ApplicationContextAware声明的 ApplicationContext.ApplicationContextAwareBeanNameAware
ApplicationEventPublisherAwareApplicationContext内部的事件发布者ApplicationContext附加的能力
BeanClassLoaderAware用于加载Bean的类的类加载器实例化Bean
BeanFactoryAware声明的BeanFactory.BeanFactory API
BeanNameAware声明的Bean的名称ApplicationContextAwareBeanNameAware
LoadTimeWeaverAware为在加载时处理类定义而定义的编织器。在Spring框架中使用AspectJ进行加载时编织
MessageSourceAware解析消息的已配置策略(支持参数化和国际化)。ApplicationContext附加的能力
NotificationPublisherAwareSpring JMX通知发布者。通知
ResourceLoaderAware配置用于低级访问资源的加载器。资源(Resources)
ServletConfigAware容器运行的当前ServletConfig。只在支持web的Spring ApplicationContext中有效。Spring MVC
ServletContextAware容器运行的当前ServletContext。只在支持web的Spring ApplicationContext中有效。Spring MVC

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

1.7. Bean定义的继承

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

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

<!-- 在指定了class的情况下,可以不指定abstract -->
<bean id="inheritedBean" abstract="true" class="TestBean">
	<property name="name" value="parent"/>
    <property name="age" value="1"/>
</bean>
<!-- parent指定需要继承配置的bean的名称 -->
<bean id="inhertedChild" class="ChildBean" parent="inheritedBean" init-method="init"> <!-- 1 -->
	<!-- 重写属性配置 -->
    <property name="name" value="override"/>
    <!-- age继承父配置 -->
</bean>
  • 1处:注意parent属性

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

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

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

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

<!-- 在未指定class的情况下,必须指定abstract -->
<bean id="inheritedBean" abstract="true" >
	<property name="name" value="parent"/>
    <property name="age" value="1"/>
</bean>
<!-- parent指定需要继承配置的bean的名称 -->
<bean id="inhertedChild" class="ChildBean" parent="inheritedBean" init-method="init">
	<!-- 重写属性配置 -->
    <property name="name" value="override"/>
    <!-- age继承父配置 -->
</bean>

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

提示

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

1.8. 容器扩展点(Container Extension Points)

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

1.8.1. 使用BeanPostProcessor定制Bean

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

您可以配置多个BeanPostProcessor实例,并且可以通过设置order属性来控制这些BeanPostProcessor实例运行的顺序。只有当BeanPostProcessor实现了Ordered接口时,你才能设置这个属性。如果编写自己的BeanPostProcessor,也应该考虑实现Ordered接口。要了解更多细节,请参阅BeanPostProcessorOrdered接口的javadoc。参见程序化注册BeanPostProcessor实例(programmatic registration of BeanPostProcessor instances)说明。

提示

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

BeanPostProcessor实例的作用域为每个容器。这只有在使用容器层次结构时才有意义。如果在一个容器中定义了BeanPostProcessor,则它只对该容器中的bean进行后置处理。换句话说,在一个容器中定义的bean不会被在另一个容器中定义的BeanPostProcessor进行后置处理,即使两个容器都是同一层次结构的一部分。

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

BeanPostProcessor接口由两个回调方法组成。当这样的类被注册为容器的后置处理器(post-processor)时,对于容器创建的每个bean实例,后置处理器

  • 容器初始化方法(如InitializingBean.afterPropertiesSet()或任何声明的init方法)被调用之前
  • 以及在任何bean初始化回调之后都会从容器获得回调。

后置处理器可以对bean实例采取任何操作,包括完全忽略回调。bean后置处理器通常检查回调接口,或者它可能用代理包装bean。为了提供代理包装逻辑,一些Spring AOP基础设施类被实现为bean后置处理器。

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

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

提示

以编程方式注册BeanPostProcessor实例

虽然BeanPostProcessor注册的推荐方法是通过ApplicationContext自动检测(如前所述),但您可以通过使用addBeanPostProcessor方法以编程方式在ConfigurableBeanFactory上注册它们。当您需要在注册之前评估条件逻辑,或者甚至在层次结构中跨上下文复制bean后置处理器时,这是非常有用的。但是请注意,以编程方式添加的BeanPostProcessor实例不遵循Ordered接口。在这里,是注册的顺序决定了执行的顺序。还要注意,无论显式排序如何,以编程方式注册的BeanPostProcessor实例总是在通过自动检测注册的实例之前被处理。

BeanPostProcessor实例和AOP自动代理

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

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

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

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

示例:Hello World, BeanPostProcessor风格

第一个示例说明了基本用法。该示例显示了一个自定义BeanPostProcessor实现,该实现在容器创建每个bean时调用toString()方法,并将结果字符串打印到系统控制台。

package scripting;

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

public class InstantiationTracingBeanPostProcessor implements BeanPostProcessor {

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

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

下面的bean元素使用了InstantiationTracingBeanPostProcessor:

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

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

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

</beans>

注意InstantiationTracingBeanPostProcessor是如何定义的。它甚至没有名称,而且因为它是一个bean,所以可以像其他bean一样对它进行依赖注入。(前面的配置还定义了一个由Groovy脚本支持的bean。Spring的动态语言支持详见动态语言支持(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

示例:AutowiredAnnotationBeanPostProcessor

将回调接口或注解与自定义BeanPostProcessor实现结合使用是扩展Spring IoC容器的常用方法。一个例子是Spring的AutowiredAnnotationBeanPostProcessor——一个随Spring发行版附带的BeanPostProcessor实现,它可以自动装配带注解的字段setter方法任意配置方法

1.8.2. 使用BeanFactoryPostProcessor自定义配置元数据

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

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

提示

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

此外,BeanFactoryPostProcessor实例的作用域为每个容器。这只有在使用容器层次结构时才有意义。如果在一个容器中定义了BeanFactoryPostProcessor,则它只应用于该容器中的bean定义一个容器中的Bean定义不会被另一个容器中的BeanFactoryPostProcessor实例进行后置处理,即使两个容器是同一层次结构的一部分。

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

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

提示

BeanPostProcessor一样,您通常不希望将BeanFactoryPostProcessors配置为延迟初始化。如果没有其他bean引用BeanFactoryPostProcessor,则该后置处理器根本不会被实例化。因此,将其标记为延迟初始化将被忽略,并且即使您在声明<beans/>元素时将default-lazy-init属性设置为true, BeanFactoryPostProcessor也将被立即实例化。

示例:类名替换PropertySourcesPlaceholderConfigurer

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

<bean class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
    <property name="locations" value="classpath:com/something/jdbc.properties"/>
</bean>

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

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

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属性中以逗号分隔的列表形式提供一个或多个位置,如下例所示:

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

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

您可以使用PropertySourcesPlaceholderConfigurer来替换类名,当您必须在运行时选择特定的实现类时,这有时很有用。下面的例子展示了如何这样做:

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

<!-- 引用属性,替换类名 -->
<bean id="serviceStrategy" class="${custom.strategy.class}"/>

补充

  • properties中配置了key为custom.strategy.class,值为com.something.DefaultStrategy
  • Bean serviceStrategyclass属性取了custom.strategy.class对应的值

如果不能在运行时将类解析为有效类,则在即将创建bean时解析失败,这是在非惰性初始化bean的ApplicationContextpreinstantiatesingleton()阶段。

示例:PropertyOverrideConfigurer

另一个bean工厂后置处理程序PropertyOverrideConfigurerPropertySourcesPlaceholderConfigurer类似,但与后者不同的是,原始定义可以为bean属性提供默认值,也可以根本没有值。如果重写的Properties文件没有某个bean属性的条目,则使用默认上下文定义。

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

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

beanName.property=value

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

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

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

也支持复合属性名,只要路径的每个组件(除了被覆盖的最后一个属性)都是非空的(可能是由构造函数初始化的)。在下面的例子中,tom bean的fred属性的bob属性的sammy属性被设置为标量值123

tom.fred.bob.sammy=123

提示

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

随着Spring 2.5中引入的上下文命名空间,可以使用专用的配置元素来配置属性重写,如下面的示例所示:

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

1.8.3. 使用FactoryBean定制实例化逻辑(Customizing Instantiation Logic with a FactoryBean

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

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

FactoryBean<T>接口提供三个方法:

  • T getObject():返回此工厂创建的对象的实例。实例可能是共享的,这取决于该工厂返回的是单例还是原型。
  • boolean isSingleton():如果此FactoryBean返回单例,则返回true,否则返回false。此方法的默认实现返回true
  • Class<?> getObjectType():返回getObject()方法返回的对象类型,如果事先不知道该类型,则返回null。

FactoryBean概念和接口在Spring框架中的许多地方都有使用。超过50个FactoryBean接口的实现随Spring本身一起发布。

当您需要向容器请求实际的FactoryBean实例本身而不是它生成的bean时,请在调用ApplicationContextgetBean()方法时,在bean的id前面加上&符号。

因此,对于id为myBean的给定FactoryBean

  • 在容器上调用getBean(“myBean”)将返回FactoryBean的产品,
  • 而调用getBean(“&myBean”)将返回FactoryBean实例本身。

1.9. 基于注解的容器配置

在配置Spring时,注解比XML更好吗?

基于注解的配置的引入提出了一个问题,即这种方法是否比XML“更好”。简短的回答是“看情况”。长篇大论的答案是,每种方法都有其优点和缺点,通常由开发者决定哪种策略更适合他们。由于它们的定义方式,

  • 注解在其声明中提供了大量上下文,从而导致更短、更简洁的配置
  • 但是,XML擅长在不触及源代码重新编译的情况下装配组件。

一些开发人员更喜欢在源代码附近进行装配,而另一些开发人员则认为带注解的类不再是pojo,而且配置变得分散,难以控制。

无论选择什么,Spring都可以容纳这两种风格,甚至可以将它们混合在一起。值得指出的是,通过它的JavaConfig选项,Spring允许以一种非侵入性的方式使用注解,而不需要触及目标组件的源代码,而且,就工具而言,所有的配置样式都受到Spring Tools for Eclipse的支持。

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

提示

注解注入在XML注入之前执行。因此,XML配置将覆盖通过这两种方法装配的注解得属性

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

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">
	<!-- 开启注解方式 -->
    <context:annotation-config/>

</beans>

<context:annotation-config/>元素隐式地注册如下的后置处理器:

提示

<context:annotation-config/>只在定义它的相同应用程序上下文中查找bean上的注解。这意味着,如果你把<context:annotation-config/>放到DispatcherServletWebApplicationContext中,它只检查**控制器(controller)**中的@Autowired bean,而不检查服务。查看DispatcherServlet获取更多信息。

1.9.1. @Required

@Required注解应用于bean属性setter方法,如下例所示:

public class SimpleMovieLister {

    private MovieFinder movieFinder;

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

    // ...
}

该注解表明,受影响的bean属性必须在配置时通过bean定义中的显式属性值或通过自动装配来填充。如果未填充受影响的bean属性容器将抛出异常。这允许立即和显式的失败,避免稍后出现NullPointerException实例或类似实例。我们仍然建议将断言放入bean类本身(例如,放入init方法中)。这样做可以强制执行那些必需的引用和值,即使在容器外部使用类时也是如此。

RequiredAnnotationBeanPostProcessor必须注册为bean,以启用对@Required注解的支持。

提示

@Required注解和RequiredAnnotationBeanPostProcessor在Spring Framework 5.1中被正式弃用,而支持使用构造函数注入来进行所需的设置(或InitializingBean.afterPropertiesSet()的自定义实现,或自定义@PostConstruct方法以及bean属性setter方法)。

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注解,以便指示容器使用哪个构造函数。有关详细信息,请参阅关于构造函数解析(constructor resolution)的讨论。

你也可以将@Autowired注解应用到传统的setter方法中,如下例所示:

public class SimpleMovieLister {

    private MovieFinder movieFinder;

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

    // ...
}

你也可以将@Autowired应用于字段,甚至可以将它与构造函数混合使用,如下面的例子所示:

public class MovieRecommender {

    private final CustomerPreferenceDao customerPreferenceDao;

    @Autowired
    private MovieCatalog movieCatalog;

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

    // ...
}

确保您的目标组件(例如,MovieCatalogCustomerPreferenceDao)是由您用于@Autowired注解注入点的类型一致声明的。否则,注入可能会因为运行时出现**“找不到类型匹配”错误而失败。对于通过类路径扫描找到的XML定义的bean或组件类,容器通常预先知道具体类型。但是,对于@Bean工厂方法,您需要确保声明的返回类型具有足够的表达性**。对于实现多个接口的组件或可能由其实现类型引用的组件,请考虑在工厂方法上声明最具体的返回类型(至少与引用bean的注入点所要求的一样具体)。

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

public class MovieRecommender {

    @Autowired
    private MovieCatalog[] movieCatalogs;

    // ...
}

这同样适用于类型化集合,如下例所示:

public class MovieRecommender {

    private Set<MovieCatalog> movieCatalogs;

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

    // ...
}

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

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

注意,标准javax.annotation.Priority注解在@Bean级别不可用,因为它不能在方法上声明。它的语义可以通过在每个类型的单个bean上结合@Order值和@Primary来建模。

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

public class MovieRecommender {

    private Map<String, MovieCatalog> movieCatalogs;

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

    // ...
}

默认情况下,当给定注入点没有匹配的候选bean可用时,自动装配将失败。对于声明的数组集合Map至少需要一个匹配元素

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

public class SimpleMovieLister {

    private MovieFinder movieFinder;

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

    // ...
}

如果非必需(non-required)的方法的依赖项(或其依赖项中的一个,在有多个参数的情况下)不可用,则根本不会调用该方法。在这种情况下,一个非必须(non-required)字段根本不会被填充,保留其默认值

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

提示

对于任何给定的bean类,只有一个构造函数可以声明@Autowired,并将所需的属性设置为true,这表明该构造函数在作为Spring bean使用时要自动装配。因此,如果required属性保持其默认值true,则只有一个构造函数可以用@Autowired注解。如果多个构造函数声明了注解它们都必须声明required=false,才能被视为自动装配的候选者(类似于XML中的自动装配=constructor)。将选择具有最多依赖项的构造函数,这些依赖项可以通过在Spring容器中匹配bean来满足。如果所有候选函数都不能满足,那么将使用主/默认构造函数(如果存在)。类似地,如果一个类声明了多个构造函数,但没有一个用@Autowired注解,那么将使用一个主/默认构造函数(如果存在)。如果一个类一开始只声明了一个构造函数,那么它将始终被使用,即使没有注解。注意,带注解的构造函数不一定是公共的。

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

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

public class SimpleMovieLister {

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

从Spring Framework 5.0开始,你还可以使用@Nullable注解(任何包中的任何类型的注解——例如JSR-305中的javax.annotation.Nullable),或者直接利用Kotlin内置的null安全(null-safety)支持:

public class SimpleMovieLister {

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

你也可以对那些众所周知的可解析依赖的接口使用@Autowired

  • BeanFactory
  • ApplicationContext
  • Environment
  • ResourceLoader
  • ApplicationEventPublisher
  • MessageSource
  • 这些接口和它们的扩展接口,如ConfigurableApplicationContextResourcePatternResolver,是自动解析的,不需要特殊的设置。

下面的例子自动装配一个ApplicationContext对象:

public class MovieRecommender {

    @Autowired
    private ApplicationContext context;

    public MovieRecommender() {
    }

    // ...
}

提示

@Autowired@Inject@Value@Resource注解是由Spring BeanPostProcessor实现处理的。这意味着您不能在自己的BeanPostProcessorBeanFactoryPostProcessor类型(如果有的话)中应用这些注解。这些类型必须通过使用XML或Spring @Bean方法**显式地“装配”**起来。

补充

  • BeanPostProcessor无法使用@Autowired@Inject@Value@Resource注解实现自动装配
  • 可以通过XML或@Bean的方式显示地装配

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

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

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

@Configuration
public class MovieConfiguration {

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

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

    // ...
}

通过前面的配置,下面的MovieRecommender会自动与firstMovieCatalog装配:

public class MovieRecommender {

    @Autowired
    private MovieCatalog movieCatalog;

    // ...
}

相应的bean定义如下:

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

    <context:annotation-config/>

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

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

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

</beans>

1.9.4. 微调带有限定词的基于注解的自动装配(Fine-tuning Annotation-based Autowiring with Qualifiers)

当可以确定一个主要候选者时,@Primary是一种有效的方法,可以根据多个实例的类型使用自动装配。当需要对选择过程进行更多控制时,可以使用Spring的@Qualifier注解。您可以将限定符值与特定的参数关联起来缩小类型匹配集,以便为每个参数选择特定的bean。在最简单的情况下,这可以是一个简单的描述性值,如下例所示:

public class MovieRecommender {

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

    // ...
}

你也可以在单个构造函数参数方法参数上指定@Qualifier注解,如下例所示:

public class MovieRecommender {

    private MovieCatalog movieCatalog;

    private CustomerPreferenceDao customerPreferenceDao;

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

    // ...
}

下面的示例显示了相应的bean定义。

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

    <context:annotation-config/>

    <bean class="example.SimpleMovieCatalog">
        <qualifier value="main"/> <!-- 具有`main`限定符值的bean与具有相同限定值的构造函数参数相装配 -->

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

    <bean class="example.SimpleMovieCatalog">
        <qualifier value="action"/> <!-- 具有`action`限定符值的bean与具有相同值限定符的构造函数参数装配在一起 -->

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

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

</beans>

对于回退匹配,bean名称被视为默认限定符值。因此,您可以使用idmain而不是嵌套的<qualifier>来定义bean,从而得到相同的匹配结果。然而,尽管您可以使用此约定按名称引用特定bean,但@Autowired基本上是关于带有可选语义限定符的类型驱动注入。这意味着限定符值,即使使用bean名称回退,也总是在类型匹配集中具有缩小语义。它们不从语义上表达对唯一bean id的引用。好的限定符值是mainEMEApersistent,它们表示独立于bean id的特定组件的特征,在使用匿名bean定义的情况下(如前面示例中的bean), bean id可能会自动生成。

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

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

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

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

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

提示

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

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

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

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

    String value();
}

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

public class MovieRecommender {

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

    private MovieCatalog comedyCatalog;

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

    // ...
}

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

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

    <context:annotation-config/>

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

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

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

</beans>

类路径扫描和托管组件(Classpath Scanning and Managed Components)中,您可以看到在XML中提供限定符元数据的基于注解的替代方法。具体来说,请参见使用注解提供限定符元数据(Providing Qualifier Metadata with Annotations)

在某些情况下,使用不带值的注解就足够了。当注解服务于更通用的目的,并且可以跨几种不同类型的依赖项应用时,这可能很有用。例如,您可以提供一个离线目录,在没有Internet连接时可以对其进行搜索。首先,定义简单注解,如下例所示:

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

}

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

public class MovieRecommender {

    @Autowired
    @Offline // 该行添加了`Offline`注解
    private MovieCatalog offlineCatalog;

    // ...
}

现在,bean定义只需要限定符类型,如下面的示例所示:

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

您还可以定义自定义限定符注解,除了接受简单的value属性之外,还接受命名属性。如果在要自动装配的字段或参数上指定了多个属性值,那么bean定义必须匹配所有这些属性值才能被认为是自动装配的候选者。作为一个例子,考虑下面的注解定义:

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

    String genre();

    Format format();
}

在本例中,Format是一个枚举(enum),定义如下:

public enum Format {
    VHS, DVD, BLURAY
}

要自动装配的字段用自定义限定符进行注解,并包括属性:genreformat的值,如下面的示例所示:

public class MovieRecommender {

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

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

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

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

    // ...
}

最后,bean定义应该包含匹配的限定符值。这个例子还演示了可以使用bean元属性代替<qualifier/>元素。如果有,<qualifier/>元素及其属性具有优先级,但是自动装配机制依赖于<meta/>标记,如果没有这样的限定符,如下例中的最后两个bean定义:

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

    <context:annotation-config/>

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

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

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

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

</beans>

1.9.5. 使用泛型作为自动装配限定符(Using Generics as Autowiring Qualifiers)

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

@Configuration
public class MyConfiguration {

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

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

假设前面的bean实现了一个通用接口(即Store<String>Store<Integer>),你可以@Autowired Store接口,并将泛型用作限定符,如下例所示:

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

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

泛型限定符也适用于自动装配列表Map实例和数组。下面的例子自动装配一个泛型列表:

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

补充

  • StringStore继承了Store<String>
  • IntegerStore继承了Store<Integer>

1.9.6. 使用CustomAutowireConfigurer

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

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

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

  • 每个<bean/>定义中的autowire-candidate
  • <beans/>元素中任何可用的default-autowire-candidate模式
  • @Qualifier注解和CustomAutowireConfigurer注册的任何自定义注解的存在

多个bean符合自动候选条件时,确定“主”的方法如下:如果候选者中只有一个bean定义的primary属性设置为true,则选择它。

1.9.7. @Resource注入

Spring还通过在字段或bean属性setter方法上使用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") // 这行用`@Resource`注入
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }
}

如果没有显式指定名称,则默认名称将从字段名称setter方法派生。如果是字段,则采用字段名对于setter方法,它采用bean属性名。下面的例子将把名为movieFinder的bean注入到它的setter方法中:

public class SimpleMovieLister {

    private MovieFinder movieFinder;

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

提示

随注解一起提供的名称由ApplicationContext解析为bean名称,CommonAnnotationBeanPostProcessor知道这个名称。如果显式地配置Spring的SimpleJndiBeanFactory,则可以通过JNDI解析这些名称。但是,我们建议您依赖默认行为并使用Spring的JNDI查找功能来保持间接级别( the level of indirection)。

没有指定显式名称@Resource使用的独占情况下,@Autowired类似@Resource查找主要类型匹配而不是特定的命名bean,并解析众所周知的可解析依赖:BeanFactory, ApplicationContext, ResourceLoader, ApplicationEventPublisherMessageSource接口。

因此,在下面的示例中,

  1. customerPreferenceDao字段首先查找名为“customerPreferenceDao”的bean
  2. 然后退回到customerPreferenceDao类型的主类型匹配
public class MovieRecommender {

    @Resource
    private CustomerPreferenceDao customerPreferenceDao;

    @Resource
    private ApplicationContext context;  // `context`字段是根据已知的可解析依赖类型`ApplicationContext`注入的

    public MovieRecommender() {
    }

    // ...
}

1.9.8. 使用@Value

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

@Component
public class MovieRecommender {

    private final String catalog;

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

使用以下配置:

//配置组件声明
@Configuration
//加载外部属性文件
@PropertySource("classpath:application.properties")
public class AppConfig{}

如下是application.properties文件的内容:

catalog.name=MovieCatalog

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

Spring提供了一个默认的宽大嵌入式值解析器。它将尝试解析属性值,如果无法解析,则会将属性名(例如${catalog.name})作为值注入。如果你想严格控制不存在的值,你应该声明一个PropertySourcesPlaceholderConfigurer bean,如下面的例子所示:

@Configuration
public class AppConfig {

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

提示

当使用JavaConfig配置PropertySourcesPlaceholderConfigurer时,@Bean方法必须是静态的(static

补充

PropertySourcesPlaceholderConfigurerBeanFactoryPostProcessor的实现类

如果无法解析任何${}占位符,使用上述配置将导致Spring初始化失败PropertySourcesPlaceholderConfigurer还提供如下方法,定制占位符:

  • setPlaceholderPrefix
  • setPlaceholderSuffix
  • setValueSeparator

提示

Spring Boot默认配置PropertySourcesPlaceholderConfigurer bean,它将从application.propertiesapplication.yml文件获取属性。

Spring提供的内置转换器支持允许自动处理简单的类型转换(例如到Integerint)。多个逗号分隔的值可以自动转换为字符串数组,而无需额外的工作。

可以提供如下默认值

@Component
public class MovieRecommender {

    private final String catalog;

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

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

@Configuration
public class AppConfig {

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

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

@Component
public class MovieRecommender {

    private final String catalog;

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

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

@Component
public class MovieRecommender {

    private final Map<String, Integer> countOfMoviesPerCatalog;

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

1.9.9. 使用@PostConstruct@PreDestroy

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

public class CachingMovieLister{
    @PostConstruct
    public void populateMovieCache(){
        //poplates the movie cache upon init...
    }
    
    @PreDestroy
    public void clearMovieCache(){
        //clears the movie cache upon destruction...
    }
}

关于多种生命周期机制组合的效果,请参见组合生命周期机制(Combining Lifecycle Mechanisms)

提示

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

1.10 类路径扫描和托管组件(Classpath Scanning and Managed Components)

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

1.10.1. @Component和更多的构造型注解

@Repository注解是实现存储库角色或原型(也称为数据访问对象或DAO)的任何类的标记。该标记的用途之一是异常的自动转换,如异常转换(Exception Translation)中所述。

Spring提供了更多的构造型注解:@Component@Service@Controller@Component是任何Spring管理组件的通用构造型@Repository@Service@Controller@Component专门化,用于更具体的用例(分别在持久化层服务层表示层)。因此,你可以用@Component来注解你的组件类,但是,通过用@Repository@Service@Controller来注解它们,你的类更适合由工具处理或与切面关联。例如,这些构造型注解是切入点的理想目标。@Repository@Service@Controller也可以在Spring框架的未来版本中携带额外的语义。因此,如果你要在服务层使用@Component@Service之间做出选择,@Service显然是更好的选择。类似地,如前所述,@Repository已经被支持作为持久层中自动异常转换的标记。

1.10.2. 使用元注解和组合注解(Using Meta-annotations and Composed Annotations)

Spring提供的许多注解都可以在您自己的代码中用作元注解。元注解是一种可以应用于其他注解的注解。例如,前面提到的@Service注解是用@Component做元注解的,如下面的例子所示:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component // `@Component`会让`@Service`和`@Component`一样被对待
public @interface Service {

    // ...
}

你也可以组合元注解来创建**“组合注解”**。例如,Spring MVC中的@RestController注解由@Controller@ResponseBody组成。

此外,组合注解可以选择性地重新声明元注解中的属性,以允许自定义。当您只想公开元注解属性的一个子集时,这可能特别有用。例如,Spring的@SessionScope注解将作用域名称硬编码到session,但仍然允许自定义proxyMode。下面的清单显示了SessionScope注解的定义:

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Scope(WebApplicationContext.SCOPE_SESSION) // 固定了作用域为session
public @interface SessionScope {

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

}

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

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

要了解更多细节,请参阅Spring注解编程模型维基页面。

1.10.3. 自动检测类和注册Bean定义(Automatically Detecting Classes and Registering Bean Definitions)

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

@Service
public class SimpleMovieLister {

    private MovieFinder movieFinder;

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

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

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

提示

为简洁起见,前面的示例可以使用注解的value属性(即@ComponentScan("org.example"))。

下面的替代方案使用XML:

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

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

</beans>

<context:componet-scan>会隐式的启用<context:annotation-config>。所以当使用<context:componet-scan>元素时,不需要包含<context:annotation-config>元素

提示

扫描类路径包需要在类路径中存在相应的目录项。当您使用Ant构建JAR时,请确保您没有激活JAR任务的仅文件开关。此外,在某些环境中,基于安全策略,类路径目录可能不会公开——例如,JDK 1.7.0_45及更高版本上的独立应用程序(这需要在您的清单中设置“Trusted-Library”——参见stackoverflow.com/questions/1…

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

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

提示

你可以禁用AutowiredAnnotationBeanPostProcessorCommonAnnotationBeanPostProcessor的注册,方法是将annotation-config属性的值设置为false

1.10.4. 使用过滤器自定义扫描(Using Filters to Customize Scanning)

默认情况下,带有@Component@Repository@Service@Controller@Configuration注解的类,或者带有@Component注解的自定义注解是唯一检测到的候选组件。

但是,您可以通过应用自定义过滤器来修改和扩展此行为。

  • 将它们添加为@ComponentScan注解的includeFiltersexcludeFilters属性
  • 在XML配置中,或作为<context:component-scan>元素的子元素<context:include-filter />或者<context:exclude-filter />

每个过滤器元素都需要typeexpression属性。过滤选项如下表所示:

过滤器类型示例表达式描述
annotation (默认)org.example.SomeAnnotation在目标组件的类型级别上呈现或元呈现的注解。
assignableorg.example.SomeClass目标组件可赋值(扩展或实现)的类(或接口)。
aspectjorg.example..*Service+目标组件要匹配的AspectJ类型表达式。
regexorg\.example\.Default.*与目标组件的类名匹配的正则表达式。
customorg.example.MyTypeFilterorg.springframework.core.type.TypeFilter接口的自定义实现。

下面的示例显示了忽略所有@Repository注解并**使用“Stub“和“Repository”**的配置:

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

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

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

提示

你也可以通过在注解中设置useDefaultFilters=false或者通过提供use-default-filters="false"作为的<component-scan/>元素的属性来禁用默认过滤器。这有效地禁用了对带有@Component@Repository@Service@Controller@RestController@Configuration注解或元注解的类的自动检测。

1.10.5. 在组件中定义Bean元数据(Defining Bean Metadata within Components)

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标记的注入点上。在这种情况下,它会导致惰性解析代理的注入。然而,这种代理方法是相当有限的。对于复杂的惰性交互,特别是与可选依赖项结合使用时,我们推荐使用ObjectProvider<MyTargetBean>代替。

如前所述,支持自动装配字段方法,并支持@Bean方法的自动装配。下面的例子展示了如何这样做:

@Component
public class FactoryMethodComponent{
    private static int i;
    
    @Bean
    @Qualifier("public") //这里标识符用于Bean的命名
    public TestBean publicInstance(){
        return new TestBean("publicInstance");
    }
    
    // 使用自定义的标识符并注入到方法参数
    // #{privateInstance.age}访问`privateInstance`的`bean`中的age成员变量
    @Bean
    protected TestBean protectedInstance(
        @Qualifier("public")TestBean spouse, // 这里是注入名称为`public`的TestBean
    	@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++);
    }
    
    //用@RequestScope设置了作用域为`request`
    @Bean
    @RequestScope
    public TestBean requestScopedInstance(){
        return new TestBean("requestScopedInstance",3);
    }
}

该示例将privateInstance的bean上的age属性的值自动装配到String类型的方法参数country。Spring表达式语言元素通过#{ <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类中的@Bean方法处理方式不同。不同之处在于@Component没有用CGLIB增强来拦截方法和字段的调用。CGLIB代理是通过调用@Configuration类中的@Bean方法中的方法或字段来创建对协作对象的bean元数据引用的方法。这些方法不是用普通的Java语义调用的,而是通过容器来提供Spring bean的通常的生命周期管理和代理,即使是在通过编程调用@Bean方法引用其他bean时也是如此。相反,在普通的@Component类中调用@Bean方法中的方法字段具有标准的Java语义,不需要应用特殊的CGLIB处理或其他约束。

提示

您可以将@Bean方法声明为静态方法,允许在不将包含它们的配置类创建为实例的情况下调用它们。在定义后置处理器bean(例如,类型为BeanFactoryPostProcessorBeanPostProcessor)时,这一点特别有意义,因为此类bean在容器生命周期的早期被初始化,并且应该避免在那时触发配置的其他部分

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

@Bean方法的Java语言可见性不会对Spring容器中生成的bean定义产生直接影响。您可以自由地在non-@Configuration类中声明工厂方法,也可以在任何地方声明静态方法。然而,@Configuration类中的常规@Bean方法需要是可重写的——也就是说,它们不能被声明为privatefinal

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

最后,单个类可以为同一个bean保存多个@Bean方法,作为在运行时根据可用依赖项使用的多个工厂方法的安排。这与在其他配置场景中选择“最贪婪”的构造函数或工厂方法的算法相同:在构造时选择具有最多可满足依赖项的变体,类似于容器如何在多个@Autowired构造函数之间进行选择。

1.10.6. 命名自动检测的组件(Naming Autodetected Components)

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

如果这样的注解不包含名称value,或者不包含任何其他检测到的组件(例如由自定义过滤器发现的组件)的名称值,则默认bean名称生成器返回未大写的非限定类名。例如,如果检测到以下组件类,名称将是myMovieListermovieFinderImpl:

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

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

如果由于多个自动检测的组件具有相同的非限定类名(即,具有相同名称但位于不同包中的类)而遇到命名冲突,则可能需要配置BeanNameGenerator,该生成器默认为生成的bean名使用完全限定类名。从Spring Framework 5.2.3开始,位于包org.springframework.context.annotation中的FullyQualifiedAnnotationBeanNameGenerator可以用于这种目的。

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

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

1.10.7. 为自动检测的组件提供作用域(Providing a Scope for Autodetected Components)

与Spring管理的组件一样,自动检测组件的默认和最常见的作用域是**单例(singleton)**的。然而,有时您需要一个可以通过@Scope注解指定的不同作用域。你可以在注解中提供作用域的名称,如下例所示:

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

提示

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

有关Spring上下文中的“request”或“session”等web特定作用域的详细信息,请参见request, session, Application和WebSocket作用域(Request, Session, Application, and WebSocket Scopes)。与针对这些作用域的预构建注解一样,您也可以通过使用Spring的元注解方法来组合自己的作用域注解:例如,使用@Scope("prototype")进行元注解的自定义注解,可能还声明自定义作用域代理模式。

补充

其它作用域相关的注解:

  • @RequestScope

  • @SessionScope

  • @ApplicationScope

提示

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

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

当使用某些非单例作用域时,可能需要为作用域对象生成代理。其原因在作用域bean作为依赖项(Scoped Beans as Dependencies)中进行了描述。为此,可以在组件扫描元素上使用作用域代理属性。三个可能的值是:nointerfacestargetClass。例如,以下配置会产生标准JDK动态代理:

@Configuration
@ComponentScan(basePackages = "org.example", scopedProxy = ScopedProxyMode.INTERFACES)
public class AppConfig {
    // ...
}
<beans>
    <context:component-scan base-package="org.example" scoped-proxy="interfaces"/>
</beans>

1.10.8. 提供带有注解的限定元数据(Providing Qualifier Metadata with Annotations)

@Qualifier注解在使用标识符微调基于注解的自动装配(Fine-tuning Annotation-based Autowiring with Qualifiers)中进行了讨论。这一节中的示例演示了如何使用@Qualifier注解和自定义修饰符注解,以便在解析自动装配候选者时提供细粒度控制。因为这些示例是基于XML bean定义的,所以限定符元数据是通过使用XML中bean元素的qualifiermeta子元素在候选bean定义上提供的。当依赖类路径扫描来自动检测组件时,您可以在候选类上提供具有类型级别注解的限定符元数据。下面三个例子演示了这种技术:

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

提示

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

1.10.9 生成候选组件的索引(Generating an Index of Candidate Components)

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

提示

您现有的@ComponentScan<context:component-scan/>指令必须保持不变,以请求上下文扫描某些包中的候选对象。当ApplicationContext检测到这样一个索引时,它会自动使用它,而不是扫描类路径。

要生成索引,需要在每个包含组件扫描指令目标组件的模块中添加额外的依赖项。下面的例子展示了如何在Maven中做到这一点:

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

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

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

在Gradle 4.6及更高版本中,依赖项应该在annotationProcessor配置中声明,如下例所示:

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

spring-context-indexer构件生成一个META-INF/spring.components文件,该文件包含在jar文件中。

提示

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

当在类路径上找到META-INF/spring.components文件时,该索引会自动启用。如果索引对某些库(或用例)是部分可用的,但不能为整个应用程序构建索引,那么您可以通过将spring.index.ignore设置为true(作为JVM系统属性或通过SpringProperties机制),回到常规的类路径安排(就好像根本没有索引一样)。