在谈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:[email protected]
* @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:[email protected]
* @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:[email protected]
* @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;
}
}