业务的解耦与代码的解耦
在性能要求比较高的接口中,执行一个比较耗时而并不关键的操作(比如上报监控信息什么的),如果同步执行必然是没有意义的,因此一般我们会想去异步处理,通常会使用MQ之类的中间件,不过Spring也提供了事件相关的处理,就是ApplicationEvent,关于这个类这里不再多言,基础的知识可以网上搜索相关教程,不过有一个问题需要注意的是,默认这种事件机制是同步的,好处是如果有事务,发送事件的方法和事件处理的方法在同一个事务里,缺点就是,可能并没有实现我们想象中的异步处理,有一种方案是在处理事件的时候使用一个线程池,通过线程池来异步处理,虽然是解决了异步的问题,但是给笔者一种脱裤子放屁的赶脚,与其这样,还不如直接扔到一个线程池里,何必还走一个事件处理?好在Spring本身也支持ApplicationEvent的异步处理,通过@Async注解就可以了,下面是相关代码:
首先是不使用@Async注解前,ApplicationEvent处理是同步的一个验证
public abstract class AbstractEvent extends ApplicationEvent {
public AbstractEvent(Object source) {
super(source);
}
/**
* 事件执行方法
*/
public abstract void execute() throws InterruptedException;
}
public class DemoEvent extends AbstractEvent {
private static final long serialVersionUID = 1L;
public DemoEvent(Object source) {
super(source);
}
@Override
public void execute() throws InterruptedException {
while (true) {
Thread.sleep(1000);
System.out.println("======================DemoEvent execute======================" + source);
}
}
}
@Component
public class AbstractEventListener implements ApplicationListener {
@Override
@Async
public void onApplicationEvent(AbstractEvent event) {
try {
event.execute();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
@RestController
public class IndexController {
@Autowired
private ApplicationContext ioc;
@RequestMapping("/index")
public void index () {
System.out.println("===================Java异步执行测试开始===================");
ioc.publishEvent(new DemoEvent("param"));
System.out.println("===================Java异步执行测试结束===================");
}
}
最后开启@EnableAsync
结果如下:
=Java异步执行测试开始=
=Java异步执行测试结束=
DemoEvent executeparam
DemoEvent executeparam
DemoEvent executeparam
public class UploadEvent extends ApplicationEvent {
public UploadEvent(Object source) {
super(source);
}
}
@Service
public class TEventListener implements ApplicationListener {
@Override
public void onApplicationEvent(UploadEvent uploadEvent) {
try {
System.out.println(">>>OK1");
TimeUnit.SECONDS.sleep(5);
System.out.println(">>>OK2");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
@EnableAsync
@Component
public class SpringContextUtil implements ApplicationContextAware {
private static ApplicationContext applicationContext; // Spring应用上下文环境
/*
*
* 实现了ApplicationContextAware 接口,必须实现该方法;
*
* 通过传递applicationContext参数初始化成员变量applicationContext
*
*/
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
SpringContextUtil.applicationContext = applicationContext;
}
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
public static T getBean(Class requiredType) {
return (T) applicationContext.getBean(requiredType);
}
public static void publishEvent(ApplicationEvent event){
applicationContext.publishEvent(event);
}
}
@Test
public void testEvent(){
UploadEvent event = new UploadEvent(this);
SpringContextUtil.publishEvent(event);
System.out.println(">>>OK");
}
@Async
@Override
public void onApplicationEvent(UploadEvent uploadEvent) {
try {
System.out.println(">>>OK1");
TimeUnit.SECONDS.sleep(5);
System.out.println(">>>OK2");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
接口 BeanFactory 和 ApplicationContext 都是用来从容器中获取 Spring beans
ApplicationContext(应用上下文):继承BeanFactory接口,简单来说就是Spring中的容器,是Spring的一个更高级的容器。可以用来获取容器中的各种bean组件,注册监听事件,加载资源文件等功能。
ApplicationContext 是 Spring 应用程序中的中央接口,用于向应用程序提供配置信息。它继承了 BeanFactory 接口,所以 ApplicationContext 包含 BeanFactory 的所有功能以及更多功能!它的主要功能是支持大型的业务应用的创建。
与 BeanFactory 懒加载的方式不同,它是预加载,所以,每一个 bean 都在 ApplicationContext 启动之后实例化。
特性:
Bean instantiation/wiring
Bean 的实例化/串联
自动的 BeanPostProcessor 注册
自动的 BeanFactoryPostProcessor 注册
方便的 MessageSource 访问(i18n)
ApplicationEvent 的发布
对资源文件(如:properties)进行存取操作的功能
ApplicationContext acxt =new ClassPathXmlApplicationContext("/applicationContext.xml");
通过虚拟路径来存取(classpath路径:存放.class等编译后文件的路径)
Resource resource = acxt.getResource(“classpath:messages_en_CN.properties”);
通过绝对路径存取资源文件
Resource resource = acxt.getResource(“file:F:/testwork/MySpring/src/messages_en_CN.properties”);
相对路径读取资源文件
Resource resource = acxt.getResource("/messages_en_CN.properties");
applicationContext.xml(主文件,包括JDBC配置,hibernate.cfg.xml,与所有的Service与DAO基类)
applicationContext-cache.xml(cache策略,包括hibernate的配置)
applicationContext-jmx.xml(JMX,调试hibernate的cache性能)
applicationContext-security.xml(acegi安全)
applicationContext-transaction.xml(事务)
moduleName-Service.xml
moduleName-dao.xml
< beans>
< import resource=“applicationContext-cache.xml”/>
< /beans>
ApplicationContext事件机制是观察者设计模式的实现,通过ApplicationEvent类和ApplicationListener接口,可以实现ApplicationContext事件处理。 如果容器中有一个ApplicationListener Bean,每当ApplicationContext发布ApplicationEvent时,ApplicationListener Bean将自动被触发。
两个重要成员
ApplicationEvent类:容器事件,必须由ApplicationContext发布;
ApplicationListener接口:监听器,可由容器中的任何监听器Bean担任。
例:
1.1. 定义容器事件 EmailEvent extends ApplicationEvent
1.2. 定义监听器 EmailNotifierListener implements ApplicationListener(容器事件的监听器类必须实现ApplicationListener接口)
1.3. 将监听器注入到spring容器
1.4. 测试:
public static void main(String arg[]){
//读取Spring容器的配置文件
@SuppressWarnings("resource")
ApplicationContext applicationContext=new ClassPathXmlApplicationContext("application.xml");
//创建一个事件对象
EmailEvent emailEvent = new EmailEvent("hello Spring!", "[email protected]", "This is SpringApplicatoinContext test!");
//主动触发事件监视机制
applicationContext.publishEvent(emailEvent);
}
preHandle:执行controller之前执行
postHandle:执行完controller,return modelAndView之前执行,主要操作modelAndView的值
afterCompletion:controller返回后执行
例:
1.1 注册拦截器,并且确定拦截器拦截哪些URL
1.2. 定义拦截器实现类
是Spring里面最底层的接口,提供了最简单的容器的功能,只提供了实例化对象和拿对象的功能;
BeanFactory 接口:
这是一个用来访问 Spring 容器的 root 接口,要访问 Spring 容器,我们将使用 Spring 依赖注入功能,使用 BeanFactory 接口和它的子接口。
通常情况,BeanFactory 的实现是使用懒加载的方式,这意味着 beans 只有在我们通过 getBean() 方法直接调用它们时才进行实例化。
ApplicationContext 包含 BeanFactory 的所有特性,通常推荐使用前者。但是也有一些限制情形,比如移动应用内存消耗比较严苛,在那些情景中,使用更轻量级的 BeanFactory 是更合理的。然而,在大多数企业级的应用中,ApplicationContext 是你的首选。
ApplicationContext在启动的时候就把所有的Bean全部实例化。它还可以为Bean配置lazy-init=true来让Bean延迟实例化;
延迟实例化的优点:(BeanFactory)
应用启动的时候占用资源很少;对资源要求较高的应用,比较有优势;
不延迟实例化的优点: (ApplicationContext)
所有的Bean在启动的时候都加载,系统运行的速度快;
在启动的时候所有的Bean都加载了,我们就能在系统启动的时候,尽早的发现系统中的配置问题
建议web应用,在启动的时候就把所有的Bean都加载了。(把费时的操作放到系统启动中完成)
SpringBoot中获取ApplicationContext的三种方式:
一、 直接使用Autowired注入
二、利用 spring4.3 的新特性
三、实现spring提供的接口 ApplicationContextAware
ApplicationEventPublisher的publishEvent实现异步快速。 首先来一个小demo感受一下spring的自定义异步事件。
1、使用ApplicationEventPublisher的publishEvent来发布事件。如下代码,这里的事件指user
idea工具可以识别是事件,我们可以在idea编辑器中看到前面的标示。点一下即可跳转到监听事件中。
@Autowired
private ApplicationEventPublisher publisher;
@GetMapping("test")
public void register() throws Exception {
User user=new User();
user.setName("电脑");
user.setAge(23);
publisher.publishEvent(user);
}
@Component
public class AbstractEventListener implements ApplicationListener {
@Override
@Async
public void onApplicationEvent(AbstractEvent event) {
try {
event.execute();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Java中定义了事件机制的两个顶层类:EventObject ,事件的顶层父类(实现了Serializable接口);
EventListener,定义了监听器的顶层接口
Spring的事件框架有如下两个重要的成员:
ApplicationEvent:容器事件,必须由ApplicationContext发布
ApplicationListener:监听器,可由容器中的任何监听器Bean担任
总的来说spring的事件机制主要包括四个步骤
1.编写ApplicationEvent类的子类,这就是我们的事件类
2.编写监听器类,这个类要实现ApplicationListener接口
3 将监听器配置在Spring的容器中
4.调用ApplicationContext的publishEvent()方法来主动触发一个容器事件,或者spring的一些内置事件发生
下面是applicationcontext事件机制的一个例子
第一条输出为spring的内置事件发生,下面为大家介绍spring的几种内置事件
1.ContextRefreshedEvent:ApplicationContext容器初始化或刷新时触发该事件。此处的初始化是指:所有的Bean被成功装载,后处理Bean被检测并激活,所有Singleton Bean 被预实例化,ApplicationContext容器已就绪可用
2.ContextStartedEvent:当使用ConfigurableApplicationContext(ApplicationContext的子接口)接口的start()方法启动ApplicationContext容器时触发该事件。容器管理声明周期的Bean实例将获得一个指定的启动信号,这在经常需要停止后重新启动的场合比较常见
3.ContextClosedEvent:当使用ConfigurableApplicationContext接口的close()方法关闭ApplicationContext时触发该事件
4.ContextStoppedEvent:当使用ConfigurableApplicationContext接口的stop()方法使ApplicationContext容器停止时触发该事件。此处的停止,意味着容器管理生命周期的Bean实例将获得一个指定的停止信号,被停止的Spring容器可再次调用start()方法重新启动
5.RequestHandledEvent:Web相关事件,只能应用于使用DispatcherServlet的Web应用。在使用Spring作为前端的MVC控制器时,当Spring处理用户请求结束后,系统会自动触发该事件。
我们刚触发的就是ContextRefreshedEvent事件
如果说BeanFactory是Spring的心脏,那么ApplicationContext就是完整的身躯了。ApplicationContext由BeanFactory派生而来,提供了更多面向实际应用的功能。
(1)ApplicationContext是什么?
ApplicationContext是比BeanFactory更加强大的Spring容器,它既可以创建bean、获取bean、还支持国际化、事件广播、获取资源等BeanFactory不具备的功能。
(2)ApplicationContext所继承的接口
Ⅰ、EnvironmentCapable:ApplicationContext继承了这个接口,表示拥有了获取环境变量的功能,可以通过ApplicationContext获取操作系统环境变量和JVM环境变量。
Ⅱ、ListableBeanFactory:ApplicationContext继承了这个接口,就拥有了获取所有beanNames、判断某个beanName是否存在beanDefinition对象、统计BeanDefinition个数、获取某个类型对应的所有beanNames等功能。
Ⅲ、HierarchicalBeanFactory:ApplicationContext继承了这个接口,就拥有了获取父BeanFactory、判断某个name是否存在bean对象的功能。
Ⅳ、MessageSource:ApplicationContext继承了这个接口,就拥有了国际化功能,比如可以直接利用MessageSource对象获取某个国际化资源(比如不同国家语言所对应的字符)
Ⅴ、ApplicationEventPublisher:ApplicationContext继承了这个接口,就拥有了事件发布功能,可以发布事件,这是ApplicationContext相对于BeanFactory比较突出、常用的功能。
Ⅵ、ResourcePatternResolver:ApplicationContext继承了这个接口,就拥有了加载并获取资源的功能,这里的资源可以是文件,图片等某个URL资源都可以。
Spring的事件框架有如下两个重要的成员:
ApplicationEvent:容器事件,必须由ApplicationContext发布;
ApplicationListener:监听器,可由容器中的任何监听器Bean担任;
实际上,Spring的事件机制与所有时间机制都基本相似,它们都需要事件源、事件和事件监听器组成。只是此处的事件源是ApplicationContext,
且事件必须由Java程序显式触发。下面的程序将演示Spring容器的事件机制。程序先定义了一个ApplicationEvent类,其对象就是一个Spring
注意:如果Bean想发布事件,则Bean必须获得其容器的引用。如果程序中没有直接获取容器的引用,则应该让Bean实现
ApplicationContextAware或者BeanFactoryAware接口,从而可以获得容器的引用。
Spring提供如下几个内置事件:
ContextRefreshedEvent:ApplicationContext容器初始化或刷新时触发该事件。此处的初始化是指:所有的Bean被成功装载,
后处理Bean被检测并激活,所有Singleton Bean 被预实例化,ApplicationContext容器已就绪可用
ContextStartedEvent:当使用ConfigurableApplicationContext(ApplicationContext的子接口)接口的start()方法启动ApplicationContext容器时
触发该事件。容器管理声明周期的Bean实例将获得一个指定的启动信号,这在经常需要停止后重新启动的场合比较常见
ContextClosedEvent:当使用ConfigurableApplicationContext接口的close()方法
关闭ApplicationContext时触发该事件
ContextStoppedEvent:当使用ConfigurableApplicationContext接口的stop()方法
使ApplicationContext容器停止时触发该事件。此处的停止,意味着容器管理生命周期的Bean实例将获得一个指定的停止信号,被停止的
Spring容器可再次调用start()方法重新启动
RequestHandledEvent:Web相关事件,只能应用于使用DispatcherServlet的Web应
用。在使用Spring作为前端的MVC控制器时,当Spring处理用户请求结束后,系统会自动触发该事件。
1)需要自定义一个事件类继承ApplicationEvent;
2)需要自定义一个监听器类实现ApplicationListener接口,或普通类的方法中使用@EventListener注解;
3)使用默认发布器ApplicationEventPublisher发布即可;
4)事件类不需要注入到IOC;监听器需要注入到IOC;ApplicationEventPublisher用Autowired注入进来即可;
5)默认情况下,事件发布与执行是同步的,事件执行完毕,发布者才会执行下面的逻辑;
public class MyEvent extends ApplicationEvent {
private Task task;
public MyEvent(Task task) {
super(task);
this.task = task;
}
public Task getTask() {
return task;
}
}
@Data
class Task {
private Long id;
private String taskName;
private String taskContext;
private boolean finish;
}
/**
* 事件监听类
* 事件的监听器需要实现org.springframework.context.ApplicationListener,并且需要注入到容器之中。
*/
@Component
@Slf4j
class MyEventListenerA implements ApplicationListener {
/**
* 监听方式1:实现ApplicationListener接口,重写onApplicationEvent方法
* 需要使用Component注入IOC
*/
@SneakyThrows
@Async
@Override
public void onApplicationEvent(MyEvent MyEvent) {
Thread.sleep(5000);
if (Objects.isNull(MyEvent)) {
return;
}
Task task = MyEvent.getTask();
log.info("监听器A接收任务:{}", JSON.toJSONString(task));
task.setFinish(true);
log.info("监听器A此时完成任务");
}
}
@Component
@Slf4j
class MyEventListenerB implements ApplicationListener {
/**
* 监听方式2:接口和注解混合使用
* 但此时 @EventListener不能与注解@Async在同一个类中使用,会报错,至于为什么,不知道;
* 需要使用Component注入IOC
*/
//@Async//加上这个,@EventListener的方法就会报java.lang.IllegalStateException: Failed to load ApplicationContext
@SneakyThrows
@Override
public void onApplicationEvent(MyEvent MyEvent) {
Thread.sleep(1000);
if (Objects.isNull(MyEvent)) {
return;
}
Task task = MyEvent.getTask();
log.info("监听器B接收任务:{}", JSON.toJSONString(task));
task.setFinish(true);
log.info("监听器B此时完成任务");
}
@EventListener
public void someMethod(MyEvent event) throws InterruptedException {
Thread.sleep(1000);
log.info("监听器@EventListenerB收到={}", event.getTask());
}
}
@Component
@Slf4j
class MyEventListennerC {
/**
* 监听方式3:注解@EventListener的监听器不需要实现任何接口
* 需要使用Component注入IOC
*/
@EventListener
public void someMethod(MyEvent event) throws InterruptedException {
Thread.sleep(1000);
log.info("监听器@EventListenerC收到={}", event.getTask());
}
}
@Slf4j
@EnableAsync
@Controller
public class PublishEventDemo {
/**
* 事件机制:
* 需要自定义一个事件类继承ApplicationEvent;
* 需要自定义一个监听器类实现ApplicationListener接口,或普通类的方法中使用@EventListener注解;
* 使用默认发布器ApplicationEventPublisher发布即可;
* 事件类不需要注入到IOC;监听器需要注入到IOC;ApplicationEventPublisher用Autowired注入进来即可;
*/
/**
* 事件发布器
*/
@Autowired
private ApplicationEventPublisher eventPublisher;
/**
* 测试类
*/
@GetMapping("/")
public void publishTest() throws InterruptedException {
Task task = new Task();
task.setId(1L);
task.setTaskName("测试任务");
task.setTaskContext("任务内容");
task.setFinish(false);
MyEvent event = new MyEvent(task);
log.info("开始发布任务");
eventPublisher.publishEvent(event);
//applicationContext.publishEvent(event);
log.info("结束发布任务");
Thread.sleep(10000);
}
}
事件发送后,会等待事件执行完毕,因此他们是同步的。若想异步执行事件,可以把@Async加到监听方法上;
使用ApplicationContext发布事件,ApplicationContext实现了ApplicationEventPublisher接口;
使用ApplicationContextEvent 定义事件,ApplicationContextEvent 继承了ApplicationEvent类;
/**
* 事件类
*
* @param
*/
@ToString
class MyEvent2 extends ApplicationContextEvent {
private T data;
public MyEvent2(ApplicationContext source) {
super(source);
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
/**
* 监听器类
*/
@Slf4j
@Component
class MyEventListener2 implements ApplicationListener {
@Override
public void onApplicationEvent(MyEvent2 event) {
log.info("监听器MyEventListener2收到={}", event);
}
@EventListener
public void someMethod(MyEvent2 event) {
log.info("监听器MyEventListener2@EventListener收到={}", event);
}
}
/**
* 监听器类
*/
@Slf4j
@Component
class MyEventListenner1 {
@EventListener
public void someMethod(MyEvent2 event) {
log.info("监听器MyEventListenner1收到={}", event);
}
}
@RestController
@Slf4j
public class PublishEventDemo2 {
/**
* 使用ApplicationContext发布事件
*/
@Autowired
ApplicationContext applicationContext;
@GetMapping("/")
public void send() {
MyEvent2 myEvent2 = new MyEvent2(applicationContext);
myEvent2.setData("数据");
log.info("开始发送事件");
applicationContext.publishEvent(myEvent2);
log.info("结束发送事件");
}
}
事件发送后,会等待事件执行完毕,因此他们是同步的。若想异步执行事件,可以把@Async加到监听方法上;
核心为 ApplicationContext 对象 以及 实现 ApplicationContextAware 接口
@Component
public class SpringContextBean implements ApplicationContextAware {
private static ApplicationContext applicationContext = null;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
if(SpringContextBean.applicationContext == null){
SpringContextBean.applicationContext = applicationContext;
}
}
/** 获取applicationContext */
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
}
SpringContextBean.getApplicationContext().publishEvent(new DisburseSuccessEvent(this, disbursement));
发布的事件,Event,ApplicationEvent是一个抽象类继承了EventObject,EventObject是JDK中的类,所有的事件类都建议继承自EventObject。
public abstract class ApplicationEvent extends EventObject{}
事件监听器,ApplicationListener是一个接口,该接口继承了EventListener接口。EventListener接口是JDK中的接口,所有的事件监听器都建议继承EventListener接口。
@FunctionalInterface
public interface ApplicationListener extends EventListener {}
事件发布,ApplicationEventPublisher,ApplicationContext继承该接口,并在抽象实现类AbstractApplicationContext中做了实现。
applicationContext.publishEvent(new FileParseEvent(this, streamBean));
AbstractApplicationContext类中publishEvent方法实现:
Spring 中也对Java中事件机制做了很多的扩展和衍生,提供了很多方便的接口和类。
对事件的定义如下:
ApplicationEvevnt:继承 EventObject 类,自定义事件源应该继承该类
ApplicationEventListener:继承EventListener接口,自定义监听者可以通过实现该接口
ApplicationEventPublisher :封装了事件发布的方法,通知所有在 Spring 中注册的监听者进行处理
基于Spring提供的基类,可以进行自定义各类符合业务和流程的事件;自定义的监听者实现类,可以由 Spring 容器进行管理,只需要通过 ApplicationEventPublisher 进行发布进行,不用自己去实现监听者的注册、通知等等过程。
// 监听 UserRegisterEvent事件
@Component
@Slf4j
@Order(0)
public class RegisterListener implements ApplicationListener {
/**
* 使用接口实现的方式来监听事件
*
* @param event 用户注册事件
*/
@Override
public void onApplicationEvent(UserRegisterEvent event) {
User user = event.getUser();
// do something
log.info("ApplicationLister注册信息,用户名:" + user.getUsername() + ",密码:" + user.getPassword());
}
}
该接口是继承了 ApplicationListener、Ordered 的子类,可以监听所有的事件,通过对支持的事件源(supportsSourceType)和支持的事件(supportsEventType)进行校验来实现对指定事件的监听,通过定义 getOrder() 方法来指定该监听者处理事件的执行顺序;具体的处理代码也是在 onApplicationEvent(E event) 中实现。
@Component
@Slf4j
public class UserRegisterListener implements SmartApplicationListener {
/**
* Determine whether this listener actually supports the given event type.
*
* @param eventType the event type (never {@code null})
*/
@Override
public boolean supportsEventType(Class extends ApplicationEvent> eventType) {
// support UserRegisterEvent.class
return eventType == UserRegisterEvent.class;
}
/**
* Determine whether this listener actually supports the given source type.
* The default implementation always returns {@code true}.
*
* @param sourceType the source type, or {@code null} if no source
*/
@Override
public boolean supportsSourceType(Class> sourceType) {
// support UserServiceImpl which publish event
return sourceType == UserServiceImpl.class;
}
/**
* Determine this listener's order in a set of listeners for the same event.
*
The default implementation returns {@link #LOWEST_PRECEDENCE}.
*/
@Override
public int getOrder() {
// 0 is highest priority
return 0;
}
@Override
public String getListenerId() {
return null;
}
/**
* Handle an application event.
*
* @param event the event to respond to
*/
@Override
public void onApplicationEvent(ApplicationEvent event) {
// change event type into target event
UserRegisterEvent userRegisterEvent = (UserRegisterEvent) event;
// get user
User user = userRegisterEvent.getUser();
// do something
// print
log.info("注册监听器001:执行顺序:" + getOrder());
log.info("注册信息,用户名:" + user.getUsername() + ",密码:" + user.getPassword());
}
}
Spring 中的事件发布比较简单,可以使用顶层接口 ApplicationEventPublisher 进行发布,也可以使用 Spring 上下文 ApplicationContext 进行发布。
当一个类实现了这个接口(ApplicationContextAware)之后,这个类就可以方便获得ApplicationContext中的所有bean。换句话说,就是这个类可以直接获取spring配置文件中,所有有引用到的bean对象。