1.6. 自定义Bean的性质(Customizing the Nature of a Bean)
Spring框架提供了许多接口,您可以使用这些接口来定制bean的性质。本节将它们分类如下:
1.6.1. 生命周期回调(Lifecycle Callbacks)
要与容器对bean生命周期的管理进行交互,您可以实现Spring InitializingBean
和DisposableBean
接口。容器对前者调用afterPropertiesSet()
,对后者调用destroy()
,让bean在初始化和销毁bean时执行某些操作。
JSR-250
@PostConstruct
和@PreDestroy
注解通常被认为是在现代Spring应用程序中接收生命周期回调的最佳实践。使用这些注解意味着您的bean没有耦合到特定于spring的接口。详情请参见使用@PostConstruct
和@PreDestroy
。如果不希望使用JSR-250注解,但仍希望消除耦合,请考虑初始化方法和销毁方法bean定义元数据。
补充
实现
bean
的生命周期管理有三种方式:
- 实现
InitializingBean
和DisposableBean
,存在和Spring接口耦合,不推荐使用- 使用JSR-250的
@PostConstruct
和@PreDestroy
,将bean
和特定接口解耦- 使用XML的
init-method
和destroy-method
在内部,Spring框架使用BeanPostProcessor
实现来处理它能找到的任何回调接口并调用适当的方法。如果您需要自定义特性或Spring默认不提供的其他生命周期行为,您可以自己实现BeanPostProcessor
。有关更多信息,请参见容器扩展点(Container Extension Points)。
除了初始化和销毁回调之外,Spring管理的对象还可以实现Lifecycle
接口,以便这些对象可以参与由容器自己的生命周期的启动和关闭过程。
本节将描述生命周期回调接口。
初始化回调(Initialization Callbacks)
org.springframework.beans.factory.InitializingBean
接口允许bean在容器在bean上设置了所有必要的属性后执行初始化工作。InitializingBean
接口指定了一个方法:
java
复制代码
public interface InitializingBean { void afterPropertiesSet() throws Exception; }
我们建议不要使用InitializingBean
接口,因为它不必要地将代码耦合到Spring。另外,我们建议使用@PostConstruct
注解或指定POJO初始化方法。对于基于XML的配置元数据,可以使用init-method
属性指定具有无效无参数签名的方法的名称。在Java配置中,您可以使用@Bean的initMethod
属性。参见接收生命周期回调(Receiving Lifecycle Callbacks)。考虑下面的例子:
xml
复制代码
<bean id="exampleInitBean" class="examples.ExampleBean" init-method="init"/>
java
复制代码
public class ExampleBean { public void init() { // do some initialization work } }
上面的示例几乎与下面的示例(包含两个清单)具有完全相同的效果:
xml
复制代码
<bean id="exampleInitBean" class="examples.AnotherExampleBean"/>
java
复制代码
public class AnotherExampleBean implements InitializingBean { @Override public void afterPropertiesSet() { // do some initialization work } }
但是,前面两个示例中的第一个示例没有将代码与Spring耦合。
销毁回调(Destruction Callbacks)
实现org.springframework.beans.factory.DisposableBean
接口可以让bean在包含它的容器被销毁时获得回调。DisposableBean
接口指定了一个方法:
java
复制代码
void destroy() throws Exception;
我们建议您不要使用DisposableBean
回调接口,因为它不必要地将代码耦合到Spring。另外,我们建议使用@PreDestroy注解或指定bean定义支持的泛型方法。对于基于XML的配置元数据,您可以在<bean/>
上使用destroy-method
属性。在Java配置中,您可以使用@Bean的destroyMethod
属性。参见接收生命周期回调(Receiving Lifecycle Callbacks)。考虑以下定义:
xml
复制代码
<bean id="exampleInitBean" class="examples.ExampleBean" destroy-method="cleanup"/>
java
复制代码
public class ExampleBean { public void cleanup() { // do some destruction work (like releasing pooled connections) } }
上述定义几乎与以下定义具有完全相同的效果:
xml
复制代码
<bean id="exampleInitBean" class="examples.AnotherExampleBean"/>
java
复制代码
public class AnotherExampleBean implements DisposableBean { @Override public void destroy() { // do some destruction work (like releasing pooled connections) } }
但是,前面两个定义中的第一个没有将代码与Spring耦合。
你可以为一个
<bea>
元素的属性destroy-method
设置一个具有一个特殊的(推断的)值,该值指示Spring自动检测特定bean类上的公共close
或shutdown
方法。(因此,任何实现java.lang.AutoCloseable
或java.io.Closeable
的类都将匹配。)您还可以在<beans>
的default-destroy-method
属性上设置这个特殊(推断的)值,用于将此行为应用于整个bean集和(请参阅默认初始化和销毁方法(Default Initialization and Destroy Methods))。注意,这是Java配置的默认行为。
默认的初始化和销毁方法(Default Initialization and Destroy Methods)
当您编写不使用Spring特定的InitializingBean
和DisposableBean
回调接口的初始化和销毁方法回调时,您通常使用init()
、initialize()
、dispose()
等名称来编写方法。理想情况下,这种生命周期回调方法的名称在整个项目中是标准化的,以便所有开发人员使用相同的方法名称并确保一致性。
您可以将Spring容器配置为“查找”每个bean上的命名初始化和销毁回调方法名。这意味着,作为应用程序开发人员,您可以编写应用程序类并使用名为init()
的初始化回调,而不必为每个bean定义配置init-method="init"
属性。在创建bean时,Spring IoC容器调用该方法(并按照前面描述的标准生命周期回调约定)。这个特性还强制了初始化和销毁方法回调的一致命名约定。
假设初始化回调方法命名为init()
,而销毁回调方法命名为destroy()
。然后,您的类类似于以下示例中的类:
java
复制代码
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中使用这个类,如下所示:
xml
复制代码
<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-method
和destroy-method
属性指定(在XML中)方法名来覆盖默认值。
Spring容器保证在为bean提供所有依赖项后立即调用已配置的初始化回调。因此,初始化回调是在原始bean引用上调用的,这意味着AOP拦截器等等还没有应用到bean上。首先完全创建目标bean,然后应用带有其拦截器链的AOP代理(例如)。如果目标bean和代理是分开定义的,那么您的代码甚至可以与原始目标bean交互,而绕过代理。因此,将拦截器应用于init
方法是不一致的,因为这样做会将目标bean的生命周期与它的代理或拦截器耦合,并在代码直接与原始目标bean交互时留下奇怪的语义。
组合生命周期机制(Combining Lifecycle Mechanisms)
从Spring 2.5开始,你有三个选项来控制bean的生命周期行为:
- InitializingBean和DisposableBean回调接口
- 自定义
init()
和destroy()
方法 - @PostConstruct和@PreDestroy注解。
您可以组合这些机制,用于控制给定的Bean。
提示
如果为一个bean配置了多个生命周期机制,并且每个机制都配置了不同的方法名,那么每个配置的方法都按照本文后面列出的顺序运行。但是,如果为这些生命周期机制中的一个以上配置了相同的方法名(例如,为初始化方法配置init()),则该方法将运行一次,如前一节所述。
为同一个bean配置了多个生命周期机制,使用不同的初始化方法,如下所示:
- 由
@PostConstruct
注解的方法 - 由
InitializingBean
回调接口定义的afterPropertiesSet()
- 一个自定义配置的
init()
方法
销毁方法有着相同的被调用的顺序:
- 由
@PreDestroy
注解的方法 - 由
DisposableBean
回调接口定义的destroy()
- 一个自定义配置的
destroy()
方法
启动和关闭的回调(Startup and Shutdown Callbacks)
Lifecycle
接口为任何有自己生命周期需求的对象定义了基本的方法(比如启动和停止一些后台进程):
java
复制代码
public interface Lifecycle { void start(); void stop(); boolean isRunning(); }
任何Spring管理的对象都可以实现Lifecycle
接口。然后,当ApplicationContext
本身接收到启动和停止信号时(例如,对于运行时的停止/重启场景),它将这些调用级联到该上下文中定义的LifeCycle
实现。它通过委托给一个LifecycleProcessor
来完成这个任务,如下面的清单所示:
java
复制代码
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
接口的定义:
java
复制代码
public interface Phased { int getPhase(); }
SmartLifecycle
接口的定义如下所示:
java
复制代码
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来覆盖默认的生命周期处理器实例。如果你只想修改超时时间,定义以下内容就足够了:
xml
复制代码
<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()
方法,如下例所示:
java
复制代码
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. AplicationContextAware
和BeanNameAware
当一个ApplicationContext
创建了一个对象实例来实现org.springframework.context.ApplicationContextAware
接口时,该实例将被提供一个对该ApplicationContext
的引用。下面的清单显示了ApplicationContextAware
接口的定义:
java
复制代码
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接口的定义:
java
复制代码
public interface BeanNameAware { void setBeanName(String name) throws BeansException; }
回调在填充普通bean属性之后调用,但在初始化回调(如InitializingBean.afterPropertiesSet())或自定义初始化方法之前调用。
1.6.3. 其它Aware
接口
除了ApplicationContextAware
和BeanNameAware
(前面讨论过)之外,Spring还提供了广泛的Aware
回调接口,让bean向容器表明它们需要特定的基础设施依赖。作为一般规则,名称指示依赖类型。下表总结了最重要的Aware
接口:
名称 | 注入的依赖 | 解释 |
---|---|---|
ApplicationContextAware | 声明的 ApplicationContext . | ApplicationContextAware 和 BeanNameAware |
ApplicationEventPublisherAware | ApplicationContext 内部的事件发布者 | ApplicationContext附加的能力 |
BeanClassLoaderAware | 用于加载Bean的类的类加载器 | 实例化Bean |
BeanFactoryAware | 声明的BeanFactory . | BeanFactory API |
BeanNameAware | 声明的Bean的名称 | ApplicationContextAware 和 BeanNameAware |
LoadTimeWeaverAware | 为在加载时处理类定义而定义的编织器。 | 在Spring框架中使用AspectJ进行加载时编织 |
MessageSourceAware | 解析消息的已配置策略(支持参数化和国际化)。 | ApplicationContext附加的能力 |
NotificationPublisherAware | Spring 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指定为该属性的值。下面的例子展示了如何这样做:
xml
复制代码
<!-- 在指定了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
,如下面的示例所示:
xml
复制代码
<!-- 在未指定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
接口。要了解更多细节,请参阅BeanPostProcessor和 Ordered接口的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()
方法,并将结果字符串打印到系统控制台。
java
复制代码
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
复制代码
<?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应用程序运行上述代码和配置:
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); } }
上述应用程序的输出类似于以下内容:
kotlin
复制代码
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
类似,但有一个主要区别:BeanFactoryPostProcessor
在bean配置元数据上操作。也就是说,Spring IoC容器允许BeanFactoryPostProcessor
读取配置元数据,并可能在容器实例化除BeanFactoryPostProcessor
实例之外的任何bean之前更改它。
您可以配置多个BeanFactoryPostProcessor
实例,并且可以通过设置order
属性来控制这些BeanFactoryPostProcessor
实例运行的顺序。但是,只有当BeanFactoryPostProcessor
实现了Ordered
接口时,您才能设置此属性。如果编写自己的BeanFactoryPostProcessor
,也应该考虑实现Ordered
接口。有关更多细节,请参阅BeanFactoryPostProcessor和Ordered接口的javadoc。
提示
如果希望更改实际的bean实例(即从配置元数据创建的对象),则需要使用
BeanPostProcessor
(前面在使用BeanPostProcessor定制bean中描述过)。虽然在技术上可以在BeanFactoryPostProcessor中使用bean实例(例如,通过使用BeanFactory.getBean()),但这样做会导致过早的bean实例化,违反标准的容器生命周期。这可能会导致负面的副作用,比如绕过bean的后期处理。此外,
BeanFactoryPostProcessor
实例的作用域为每个容器。这只有在使用容器层次结构时才有意义。如果在一个容器中定义了BeanFactoryPostProcessor
,则它只应用于该容器中的bean定义。一个容器中的Bean定义不会被另一个容器中的BeanFactoryPostProcessor
实例进行后处理,即使两个容器是同一层次结构的一部分。
当在ApplicationContext
中声明bean工厂后处理器时,它会自动运行,以便将更改应用于定义容器的配置元数据。Spring包括许多预定义的bean工厂后处理器,如PropertyOverrideConfigurer
和PropertySourcesPlaceholderConfigurer
。您还可以使用自定义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定义文件的复杂性或风险。
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样式。
ini
复制代码
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属性中以逗号分隔的列表形式提供一个或多个位置,如下例所示:
xml
复制代码
<context:property-placeholder location="classpath:com/something/jdbc.properties"/>
PropertySourcesPlaceholderConfigurer
不仅在您指定的Properties
文件中查找属性。默认情况下,如果在指定的属性文件中找不到属性,它将检查Spring Environment
属性和常规Java系统属性。
您可以使用PropertySourcesPlaceholderConfigurer
来替换类名,当您必须在运行时选择特定的实现类时,这有时很有用。下面的例子展示了如何这样做:
xml
复制代码
<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
serviceStrategy
的class
属性取了custom.strategy.class
对应的值
如果不能在运行时将类解析为有效类,则在即将创建bean时解析失败,这是在非惰性初始化bean的ApplicationContext
的preinstantiatesingleton()
阶段。
示例:PropertyOverrideConfigurer
另一个bean工厂后处理程序PropertyOverrideConfigurer
与PropertySourcesPlaceholderConfigurer
类似,但与后者不同的是,原始定义可以为bean属性提供默认值,也可以根本没有值。如果重写的Properties
文件没有某个bean属性的条目,则使用默认上下文定义。
注意,bean定义不知道被覆盖了,因此从XML定义文件中不能立即看出正在使用覆盖配置器。如果多个PropertyOverrideConfigurer
实例为同一个bean属性定义了不同的值,由于覆盖机制,最后一个实例获胜。
属性文件配置行采用以下格式:
ini
复制代码
beanName.property=value
下面的清单显示了该格式的一个示例:
ini
复制代码
dataSource.driverClassName=com.mysql.jdbc.Driver dataSource.url=jdbc:mysql:mydb
该示例文件可以与容器定义一起使用,该容器定义包含一个名为dataSource
的bean,该bean具有driver
和url
属性。
也支持复合属性名,只要路径的每个组件(除了被覆盖的最后一个属性)都是非空的(可能是由构造函数初始化的)。在下面的例子中,tom
bean的fred
属性的bob
属性的sammy
属性被设置为标量值123
:
ini
复制代码
tom.fred.bob.sammy=123
提示
指定的覆盖值总是文字值。它们不会被翻译成bean引用。当XML bean定义中的原始值指定一个bean引用时,这种约定也适用。
随着Spring 2.5中引入的上下文命名空间,可以使用专用的配置元素来配置属性重写,如下面的示例所示:
xml
复制代码
<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时,请在调用ApplicationContext
的getBean()
方法时,在bean的id前面加上&
符号。
因此,对于id为myBean
的给定FactoryBean
,
- 在容器上调用
getBean(“myBean”)
将返回FactoryBean
的产品, - 而调用
getBean(“&myBean”)
将返回FactoryBean
实例本身。
作者:UpUpLiu
链接:https://juejin.cn/post/7240380161555021884
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。