在谈Spring的事件监听之前,让我们先了解一下Spring容器,什么是ApplicationContext ?
它是Spring的核心,Context我们通常解释为上下文环境,但是理解成容器会更好些。
ApplicationContext则是应用的容器。
Spring把Bean(object)放在容器中,需要用就通过get方法取出来。
下面我们结合源码注视和ApplicationContext的类图来看看它到底提供给我们哪些能力
源码:
Copyright 2002-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.context; import org.springframework.beans.factory.HierarchicalBeanFactory; import org.springframework.beans.factory.ListableBeanFactory; import org.springframework.beans.factory.config.AutowireCapableBeanFactory; import org.springframework.core.env.EnvironmentCapable; import org.springframework.core.io.support.ResourcePatternResolver; import org.springframework.lang.Nullable; /** * Central interface to provide configuration for an application. * This is read-only while the application is running, but may be * reloaded if the implementation supports this. * *
An ApplicationContext provides: *
*
* *- Bean factory methods for accessing application components. * Inherited from {@link org.springframework.beans.factory.ListableBeanFactory}. *
- The ability to load file resources in a generic fashion. * Inherited from the {@link org.springframework.core.io.ResourceLoader} interface. *
- The ability to publish events to registered listeners. * Inherited from the {@link ApplicationEventPublisher} interface. *
- The ability to resolve messages, supporting internationalization. * Inherited from the {@link MessageSource} interface. *
- Inheritance from a parent context. Definitions in a descendant context * will always take priority. This means, for example, that a single parent * context can be used by an entire web application, while each servlet has * its own child context that is independent of that of any other servlet. *
In addition to standard {@link org.springframework.beans.factory.BeanFactory} * lifecycle capabilities, ApplicationContext implementations detect and invoke * {@link ApplicationContextAware} beans as well as {@link ResourceLoaderAware}, * {@link ApplicationEventPublisherAware} and {@link MessageSourceAware} beans. * * @author Rod Johnson * @author Juergen Hoeller * @see ConfigurableApplicationContext * @see org.springframework.beans.factory.BeanFactory * @see org.springframework.core.io.ResourceLoader */ public interface ApplicationContext extends EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory, MessageSource, ApplicationEventPublisher, ResourcePatternResolver { /** * Return the unique id of this application context. * @return the unique id of the context, or {@code null} if none */ @Nullable String getId(); /** * Return a name for the deployed application that this context belongs to. * @return a name for the deployed application, or the empty String by default */ String getApplicationName(); /** * Return a friendly name for this context. * @return a display name for this context (never {@code null}) */ String getDisplayName(); /** * Return the timestamp when this context was first loaded. * @return the timestamp (ms) when this context was first loaded */ long getStartupDate(); /** * Return the parent context, or {@code null} if there is no parent * and this is the root of the context hierarchy. * @return the parent context, or {@code null} if there is no parent */ @Nullable ApplicationContext getParent(); /** * Expose AutowireCapableBeanFactory functionality for this context. *
This is not typically used by application code, except for the purpose of * initializing bean instances that live outside of the application context, * applying the Spring bean lifecycle (fully or partly) to them. *
Alternatively, the internal BeanFactory exposed by the * {@link ConfigurableApplicationContext} interface offers access to the * {@link AutowireCapableBeanFactory} interface too. The present method mainly * serves as a convenient, specific facility on the ApplicationContext interface. *
NOTE: As of 4.2, this method will consistently throw IllegalStateException * after the application context has been closed. In current Spring Framework * versions, only refreshable application contexts behave that way; as of 4.2, * all application context implementations will be required to comply. * @return the AutowireCapableBeanFactory for this context * @throws IllegalStateException if the context does not support the * {@link AutowireCapableBeanFactory} interface, or does not hold an * autowire-capable bean factory yet (e.g. if {@code refresh()} has * never been called), or if the context has been closed already * @see ConfigurableApplicationContext#refresh() * @see ConfigurableApplicationContext#getBeanFactory() */ AutowireCapableBeanFactory getAutowireCapableBeanFactory() throws IllegalStateException; }
类图
由源码注释:
此接口提供给Spring应用配置的能力,当应用启动时,此接口的实现是只读的,但是如果该实现支持,其内容也是可以重新载入的。
ApplicationContext大概提功能如下能力:
- 获取应用组件的bean工厂方法,此能力继承自
org.springframework.beans.factory.ListableBeanFactory
。 - 加载资源文件的能力,此能力继承自
org.springframework.core.io.ResourceLoader
- 发布事件到已注册的监听器,此能力继承自
ApplicationEventPublisher
- 提供国际化的消息访问,此能力继承自
MessageSource
好对ApplicationContext有一定了解之后我们再来看看Spring提供的事件监听。
为了实现事件监听的能力Spring为我们提供了两个顶层接口/抽象类
ApplcationEvent:是个抽象类,里面只有一个构造函数和一个长整型的timestamp。我们自定义的Application event 需要继承这个抽象类.
ApplicationListener:是一个接口,里面只有一个方法onApplicationEvent
,每一个实现改接口的类都要自己实现这个方法。
Spring的事件监听是基于标准的观察者模式的,如果在ApplicationContext部署了一个实现了ApplicationListener的bean,那么当一个ApplicationEvent发布到
ApplicationContext时,这个bean得到通知并作特定的处理。
从上面这段话我们很容易产生两点思考:
- 实现了ApplicationListener的bean如何部署到ApplicationContext
- 一个ApplicationEvent如何发布到ApplicationContext
下面我们就通过具体的代码来看看这两个问题
先自定义一个MsgEvent,它本身提供一个print()方法:
package com.faunjoe.demo.springbootstart.event;
import org.springframework.context.ApplicationEvent;
public class MsgEvent extends ApplicationEvent {
private String text;
/**
* Create a new ApplicationEvent.
*
* @param source the object on which the event initially occurred (never {@code null})
*/
public MsgEvent(Object source) {
super(source);
}
public MsgEvent(Object source, String text) {
super(source);
this.text = text;
}
public void print() {
System.out.println("print event content:" + this.text);
}
}
再自定义一个PringListener实现ApplicationListener:
package com.faunjoe.demo.springbootstart.event;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;
public class PrintListener implements ApplicationListener {
@Override
public void onApplicationEvent(MsgEvent event) {
System.out.println("调用MsgEvent的print方法输出其内容:");
event.print();
}
}
现在自定义事件和监听器都好了,我们就来看看第一个问题,监听器如何部署到ApplicationContext,有四种方式可以实现,我们一个一个看:
- 应用启动之前调用SpringApplication的addListeners方法将自定义的监听器加入。
@SpringBootApplication
public class SpringBootStartApplication1 {
public static void main(String[] args) {
SpringApplication springApplication = new SpringApplication(SpringBootStartApplication.class);
springApplication.setWebEnvironment(true);
//配置事件监听
springApplication.addListeners(new PrintListener());
//配置事件监听
ConfigurableApplicationContext context = springApplication.run(args);
//发布事件
context.publishEvent(new MsgEvent(new Object(),"hello world."));
}
}
运行结果:
- 监听器部署到ApplicationContext,实际上就是将将监听器交给Spring 容器管理,所以最简单的方法只需在自定义的PrintListener上加上@Component注解就行了,代码如下
package com.faunjoe.demo.springbootstart.event;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;
@Component
public class PrintListener implements ApplicationListener {
@Override
public void onApplicationEvent(MsgEvent event) {
System.out.println("调用MsgEvent的print方法输出其内容:");
event.print();
}
}
启动处的代码注掉配置事件监听一行,如下:
@SpringBootApplication
public class SpringBootStartApplication1 {
public static void main(String[] args) {
SpringApplication springApplication = new SpringApplication(SpringBootStartApplication.class);
springApplication.setWebEnvironment(true);
//配置事件监听
//springApplication.addListeners(new PrintListener());
//配置事件监听
ConfigurableApplicationContext context = springApplication.run(args);
//发布事件
context.publishEvent(new MsgEvent(new Object(),"hello world."));
}
}
启动运行,结果如下:
- 在配置文件中加入
context.listener.classes: com.faunjoe.demo.springbootstart.event.PrintListener
注释掉PrintListener上的@Component注解
源码分析:
Spring内置DelegatingApplicationListener
会监听ApplicationEnvironmentPreparedEvent
事件(Environment已经准备好但是Context还没有创建事件),
读取Environment中的context.listener.classes属性值(监听器类全路径,多个以逗号隔开)来创建ApplicationListener,详情见如下源码中我的注释。
/*
* Copyright 2012-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.context.config;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.springframework.beans.BeanUtils;
import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;
import org.springframework.context.ApplicationContextException;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.SimpleApplicationEventMulticaster;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
/**
* {@link ApplicationListener} that delegates to other listeners that are specified under
* a {@literal context.listener.classes} environment property.
*
* @author Dave Syer
* @author Phillip Webb
*/
public class DelegatingApplicationListener
implements ApplicationListener, Ordered {
// NOTE: Similar to org.springframework.web.context.ContextLoader
private static final String PROPERTY_NAME = "context.listener.classes”;//配置文件中配置的配置
private int order = 0;
private SimpleApplicationEventMulticaster multicaster;
@Override
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ApplicationEnvironmentPreparedEvent) {//监听ApplicationEnvironmentPreparedEvent事件
List> delegates = getListeners(
((ApplicationEnvironmentPreparedEvent) event).getEnvironment());
if (delegates.isEmpty()) {
return;
}
this.multicaster = new SimpleApplicationEventMulticaster();
for (ApplicationListener listener : delegates) {
this.multicaster.addApplicationListener(listener);//将监听事件加入spring容器中
}
}
if (this.multicaster != null) {
this.multicaster.multicastEvent(event);
}
}
@SuppressWarnings("unchecked")
private List> getListeners(
ConfigurableEnvironment environment) {
if (environment == null) {
return Collections.emptyList();
}
String classNames = environment.getProperty(PROPERTY_NAME);//获取配置文件中配置的监听器
List> listeners = new ArrayList<>();
if (StringUtils.hasLength(classNames)) {
for (String className : StringUtils.commaDelimitedListToSet(classNames)) {
try {
Class clazz = ClassUtils.forName(className,
ClassUtils.getDefaultClassLoader());
Assert.isAssignable(ApplicationListener.class, clazz, "class ["
+ className + "] must implement ApplicationListener");
listeners.add((ApplicationListener) BeanUtils
.instantiateClass(clazz));
}
catch (Exception ex) {
throw new ApplicationContextException(
"Failed to load context listener class [" + className + "]",
ex);
}
}
}
AnnotationAwareOrderComparator.sort(listeners);
return listeners;
}
public void setOrder(int order) {
this.order = order;
}
@Override
public int getOrder() {
return this.order;
}
}
运行结果:
- 使用@EventListener注解,先看代码,建立一个普通的java类并交给spring容器,其中一个处理event的方法,加上该注解,删掉配置文件中的配置。
package com.faunjoe.demo.springbootstart.event;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
/**
* @author faunjoe E-mail:aijun.fu@mtime.com
* @version 创建时间:2018-11-29 14:48
*/
@Component
public class MsgEventHandle {
@EventListener
public void handle(MsgEvent event){
System.out.println("调用MsgEvent的print方法输出其内容:");
event.print();
}
}
运行结果:
源码分析:
我们首先看@EventListener
注解的源码,里面有这样一段注释:
它指引我们去看EventListenerMethodProcessor
类,这个类是application启动时自动注册执行的。该类的功能是扫描@EventListener注解并生成一个ApplicationListener实例。
其中有个这样的方法:就是用来扫描容器中bean的方法上所有的@EventListener,循环创建ApplicationListener。
protected void processBean(
final List factories, final String beanName, final Class targetType) {
if (!this.nonAnnotatedClasses.contains(targetType)) {
Map annotatedMethods = null;
try {
annotatedMethods = MethodIntrospector.selectMethods(targetType,
(MethodIntrospector.MetadataLookup) method ->
AnnotatedElementUtils.findMergedAnnotation(method, EventListener.class));
}
catch (Throwable ex) {
// An unresolvable type in a method signature, probably from a lazy bean - let's ignore it.
if (logger.isDebugEnabled()) {
logger.debug("Could not resolve methods for bean with name '" + beanName + "'", ex);
}
}
if (CollectionUtils.isEmpty(annotatedMethods)) {
this.nonAnnotatedClasses.add(targetType);
if (logger.isTraceEnabled()) {
logger.trace("No @EventListener annotations found on bean class: " + targetType.getName());
}
}
else {
// Non-empty set of methods
ConfigurableApplicationContext context = getApplicationContext();
for (Method method : annotatedMethods.keySet()) {
for (EventListenerFactory factory : factories) {
if (factory.supportsMethod(method)) {
Method methodToUse = AopUtils.selectInvocableMethod(method, context.getType(beanName));
ApplicationListener applicationListener =
factory.createApplicationListener(beanName, targetType, methodToUse);
if (applicationListener instanceof ApplicationListenerMethodAdapter) {
((ApplicationListenerMethodAdapter) applicationListener).init(context, this.evaluator);
}
context.addApplicationListener(applicationListener);
break;
}
}
}
if (logger.isDebugEnabled()) {
logger.debug(annotatedMethods.size() + " @EventListener methods processed on bean '" +
beanName + "': " + annotatedMethods);
}
}
}
}
以上四点就回答了我们开头提出的第一个问题:
实现了ApplicationListener的bean如何部署到ApplicationContext。接下来轮到第二个问题了,
一个ApplicationEvent如何发布到ApplicationContext。我们还是来看测试的主代码:
@SpringBootApplication
public class SpringBootStartApplication1 {
public static void main(String[] args) {
SpringApplication springApplication = new SpringApplication(SpringBootStartApplication.class);
springApplication.setWebEnvironment(true);
//配置事件监听
//springApplication.addListeners(new PrintListener());
//配置事件监听
ConfigurableApplicationContext context = springApplication.run(args);
//发布事件
context.publishEvent(new MsgEvent(new Object(),"hello world."));
}
}
上面的这段代码可以看出ConfigurableApplicationContext
具有发布事件的能力,而通过下面的类图我们知道ConfigurableApplicationContext
是ApplicationContext
的子接口。
文章开头对ApplicationContext的分析我们知道ApplicationContext具有发布事件的能力,这项能力是通过继承ApplicationEventPublisher获得的。
通过以上分析,我们不难发现,只要我们在一个普通的java bean注入ApplicationContext的实例,那么这个bean就获得了发布事件的能力。
那么怎样才能在一个普通的bean中注入ApplicationContext的实例呢?看ApplicationContext的源码,源码中有这一段注释:
这段注释大概的意思是ApplicationContext的实例可以探测和唤起AppliationContextAware ,ResourceLoaderAware,ApplicationEventPublisherAware,MessageSourceAware等,
我们顺势看下AppliationContextAware的代码,如下:
/* * Copyright 2002-2012 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.context; import org.springframework.beans.BeansException; import org.springframework.beans.factory.Aware; /** * Interface to be implemented by any object that wishes to be notified * of the {@link ApplicationContext} that it runs in. * *
Implementing this interface makes sense for example when an object * requires access to a set of collaborating beans. Note that configuration * via bean references is preferable to implementing this interface just * for bean lookup purposes. * *
This interface can also be implemented if an object needs access to file * resources, i.e. wants to call {@code getResource}, wants to publish * an application event, or requires access to the MessageSource. However, * it is preferable to implement the more specific {@link ResourceLoaderAware}, * {@link ApplicationEventPublisherAware} or {@link MessageSourceAware} interface * in such a specific scenario. * *
Note that file resource dependencies can also be exposed as bean properties * of type {@link org.springframework.core.io.Resource}, populated via Strings * with automatic type conversion by the bean factory. This removes the need * for implementing any callback interface just for the purpose of accessing * a specific file resource. * *
{@link org.springframework.context.support.ApplicationObjectSupport} is a * convenience base class for application objects, implementing this interface. * *
For a list of all bean lifecycle methods, see the * {@link org.springframework.beans.factory.BeanFactory BeanFactory javadocs}. * * @author Rod Johnson * @author Juergen Hoeller * @author Chris Beams * @see ResourceLoaderAware * @see ApplicationEventPublisherAware * @see MessageSourceAware * @see org.springframework.context.support.ApplicationObjectSupport * @see org.springframework.beans.factory.BeanFactoryAware */ public interface ApplicationContextAware extends Aware { /** * Set the ApplicationContext that this object runs in. * Normally this call will be used to initialize the object. *
Invoked after population of normal bean properties but before an init callback such * as {@link org.springframework.beans.factory.InitializingBean#afterPropertiesSet()} * or a custom init-method. Invoked after {@link ResourceLoaderAware#setResourceLoader}, * {@link ApplicationEventPublisherAware#setApplicationEventPublisher} and * {@link MessageSourceAware}, if applicable. * @param applicationContext the ApplicationContext object to be used by this object * @throws ApplicationContextException in case of context initialization errors * @throws BeansException if thrown by application context methods * @see org.springframework.beans.factory.BeanInitializationException */ void setApplicationContext(ApplicationContext applicationContext) throws BeansException; }
通过ApplicationContextAware
的源码我们可以看出其有一个setApplicationContext
方法可以将ApplicationContext实例注入,下面我们写一个java类去实现 ApplicationContextAware看看。
代码如下:
package com.faunjoe.demo.springbootstart.event;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationEvent;
import org.springframework.stereotype.Component;
/**
* @author faunjoe E-mail:aijun.fu@mtime.com
* @version 创建时间:2018-11-29 12:50
*/
@Component
public class FaunjoeApplicationContextAware implements ApplicationContextAware {
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
public void publish(ApplicationEvent event){
applicationContext.publishEvent(event);
}
}
package com.faunjoe.demo.springbootstart.controller;
import com.faunjoe.demo.springbootstart.domain.Person;
import com.faunjoe.demo.springbootstart.event.FaunjoeApplicationContextAware;
import com.faunjoe.demo.springbootstart.event.MsgEvent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author faunjoe E-mail:aijun.fu@mtime.com
* @version 创建时间:2018-11-27 14:40
*/
@RestController
public class PersonRestController {
private final Person person;
@Autowired
private FaunjoeApplicationContextAware faunjoeApplicationContextAware;
@Autowired
public PersonRestController(Person person) {
this.person = person;
}
@GetMapping("/person")
public Person person() {
faunjoeApplicationContextAware.publish(new MsgEvent(new Object(),"有人访问我,我得跟他们说:hello"));
return person;
}
}
@SpringBootApplication
public class SpringBootStartApplication1 {
public static void main(String[] args) {
SpringApplication springApplication = new SpringApplication(SpringBootStartApplication.class);
springApplication.setWebEnvironment(true);
//配置事件监听
//springApplication.addListeners(new PrintListener());
//配置事件监听
ConfigurableApplicationContext context = springApplication.run(args);
//发布事件
//context.publishEvent(new MsgEvent(new Object(),"hello world."));
}
}
我们注意到ApplicationContext的事件发布能力是继承自ApplicationEventPublisher,并且ApplicationContextAware中有这样一段注释:
这段注释可以看出,如果仅仅想获取发布事件的能力,只需要实现ApplicationEventPublisherAware就行了。
照着这样的思路代码如下:定义一个事件发布器
package com.faunjoe.demo.springbootstart.event;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.stereotype.Component;
@Component
public class FaunjoeEventPublisher implements ApplicationEventPublisherAware {
private ApplicationEventPublisher publisher;
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.publisher = applicationEventPublisher;
}
public void publish(ApplicationEvent event){
publisher.publishEvent(event);
}
}
@RestController
public class PersonRestController {
private final Person person;
@Autowired
private FaunjoeApplicationContextAware faunjoeApplicationContextAware;
@Autowired
private FaunjoeEventPublisher faunjoeEventPublisher;
@Autowired
public PersonRestController(Person person) {
this.person = person;
}
@GetMapping("/person")
public Person person() {
faunjoeApplicationContextAware.publish(new MsgEvent(new Object(),"有人访问我,我得跟他们说:hello"));
faunjoeEventPublisher.publish(new MsgEvent(new Object(),"有人访问我,我得跟他们说:hello"));
return person;
}
}