问题描述 :项目中配置事件监听,监听当容器加载完成之后,做一些初始化工作。项目运行之后,发现初始化工作被重复做了两次。为了便于分析,去掉代码中的业务逻辑,只留下场景。
@Component public class FreshListener implements ApplicationListener<ContextRefreshedEvent>{ private final Logger logger = LoggerFactory.getLogger(getClass()); @Override public void onApplicationEvent(ContextRefreshedEvent event) { //业务代码 logger.error("将有权限人员放入缓存。。。。"); } } 复制代码
配置FreshListener监听器,监听当容器加载完成之后,将管理员名单加入缓存。却发现,名单被加载了两次。 WHY???
由于源码中的个方法较长,所以只贴出 重点 且与 主题相关 的代码。建议结合本地源码一起看。
public class User { private String username; private String password; private String sms; public User(String username, String password, String sms) { this.username = username; this.password = password; this.sms = sms; } } 复制代码
public interface UserListener extends EventListener { void onRegister(UserEvent event); } 复制代码
public class SendSmsListener implements UserListener { @Override public void onRegister(UserEvent event) { if (event instanceof SendSmsEvent) { Object source = event.getSource(); User user = (User) source; System.out.println("send sms to " + user.getUsername()); } } } 复制代码
public class UserEvent extends EventObject { public UserEvent(Object source){ super(source); } } 复制代码
public class SendSmsEvent extends UserEvent { public SendSmsEvent(Object source) { super(source); } } 复制代码
public class UserService { private List<UserListener> listenerList = new ArrayList<>(); //当用户注册的时候,触发发送短信事件 public void register(User user){ System.out.println("name= " + user.getUsername() + " ,password= " + user.getPassword() + " ,注册成功"); publishEvent(new SendSmsEvent(user)); } public void publishEvent(UserEvent event){ for(UserListener listener : listenerList){ listener.onRegister(event); } } public void addListeners(UserListener listener){ this.listenerList.add(listener); } } 复制代码
public class EventApp { public static void main(String[] args) { UserService service = new UserService(); service.addListeners(new SendSmsListener()); //添加其他监听器 ... User user = new User("foo", "123456", "注册成功啦!!"); service.register(user); } } 复制代码
启动项目,模拟用户注册,触发了短信发送事件。从上述简单的模拟事件代码中,可以归结出三个名词, 事件(SendSmsEvent) , 监听器(SendSmsListener) , 事件源(用户注册) 。可以将上述流程描述为:用户注册==>触发发送短息事件==>短信监听器监听到消息。
/** * A tagging interface that all event listener interfaces must extend. * @since JDK1.1 */ public interface EventListener { } 复制代码
该接口为标识接口
/** * <p> * The root class from which all event state objects shall be derived. * <p> * All Events are constructed with a reference to the object, the "source", * that is logically deemed to be the object upon which the Event in question * initially occurred upon. * @since JDK1.1 */ public class EventObject implements java.io.Serializable { private static final long serialVersionUID = 5516075349620653480L; /** * The object on which the Event initially occurred. */ protected transient Object source; /** * Constructs a prototypical Event. * @param source The object on which the Event initially occurred. * @exception IllegalArgumentException if source is null. */ public EventObject(Object source) { if (source == null) throw new IllegalArgumentException("null source"); this.source = source; } /** * The object on which the Event initially occurred. * @return The object on which the Event initially occurred. */ public Object getSource() { return source; } /** * Returns a String representation of this EventObject. * @return A a String representation of this EventObject. */ public String toString() { return getClass().getName() + "[source=" + source + "]"; } } 复制代码
该接口中仅有source参数,无特殊含义,类似于存放数据源
对比上面jdk事件的Demo,咱们分析spring源码
我们之前分析了Spring中bean是如何加载的,并且分析了项目启动的入口,不做赘叙,将其作为已知条件。
@Override public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { // Prepare this context for refreshing. prepareRefresh(); // Tell the subclass to refresh the internal bean factory. ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); // Prepare the bean factory for use in this context. prepareBeanFactory(beanFactory); try { // Allows post-processing of the bean factory in context subclasses. postProcessBeanFactory(beanFactory); // Invoke factory processors registered as beans in the context. invokeBeanFactoryPostProcessors(beanFactory); // Register bean processors that intercept bean creation. registerBeanPostProcessors(beanFactory); // Initialize message source for this context. initMessageSource(); // Initialize event multicaster for this context. initApplicationEventMulticaster(); // Initialize other special beans in specific context subclasses. onRefresh(); // Check for listener beans and register them. registerListeners(); // Instantiate all remaining (non-lazy-init) singletons. finishBeanFactoryInitialization(beanFactory); // Last step: publish corresponding event. finishRefresh(); } } } 复制代码
这个方法中有三句话与Spring事件相关,把这三句话分析明白了,Spring事件机制也就了然了。挨个分析:
String[] listenerBeanNames = getBeanNamesForType(ApplicationListener.class, true, false); 复制代码
从容器中的所有bean中获取实现 ApplicationListener 接口的类。换言之,如果我们想使用 Spring 事件机制 来为我们项目服务,那我们所写的监听器必须实现ApplicationListener接口。
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener { /** * Handle an application event. * @param event the event to respond to */ void onApplicationEvent(E event); } 复制代码
ApplicationListener 接口继承自 jdk事件机制 中的EventListener,可以看出 Spring事件机制 改编自 jdk事件机制 。Spring在监听器接口中添加了 onApplicationEvent() 方法,便于事件被触发时执行任务,类似于上午 UserListener 中的 onRegister() 方法。
回到 registerListeners() 方法,获取到监听器类之后,存放在了 事件广播器(applicationEventMulticaster) 中,便于后面使用。
publishEvent(new ContextRefreshedEvent(this)); 复制代码
这句话类似于UserService中的 publishEvent(new SendSmsEvent(user)) ,而 ContextRefreshedEvent 类似于上文中的 发送短信事件 。 ContextRefreshedEvent 代表的事件是容器初始化完成。如果容器初始化完成了,那么所对应的 事件监听器 将会被触发。继续层层跟进,来到:
publishEvent(Object event, ResolvableType eventType) 复制代码
getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType); 复制代码
getApplicationListeners(event, type) 复制代码
这句话的意思是根据事件类型获取监听器。因为咱们在项目里面可能会配置很多 监听器 ,每一个监听器都会有自己所对应的 事件类型 ,只有自己所对应的 事件 发生了, 监听器 才会被触发。
invokeListener(listener, event); 复制代码
doInvokeListener(listener, event); 复制代码
listener.onApplicationEvent(event); 复制代码
看到了 onApplicationEvent 在此执行了,类似于UserService中 listener.onRegister(event) 。
至此,事件机制分析完毕。
// Publish event via parent context as well... if (this.parent != null) { if (this.parent instanceof AbstractApplicationContext) { ((AbstractApplicationContext) this.parent).publishEvent(event, eventType); } else { this.parent.publishEvent(event); } } 复制代码
判断该容器是否有父容器,若存在入容器,再一次触发父容器中的事件监听器。
从上文的源码分析中,咱们知道了 ContextRefreshedEvent事件监听器 是在 refresh() 方法内被触发的,更准确地讲,是 refresh() 方法中的 finishRefresh() 触发了 ContextRefreshedEvent事件监听器 。而我们在之前的文章中,得出一个结论: 子容器可以获取父容器bean,反之不行。 这里是因为 Spring容器 初始化执行 refresh() 方法时,触发了 ContextRefreshedEvent事件监听器 ,而 SpringMvc容器 初始化时也执行了 refresh() 方法,当代码执行到
publishEvent(Object event, ResolvableType eventType); 复制代码
其中有一段代码判断了是否存在 父容器 。若存在,会将 父容器 中的监听器执行一遍。所以再一次触发了 ContextRefreshedEvent事件监听器 。所以从直观上看,初始化了两次。
@Component public class EvolvedFreshListener implements ApplicationListener<ContextRefreshedEvent>{ private final Logger logger = LoggerFactory.getLogger(getClass()); @Override public void onApplicationEvent(ContextRefreshedEvent event) { if (event.getApplicationContext().getParent() == null){ logger.error("进化版====将有权限人员放入缓存。。。。"); } } } 复制代码
/** * 实现此类, 可以在Spring容器完全初始化完毕时获取到Spring容器 */ public abstract class ContextRefreshListener implements ApplicationListener<ContextRefreshedEvent> { private volatile boolean initialized = false; @Override public synchronized void onApplicationEvent(ContextRefreshedEvent event) { if (!initialized) { System.out.println("加锁====将有权限人员放入缓存。。。。"); initialized = true } } }复制代码
BLOG地址 : www.liangsonghua.me
关注微信公众号:松花皮蛋的黑板报,获取更多精彩!
公众号介绍:分享在京东工作的技术感悟,还有JAVA技术和业内最佳实践,大部分都是务实的、能看懂的、可复现的