说到Spring的事件通知也许大家都比较陌生,但说到设计模式中的观察者模式大家肯定比较熟悉,它是GoF23设计模式之一,它的概念我就不再此处赘述了引用百度百科原话:一个目标物件管理所有相依于它的观察者物件,并且在它本身的状态改变时主动发出通知。这通常透过呼叫各观察者所提供的方法来实现。此种模式通常被用来实现事件处理系统。出处:观察者模式_百度百科观察者模式(有时又被称为模型(Model)-视图(View)模式、源-收听者(Listener)模式或从属者模式)是软件设计模式的一种。在此种模式中,一个目标物件管理所有相依于它的观察者物件,并且在它本身的状态改变时主动发出通知。这通常透过呼叫各观察者所提供的方法来实现。此种模式通常被用来实现事件处理系统。https://baike.baidu.com/item/%E8%A7%82%E5%AF%9F%E8%80%85%E6%A8%A1%E5%BC%8F/5881786?fr=aladdin。Spring的事件通知其实就是观察者模式的一种体现。观察者模式三大核心:观察者、被观察主题、订阅者。
观察者:在spring中IOC容器其实就是事件广播器可以理解为就是观察者【即ApplicationContext】。
事件源:就是需要被观察的主题。
监听器:其实就是订阅者。
也许这么说还是比较迷糊,上图(图画的比较粗糙):
啰嗦了这么多下面介绍下Spring事件监听器的使用,Sping内置的事件监听器是ApplicationListener,Spring源码片段:
@FunctionalInterface
public interface ApplicationListener extends EventListener {
/**
* Handle an application event.
* @param event the event to respond to
*/
void onApplicationEvent(E event);
/**
* Create a new {@code ApplicationListener} for the given payload consumer.
* @param consumer the event payload consumer
* @param the type of the event payload
* @return a corresponding {@code ApplicationListener} instance
* @since 5.3
* @see PayloadApplicationEvent
*/
static ApplicationListener> forPayload(Consumer consumer) {
return event -> consumer.accept(event.getPayload());
}
}
我们主要看方法一onApplicationEvent:字面意思处理要监听的事件;大概看了下里面有一个泛型,大概明白他的用意了,这个泛型代表我们想监听的事件。我们如果要实现事件监听实现ApplicationListener接口即可。
Spring也支持注解形式的 EventListener Spring源代码片段:
/**
* @author Stephane Nicoll
* @author Sam Brannen
* @since 4.2
* @see EventListenerMethodProcessor
* @see org.springframework.transaction.event.TransactionalEventListener
*/
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface EventListener {
/**
* Alias for {@link #classes}.
*/
@AliasFor("classes")
Class>[] value() default {};
/**
* The event classes that this listener handles.
* If this attribute is specified with a single value, the
* annotated method may optionally accept a single parameter.
* However, if this attribute is specified with multiple values,
* the annotated method must not declare any parameters.
*/
@AliasFor("value")
Class>[] classes() default {};
/**
* Spring Expression Language (SpEL) expression used for making the event
* handling conditional.
*
The event will be handled if the expression evaluates to boolean
* {@code true} or one of the following strings: {@code "true"}, {@code "on"},
* {@code "yes"}, or {@code "1"}.
*
The default expression is {@code ""}, meaning the event is always handled.
*
The SpEL expression will be evaluated against a dedicated context that
* provides the following metadata:
*
* - {@code #root.event} or {@code event} for references to the
* {@link ApplicationEvent}
* - {@code #root.args} or {@code args} for references to the method
* arguments array
* - Method arguments can be accessed by index. For example, the first
* argument can be accessed via {@code #root.args[0]}, {@code args[0]},
* {@code #a0}, or {@code #p0}.
* - Method arguments can be accessed by name (with a preceding hash tag)
* if parameter names are available in the compiled byte code.
*
*/
String condition() default "";
/**
* An optional identifier for the listener, defaulting to the fully-qualified
* signature of the declaring method (e.g. "mypackage.MyClass.myMethod()").
* @since 5.3.5
* @see SmartApplicationListener#getListenerId()
* @see ApplicationEventMulticaster#removeApplicationListeners(Predicate)
*/
String id() default "";
}
从类注释中了解到,大概看了下它是在Spring4.2引入的注解(小于4.2版本的的小伙伴不用找啦),注解具体的处理逻辑在这里EventListenerMethodProcessor,注解可以用于方法上,参数可以是classs数组,可以写多个事件,等等;本节的主要目的是了解加使用这里就不再赘述了。
为了快速上手我们来用ContextRefreshedEvent(它代表Spring容器刷新完毕)来编写一个事件监听器:代码如下:
@Component
public class ContextRefreshedEventApplicationListener implements ApplicationListener{
/**
* @Title: onApplicationEvent
* @Description: 这是一个事件监听
* @param: event 要监听的事件
* @throws
*/
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
System.out.println("ContextRefreshedEventApplicationListener监听到ContextRefreshedEvent刷新事件!");
}
}
写一个启动类:
package com.example.demo.listener;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class ContextRefreshedEventListenerTest {
public static void main(String[] args) {
System.out.println("初始化容器");
AnnotationConfigApplicationContext ctx=new AnnotationConfigApplicationContext("com.example.demo");
System.out.println("初始化容器完成");
ctx.close();
System.out.println("IOC容器被关闭了");
}
}
最终输出结果
ContextRefreshedEventApplicationListener监听到ContextRefreshedEvent刷新事件!
初始化容器完成
18:32:54.135 [main] DEBUG org.springframework.context.annotation.AnnotationConfigApplicationContext - Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@12c8a2c0, started on Sat Aug 27 18:32:53 CST 2022
IOC容器被关闭了
编写一个注解形式的事件监听
package com.example.demo.listener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
@Component
public class AnnotationContextRefreshedEventApplicationListener{
/**
* @Title: onApplicationEvent
* @Description: 这是一个事件监听
* @param: event 要监听的事件
* @throws
*/
@EventListener
public void onApplicationEvent(ContextRefreshedEvent event) {
System.out.println("ContextRefreshedEventApplicationListener注解形式监听到ContextRefreshedEvent刷新事件!");
}
}
还是调用上方的ContextRefreshedEventListenerTest执行结果如下:
ContextRefreshedEventApplicationListener监听到ContextRefreshedEvent刷新事件!
ContextRefreshedEventApplicationListener注解形式监听到ContextRefreshedEvent刷新事件!
初始化容器完成
19:07:15.491 [main] DEBUG org.springframework.context.annotation.AnnotationConfigApplicationContext - Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@12c8a2c0, started on Sat Aug 27 19:07:14 CST 2022
IOC容器被关闭了
在很多网站用户注册完成后会有短信通知或者邮件通知用户等事件发布,如果用硬编码的形式大家肯定会在注册完成后写如下方法,这样一直堆积的后果就是(我和上帝的秘密+祖传代码勿动)。
是不是觉得很简单?对只是继承下就完了。
package com.example.demo.listener;
import org.springframework.context.ApplicationEvent;
/**
* @ClassName: RegisterEvent
* @Description:注册成果的事件
* @date: 2022年8月27日 下午7:17:48
*
*/
public class RegisterEvent extends ApplicationEvent {
public RegisterEvent(Object source) {
super(source);
}
}
package com.example.demo.listener;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;
@Component
public class ShortMsgListener implements ApplicationListener {
@Override
public void onApplicationEvent(RegisterEvent event) {
System.out.println("监听到用户注册成功,发送短信");
}
}
package com.example.demo.listener;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
@Component
public class EmailListener{
@EventListener
public void onApplicationEvent(RegisterEvent event) {
System.out.println("监听到用户注册成功,发送邮件");
}
}
发现这些做完了但是我怎样发布这个事件呢?
package com.example.demo.service;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.stereotype.Service;
import com.example.demo.listener.RegisterEvent;
@Service
public class RegisterService implements ApplicationEventPublisherAware {
ApplicationEventPublisher publisher;
public void register(String id) {
//注册逻辑
System.out.println("用户id:"+id+"注册成功!");
//发布事件
publisher.publishEvent(new RegisterEvent(id));
}
//这里采用了回调注入的方式,也可以用set方式直接传入
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
this.publisher=publisher;
}
}
执行测试类
package com.example.demo.listener;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import com.example.demo.service.RegisterService;
public class RegisterEventListenerTest {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx=new AnnotationConfigApplicationContext("com.example.demo");
RegisterService registerService = ctx.getBean(RegisterService.class);
registerService.register("00001");
}
}
执行结果,发现执行成功了,但是并没有解耦啊,上面只是简单列举了下,可以使用aop切面在方法中编写发布事件哦。相比堆积方法是不是清晰了很多了呢?
用户id:00001注册成功!
监听到用户id:00001注册成功,发送短信
监听到用户id:00001注册成功,发送邮件
代码不是炫技,以上列举的只是简单的场景实现,实际场景相信会有比上述场景较复杂的业务还有很多,引入特性和设计模式的初衷不是用来表现的很高级,最终的目的还是优雅的实现业务,保证项目的稳定运行才是王道。最后也希望大家可以看完这个文章对自己有真的帮助。