欢迎访问shiker.tech

请允许在我们的网站上展示广告

您似乎使用了广告拦截器,请关闭广告拦截器。我们的网站依靠广告获取资金。

【译文】spring framework核心功能-IOC【二】
(last modified Nov 28, 2023, 9:39 PM )
by
侧边栏壁纸
  • 累计撰写 176 篇文章
  • 累计创建 61 个标签
  • 累计收到 4 条评论

目 录CONTENT

文章目录

【译文】spring framework核心功能-IOC【二】

橙序员
2022-08-22 / 0 评论 / 0 点赞 / 378 阅读 / 12 字 / 正在检测百度是否收录... 正在检测必应是否收录...
文章摘要(AI生成)

1.5. Bean范围当您创建一个 bean 定义时,您创建了一个用于创建由该 bean 定义定义的类的实际实例的方法。bean 定义是一个配方的想法很重要,因为这意味着,与一个类一样,您可以从一个配方创建许多对象实例。您不仅可以控制要插入到从特定 bean 定义创建的对象中的各种依赖项和配置值,还

1.5. Bean作用域

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

您不仅可以控制要插入到从特定 bean 定义创建的对象中的各种依赖项和配置值,还可以控制从特定 bean 定义创建的对象的作用域。这种方法功能强大且灵活,因为您可以通过配置选择您创建的对象的作用域,而不必在 Java 类级别烘焙对象的作用域。可以将 Bean 定义为部署在多个作用域之一中。Spring 框架支持六个作用域,其中四个仅在您使用 web-aware ApplicationContext时可用。您还可以创建 自定义作用域。

下表描述了支持的作用域:

作用域 描述
singleton (默认)将单个 bean 定义限定为每个 Spring IoC 容器的单个对象实例。
prototype 将单个 bean 定义限定为任意数量的对象实例。
request 将单个 bean 定义限定为单个 HTTP 请求的生命周期。也就是说,每个 HTTP 请求都有自己的 bean 实例,该实例是在单个 bean 定义的后面创建的。仅在 Web 感知 Spring 的上下文中有效ApplicationContext
session 将单个 bean 定义限定为 HTTP 的生命周期Session。仅在 Web 感知 Spring 的上下文中有效ApplicationContext
application 将单个 bean 定义限定为ServletContext. 仅在 Web 感知 Spring 的上下文中有效ApplicationContext
application 将单个 bean 定义限定为WebSocket. 仅在 Web 感知 Spring 的上下文中有效ApplicationContext

从 Spring 3.0 开始,线程作用域可用,但默认情况下未注册。有关详细信息,请参阅 SimpleThreadScope. 有关如何注册此或任何其他自定义作用域的说明,请参阅 使用自定义作用域

1.5.1. 单例作用域

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

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

单身人士

Spring 的单例 bean 概念不同于设计模式 (GoF) 模式书中定义的单例模式。GoF 单例对对象的作用域进行硬编码,以便每个 ClassLoader 创建一个且仅一个特定类的实例。Spring 单例的作用域最好描述为每个容器和每个 bean。这意味着,如果您在单个 Spring 容器中为特定类定义一个 bean,则 Spring 容器会创建该 bean 定义所定义的类的一个且仅一个实例。单例作用域是 Spring 中的默认作用域。要将 bean 定义为 XML 中的单例,您可以定义一个 bean,如下例所示:

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

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

1.5.2. 原型作用域

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

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

原型

(数据访问对象 (DAO) 通常不配置为原型,因为典型的 DAO 不保存任何会话状态。我们更容易重用单例图的核心。)

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

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

与其他作用域相比,Spring 不管理原型 bean 的完整生命周期。容器实例化、配置和以其他方式组装原型对象并将其交给客户端,而没有进一步记录该原型实例。因此,尽管在所有对象上调用初始化生命周期回调方法而不考虑作用域,但在原型的情况下,不会调用配置的销毁生命周期回调。客户端代码必须清理原型作用域的对象并释放原型 bean 拥有的昂贵资源。要让 Spring 容器释放原型作用域 bean 持有的资源,请尝试使用自定义bean 后处理器,它包含对需要清理的 bean 的引用。

在某些方面,Spring 容器在原型作用域 bean 方面的角色是 Javanew运算符的替代品。此后的所有生命周期管理都必须由客户处理。(有关 Spring 容器中 bean 的生命周期的详细信息,请参阅Lifecycle Callbacks。)

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

当您使用具有原型 bean 依赖关系的单例作用域 bean 时,请注意依赖关系在实例化时解决。因此,如果您将原型作用域的 bean 依赖注入到单例作用域的 bean 中,则会实例化一个新的原型 bean,然后将依赖注入到单例 bean 中。原型实例是提供给单例作用域 bean 的唯一实例。

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

1.5.4. 请求、会话、应用程序和 WebSocket 作用域

requestsessionapplicationwebsocket作用域仅在您使用可Web ApplicationContext的 Spring实现(例如XmlWebApplicationContext )时才可用。如果您将这些作用域与常规 Spring IoC 容器(例如ClassPathXmlApplicationContext )一起使用,则会抛出一个抱怨未知 bean 作用域的IllegalStateException问题。

初始 Web 配置

为了支持requestsessionapplicationwebsocket级别的 bean 作用域(网络作用域的 bean),在定义 bean 之前需要进行一些小的初始配置。(标准作用域不需要此初始设置:singletonprototype。)

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

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

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

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

或者,如果您的侦听器设置存在问题,请考虑使用 Spring 的 RequestContextFilter. 过滤器映射取决于周围的 Web 应用程序配置,因此您必须根据需要进行更改。以下清单显示了 Web 应用程序的过滤器部分:

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

DispatcherServlet, RequestContextListener, 和RequestContextFilter都做完全相同的事情,即将 HTTP 请求对象绑定到为该Thread请求提供服务的对象。这使得请求和会话作用域的 bean 在调用链的下游可用。

请求作用域

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

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

Spring 容器通过为每个 HTTP 请求使用LoginAction bean 定义来创建loginAction bean 的新实例。也就是说, loginActionbean 的作用域是 HTTP 请求级别。您可以根据需要更改创建的实例的内部状态,因为从同一loginActionbean 定义创建的其他实例看不到这些状态更改。它们是针对个人要求的。当请求完成处理时,该请求作用域内的 bean 将被丢弃。

当使用注解驱动的组件或 Java 配置时,@RequestScope注解可用于将组件分配给request作用域。以下示例显示了如何执行此操作:

@RequestScope
@Component
public class LoginAction {
    // ...
}
会话作用域

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

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

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

在使用注解驱动的组件或 Java 配置时,您可以使用 @SessionScope注解将组件分配给session作用域。

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

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

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

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

在使用注解驱动的组件或 Java 配置时,您可以使用 @ApplicationScope注解将组件分配给application作用域。以下示例显示了如何执行此操作:

@ApplicationScope
@Component
public class AppPreferences {
    // ...
}
WebSocket 作用域

WebSocket 作用域与 WebSocket 会话的生命周期相关联,适用于 STOMP over WebSocket 应用程序,请参阅 WebSocket 作用域了解更多详细信息。

作用域 Bean 作为依赖项

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

您还可以在作用域为singleton 的 bean 之间使用<aop:scoped-proxy/>,然后引用通过可序列化的中间代理,因此能够在反序列化时重新获取目标单例 bean。

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

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

作为扩展变体,您可以为ObjectProvider<MyTargetBean> 声明提供几个额外的访问变体,包括getIfAvailablegetIfUnique。调用它的 JSR-330 变体,Provider并与Provider<MyTargetBean> 声明和get()每次检索尝试的相应调用一起使用。有关整体 JSR-330 的更多详细信息,请参见此处

以下示例中的配置只有一行,但重要的是要了解其背后的“为什么”以及“如何”:

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

    <!-- an HTTP Session-scoped bean exposed as a proxy -->
    <bean id="userPreferences" class="com.something.UserPreferences" scope="session">
        <!-- instructs the container to proxy the surrounding bean -->
        <aop:scoped-proxy/> 
    </bean>

    <!-- a singleton-scoped bean injected with a proxy to the above bean -->
    <bean id="userService" class="com.something.SimpleUserService">
        <!-- a reference to the proxied userPreferences bean -->
        <property name="userPreferences" ref="userPreferences"/>
    </bean>
</beans>

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

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

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

在前面的示例中,单例 bean ( userManager) 被注入了对 HTTPSession作用域 bean ( userPreferences) 的引用。这里的重点是 userManagerbean 是一个单例:每个容器只实例化一次,并且它的依赖项(在本例中只有一个,userPreferencesbean)也只注入一次。这意味着userManagerbean 仅对完全相同的userPreferences对象(即最初注入的对象)进行操作。

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

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

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

<bean id="userManager" class="com.something.UserManager">
    <property name="userPreferences" ref="userPreferences"/>
</bean>
选择要创建的代理类型

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

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

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

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

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

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

1.5.5. 自定义作用域

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

创建自定义作用域

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

Scope接口有四种方法可以从作用域中获取对象,将它们从作用域中移除,并让它们被销毁。

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

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

例如,会话作用域实现从底层会话中删除会话作用域 bean。该对象应该被返回,但是null如果没有找到具有指定名称的对象,您可以返回。以下方法从底层作用域中删除对象:

Object remove(String name)

以下方法注册了一个回调,当作用域被销毁或作用域中的指定对象被销毁时应调用该回调:

void registerDestructionCallback(String name, Runnable destructionCallback)

有关销毁回调的更多信息,请参阅javadoc 或 Spring 作用域实现。

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

String getConversationId()

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

使用自定义作用域

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

void registerScope(String scopeName, Scope scope);

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

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

假设您编写了自定义Scope实现,然后按照下一个示例所示进行注册。

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

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

然后,您可以创建符合您的自定义作用域Scope规则的 bean 定义, 如下所示:

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

使用自定义Scope实现,您不仅限于作用域的编程注册。您还可以Scope使用 CustomScopeConfigurer类以声明方式进行注册,如以下示例所示:

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

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

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

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

</beans>

当您在实现的 FactoryBean<bean>声明中放置<aop:scoped-proxy/>时,作用域是工厂 bean 本身,而不是从getObject().

1.6. 自定义 Bean 的性质

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

1.6.1. 生命周期回调

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

JSR-250@PostConstruct@PreDestroy注解通常被认为是在现代 Spring 应用程序中接收生命周期回调的最佳实践。使用这些注解意味着您的 bean 不会耦合到 Spring 特定的接口。有关详细信息,请参阅使用@PostConstruct@PreDestroy。如果您不想使用 JSR-250 注解但仍想移除耦合,请考虑使用init-methoddestroy-methodbean 定义元数据。

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

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

本节介绍生命周期回调接口。

初始化回调

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

void afterPropertiesSet() throws Exception;

我们建议您不要使用InitializingBean接口,因为它不必要地将代码耦合到 Spring。或者,我们建议使用@PostConstruct注解或指定 POJO 初始化方法。在基于 XML 的配置元数据的情况下,您可以使用init-method属性来指定具有无效无参数签名的方法的名称。通过 Java 配置,您可以使用 @BeaninitMethod属性. 请参阅接收生命周期回调。考虑以下示例:

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

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

前面的示例与下面的示例(由两个列表组成)具有几乎完全相同的效果:

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

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

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

销毁回调

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

void destroy() throws Exception;

我们建议您不要使用DisposableBean回调接口,因为它不必要地将代码耦合到 Spring。或者,我们建议使用@PreDestroy注解或指定 bean 定义支持的通用方法。使用基于 XML 的配置元数据,您可以使用<bean/>destroy-method属性. 通过 Java 配置,您可以使用@BeandestroyMethod属性. 请参阅 接收生命周期回调。考虑以下定义:

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

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

前面的定义与下面的定义几乎完全相同:

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

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

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

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

默认初始化和销毁方法

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

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

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

public class DefaultBlogService implements BlogService {

    private BlogDao blogDao;

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

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

然后,您可以在类似于以下内容的 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 交互时留下奇怪的语义。

结合生命周期机制

从 Spring 2.5 开始,您可以通过三个选项来控制 bean 生命周期行为:

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

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

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

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

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

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。另外,请注意,停止通知不能保证在销毁之前发出。在常规关闭时,所有Lifecyclebean 在传播一般销毁回调之前首先收到停止通知。但是,在上下文的生命周期内进行热刷新或停止刷新尝试时,只会调用销毁方法。

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

public interface Phased {

    int getPhase();
}

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

public interface SmartLifecycle extends Lifecycle, Phased {

    boolean isAutoStartup();

    void stop(Runnable callback);
}

启动时,相位最低的对象首先启动。停止时,按照相反的顺序。因此,实现SmartLifecycle并且其getPhase()方法返回的对象Integer.MIN_VALUE将是第一个开始和最后一个停止的对象。在频谱的另一端,相位值 Integer.MAX_VALUE表示对象应该最后启动并首先停止(可能是因为它依赖于正在运行的其他进程)。在考虑阶段值时,了解任何未实现SmartLifecycle的“正常”Lifecycle对象的默认阶段为0也很重要。因此,任何负相位值都表示对象应该在这些标准组件之前开始(并在它们之后停止)。对于任何正相位值,反之亦然。

SmartLifecycle定义的 stop 方法接受一个回调。在该实现的关闭过程完成后,任何实现都必须调用该回调的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()已被显式调用一样,但它发生在上下文关闭时。另一方面,“刷新”回调启用了 SmartLifecyclebean 的另一个特性。刷新上下文时(在所有对象都已实例化和初始化之后),将调用该回调。此时,默认生命周期处理器会检查每个 SmartLifecycle对象的isAutoStartup()方法返回的布尔值。如果true,则该对象在该点启动,而不是等待上下文或其自身的显式调用start()方法(与上下文刷新不同,对于标准上下文实现,上下文启动不会自动发生)。如前所述,phase值和任何“依赖”关系决定了启动顺序。

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

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

如果您在非 Web 应用程序环境中(例如,在富客户端桌面环境中)使用 Spring 的 IoC 容器,请向 JVM 注册一个关闭挂钩。这样做可确保正常关闭并在单例 bean 上调用相关的销毁方法,以便释放所有资源。您仍然必须正确配置和实现这些销毁回调。

要注册关闭挂钩,请调用接口registerShutdownHook()上声明的方法ConfigurableApplicationContext,如以下示例所示:

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

public final class Boot {

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

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

当 anApplicationContext创建一个实现 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 的附加功能中进行了描述。

自动装配是获取 ApplicationContext 引用的另一种替代方法。 传统 constructor模式和自动装配byType模式(如Autowiring Collaborators中所述)可以分别为构造函数参数或 setter 方法参数提供ApplicationContext类型依赖 。要获得更大的灵活性,包括自动装配字段和多个参数方法的能力,请使用基于注解的自动装配功能。如果你这样做,如果相关的字段、构造函数或方法带有@Autowired注解,则ApplicationContext会自动装配到期望ApplicationContext类型的字段、构造函数参数或方法参数中。有关详细信息,请参阅 使用.@Autowired

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

public interface BeanNameAware {

    void setBeanName(String name) throws BeansException;
}

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

1.6.3. 其他Aware接口

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

名称 注入依赖 解释…
ApplicationContextAware 声明ApplicationContext. ApplicationContextAwareBeanNameAware
ApplicationEventPublisherAware 封闭的事件发布者ApplicationContext 的附加功能ApplicationContext
BeanClassLoaderAware 类加载器用于加载 bean 类。 实例化 Bean
BeanFactoryAware 声明BeanFactory. BeanFactoryAPI _
BeanNameAware 声明 bean 的名称。 ApplicationContextAwareBeanNameAware
LoadTimeWeaverAware 定义的编织器,用于在加载时处理类定义。 在 Spring 框架中使用 AspectJ 进行加载时编织
MessageSourceAware 用于解析消息的配置策略(支持参数化和国际化)。 的附加功能ApplicationContext
NotificationPublisherAware Spring JMX 通知发布者。 通知
ResourceLoaderAware 为对资源进行低级访问而配置的加载程序。 资源
ServletConfigAware 当前ServletConfig容器在其中运行。仅在可感知网络的 Spring 中有效 ApplicationContext spring MVC
ServletContextAware 当前ServletContext容器在其中运行。仅在可感知网络的 Spring 中有效 ApplicationContext spring MVC

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

1.7. Bean定义继承

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

如果您以编程方式使用接口ApplicationContext,则子 bean 定义由ChildBeanDefinition类表示。大多数用户不在此级别上与他们合作。相反,他们在诸如ClassPathXmlApplicationContext. 当您使用基于 XML 的配置元数据时,您可以通过使用属性来指示子 bean 定义parent,将父 bean 指定为该属性的值。以下示例显示了如何执行此操作:

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

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

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

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

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

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

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

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

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

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

1.8. 容器扩展点

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

1.8.1. 通过使用自定义 BeanBeanPostProcessor

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

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

BeanPostProcessor实例对 bean(或对象)实例进行操作。也就是说,Spring IoC 容器实例化一个 bean 实例,然后BeanPostProcessor 实例完成它们的工作。BeanPostProcessor实例的作用域是每个容器。这仅在您使用容器层次结构时才相关。如果您在一个容器中定义BeanPostProcessor,它只会对该容器中的 bean 进行后处理。换句话说,在一个容器中定义的 bean 不会被另一个容器中定义的 bean 进行后处理BeanPostProcessor,即使两个容器是同一层次结构的一部分。要更改实际的 bean 定义(即定义 bean 的蓝图),您需要使用 aBeanFactoryPostProcessor,如 使用自定义配置元数据中所述BeanFactoryPostProcessor

org.springframework.beans.factory.config.BeanPostProcessor接口恰好由两个回调方法组成。当这样的类注册为容器的后处理器时,对于容器创建的每个 bean 实例,后处理器都会在容器初始化方法(例如InitializingBean.afterPropertiesSet()或任何声明init的方法)之前从容器中获取回调调用,并在任何 bean 初始化回调之后。后处理器可以对 bean 实例采取任何行动,包括完全忽略回调。一个 bean 后处理器通常检查回调接口,或者它可以用代理包装一个 bean。一些 Spring AOP 基础结构类被实现为 bean 后处理器,以提供代理包装逻辑。

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

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

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

BeanPostProcessor实例和 AOP 自动代理实现BeanPostProcessor接口的类是特殊的,被容器区别对待。它们直接引用的所有BeanPostProcessor实例和 bean 都在启动时实例化,作为ApplicationContext. 接下来,所有BeanPostProcessor实例都以排序方式注册并应用于容器中的所有其他 bean。因为 AOP 自动代理是作为BeanPostProcessor自身实现的,所以BeanPostProcessor 实例和它们直接引用的 bean 都没有资格进行自动代理,因此没有将方面编织到其中。对于任何这样的 bean,您应该会看到一条信息性日志消息:Bean someBean is not eligible for getting processed by all BeanPostProcessor interfaces (for example: not eligible for auto-proxying).如果您BeanPostProcessor使用自动装配或 @Resource(可能回退到自动装配)将 bean 连接到您的 bean,则 Spring 在搜索类型匹配依赖项候选时可能会访问意外的 bean,因此,使它们没有资格进行自动代理或其他类型的 bean 发布-加工。例如,如果您有一个依赖项注解,@Resource其中字段或 setter 名称不直接对应于 bean 的声明名称并且没有使用 name 属性,则 Spring 会访问其他 bean 以按类型匹配它们。

以下示例展示了如何BeanPostProcessorApplicationContext.

示例:Hello World, BeanPostProcessor-style

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

以下清单显示了自定义BeanPostProcessor实现类定义:

package scripting;

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

public class InstantiationTracingBeanPostProcessor implements BeanPostProcessor {

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

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

以下beans元素使用InstantiationTracingBeanPostProcessor:

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

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

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

</beans>

请注意InstantiationTracingBeanPostProcessor是如何定义的。它甚至没有名字,而且,因为它是一个 bean,它可以像任何其他 bean 一样被依赖注入。(前面的配置还定义了一个由 Groovy 脚本支持的 bean。Spring 动态语言支持在“ 动态语言支持”一章中有详细说明。)

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

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

public final class Boot {

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

}

上述应用程序的输出类似于以下内容:

Bean“信使”创建:org.springframework.scripting.groovy.GroovyMessenger@272961 
org.springframework.scripting.groovy.GroovyMessenger@272961
示例:AutowiredAnnotationBeanPostProcessor

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

1.8.2. 自定义配置元数据BeanFactoryPostProcessor

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

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

如果您想更改实际的 bean 实例(即从配置元数据创建的对象),那么您需要使用 a BeanPostProcessor (前面在使用 a 自定义 BeanBeanPostProcessor中进行了描述)。虽然在技术上可以在 a 中使用 bean 实例BeanFactoryPostProcessor(例如,通过使用 BeanFactory.getBean()),但这样做会导致 bean 过早实例化,从而违反标准容器生命周期。这可能会导致负面影响,例如绕过 bean 后处理。此外,BeanFactoryPostProcessor实例的作用域是每个容器。这仅在您使用容器层次结构时才相关。如果您在一个容器中定义 BeanFactoryPostProcessor,它仅适用于该容器中的 bean 定义。一个容器中的 Bean 定义不会由另一个容器中的BeanFactoryPostProcessor实例进行后处理,即使两个容器都属于同一层次结构。

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

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

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

示例:类名替换PropertySourcesPlaceholderConfigurer

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

考虑以下基于 XML 的配置元数据片段,其中 定义了带有占位符值的DataSource

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

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

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

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

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

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

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

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

PropertySourcesPlaceholderConfigurer不仅在您指定的文件中查找属性Properties 。默认情况下,如果在指定的属性文件中找不到属性,它会检查 SpringEnvironment属性和常规 JavaSystem属性。

您可以使用PropertySourcesPlaceholderConfigurer替换类名,当您必须在运行时选择特定的实现类时,这有时很有用。以下示例显示了如何执行此操作:

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

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

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

示例:PropertyOverrideConfigurer

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

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

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

beanName.property=值

以下清单显示了格式的示例:

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

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

还支持复合属性名称,只要路径的每个组件(除了要覆盖的最终属性)都已经非空(可能由构造函数初始化)。在以下示例中,将 bean tom的属性fred的属性bobsammy属性设置为标量值123

tom.fred.bob.sammy=123

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

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

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

1.8.3. 自定义实例化逻辑FactoryBean

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

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

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

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

Spring FactoryBean框架中的许多地方都使用了概念和接口。Spring FactoryBean本身提供了超过 50 个接口的实现。

当您需要向容器请求实际的 FactoryBean 实例本身而不是它生成的 bean 时,请在调用 ApplicationContext 的 getBean() 方法时在 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注解强制执行所需属性的可能性。Spring 2.5 使得遵循相同的通用方法来驱动 Spring 的依赖注入成为可能。本质上,@Autowiredannotation 提供了与Autowiring Collaborators中描述的相同的功能,但具有更细粒度的控制和更广泛的适用性。Spring 2.5 还增加了对 JSR-250 注解的支持,例如 @PostConstruct@PreDestroy. Spring 3.0 增加了对包中包含的 JSR-330(Java 依赖注入)注解的支持,javax.inject例如@Inject@Named. 有关这些注解的详细信息,请参见 相关部分

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

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

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        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 上的注解。这意味着,如果您在为 DispatcherServlet所属的WebApplicationContext中配置 <context:annotation-config/> ,它只会检查您的controllers中的@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 方法)。即使您在容器外部使用类,这样做也会强制执行这些必需的引用和值。

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

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

1.9.2. 使用@Autowired

在本节包含的示例中,@Inject可以使用JSR 330 的注解代替 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;
    }

    // ...
}

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

您还可以通过将@Autowired注解添加到需要该类型数组的字段或方法来指示 SpringApplicationContext 提供特定类型的所有 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 定义的注册顺序。您可以@Order在目标类级别和@Bean方法上声明注解,可能针对单个 bean 定义(在使用相同 bean 类的多个定义的情况下)。@Order值可能会影响注入点的优先级,但请注意它们不会影响单例启动顺序,这是由依赖关系和@DependsOn声明确定的正交问题。请注意,标准javax.annotation.Priority注解在该 @Bean级别不可用,因为它不能在方法上声明。它的语义可以通过@Order结合@Primary每个类型的单个 bean 的值来建模。

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

public class MovieRecommender {

    private Map<String, MovieCatalog> movieCatalogs;

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

    // ...
}

默认情况下,当给定注入点没有匹配的候选 bean 时,自动装配会失败。在声明的数组、集合或映射的情况下,至少需要一个匹配元素。

默认行为是将带注解的方法和字段视为指示所需的依赖项。您可以按照以下示例所示更改此行为,使框架能够通过将其标记为非必需(即,通过将required属性设置@Autowiredfalse)来跳过不可满足的注入点:

public class SimpleMovieLister {

    private MovieFinder movieFinder;

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

    // ...
}

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

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

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

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

public class SimpleMovieLister {

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

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

public class SimpleMovieLister {

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

您还可以@Autowired用于众所周知的可解析依赖项的接口:BeanFactoryApplicationContextEnvironmentResourceLoaderApplicationEventPublisherMessageSource. 这些接口及其扩展接口,例如ConfigurableApplicationContextResourcePatternResolver,会自动解析,无需特殊设置。以下示例自动装配一个ApplicationContext对象:

public class MovieRecommender {

    @Autowired
    private ApplicationContext context;

    public MovieRecommender() {
    }

    // ...
}

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

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

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

考虑以下定义firstMovieCatalog为主要的配置MovieCatalog

@Configuration
public class MovieConfiguration {

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

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

    // ...
}

使用上述配置,以下MovieRecommender内容与 自动装配 firstMovieCatalog

public class MovieRecommender {

    @Autowired
    private MovieCatalog movieCatalog;

    // ...
}

对应的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. 使用限定符微调基于注解的自动装配

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

public class MovieRecommender {

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

    // ...
}

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

public class MovieRecommender {

    private MovieCatalog movieCatalog;

    private CustomerPreferenceDao customerPreferenceDao;

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

    // ...
}

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

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

    <context:annotation-config/>

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

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

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

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

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

</beans>

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

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

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

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

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

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

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

@Autowired适用于字段、构造函数和多参数方法,允许在参数级别通过限定符注解缩小作用域。相反,@Resource 仅支持具有单个参数的字段和 bean 属性设置器方法。因此,如果您的注入目标是构造函数或多参数方法,您应该坚持使用限定符。

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

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

    String value();
}

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

public class MovieRecommender {

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

    private MovieCatalog comedyCatalog;

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

    // ...
}

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

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

    <context:annotation-config/>

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

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

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

</beans>

Classpath Scanning and Managed Components中,您可以看到基于注解的替代方法,以在 XML 中提供限定符元数据。具体来说,请参阅提供带有注解的限定符元数据

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

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

}

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

public class MovieRecommender {

    @Autowired
    @Offline 
    private MovieCatalog offlineCatalog;

    // ...
}

现在 bean 定义只需要一个 qualifier type,如下例所示:

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

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

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

    String genre();

    Format format();
}

在这种情况下Format是一个枚举,定义如下:

public enum Format {
    VHS, DVD, BLURAY
}

要自动装配的字段使用自定义限定符进行注解,并包括两个属性的值: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. 使用泛型作为自动装配限定符

除了@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>),您可以@AutowireStore接口和泛型用作限定符,如以下示例所示:

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

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

通用限定符也适用于自动装配列表、Map实例和数组。以下示例自动装配一个泛型List

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

1.9.6. 使用CustomAutowireConfigurer

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

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

通过以下AutowireCandidateResolver方式确定自动接线候选者:

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

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

1.9.7. 注射用@Resource

Spring 还通过在字段或 bean 属性设置器方法上使用 JSR-250@Resource注解 ( )来支持注入。javax.annotation.Resource这是 Java EE 中的常见模式:例如,在 JSF 管理的 bean 和 JAX-WS 端点中。Spring 也支持 Spring 管理的对象的这种模式。

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

public class SimpleMovieLister {

    private MovieFinder movieFinder;

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

如果没有明确指定名称,则默认名称派生自字段名称或 setter 方法。如果是字段,则采用字段名称。对于 setter 方法,它采用 bean 属性名称。以下示例将把名为 bean 的 beanmovieFinder注入到它的 setter 方法中:

public class SimpleMovieLister {

    private MovieFinder movieFinder;

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

随注解提供的名称被解析为 bean 名称,由 bean ApplicationContext知道CommonAnnotationBeanPostProcessor。如果显式配置 Spring,则可以通过 JNDI 解析名称 SimpleJndiBeanFactory 。但是,我们建议您依赖默认行为并使用 Spring 的 JNDI 查找功能来保留间接级别。

在没有指定显式名称的排他性@Resource使用情况下,类似于@Autowired@Resource查找主类型匹配而不是特定的命名 bean 并解析众所周知的可解析依赖项:BeanFactoryApplicationContextResourceLoaderApplicationEventPublisherMessageSource 接口。

因此,在以下示例中,该customerPreferenceDao字段首先查找名为“customerPreferenceDao”的 bean,然后回退到 type 的主要类型匹配 CustomerPreferenceDao

public class MovieRecommender {

    @Resource
    private CustomerPreferenceDao customerPreferenceDao;

    @Resource
    private ApplicationContext context; 

    public MovieRecommender() {
    }

    // ...
}

context字段是根据已知的可解析依赖类型注入的: ApplicationContext.

1.9.8. 使用@Value

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

@Component
public class MovieRecommender {

    private final String catalog;

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

使用以下配置:

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

以及以下application.properties文件:

catalog.name=MovieCatalog

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

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

@Configuration
public class AppConfig {

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

配置PropertySourcesPlaceholderConfigurer使用 JavaConfig 时, @Bean方法必须是static.

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

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

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

可以提供如下默认值:

@Component
public class MovieRecommender {

    private final String catalog;

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

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

@Configuration
public class AppConfig {

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

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

@Component
public class MovieRecommender {

    private final String catalog;

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

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

@Component
public class MovieRecommender {

    private final Map<String, Integer> countOfMoviesPerCatalog;

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

1.9.9. 使用@PostConstruct@PreDestroy

CommonAnnotationBeanPostProcessor不仅可以识别@Resource注解,还可以识别 JSR-250 生命周期注解:javax.annotation.PostConstructjavax.annotation.PreDestroy. 在 Spring 2.5 中引入,对这些注解的支持提供了初始化回调销毁回调中描述的生命周期回调机制的替代方案 。如果 在 Spring ApplicationContext中注册了CommonAnnotationBeanPostProcessor,则在生命周期中与对应的 Spring 生命周期接口方法或显式声明的回调方法相同的点调用带有这些注解之一的方法。在以下示例中,缓存在初始化时预先填充并在销毁时清除:

public class CachingMovieLister {

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

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

组合各种生命周期机制的效果的详细信息,请参见 组合生命周期机制

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

0

评论区