23种设计模式中的观察者模式,主要有观察者和被观察者两个角色,是一种对象间一对多
依赖关系的实现,在软件设计中也被广泛使用。
Spring的事件机制也是通过这个模式驱动的,事件发布者是被观察者,事件中的监听者则是观察者,以 发布-订阅
模式实现。
在跨进程之间,通常会采用 MQ(消息队列)来实现消息的发布和订阅,从而进行通信;在同一进程内,很多时候也可以使用事件驱动机制来进行逻辑和功能上的解耦,常见的比如注册、订单等。
public void register(){
// 保存用户信息
doSaveUserIno();
// 发送邮件通知
doEmail();
}
用户注册流程有验证用户信息、保存用户信息、邮件提醒注册成功,如果想要在注册的时候添加别的流程,例如将用户信息存储到其他地方,就需要去改动这边的逻辑实现;其他地方想要复用,必然要重新封装到新的类中,虽然有些情况下改动量较少,但是对代码的复用性和可读性、扩展性都不佳。
在这种情况下,很多人都会想到:将功能模块化,提出公共的方法,使用的时候去调用,这是一种常见的优化思路。在此基础上,通过事件发布-订阅机制,可以大大增加该场景下代码的扩展性和复用性。
事件机制主要有三部分:事件源、事件对象、监听器。
事件源:事件发生的起源,通常是处理业务流程中
事件对象:事件实体,也记录了事件源
监听器:监听事件对象,对事件对象进行处理
Java中定义了事件机制的两个顶层类:EventObject
,事件的顶层父类(实现了Serializable
接口);
EventListener
,定义了监听器的顶层接口
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 + "]";
}
}
/**
* A tagging interface that all event listener interfaces must extend.
* @since JDK1.1
*/
public interface EventListener {
}
Spring 中也对Java中事件机制做了很多的扩展和衍生,提供了很多方便的接口和类。
对事件的定义如下:
ApplicationEvevnt
:继承 EventObject
类,自定义事件源应该继承该类
ApplicationEventListener
:继承EventListener
接口,自定义监听者可以通过实现该接口
ApplicationEventPublisher
:封装了事件发布的方法,通知所有在 Spring 中注册的监听者进行处理
基于Spring提供的基类,可以进行自定义各类符合业务和流程的事件;自定义的监听者实现类,可以由 Spring 容器进行管理,只需要通过 ApplicationEventPublisher
进行发布进行,不用自己去实现监听者的注册、通知等等过程。
public abstract class ApplicationEvent extends EventObject {
/** use serialVersionUID from Spring 1.2 for interoperability. */
private static final long serialVersionUID = 7099057708183571937L;
/** System time when the event happened. */
private final long timestamp;
/**
* Create a new {@code ApplicationEvent}.
* @param source the object on which the event initially occurred or with
* which the event is associated (never {@code null})
*/
public ApplicationEvent(Object source) {
super(source);
this.timestamp = System.currentTimeMillis();
}
/**
* Return the system time in milliseconds when the event occurred.
*/
public final long getTimestamp() {
return this.timestamp;
}
}
@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> 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 <T> ApplicationListener<PayloadApplicationEvent<T>> forPayload(Consumer<T> consumer) {
return event -> consumer.accept(event.getPayload());
}
}
@FunctionalInterface
public interface ApplicationEventPublisher {
default void publishEvent(ApplicationEvent event) {
publishEvent((Object) event);
}
void publishEvent(Object event);
}
继承 ApplicationEvent
可以在Event中自定义类属性
原生的 ApplicationEvent
事件中,有属性 timestamp
时间戳,用来标识事件发生的时间,我们可以自己定义事件中的属性,将一些消息传递给监听者处理的时候使用。
下面案例中,我定义了一个 User
类,用来记录用户注册时的信息:用户名、密码等等
public class UserRegisterEvent extends ApplicationEvent {
/**
* serial Version UID
*/
private static final long serialVersionUID = -5058858385060733297L;
/**
* Custom entity
*/
private final User user;
/**
* Create a new {@code ApplicationEvent}.
*
* @param source the object on which the event initially occurred or with
* which the event is associated (never {@code null})
*/
public UserRegisterEvent(Object source, User user) {
super(source);
this.user = user;
}
public User getUser() {
return user;
}
}
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class User implements Serializable {
private static final long serialVersionUID = -8967972309006473679L;
/**
* 用户名
*/
private String username;
/**
* 密码
*/
private String password;
// 等等注册信息
}
定义监听者,对指定的事件进行处理,只要在监听者内容的处理代码不同,就可以对一个事件的多个业务进行处理。
ApplicationListener
接口通过实现 ApplicationListener
接口来实现一个事件监听者,ApplicationListener
接口可以通过泛型事件的传入,来实现对指定事件的监听;通过重写 onApplicationEvent(E event)
方法来实现对事件具体的业务处理
// 监听 UserRegisterEvent事件
@Component
@Slf4j
@Order(0)
public class RegisterListener implements ApplicationListener<UserRegisterEvent> {
/**
* 使用接口实现的方式来监听事件
*
* @param event 用户注册事件
*/
@Override
public void onApplicationEvent(UserRegisterEvent event) {
User user = event.getUser();
// do something
log.info("ApplicationLister注册信息,用户名:" + user.getUsername() + ",密码:" + user.getPassword());
}
}
@EventListener
注解使用该注解,可以将类中的某个方法标记为监听者,参数为需要监听的事件
@Component
@Slf4j
public class AnnotationRegisterListener {
/**
* 使用注解{@linkplain EventListener}的方式来监听用户注册事件 UserRegisterEvent
* {@linkplain Order}来标记同步时的执行顺序
*
* @param userRegisterEvent 用户注册事件
*/
@EventListener
@Order(value = 3)
public void register(UserRegisterEvent userRegisterEvent) {
// get register user
User user = userRegisterEvent.getUser();
// do something
log.info("@EventListener注册信息,用户名:" + user.getUsername() + ",密码:" + user.getPassword());
}
}
SmartApplicationListener
接口该接口是继承了 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
进行发布。
@Resource
private ApplicationContext applicationContext;
@Resource
private ApplicationEventPublisher publisher;
public void register(User user) {
if (Objects.isNull(user)) {
log.error("User is null.");
return;
}
if (StringUtils.isEmpty(user.getUsername()) || StringUtils.isEmpty(user.getPassword())) {
log.error("User name and password is null.");
return;
}
// do something
// 使用 Spring 上下文 ApplicationContext 发布事件
applicationContext.publishEvent(new UserRegisterEvent(this, user));
// 使用 ApplicationEventPublisher 发布事件
publisher.publishEvent(new UserRegisterEvent(this, user));
}