1.6。自定义bean的性质
Spring框架提供了许多接口,可用于自定义Bean的性质。本节将它们分组如下:
- 生命周期回调Lifecycle Callbacks
- ApplicationContextAware 和 BeanNameAware
- 其他Aware
1.6.1。生命周期回调
为了与参与容器对bean生命周期的管理,您可以实现Spring 的InitializingBean和DisposableBean接口。
容器初始化会调用 afterPropertiesSet()方法和销毁会调用destroy()方法来执行某些操作。
通常,@PostConstruct和@PreDestroy注解被认为是在Spring应用程序中接收生命周期回调的最佳实践。
使用这些注解意味着您的bean没有耦合到特定于Spring的接口。
如果你不希望使用JSR-250注解,但你仍然要删除的耦合,考虑init-method和destroy-methodbean定义元数据。
public class InitAndDestroyBean implements InitializingBean, DisposableBean {
@Override
public void destroy() throws Exception {
System.out.println("DisposableBean destroy");
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("InitializingBean init" );
}
@PostConstruct
public void init(){
System.out.println("PostConstruct init");
}
@PreDestroy
public void destroy2(){
System.out.println("PreDestroy destroy");
}
public void initMethod(){
System.out.println("Init-method init");
}
public void destroyMethod(){
System.out.println("Destroy-method destroy");
}
}
控制台打印:
PostConstruct init
InitializingBean init
Init-method init
.
.
.
PreDestroy destroy
DisposableBean destroy
Destroy-method destroy
在内部,Spring框架使用BeanPostProcessor实现来处理它可以找到的任何回调接口并调用适当的方法。如果您需要自定义功能或其他Spring默认不提供生命周期行为,则您可以BeanPostProcessor自己实现。
除了初始化和销毁回调之外,spring管理的对象还可以实现Lifecycle 接口,以便这些对象可以参与启动和关闭过程,这是由容器自己的生命周期驱动的。
上述的Lifecycle类上有这么一个注释
* Note that the present {@code Lifecycle} interface is only supported on
* top-level singleton beans. On any other component, the {@code Lifecycle}
* interface will remain undetected and hence ignored. Also, note that the extended
* {@link SmartLifecycle} interface provides sophisticated integration with the
* application context's startup and shutdown phases.
一开始看没看懂top-level singleton beans的含义自己试了一下实现Lifecycle
@Component
public class CustomLifecycle implements Lifecycle {
@Override
public void start() {
System.out.println("start-----------");
}
@Override
public void stop() {
System.out.println("stop-----------");
}
@Override
public boolean isRunning() {
return false;
}
}
控制台输出
PostConstruct init
InitializingBean init
Init-method init
PreDestroy destroy
DisposableBean destroy
Destroy-method destroy
这些代码并没有起作用
调试了源码发现在启动关闭容器的时候,容器确实找到Lifecycle的所有实现类但是还有一个if操作
if (bean instanceof SmartLifecycle) {
.
.
.
}
更改了继承关系
@Component
public class CustomSmartLifecycle implements SmartLifecycle {
@Override
public void start() {
System.out.println("start-----------");
}
@Override
public void stop() {
System.out.println("stop-----------");
}
@Override
public boolean isRunning() {
return false;
}
}
控制台输出
PostConstruct init
InitializingBean init
Init-method init
start-----------
PreDestroy destroy
DisposableBean destroy
Destroy-method destroy
这才稍微理解那段注释,意思大概就是如果你直接继承自Lifecycle接口,
那么容器不会调用你的自定义接口,只有继承SmartLifecycle才行
本节介绍了生命周期回调接口。
初始化回调
org.springframework.beans.factory.InitializingBean接口允许容器在bean上设置了所有必要的属性之后执行初始化工作。
InitializingBean接口指定了一个方法:
void afterPropertiesSet() throws Exception;
我们建议您不要使用该InitializingBean接口,因为它将您的代码耦合到Spring。
另外,我们建议使用@PostConstruct注释或指定POJO初始化方法。
对于基于XML的配置元数据,可以使用init-method属性指定具有无效无参数签名的方法的名称。
通过Java配置,您可以使用的initMethod属性@Bean。
请参阅接收生命周期回调。考虑以下示例:
public class ExampleBean {
public void init() {
// do some initialization work
}
}
您可以使用xml
或者JAVA Bean配置
@Bean(initMethod = "init")
public void exampleBean(){
return new ExampleBean();
}
前面的示例与下面的示例几乎具有完全相同的效果:
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的配置元数据时,您可以在destroy-method上使用属性
通过Java配置,您可以使用的destroyMethod属性@Bean。
考虑以下定义:
public class ExampleBean {
public void cleanup() {
// do some destruction work (like releasing pooled connections)
}
}
您可以使用xml
或者JAVA Bean配置
@Bean(destroyMethod = "cleanup")
public void exampleBean(){
return new ExampleBean();
}
前面的定义与下面的定义几乎具有完全相同的效果:
public class AnotherExampleBean implements DisposableBean {
@Override
public void destroy() {
// do some destruction work (like releasing pooled connections)
}
}
但是,前面两个定义中的第一个没有将代码耦合到Spring。
您可以为元素的destroy-method属性指定一个特殊的(inferred)值,
该值指示Spring自动检测特定bean类上的公共close 或shutdown 方法(任何实现java.lang.AutoCloseable or java.io.Closeable也会匹配)。
@Component
public class CustomAutoCloseable implements AutoCloseable {
@Override
public void close() {
System.out.println("AutoCloseable close");
}
}
@Component
public class CustomCloseable implements Closeable {
@Override
public void close() {
System.out.println("Closeable close");
}
}
控制台输出:
Closeable close
AutoCloseable close
您还可以在元素的default-destroy-method属性上设置这个特殊的(inferred)值,
以便将此行为应用于整个bean集合。
默认初始化和销毁方法
当您编写初始化和销毁没有使用特定的spring的InitializingBean和DisposableBean回调接口的特定方法时,
您通常编写具有init()、initialize()、dispose()等名称的方法。
理想情况下,这种生命周期回调方法的名称在整个项目中标准化,
以便所有开发人员使用相同的方法名称并确保一致性。
您可以配置Spring容器来“查找”已命名的初始化,并销毁每个bean上的回调方法名称。
这意味着,作为应用程序开发人员,您可以编写应用程序类并使用名为init()的初始化回调,而不必为每个bean定义配置init-method="init"属性。
Spring IoC容器在创建bean时调用该方法(并且与前面描述的标准生命周期回调契约一致)。该特性还强制对初始化和销毁方法回调执行一致的命名约定。
假设您的初始化回调方法名为init(),而销毁回调方法名为destroy()。然后,您的类就像下面示例中的类:
public class DefaultBlogService implements BlogService {
private BlogDao blogDao;
public void setBlogDao(BlogDao blogDao) {
this.blogDao = blogDao;
}
// this is (unsurprisingly) the initialization callback method
public void init() {
if (this.blogDao == null) {
throw new IllegalStateException("The [blogDao] property must be set.");
}
}
}
然后,您可以在类似于以下内容的Bean中使用该类:
在顶级
在创建和组装bean时,如果bean类有这样的方法,则会在适当的时候调用它。
您可以使用顶级
如果现有的bean类已经有了根据约定命名的回调方法,那么您可以通过使用
Spring容器保证在为bean提供所有依赖项后立即调用已配置的初始化回调。
因此,初始化回调是在原始bean引用上调用的,这意味着AOP拦截器等还没有应用到bean。
所以AOP执行顺序是先创建一个目标bean,然后再应用一个带有拦截器链的AOP代理(例如)。
因此,将拦截器应用到init方法是不适用的。
组合生命周期机制
从Spring 2.5开始,您可以使用三个选项来控制Bean生命周期行为:
- InitializingBean和 DisposableBean回调接口
- 自定义init()和destroy()方法
- 在@PostConstruct和@PreDestroy 注释。
您可以结合使用这些机制来控制给定的bean。
如果为一个bean配置了多个生命周期机制,并且为每个机制配置了不同的方法名称,则每个配置的方法都将按照此注释后列出的顺序运行。
但是,如果init()同时带有@PostConstruct注解和xml的init-method方法,那么这个init()方法就会被执行一次。
使用不同的初始化方法,为同一个bean配置的多个生命周期机制如下所示:
初始化方法的调用顺序:
- 用注释的方法 @PostConstruct
- afterPropertiesSet()由InitializingBean回调接口定义
- 定制配置的init()方法
销毁方法的调用顺序相同:
- 用注释的方法 @PreDestroy
- destroy()由DisposableBean回调接口定义
- 定制配置的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。
另外,请注意,不能保证会在销毁之前发出停止通知。在常规关闭时,Lifecycle在传播常规销毁回调之前,所有Bean首先都会收到停止通知。但是,在上下文生命周期中进行热刷新或停止刷新尝试时,仅调用destroy方法。
启动和关闭调用的顺序可能很重要。
如果任何两个对象之间存在“依赖”关系,则依赖方在其依赖之后开始,而在依赖之前停止。
但是,有时直接依赖项是未知的。
您可能只知道某种类型的对象应该先于另一种类型的对象开始。
在这些情况下,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将是第一个开始且最后一个停止的对象。
在频谱的另一端,相位值Integer.MAX_VALUE表示该对象应最后启动并首先停止(可能因为该对象取决于正在运行的其他进程)。
当考虑相位值,同样重要的是要知道,对于任何“正常”的默认阶段 Lifecycle目标没有实现SmartLifecycle的0。
因此,任何负相位值都表明对象应在这些标准组件之前开始(并在它们之后停止)。对于任何正相位值,反之亦然。
SmartLifecycle的stop方法接受Runnable callback作为方法入参。
default void stop(Runnable callback) {
stop();
callback.run();
}
在该stop()的子类的实现执行完成之后,调用该callback.run()的方法。
由于LifecycleProcessor接口的默认实现DefaultLifecycleProcessor会在每个阶段内的对象组等待其超时值,以调用该回调,因此可以在必要时启用异步关闭。
默认的每阶段超时为30秒。
您可以通过定义lifecycleProcessor上下文中命名的bean来覆盖默认的生命周期处理器实例 。
如果只想修改超时,则定义以下内容即可:
正如前面提到的,LifecycleProcessor接口还定义了刷新和关闭上下文的回调方法。
后者驱动关闭过程,就好像是显式地调用了stop(),但是它发生在上下文关闭时。
另一方面,“刷新”回调可以实现SmartLifecycle bean的另一个特性。
当上下文被刷新时(在所有对象被实例化和初始化之后),回调被调用。
这时,默认的生命周期处理器会检查每个SmartLifecycle对象的isAutoStartup()方法返回的布尔值。
如果为真,则在该点启动该对象,而不是等待上下文或其自己的start()方法的显式调用(与上下文刷新不同,对于标准上下文实现,上下文启动不会自动发生)。
如上所述,相位值和任何“依赖”关系决定启动顺序。
Map lifecycleBeans = getLifecycleBeans();
Map phases = new HashMap<>();
lifecycleBeans.forEach((beanName, bean) -> {
//DefaultLifecycleProcessor 调用该方法autoStartupOnly始终未true
//如果bean instanceof SmartLifecycle 并且isAutoStartup()方法返回true
if (!autoStartupOnly || (bean instanceof SmartLifecycle && ((SmartLifecycle) bean).isAutoStartup())) {
int phase = getPhase(bean);
LifecycleGroup group = phases.get(phase);
if (group == null) {
group = new LifecycleGroup(phase, this.timeoutPerShutdownPhase, lifecycleBeans, autoStartupOnly);
phases.put(phase, group);
}
group.add(beanName, bean);
}
});
if (!phases.isEmpty()) {
List keys = new ArrayList<>(phases.keySet());
Collections.sort(keys);
for (Integer key : keys) {
phases.get(key).start();
}
}
}
在非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。ApplicationContextAware和BeanNameAware
当ApplicationContext创建一个实现org.springframework.context.ApplicationContextAware的对象实例时,该实例提供对ApplicationContext的引用。下面的清单显示了applicationcontext taware接口的定义:
public interface ApplicationContextAware {
void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
}
因此,bean可以通过ApplicationContext接口或通过将引用转换为该接口的已知子类(比如ConfigurableApplicationContext,它公开了其他功能),以编程方式操作创建它们的ApplicationContext。
一种用途是对其他bean进行编程检索。
有时这个功能是有用的。
但是,通常应该避免使用它,因为它将代码与Spring结合在一起,并且不遵循控制反转风格,在这种风格中协作者作为属性提供给bean。
ApplicationContext的其他方法提供了对文件资源的访问、发布应用程序事件和访问消息源。
这些附加特性在ApplicationContext的附加功能中进行了描述。
自动装配是获得对ApplicationContext的引用的另一种选择。
传统的constructor和byType自动装配模式可以分别为构造函数参数或setter方法参数提供类型ApplicationContext的依赖关系。
为了获得更大的灵活性,包括自动装配字段和多个参数方法的能力,可以使用基于注解的自动装配特性。如果你这样做了,ApplicationContext就会自动生成一个字段、构造函数参数或方法参数,如果有问题的字段、构造函数或方法带有@Autowired注解,那么这些参数期望得到ApplicationContext类型。
说了半天不如看代码:
public class CustomAutowiringApplicationContext {
@Autowired
private ApplicationContext applicationContext;
public CustomAutowiringApplicationContext(ApplicationContext applicationContext){
System.out.println(applicationContext);
}
public void setApplicationContext(ApplicationContext applicationContext){
System.out.println(applicationContext);
}
}
//只有配置byType 或者 byName set注入才会生效
当ApplicationContext创建一个实现org.springframework.beans.factory.BeanNameAware的类时。BeanNameAware接口,提供当前bean的名称的引用。下面的清单显示了BeanNameAware接口的定义:
public interface BeanNameAware {
void setBeanName(String name) throws BeansException;
}
源码:
private void invokeAwareMethods(String beanName, Object bean) {
if (bean instanceof Aware) {
if (bean instanceof BeanNameAware) {
((BeanNameAware) bean).setBeanName(beanName);
}
if (bean instanceof BeanClassLoaderAware) {
ClassLoader bcl = getBeanClassLoader();
if (bcl != null) {
((BeanClassLoaderAware) bean).setBeanClassLoader(bcl);
}
}
if (bean instanceof BeanFactoryAware) {
((BeanFactoryAware) bean).setBeanFactory(AbstractAutowireCapableBeanFactory.this);
}
}
}
执行顺序源码:
if (System.getSecurityManager() != null) {
AccessController.doPrivileged((PrivilegedAction
BeanNameAware,BeanClassLoaderAware,BeanFactoryAware 执行是在填充普通bean属性之后,但在初始化回调(如InitializingBean、afterPropertiesSet或自定义init-method)之前调用回调。
1.6.3。其他Aware介面
除了applicationcontext taware和BeanNameAware(前面讨论过)之外,Spring还提供了广泛的可感知回调接口,让bean向容器表明它们需要某种基础设施依赖关系。作为一般规则,名称表示依赖类型。下表总结了最重要的感知接口:
表4.Aware接口 好多没用过,没法做详细解释后续再补充,此处进行简单罗列
名称 | 注入的对象 |
---|---|
ApplicationContextAware | ApplicationContext |
ApplicationEventPublisherAware | 事件发布者ApplicationContext |
BeanClassLoaderAware | 类加载器,用于加载Bean类。 |
BeanFactoryAware | BeanFactory |
BeanNameAware | bean的名称。 |
BootstrapContextAware | BootstrapContext容器在其中运行的资源适配器。 通常仅在支持JCA的ApplicationContext实例中可用。 |
LoadTimeWeaverAware | 定义了在加载时处理类定义的weaver |
MessageSourceAware | 解决消息的已配置策略(支持参数化和国际化) |
NotificationPublisherAware | Spring JMX通知发布者 |
ResourceLoaderAware | 已配置用于解析消息的策略(支持参数化和国际化) |
ServletConfigAware | 当前ServletConfig容器在其中运行。 仅在WebApplicationContext环境下运行 |
ServletContextAware | 当前ServletContext容器在其中运行。 仅在WebApplicationContext环境下运行 |
再次注意,使用这些接口会将您的代码与Spring API绑定在一起,并且不遵循“控制反转”样式。因此,我们建议将它们用于需要以编程方式访问容器的基础结构Bean。
1.7。Bean Definition继承
bean定义可以包含许多配置信息,包括构造函数参数、属性值和特定于容器的信息,比如初始化方法、静态工厂方法名,等等。子bean定义从父bean定义继承配置数据。子定义可以覆盖某些值,也可以根据需要添加其他值。使用父bean和子bean定义可以节省大量输入。实际上,这是一种模板形式。
如果您以编程方式使用ApplicationContext接口,则子bean定义由ChildBeanDefinition类表示。大多数用户不会在这个级别上使用它们。相反,它们在类(如ClassPathXmlApplicationContext)中声明性地配置bean定义。当您使用基于xml的配置元数据时,您可以通过使用父属性来指示子bean定义,并将父bean指定为该属性的值。下面的例子展示了如何做到这一点:
如果没有指定bean 的class属性,子bean定义使用父定义中的bean的class,但是也可以覆盖它。在后一种情况下,子bean类必须与父bean兼容(也就是说,它必须接受父bean的属性值)。
子bean定义从父bean继承scope、构造函数参数值、属性值,并且覆盖父类方法,并提供添加新值的选项。
您指定的任何Scope、初始化方法、销毁方法或静态工厂方法设置都会覆盖相应的父设置。
其余的设置总是取自子定义:依赖、自动装配模式、依赖检查、单例和 lazy init。
前面的示例通过使用抽象属性显式地将父bean定义标记为抽象。如果父定义没有指定类,则需要显式地将父bean定义标记为抽象,如下面的示例所示:
父bean不能自己实例化,因为它不完整,而且它还显式地标记为抽象。当定义是抽象的时候,它只能作为纯模板bean定义使用,作为子定义的父定义。试图单独使用这样一个抽象的父bean,通过引用它作为另一个bean的ref属性,或者使用父bean ID执行显式的getBean()调用,都会返回错误。类似地,容器的内部预实例化esingletons()方法忽略被定义为抽象的bean定义。
默认情况下,ApplicationContext预实例化所有单例。
因此,如果你有一个(父)bean定义你只打算使用作为模板,
并且这个父bean定义指定了一个类,您必须确保设置抽象属性为true,
否则应用程序上下文会(试图)预实例化abstract的bean。