文档章节

Spring4-容器9-定制bean特性

王胜_淡如止水
 王胜_淡如止水
发布于 2017/03/26 21:39
字数 2778
阅读 26
收藏 1

    Spring提供了几个标志接口(marker interface),这些接口用来改变容器中bean的行为;它们包括InitializingBean和DisposableBean。 实现这两个接口的bean在初始化和析构时容器会调用前者的afterPropertiesSet()方法,以及后者的destroy()方法。

在现代的Spring应用中,The JSR-250 @PostConstruct and @PreDestroy 接口一般认为是接收生命周期回调的最佳做法。 使用这些注解意味着bean没有耦合到Spring具体的接口。详情见Section 5.9.7, “@PostConstruct and @PreDestroy”

如果你不想使用JSR-250 注解,但你还是寻找消除耦合,考虑使用对象的init方法和destroy方法定义元数据。

    Spring在内部使用 BeanPostProcessor 实现来处理它能找到的任何回调接口并调用相应的方法。如果你需要自定义特性或者生命周期行为,你可以实现自己的BeanPostProcessor 。更多信息,详情见Section 5.8, “容器拓展点”。 

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

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

1 初始化回调函数

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

void afterPropertiesSet() throws Exception;

    通常,要避免使用 InitializingBean 接口并且不鼓励使用该接口,因为这样会将代码和Spring耦合起来。 使用@PostConstruct注解或者指定一个POJO的初始化方法。 在XML配置元数据的情况下,使用 init-method 属性去指定方法名,并且该方法无参数签名。 例如,下面的定义:

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

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

}

下面代码和上面是完全一样的,但是没有将代码与Spring耦合在一起。

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

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

}

2 析构回调函数

    实现 org.springframework.beans.factory.DisposableBean 接口,允许一个bean当容器需要其销毁时获得一次回调。 DisposableBean 接口也只规定了一个方法:

void destroy() throws Exception;

    建议不使用 DisposableBean 回调接口,因为会与Spring耦合。使用@PreDestroy 注解或者指定一个普通的方法,但能由bean定义支持。基于XML配置的元数据,使用 <bean/> 的 destroy-method 属性。例如,下面的定义:

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

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

}

    下面代码与上面效果相同,但是不与Spring耦合。

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

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

}

3 全局的初始化和析构方法

    我们可以使用 <beans/> 元素中的 default-init-method 属性和 default-destroy-method 属性来定义全局的初始化和销毁方法,代码如下:

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

注意:

(1)如果你既定义了全局的初始化和销毁方法,又定义了基于bean的初始化方法,那么只会执行基于Bean的初始化和销毁方法。

(2)Spring容器保证在bean的所有依赖都满足后立即执行配置的初始化回调。这意味着初始化回调在原生bean上调用,这也意味着这个时候任何诸如AOP拦截器之类的将不能被应用。 一个目标bean是首先完全创建,然后才应用诸如AOP代理等拦截器链。注意,如果目标bean和代理是分开定义了,你的代码甚至可以绕开代理直接和原生bean通信。 因此,在初始化方法上使用拦截器将产生未知的结果,因为这将目标bean和它的代理/拦截器的生命周期绑定并且留下了和初始bean直接通信这样奇怪的方式。

4 组合生命周期机制

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

  • @PostConstruct 元注释
  • InitializingBean 的 afterPropertiesSet() 定义
  • 自定义 init() 方法

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

  • @PreDestroy 元注释
  • DisposableBean 的 destroy() 定义
  • 自定义 destroy() 方法

    如果bean存在多种的生命周期机制配置并且每种机制都配置为不同的方法名, 那所有配置的方法将会按照上面的顺利执行。然而如果配置了相同的方法名 - 例如, init()初始化方法 - 采用多种机制配置后,只会执行一次。

4.1 代码示例

4.1.1 准备Bean

package com.ws.edu.spring;

import org.springframework.beans.factory.InitializingBean;
public class Game implements InitializingBean{
	public void init(){
		System.out.println("Game执行初始化");
	}
	
	public void init1(){
		System.out.println("Game执行初始化1");
	}
	public void destory(){
		System.out.println("Game销毁执行!");
	}

	@Override
	public void afterPropertiesSet() throws Exception {
		System.out.println("Game afterPropertiesSet");
	}
}
package com.ws.edu.spring;

import org.springframework.beans.factory.InitializingBean;

public class Person implements InitializingBean{
	public void init(){
		System.out.println("Person初始化执行!");
	}
	public void destory(){
		System.out.println("Person销毁执行!");
	}
	@Override
	public void afterPropertiesSet() throws Exception {
		System.out.println("Person afterPropertiesSet");
	}
}

4.1.2 准备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"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
	        http://www.springframework.org/schema/beans/spring-beans.xsd" default-init-method="init1">
	<bean id="person" class="com.ws.edu.spring.Person" init-method="init" destroy-method="destory"/>
	<bean id="game" class="com.ws.edu.spring.Game" init-method="init" destroy-method="destory"/>
</beans>

4.1.3 准备启动类

package com.ws.edu.spring;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class App {
	@SuppressWarnings("resource")
	public static void main(String[] args) {
		ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
		System.out.println(context.getBean(Person.class));
		System.out.println(context.getBean(Game.class));
	}
}

4.1.4 输出结果

注意:@PostConstruct和@PreDestroy注解只能在注解扫描的时候才会起作用!

5 启动和关闭回调

Lifecycle 接口 为任何有它自己生命周期要求的对象定义基本方法(例如 开始和停止一些后台处理):

public interface Lifecycle {

    void start();

    void stop();

    boolean isRunning();

}

    任何Spring管理的对象可能实现那个接口。然后,当 ApplicationContext 开始和停止的时候,它会将那些调用的所有生命周期的实现 定义在这样的上下文中。通过 LifecycleProcessor :

public interface LifecycleProcessor extends Lifecycle {

    void onRefresh();

    void onClose();

}

LifecycleProcessor 本身扩展于 Lifecycle 接口。它还增加了两个其他的方法,用于对上下文进行刷新和关闭。

    启动和关闭的顺序调用也很重要。如果任何两个对象之间存在依赖关系,依赖方将会在依赖后开始,在依赖前停止。然而,有时候直接依赖关系是未知的。 你可能只知道某个类型的对象应该在另一种类型的对象之前开始。在这种情况下, SmartLifecycle 接口定义另一种选择,换句话说, 作为其超级接口,Phased 定义 getPhase()方法。

public interface Phased {

    int getPhase();

}
public interface SmartLifecycle extends Lifecycle, Phased {

    boolean isAutoStartup();

    void stop(Runnable callback);

}

    当开始的时候,最低阶段的对象首先开始,并且当停止的时候,是反向的顺序。因此,实现 SmartLifecycle 接口和返回值是 Integer.MIN_VALUE 的 getPhase() 方法 的对象将在第一个开始和最后一个停止。在另一方面,相位值Integer.MAX_VALUE 将表明对象应该第一个停止和最后开始( 可能是因为它依赖于其他进程的运行)。当考虑相位值的时候,同样重要的是要知道任何没有实现 SmartLifecycle 的 Lifecycle 对象默认的相位是0。 因此,任何负相值都会显示一个对象应该在那些标准组件前开始(并且在他们之后停止),反之为任何正相位值。

    正如你所看到的, SmartLifecycle 接受回调,定义了停止方法。任何实现必须调用回调的 run() 方法,在实现的关闭进程完成之后。 那使得异步关闭,在默认实现的 lifecycleprocessor 接口开始,DefaultLifecycleProcessor ,将等待其在每个阶段中的对象组的超时值来调用这个回调。 默认的 per-phase 超时时间是30秒。你可以在上下文中定义一个名为"lifecycleProcessor"的bean来重写默认生命周期处理器实例。如果你打算修改超时时间, 然后定义以下的就足够了:

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

    如上所述, LifecycleProcessor 接口对于上下文的刷新和关闭定义了回调方法。如果` stop() 被显式调用,后者将简单的驱动关闭进程, 但当上下文关闭时会发生。 'refresh' 回调在另一方面来说是 `SmartLifecycle bean的另一个特点。当上下文被刷新的时候(在所有对象被实例化和 初始化之后),回调将被调用,而在这一点上,默认生命周期处理器将检查通过每一个 SmartLifecycle 对象的 isAutoStartup() 方法返回的布尔值。 如果返回 "true",这个对象将在这一点上开始,而不是等待上下文的或者本身的 start() 方法的显示调用(与上下文刷新不同,上下文开始不会 为一个标准的上下文实现自动发生)。 "phase" 值以及 "depends-on" 关系将以相同的方式确定启动顺序,如上所述。

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

    本节仅适用于非web应用程序。在基于web的ApplicationContext实现中已有相应的代码来处理关闭web应用时如何恰当地关闭Spring IoC容器。

    如果你正在一个非web应用的环境下使用Spring的IoC容器;例如在桌面富客户端环境下,你想让容器优雅的关闭,并调用singleton bean上的相应析构回调方法, 你需要在JVM里注册一个“关闭钩子”(shutdown hook)。这一点非常容易做到,并且将会确保你的Spring IoC容器被恰当关闭,以及所有由单例持有的资源都会 被释放(当然,为你的单例配置销毁回调,并正确实现销毁回调方法,依然是你的工作)。

    为了注册“关闭钩子”,你只需要简单地调用在 AbstractApplicationContext 实现中的 registerShutdownHook() 方法即可。也就是:

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

public final class Boot {

    public static void main(final String[] args) throws Exception {

        AbstractApplicationContext ctx = new ClassPathXmlApplicationContext(
                new String []{"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...

    }
}

7 ApplicationContextAware

    当一个Bean实现 org.springframework.context.ApplicationContextAware 接口时,该Bean可以获得ApplicationContext对象。

public interface ApplicationContextAware {

    void setApplicationContext(ApplicationContext applicationContext) throws BeansException;

}

因此,我们可以通过编程的方式手动的通过ApplicationContext对象注册一个Bean,或者通过将引用转换为该接口的已知子类,例如ConfigurableApplicationContext暴露其他功能。然而,一般来说,你应该避免它,因为它将代码耦合到Spring,并且不遵循Spring特定的规则。

8 BeanNameAware

    当一个Bean实现 org.springframework.beans.factory.BeanNameAware 接口时,该Bean可以获得自己在容器中的所定义的标识符。

public interface BeanNameAware {

    void setBeanName(string name) throws BeansException;

}

    回调是在所有正常bean属性之后,但是在如 InitializingBean afterPropertiesSet 或者一个自定义的初始化方法 的初始化回调之前被调用。

© 著作权归作者所有

王胜_淡如止水
粉丝 4
博文 52
码字总数 81235
作品 0
杭州
私信 提问
Spring4新特性——Web开发的增强

Spring4新特性——泛型限定式依赖注入 Spring4新特性——核心容器的其他改进 Spring4新特性——Web开发的增强 Spring4新特性——集成Bean Validation 1.1(JSR-349)到SpringMVC Spring4新特性...

张升强
2014/04/02
144
0
Spring4新特性——泛型限定式依赖注入

Spring4新特性——泛型限定式依赖注入 Spring4新特性——核心容器的其他改进 Spring4新特性——Web开发的增强 Spring4新特性——集成Bean Validation 1.1(JSR-349)到SpringMVC Spring4新特性...

Big_BoBo
2013/12/26
448
0
5分钟构建spring web mvc REST风格HelloWorld

当然写本文的目的不是为了速度,只是表明现在构建一个Spring web mvc Rest风格的HelloWorld应用会很简单。不过如果看过Spring Boot这个项目,可能只需要最多3分钟就能构建一个简单的Rest风格...

黄金小猪2号
2014/01/07
4.3K
2
Spring4新特性——集成Bean Validation 1.1(JSR-349)到SpringMVC

Spring4新特性——泛型限定式依赖注入 Spring4新特性——核心容器的其他改进 Spring4新特性——Web开发的增强 Spring4新特性——集成Bean Validation 1.1(JSR-349)到SpringMVC Spring4新特性...

咖啡杯
2014/02/18
284
0
Spring4新特性——集成Bean Validation 1.1(JSR-349)到SpringMVC

Spring4新特性——泛型限定式依赖注入 Spring4新特性——核心容器的其他改进 Spring4新特性——Web开发的增强 Spring4新特性——集成Bean Validation 1.1(JSR-349)到SpringMVC Spring4新特性...

咖啡杯
2014/02/18
5K
0

没有更多内容

加载失败,请刷新页面

加载更多

Spring使用ThreadPoolTaskExecutor自定义线程池及实现异步调用

多线程一直是工作或面试过程中的高频知识点,今天给大家分享一下使用 ThreadPoolTaskExecutor 来自定义线程池和实现异步调用多线程。 一、ThreadPoolTaskExecutor 本文采用 Executors 的工厂...

CREATE_17
今天
5
0
CSS盒子模型

CSS盒子模型 组成: content --> padding --> border --> margin 像现实生活中的快递: 物品 --> 填充物 --> 包装盒 --> 盒子与盒子之间的间距 content :width、height组成的 内容区域 padd......

studywin
今天
7
0
修复Win10下开始菜单、设置等系统软件无法打开的问题

因为各种各样的原因导致系统文件丢失、损坏、被修改,而造成win10的开始菜单、设置等系统软件无法打开的情况,可以尝试如下方法解决 此方法只在部分情况下有效,但值得一试 用Windows键+R打开...

locbytes
昨天
8
0
jquery 添加和删除节点

本文转载于:专业的前端网站➺jquery 添加和删除节点 // 增加一个三和一节点function addPanel() { // var newPanel = $('.my-panel').clone(true) var newPanel = $(".triple-panel-con......

前端老手
昨天
8
0
一、Django基础

一、web框架分类和wsgiref模块使用介绍 web框架的本质 socket服务端 与 浏览器的通信 socket服务端功能划分: 负责与浏览器收发消息(socket通信) --> wsgiref/uWsgi/gunicorn... 根据用户访问...

ZeroBit
昨天
10
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部