1.Spring内置事件

        Spring内置事件是Spring在内部帮我们写好的事件,在使用时,不需要重写他们,只需在容器中注入重写后的监听器即可。
框架源码专题:Spring的事件监听、发布机制 ApplicationListener_Spring

事件 说明
ContextRefreshedEvent 所有单例bean都已被实例化, 所有的容器对象都已准备好可使用,如果容器支持热重载,则refresh可以被触发多次(XmlWebApplicatonContext支持热刷新,而 GenericApplicationContext则不支持)
ContextStartedEvent 当容器启动时发布,即调用start()方法, 已启用意味着所有的Lifecycle bean都已显式接收到了start 信号
ContextStoppedEvent 当容器停止时发布,即调用stop()方法, 即所有的Lifecycle bean都已显式接收到了stop信号 , 关闭 的容器可以通过start()方法重启
ContextClosedEvent 当容器关闭时发布,即调用close方法, 关闭意味着所有的单例bean都已被销毁.关闭的容器不能被重启 或refresh
RequestHandledEvent 这只在使用spring的DispatcherServlet时有效,当一个请求被处理完成时发布

 

2.自定义事件

自定义事件类需要继承ApplicationEvent,这个类有一个构造方法需要调用父类super()

//自定义事件   不能加@Component注解,否则会报错
public class MyEvent extends ApplicationEvent{

	private String name ;

	public MyEvent(Object source,String name ) {
		//需要调用父类构造器
		super(source);
		System.out.println("我的自定义事件:"+name);
		this.name = name;
	}

	public MyEvent(Object source) {
		super(source);
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}
}

 

3.事件监听器

事件监听器-基于接口
事件监听器需要实现ApplicationListener接口,这是个泛型接口,泛型类类型就是事件类型,其次需要是spring容器托 管的bean,所以这里加了@component,只有一个方法,就是onApplicationEvent。

//自定义事件监听器
@Component
public class MyListener implements ApplicationListener<MyEvent> {
	@Override
	public void onApplicationEvent(MyEvent event) {

		String name = event.getName();
		if ("I love you".equals(name)){
			System.out.println("我的自定义监听器: I love you too!");
		}
	}
}

事件监听器-基于注解

//自定义事件监听器
@Component
public class MyListener {

	@EventListener(MyEvent.class)
	public void onApplicationEvent(MyEvent event) {

		String name = event.getName();
		if ("I love you".equals(name)){
			System.out.println("我的自定义监听器: I love you too!");
		}
	}
}

 

4.事件发布 publishEvent

事件发布方式

  • 可以使用Spring 提供的ApplicationEventPublisher来发布自定义事件。
@RestController
@RequestMapping("/event")
public class EventController {

    private static final Logger logger = LoggerFactory.getLogger(EventController.class);

	//注入ApplicationEventPublisher 
    @Autowired
    private ApplicationEventPublisher applicationEventPublisher;

    @GetMapping("/notice/{message}")
    public void notice(@PathVariable(name = "message") String message) {
    
       	Object o = new Object();
        applicationEventPublisher.publishEvent(new MyEvent(o,"I love you"));
        
    }
}

  • 也可以使用Spring 容器AnnotationConfigApplicationContext来发布自定义事件
public class MainClass {
	public static void main(String[] args) {

		AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig.class);
		Object o = new Object();
		
		//publishEvent方法可以发布自定义事件
		context.publishEvent(new MyEvent(o,"I love you"));
	
	}
}

注意:

  • 如果在@PostConstruct中发布事件的话,必须使用ApplicationEventPublisher来发布,因为此时容器AnnotationConfigApplicationContext可能还未初始化完成!
  • 上面两种发送的都是同步事件,受事务方法控制!如果监听逻辑出现异常,事务方法也会回滚,相当于事务方法和事件是同步执行的!

发送异步事件

  • 如果需要发送异步事件,需要在配置类上加上@EnableAsync注解,并在onApplicationEvent方法上加上@Async注解,此时事件是异步的,但已经不受事务方法所管控。如果监听器中要获取事务方法在数据库中生成的数据,可能会获取不到,因为在执行监听逻辑时,可能由于事务方法未提交,监听器获取的值为null;
  • 开启异步后,会使用Spring内部默认的线程池,我们也可以自定义这个线程池。创建配置类(加上@Configuration),实现AsyncConfigurer接口,重写Executor方法。这里我们可以将原先配置在启动类上的@EnableAsync注解放到这个类上。如下所示
@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {

    private final Logger log = LoggerFactory.getLogger(this.getClass());

    /**
     * 自定义异步线程池,若不重写会使用默认的线程池
     */
    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
        // 核心线程数
        taskExecutor.setCorePoolSize(2);
        // 最大线程数
        taskExecutor.setMaxPoolSize(10);
        // 队列大小
        taskExecutor.setQueueCapacity(15);
        // 线程名的前缀
        taskExecutor.setThreadNamePrefix("async-thread-");
        taskExecutor.initialize();
        return taskExecutor;
    }

    /**
     * 捕捉IllegalArgumentException异常
     */
    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return new MyAsyncExceptionHandler();
    }

    class MyAsyncExceptionHandler implements AsyncUncaughtExceptionHandler {
        @Override
        public void handleUncaughtException(Throwable throwable, Method method, Object... objects) {
            log.info("TASK Exception message - " + throwable.getMessage());
            log.info("Method name - " + method.getName());
            for (Object param : objects) {
                log.info("Parameter value - " + param);
            }
        }
    }
}

重新访问接口时,会发现异步线程名就是 我们自定义的async-thread-,说明使用的是我们自定义的线程池!

 

4.Spring事件原理

Spring事件机制是观察者模式的一种实现,但是除了发布者和监听者者两个角色之外,还有一个EventMultiCaster的角色负责把事件转发给监听者,spring的事件监听由三个部分组成:

  • 事件(ApplicationEvent)

  • 监听器(ApplicationListener) 对应于观察者模式中的观察者。监听器监听特定事件,并在内部定义了事件发生后的响应逻辑。

  • 事件发布器(多播器)(ApplicationEventMulticaster ) 对应于观察者模式中的被观察者/主题, 负责通知观察者对外提供发布事件和增删事件监听器的接口,维护事件和事件监听器之间的映射关系,并在事件发生时负责通知相关监听器

步骤:

①:先初始化一个事件多播器SimpleApplicationEventMulticaster

	// 创建事件多播器
	initApplicationEventMulticaster();

②:initApplicationEventMulticaster内部有创建了一个事件多播器!

	//spring ioc显示的new 一个SimpleApplicationEventMulticaster对象保存在applicatoinContext对象中
	this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);

③:然后把事件监听器注册到多播器上

	//把我们的事件监听器注册到多播器上
	registerListeners();

registerListeners()方法内部:

		//获取bean定义中的实现了接口的监听器对象,注解的方式拿不到!!!
		String[] listenerBeanNames = getBeanNamesForType(ApplicationListener.class, true, false);
		//把监听器的名称注册到我们的多播器上
		for (String listenerBeanName : listenerBeanNames) {
			getApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName);
		}

		//在这里获取我们的早期事件
		Set<ApplicationEvent> earlyEventsToProcess = this.earlyApplicationEvents;
		this.earlyApplicationEvents = null;
		if (earlyEventsToProcess != null) {
			//通过多播器进行播发早期事件
			for (ApplicationEvent earlyEvent : earlyEventsToProcess) {
				getApplicationEventMulticaster().multicastEvent(earlyEvent);
			}
		}

④:此时我们的监听器已经添加到多播器SimpleApplicationEventMulticaster中了,earlyEvent 早期事件也已经执行完毕。

⑤:那我们注册到多播器中的监听器逻辑怎么执行的呢?
答案是会在IOC容器刷新接口refresh()方法的最后一个finishRefresh()中执行监听器逻辑

	// 最后容器刷新 发布刷新事件(Spring cloud也是从这里启动的)
	finishRefresh();

finishRefresh()方法内部代码如下

	protected void finishRefresh() {
		// Clear context-level resource caches (such as ASM metadata from scanning).
		clearResourceCaches();

		// Initialize lifecycle processor for this context.
		initLifecycleProcessor();

		// Propagate refresh to lifecycle processor first.
		getLifecycleProcessor().onRefresh();

		// 容器中的类全部初始化完毕后,触发刷新事件!
		publishEvent(new ContextRefreshedEvent(this));

		// Participate in LiveBeansView MBean, if active.
		LiveBeansView.registerApplicationContext(this);
	}

publishEvent方法中执行了所有注册到多播器中的监听器逻辑

	protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
		Assert.notNull(event, "Event must not be null");
		if (logger.isTraceEnabled()) {
			logger.trace("Publishing event in " + getDisplayName() + ": " + event);
		}

		// Decorate event as an ApplicationEvent if necessary
		ApplicationEvent applicationEvent;
		if (event instanceof ApplicationEvent) {
			applicationEvent = (ApplicationEvent) event;
		}
		else {
			applicationEvent = new PayloadApplicationEvent<>(this, event);
			if (eventType == null) {
				eventType = ((PayloadApplicationEvent<?>) applicationEvent).getResolvableType();
			}
		}

		// Multicast right now if possible - or lazily once the multicaster is initialized
		if (this.earlyApplicationEvents != null) {
			this.earlyApplicationEvents.add(applicationEvent);
		}
		else {
		
			//在这一步,执行了所有注册到多播器中的监听器逻辑
			// multicastEvent方法在下面会有讲解!
			getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
		}

		// 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);
			}
		}
	}

上面介绍了spring内部事件的发布,不需要显式调用publishEvent()方法,因为spring已经帮我们封装好了。但当我们显式调用如下publishEvent代码时,spring是怎么通知所有的监听器监听MyEvent事件呢?

context.publishEvent(new MyEvent(o,"I love you"));

publishEvent内部有一段代码如下,multicastEvent会执行监听逻辑,这里跟上面spring的类似

getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);

multicastEvent代码如下:

	@Override
	public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
		ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
		//从多播器中获取出所有的监听器
		for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) {
			//判断多播器中是否支持异步多播的
			Executor executor = getTaskExecutor();
			if (executor != null) {
				//异步播发事件
				executor.execute(() -> invokeListener(listener, event));
			}
			else {//同步播发
				invokeListener(listener, event);
			}
		}
	}

播发方法invokeListener(listener, event)代码如下:

	private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
		try {
			//看到这里就明白了吧!
			//最后在这里调用了监听器中的onApplicationEvent方法!
			listener.onApplicationEvent(event);
		}
		catch (ClassCastException ex) {
			String msg = ex.getMessage();
			if (msg == null || matchesClassCastMessage(msg, event.getClass())) {
				// Possibly a lambda-defined listener which we could not resolve the generic event type for
				// -> let's suppress the exception and just log a debug message.
				Log logger = LogFactory.getLog(getClass());
				if (logger.isDebugEnabled()) {
					logger.debug("Non-matching event type for listener: " + listener, ex);
				}
			}
			else {
				throw ex;
			}
		}
	}

那么上面针对接口形式的事件监听器已经分析完毕,还有基于注解的形式的监听器!他们是由下面这两个创世纪的类来搞定的!!在这里就不展开分析了,一般也是基于接口完成事件监听的!
框架源码专题:Spring的事件监听、发布机制 ApplicationListener_Spring_02

 

5. 面试题:怎么样可以在所有Bean创建完后做扩展代码?

1. 使用spring的事件监听器,监听spring内部ContextRefreshedEvent事件

	@Component  //注意这个类也需要加入容器中
	public class RefreshListener implements ApplicationListener<ContextRefreshedEvent> {
		@Override
		public void onApplicationEvent(ContextRefreshedEvent event) {
			System.out.println("ContextRefreshedEvent的监听器:容器的bean全部初始化完毕!!!");
		}
	}

运行结果:
框架源码专题:Spring的事件监听、发布机制 ApplicationListener_Spring_03
2. bean实现SmartInitializingSingleton接口

在finishBeanFactoryInitialization(beanFactory)初始化bean的接口中,当所有bean初始化后会遍历所有的bean,看是否实现了SmartInitializingSingleton接口,如果有实现,就会触发实例化之后的方法afterSingletonsInstantiated方法,代码如下

		//获取所有的bean的名称 ...........到这里所有的单实例的bean已经记载到单实例bean到缓存中
		for (String beanName : beanNames) {
			//从单例缓存池中获取所有的对象
			Object singletonInstance = getSingleton(beanName);
			//判断当前的bean是否实现了SmartInitializingSingleton接口,这里可以在所有bean创建完成后做扩展!!!!
			if (singletonInstance instanceof SmartInitializingSingleton) {
				final SmartInitializingSingleton smartSingleton = (SmartInitializingSingleton) singletonInstance;
				if (System.getSecurityManager() != null) {
					AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
						smartSingleton.afterSingletonsInstantiated();
						return null;
					}, getAccessControlContext());
				}
				else {
					//触发实例化之后的方法afterSingletonsInstantiated
					smartSingleton.afterSingletonsInstantiated();
				}
			}
		}

示例:

@Component
public class Context implements SmartInitializingSingleton {

	@Override
	public void afterSingletonsInstantiated() {
		System.out.println("单例bean实现了SmartInitializingSingleton:容器的bean全部初始化完毕!!!");

	}
}

项目启动运行结果:

框架源码专题:Spring的事件监听、发布机制 ApplicationListener_Spring_04

 

6. 修改事件多播器的广播逻辑,使其根据注解切换同步、异步

        我们知道,事件发布是依靠调用实现了ApplicationEventPublisher接口类的publishEvent方法进行发布事件,而publishEvent 方法又是通过调用实现了ApplicationEventMulticaster接口的类的multicastEvent方法进行事件的广播的。

        ApplicationEventMulticaster中保存了所有的实现了ApplicationListener接口的监听器,我们看一下spring内部实现ApplicationEventMulticaster接口的一个广播器SimpleApplicationEventMulticaster的源码:

public class SimpleApplicationEventMulticaster extends AbstractApplicationEventMulticaster {
    private Executor taskExecutor;

    public SimpleApplicationEventMulticaster() {
    }

    public SimpleApplicationEventMulticaster(BeanFactory beanFactory) {
        this.setBeanFactory(beanFactory);
    }

    public void setTaskExecutor(Executor taskExecutor) {
        this.taskExecutor = taskExecutor;
    }

    protected Executor getTaskExecutor() {
        return this.taskExecutor;
    }

	//广播逻辑
    public void multicastEvent(final ApplicationEvent event) {
    	//获取所有得到事件监听器
        Iterator i$ = this.getApplicationListeners(event).iterator();

        while(i$.hasNext()) {
            final ApplicationListener listener = (ApplicationListener)i$.next();
            Executor executor = this.getTaskExecutor();
            if (executor != null) {
                executor.execute(new Runnable() {
                    public void run() {
                        listener.onApplicationEvent(event);
                    }
                });
            } else {
                listener.onApplicationEvent(event);
            }
        }

    }
}

        可以看到,异步事件通知主要依靠SimpleApplicationEventMulticaster 类中的Executor去实现的,如果这个变量不配置的话默认事件通知是同步的, 否则就是异步通知了,要实现同时支持同步通知和异步通知就得从这里下手;

        我的实现方式是为每个监听方法加个自定义注解,然后在multicastEvent方法中获取对应监听器上的注解,根据注解去决定是同步通知还是异步通知,这样就可以同时支持了,废话不多说,直接看代码

  1. 自定义注解EventType
/**
 * event listener 专用注解,只能加在实现了ApplicationListener 的onApplicationEvent方法上
 * 用来标识是同步监听还是异步监听
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface EventType {
    EventTypeEnum value() default EventTypeEnum.ASYNC;
}
  1. 自定义枚举类EventTypeEnum
/**
 * @Description: 事件监听枚举类
 * @Author Mr.huang
 * @Date 2020/3/20 0020
 * @Version V1.0
 **/
public enum  EventTypeEnum {
    ASYNC,  //异步
    SYNC;   //同步
}
  1. 继承SimpleApplicationEventMulticaster 类,并重写multicastEvent方法,修改广播逻辑
/**
 * @Description: 事件广播基础类,支持自定义注解实现同步或异步的事件监听
 **/
public class BaseApplicationEventMulticaster extends SimpleApplicationEventMulticaster {
    private static Logger log = LoggerFactory.getLogger(BaseApplicationEventMulticaster.class);
    @SuppressWarnings("unchecked")
    public void multicastEvent(final ApplicationEvent event) {
        //默认异步
        EventTypeEnum defaultEventType = EventTypeEnum.ASYNC;
        for (final ApplicationListener listener : getApplicationListeners(event)) {
            try {
                Class listenerClass = Class.forName(listener.getClass().getName());
                if(listenerClass!=null){
                    Method onApplicationEventMethod = listenerClass.getMethod("onApplicationEvent",ApplicationEvent.class);
                    if(onApplicationEventMethod.isAnnotationPresent(EventType.class)){
                        //获取该元素上指定类型的注解
                        EventType eventMethodAnnotation = onApplicationEventMethod.getAnnotation(EventType.class);
                        defaultEventType = eventMethodAnnotation.value();
                    }
                }
            } catch (Exception e) {
                log.error("获取监听类实例出错~");
            }

            Executor executor = getTaskExecutor();
            if (executor != null&&defaultEventType==EventTypeEnum.ASYNC) {
                executor.execute(new Runnable() {
                    public void run() {
                        listener.onApplicationEvent(event);
                    }
                });
            }
            else {
                listener.onApplicationEvent(event);
            }
        }
    }
}
  1. 最后在实现了ApplicationListener接口类中的onApplicationEvent方法上加上注解即可
/**
 * @Description: TODO
 * @Author Mr.huang
 * @Date 2020/3/21 0021
 * @Version V1.0
 **/
public class UserChargeService implements ApplicationListener<UserChargeEvent> {
    @EventType(value = EventTypeEnum.ASYNC)
    @Override
    public void onApplicationEvent(UserChargeEvent userChargeEvent) {
        //业务逻辑
    }
}