問題描述:項目中配置事件監聽,監聽當容器加載完成以後,作一些初始化工做。項目運行以後,發現初始化工做被重複作了兩次。爲了便於分析,去掉代碼中的業務邏輯,只留下場景。java
@Component
public class FreshListener implements ApplicationListener<ContextRefreshedEvent>{
private final Logger logger = LoggerFactory.getLogger(getClass());
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
//業務代碼
logger.error("將有權限人員放入緩存。。。。");
}
}
複製代碼
配置FreshListener監聽器,監聽當容器加載完成以後,將管理員名單加入緩存。卻發現,名單被加載了兩次。WHY???spring
因爲源碼中的個方法較長,因此只貼出重點且與主題相關的代碼。建議結合本地源碼一塊兒看。緩存
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);
}
}
複製代碼
/**
* A tagging interface that all event listener interfaces must extend.
* @since JDK1.1
*/
public interface EventListener {
}
複製代碼
該接口爲標識接口bash
/**
* <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源碼app
咱們以前分析了Spring中bean是如何加載的,而且分析了項目啓動的入口,不作贅敘,將其做爲已知條件。ide
@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接口。post
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技術和業內最佳實踐,大部分都是務實的、能看懂的、可復現的