这里总结三种方法:
一:InitializingBean 接口
这说明在spring初始化bean的时候,如果bean实现了InitializingBean接口,会自动调用afterPropertiesSet方法。
原文地址:
问题
实现InitializingBean接口与在配置文件中指定init-method有什么不同?
InitializingBean接口为bean提供了初始化方法的方式,它只包括afterPropertiesSet方法,凡是继承该接口的类,在初始化bean的时候会执行该方法。
测试程序如下:
1. import org.springframework.beans.factory.InitializingBean;
2. public class TestInitializingBean implements InitializingBean{
3. @Override
4. public void afterPropertiesSet() throws Exception {
5. System.out.println("ceshi InitializingBean");
6. }
7. public void testInit(){
8. System.out.println("ceshi init-method");
9. }
10. }
配置文件如下:
1. <bean id="testInitializingBean" class="com.TestInitializingBean" ></bean>
Main主程序如下:
1. public class Main {
2. public static void main(String[] args){
3. ApplicationContext context = new FileSystemXmlApplicationContext("/src/main/java/com/beans.xml");
4. }
5. }
运行Main程序,打印如下结果:
- ceshi InitializingBean
这说明在spring初始化bean的时候,如果bean实现了InitializingBean接口,会自动调用afterPropertiesSet方法。
问题
实现InitializingBean接口与在配置文件中指定init-method有什么不同?
修改配置文件,加上init-method配置,修改如下:
1. <bean id="testInitializingBean" class="com.TestInitializingBean" init-method="testInit"></bean>
在配置文件中加入init-method="testInit"。
运行Main程序,打印如下结果:
1. ceshi InitializingBean
2. ceshi init-method
由结果可看出,在spring初始化bean的时候,如果该bean是实现了InitializingBean接口,并且同时在配置文件中指定了init-method,系统则是先调用afterPropertiesSet方法,然后在调用init-method中指定的方法。
这方式在spring中是怎么实现的?
通过查看spring的加载bean的源码类(AbstractAutowireCapableBeanFactory)可看出其中奥妙
AbstractAutowireCapableBeanFactory类中的invokeInitMethods讲解的非常清楚,源码如下:
1. protected void invokeInitMethods(String beanName, final Object bean, RootBeanDefinition mbd) throws Throwable {
2. //判断该bean是否实现了实现了InitializingBean接口,如果实现了InitializingBean接口,则只掉调用bean的afterPropertiesSet方法
3. boolean isInitializingBean = (bean instanceof InitializingBean);
4. if (isInitializingBean && (mbd == null || !mbd.isExternallyManagedInitMethod("afterPropertiesSet"))) {
5. if (logger.isDebugEnabled()) {
6. logger.debug("Invoking afterPropertiesSet() on bean with name '" + beanName + "'");
7. }
8.
9. if (System.getSecurityManager() != null) {
10. try {
11. AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() {
12. public Object run() throws Exception {
13. //直接调用afterPropertiesSet
14. ((InitializingBean) bean).afterPropertiesSet();
15. return null;
16. }
17. },getAccessControlContext());
18. } catch (PrivilegedActionException pae) {
19. throw pae.getException();
20. }
21. }
22. else {
23. //直接调用afterPropertiesSet
24. ((InitializingBean) bean).afterPropertiesSet();
25. }
26. }
27. if (mbd != null) {
28. String initMethodName = mbd.getInitMethodName();
29. //判断是否指定了init-method方法,如果指定了init-method方法,则再调用制定的init-method
30. if (initMethodName != null && !(isInitializingBean && "afterPropertiesSet".equals(initMethodName)) &&
31. !mbd.isExternallyManagedInitMethod(initMethodName)) {
32. //进一步查看该方法的源码,可以发现init-method方法中指定的方法是通过反射实现
33. invokeCustomInitMethod(beanName, bean, mbd);
34. }
35. }
36. }
总结
1:spring为bean提供了两种初始化bean的方式,实现InitializingBean接口,实现afterPropertiesSet方法,或者在配置文件中同过init-method指定,两种方式可以同时使用
2:实现InitializingBean接口是直接调用afterPropertiesSet方法,比通过反射调用init-method指定的方法效率相对来说要高点。但是init-method方式消除了对spring的依赖
3:如果调用afterPropertiesSet方法时出错,则不调用init-method指定的方法。
二:@PostConstruct
@PostConstruct的API使用说明:
PostConstruct 注释用于在依赖关系注入完成之后需要执行的方法上,以执行任何初始化。此方法必须在将类放入服务之前调用。支持依赖关系注入的所有类都必须支持此注释。即使类没有请求注入任何资源,用 PostConstruct 注释的方法也必须被调用。只有一个方法可以用此注释进行注释。应用 PostConstruct 注释的方法必须遵守以下所有标准:该方法不得有任何参数,除非是在 EJB 拦截器 (interceptor) 的情况下,根据 EJB 规范的定义,在这种情况下它将带有一个 InvocationContext 对象 ;该方法的返回类型必须为 void;该方法不得抛出已检查异常;应用 PostConstruct 的方法可以是 public、protected、package private 或 private;除了应用程序客户端之外,该方法不能是 static;该方法可以是 final;如果该方法抛出未检查异常,那么不得将类放入服务中,除非是能够处理异常并可从中恢复的 EJB。
总结为一下几点:
- 只有一个方法可以使用此注释进行注解;
- 被注解方法不得有任何参数;
- 被注解方法返回值为void;
- 被注解方法不得抛出已检查异常;
- 被注解方法需是非静态方法;
- 此方法只会被执行一次;
三:applicationlistener
什么是ApplicationContext? 它是Spring的核心,Context我们通常解释为上下文环境,但是理解成容器会更好些。 ApplicationContext则是应用的容器。Spring把Bean(object)放在容器中,需要用就通过get方法取出来。ApplicationEven:是个抽象类,里面只有一个构造函数和一个长整型的timestamp。ApplicationListener:是一个接口,里面只有一个onApplicationEvent方法。
package org.springframework.context;
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
/**
* Handle an application event.
* @param event the event to respond to
*/
void onApplicationEvent(E event);
}
所以自己的类在实现该接口的时候,要实装该方法。
如果在上下文中部署一个实现了ApplicationListener接口的bean,那么每当在一个ApplicationEvent发布到ApplicationContext时,这个bean得到通知。其实这就是标准的Oberver设计模式。
二、使用场景
2.1、初始化处理
在一些业务场景中,当容器初始化完成之后,需要处理一些操作,比如一些数据的加载、初始化缓存、特定任务的注册等等。一般来说一个项目启动时需要加载或者执行一些特殊的任务来初始化系统,通常的做法就是用servlet去初始化,但是servlet在使用spring bean时不能直接注入,还需要在web.xml配置,比较麻烦(见)。这个时候我们就可以使用Spring提供的ApplicationListener来进行操作。
本文以在Spring boot下的使用为例来进行说明。首先,需要实现ApplicationListener接口并实现onApplicationEvent方法。把需要处理的操作放在onApplicationEvent中进行处理:
然后,实例化ApplicationStartListener这个类,在Spring boot中通过一个配置类来进行实例化:
随后,启动Spring boot服务,打印出一下内容:
从打印的结果可以看出,ApplicationStartListener的onApplicationEvent方法在容器启动时已经被成功调用了。而此时初始化的容器为root容器。
下面给出例子:
首先创建一个ApplicationEvent实现类:
import org.springframework.context.ApplicationEvent;
public class EmailEvent extends ApplicationEvent {
/**
* <p>Description:</p>
*/
private static final long serialVersionUID = 1L;
public String address;
public String text;
public EmailEvent(Object source) {
super(source);
}
public EmailEvent(Object source, String address, String text) {
super(source);
this.address = address;
this.text = text;
}
public void print(){
System.out.println("hello spring event!");
}
}
给出监听器:
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
public class EmailListener implements ApplicationListener {
public void onApplicationEvent(ApplicationEvent event) {
if(event instanceof EmailEvent){
EmailEvent emailEvent = (EmailEvent)event;
emailEvent.print();
System.out.println("the source is:"+emailEvent.getSource());
System.out.println("the address is:"+emailEvent.address);
System.out.println("the email's context is:"+emailEvent.text);
}
}
}
applicationContext.xml文件配置:
<bean id="emailListener" class="com.spring.event.EmailListener"></bean>
测试类:
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
//HelloBean hello = (HelloBean) context.getBean("helloBean");
//hello.setApplicationContext(context);
EmailEvent event = new EmailEvent("hello","boylmx@163.com","this is a email text!");
context.publishEvent(event);
//System.out.println();
}
}
测试结果:
hello spring event!
the source is:hello
the address is:boylmx@163.com
the email's context is:this is a email text!
-------------------------------------------------------------------------------------------------------------------------
当spring 容器初始化完成后执行某个方法 防止onApplicationEvent方法被执行两次
在做web项目开发中,尤其是企业级应用开发的时候,往往会在工程启动的时候做许多的前置检查。
比如检查是否使用了我们组禁止使用的Mysql的group_concat函数,如果使用了项目就不能启动,并指出哪个文件的xml文件使用了这个函数。
而在Spring的web项目中,我们可以介入Spring的启动过程。我们希望在Spring容器将所有的Bean都初始化完成之后,做一些操作,这个时候我们就可以实现一个接口:
package com.yk.test.executor.processor
public class InstantiationTracingBeanPostProcessor implements ApplicationListener<ContextRefreshedEvent> {
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
//需要执行的逻辑代码,当spring容器初始化完成后就会执行该方法。
}
}
同时在Spring的配置文件中,添加注入:
<bean class="com.yk.test.executor.processor.InstantiationTracingBeanPostProcessor"/>
但是这个时候,会存在一个问题,在web 项目中(spring mvc),系统会存在两个容器,一个是root application context ,另一个就是我们自己的 projectName-servlet context(作为root application context的子容器)。
这种情况下,就会造成onApplicationEvent方法被执行两次。为了避免上面提到的问题,我们可以只在root application context初始化完成后调用逻辑代码,其他的容器的初始化完成,则不做任何处理,修改后代码
如下:
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
if(event.getApplicationContext().getParent() == null){//root application context 没有parent,他就是老大.
//需要执行的逻辑代码,当spring容器初始化完成后就会执行该方法。
}
}
Spring 的事件传播机制 是基于观察者模式(Observer)实现的,它可以将 Spring Bean 的改变定义为事件 ApplicationEvent,通过 ApplicationListener 监听 ApplicationEvent 事件,一旦Spring Bean 使用 ApplicationContext.publishEvent( ApplicationEvent event )发布事件后,Spring 容器会通知注册在 bean.xml 中所有 ApplicationListener 接口的实现类,最后 ApplicationListener 接口实现类判断是否响应刚发布出来的 ApplicationEvent 事件。
所以,要使用 Spring 事件传播机制需要以下四点:
1. 建立事件类,继承 ApplicationEvent 父类
2. 建立监听类,实现 ApplicationListener 接口
3. 在配置文件 bean.xml 中注册写好的所有 事件类 和 监听类
4. 需要发布事件的类 要实现 ApplicationContextAware 接口,并获取 ApplicationContext 参数
随后便可以开始使用 Spring 事件传播机制为我们服务:(为了讲解流程的连贯性,续以上步骤来测试)
4.1 在自己编写的需要发布事件的 Action 类中实例化 1 中编写好的事件类,并使用 ApplicationContext.publishEvent 发布事件
5. 通过 Spring 调用 Action 方法,观察输出结果(本文使用 Junit 测试)
以下为1-5步骤的源码:
1. 建立事件类 ActionEvent.java
[java]
1. public class ActionEvent extends ApplicationEvent{
2.
3. public ActionEvent(Object source) {
4. super(source);
5. "This is ActionEvent");
6. }
7. }
2. 建立监听类 ActionListener1.java、ActionListener2.java
[java]
1. public class ActionListener1 implements ApplicationListener {
2.
3. public void onApplicationEvent(ApplicationEvent event) {
4. if(event instanceof ActionEvent){
5. "ActionListener1: "+event.toString());
6. }
7. }
8.
9. }
[java]
1. public class ActionListener2 implements ApplicationListener {
2.
3. public void onApplicationEvent(ApplicationEvent event) {
4. if(event instanceof ActionEvent){
5. "ActionListener2: "+event.toString());
6. }
7. }
8.
9. }
3. 在 bean.xml 中注册事件类和监听类
[java]
1. <bean id="loginaction" class="com.ayali.action.LoginAction"/>
2. <bean id="listener1" class="com.ayali.action.ActionListener1"/>
3. <bean id="listener2" class="com.ayali.action.ActionListener2"/>
4. 编写 需要发布事件的 loginAction.java
[java]
1. public class LoginAction implements ApplicationContextAware{
2.
3. private ApplicationContext applicationContext;
4.
5. public void setApplicationContext(ApplicationContext applicationContext)
6. throws BeansException {
7. this.applicationContext = applicationContext;
8. }
9.
10. public void login(String username, String password){
11. ActionEvent event = new ActionEvent(username);
12. this.applicationContext.publishEvent(event);
13. }
14.
15. }
5. 编写测试方法
[java]
1. public void testActionListener(){
2. ApplicationContext ctx = new FileSystemXmlApplicationContext("bean.xml");
3. "loginaction");
4. "jack", "123");
5. }
输出结果为:
- This is ActionEvent
- ActionListener1:com.ayali.action.ActionEvent[source=jack]
- ActionListener2:com.ayali.action.ActionEvent[source=jack]