最近项目中遇到一个业务场景,就是在Spring容器启动后获取所有的Bean中实现了一个特定接口的对象,第一个想到的是ApplicationContextAware,在setApplicationContext中去通过ctx获取所有的bean,后来发现好像逻辑不对,这个方法不是在所有bean初始化完成后实现的,后来试了一下看看有没有什么Listener之类的,发现了好东西ApplicationListener,然后百度一下ApplicationListener用法,原来有一大堆例子,我也记录一下我的例子好了。

很简单,只要实现ApplicationListener<ContextRefreshedEvent>接口,然后把实现类进行@Component即可,代码如下:

Java代码  


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15



​@Component​​ 

​public​​ ​​class​​ ​​ContextRefreshedListener ​​​​implements​​ ​​ApplicationListener<ContextRefreshedEvent> { ​

 

​@Override​​ 

​public​​ ​​void​​ ​​onApplicationEvent(ContextRefreshedEvent event) { ​

​// 根容器为Spring容器 ​

​if​​​​(event.getApplicationContext().getParent()==​​​​null​​​​){ ​

//需要执行的逻辑代码,当spring容器初始化完成后就会执行该方法。 

​} ​

​} ​

​}​


  

 其中,通过event.getApplicationContext().getBeansWithAnnotation获取到所有拥有特定注解的Beans集合,然后遍历所有bean实现业务场景。

总结思考:这样的功能可以实现系统参数的初始化,获取系统中所有接口服务清单等一系列需要在Spring启动后初始化的功能。

ApplicationListener和ContextRefreshedEvent一般都是成对出现的。

事件机制作为一种编程机制,在许多语言中都提供了支持。JAVA语言也不例外,​java​中的事件机制的参与者有3种角色:

  1. event object
  2. event source
  3. event listener

这三个角色的含义字面上很好解,它们就定义了事件机制的一个基本模型。作为一种常用的编程设计机制,许多开源框架的设计中都使用了事件机制。SpringFramework也不例外。

在IOC的容器的启动过程,当所有的bean都已经处理完成之后,spring ioc容器会有一个发布事件的动作。从 AbstractApplicationContext 的源码中就可以看出:


1

2

3

4

5

6

7

8

9

10

11



protected ​void​ ​finishRefresh() {​

// Initialize lifecycle processor for this context.

initLifecycleProcessor();

// Propagate refresh to lifecycle processor first.

getLifecycleProcessor().onRefresh();

// Publish the final event.

publishEvent(​new​ ​ContextRefreshedEvent(​​this​​));​

// 业余草:www.xttblog.com

// Participate in LiveBeansView MBean, if active.

LiveBeansView.registerApplicationContext(​this​​);​

}


这样,当ioc容器加载处理完相应的bean之后,也给我们提供了一个机会(先有InitializingBean,后有ApplicationListener<ContextRefreshedEvent>),可以去做一些自己想做的事。其实这也就是spring ioc容器给提供的一个扩展的地方。我们可以这样使用这个扩展机制。


1

2



org.springframework.context.ApplicationEvent

org.springframework.context.ApplicationListener


一个最简单的方式就是,让我们的bean实现ApplicationListener接口,这样当发布事件时,spring的ioc容器就会以容器的实例对象作为事件源类,并从中找到事件的监听者,此时ApplicationListener接口实例中的onApplicationEvent(E event)方法就会被调用,我们的逻辑代码就会写在此处。这样我们的目的就达到了。但这也带来一个思考,有人可能会想,这样的代码我们也可以通过实现​spring​的InitializingBean接口来实现啊,也会被​spring​容器去自动调用,但是大家应该想到,如果我们现在想做的事,是必须要等到所有的bean都被处理完成之后再进行,此时InitializingBean接口的实现就不合适了,所以需要深刻理解事件机制的应用场合。

曾经有一位同事利用 ApplicationListener,重复加载了好几次 xml 配置文件。所以基础知识一定要掌握。

下面是一个完整的例子:


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36



public ​class​ ​ApplicationContextListener ​​implements​

ApplicationListener<ContextRefreshedEvent> {

private ​static​ ​Logger log = LoggerFactory.getLogger​

    (ApplicationContextListener.​​class​);​

@Override

public ​void​ ​onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent){​

// root application context

if​(​​null​ ​== contextRefreshedEvent.getApplicationContext().getParent()) {​

log.debug(​">>>>> spring初始化完毕 <<<<<"​​);​

// spring初始化完毕后,通过反射调用所有使用BaseService注解的initMapper方法

Map<String, Object> baseServices =

contextRefreshedEvent.getApplicationContext().

                getBeansWithAnnotation(BaseService.​​class​);​

for​(Object service : baseServices.values()) {​

log.debug(​">>>>> {}.initMapper()"​​, service.getClass().getName());​

try ​{​

Method initMapper = service.getClass().getMethod(​"initMapper"​​);​

initMapper.invoke(service);

​catch​ ​(Exception e) {​

log.error(​"初始化BaseService的initMapper方法异常"​​, e);​

e.printStackTrace();

}

}

// 系统入口初始化,业余草:www.xttblog.com

Map<String, BaseInterface> baseInterfaceBeans =

contextRefreshedEvent.getApplicationContext().

                getBeansOfType(BaseInterface.​​class​);​

for​(Object service : baseInterfaceBeans.values()) {​

_log.debug(​">>>>> {}.init()"​​, service.getClass().getName());​

try ​{​

Method init = service.getClass().getMethod(​"init"​​);​

init.invoke(service);

​catch​ ​(Exception e) {​

_log.error(​"初始化BaseInterface的init方法异常"​​, e);​

e.printStackTrace();

}

}

}

}

}


所以,我们的重点来了,我们如果要实现的是在所有的bean都被处理完成之后再进行操作,

就需要实现ApplicationListener<ContextRefreshedEvent>接口进行操作,同时,

applicationontext和使用MVC之后的webApplicationontext会两次调用上面的方法,如何区分这个两种容器呢? 

但是这个时候,会存在一个问题,在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容器初始化完成后就会执行该方法。  
        }  

      }  

延伸一下:除了以上启动后事件外,还有其他三个事件

ApplicationListener<ContextRefreshedEvent>接口,Spring启动后获取所有拥有特定注解的Bean_初始化

Closed在关闭容器的时候调用,

Started理论上在容器启动的时候调用,

Stopped理论上在容器关闭的时候调用。

我通过TomcatServer进行启动停止,只看到了Refreshed和Closed,不知道为啥,有空再继续研究