使用Spring实现观察者模式


观察者模式定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新,其主要解决一个对象状态改变给其他关联对象通知的问题,保证易用和低耦合。更多观察者模式的资料:观察者模式

一个典型的应用场景是:当用户注册以后,需要给用户发送邮件,发送优惠券等操作,如下图所示。

使用观察者模式后:

UserService 在完成自身的用户注册逻辑之后,仅仅只需要发布一个 UserRegisterEvent 事件,而无需关注其它拓展逻辑。其它 Service 可以自己订阅 UserRegisterEvent 事件,实现自定义的拓展逻辑。Spring的事件机制主要由3个部分组成。

  • ApplicationEvent:通过继承它,实现自定义事件。另外,通过它的 source 属性可以获取事件源,timestamp 属性可以获得发生时间。
  • ApplicationEventPublisher:通过实现它,来发布变更事件。
  • ApplicationEventListener:通过实现它,来监听指定类型事件并响应动作。这里就以上面的用户注册为例,来看看代码示例。首先定义用户注册事件 UserRegisterEvent。
import org.springframework.context.ApplicationEvent;

/**
 * @author 方曦
 */
public class UserRegisterEvent extends ApplicationEvent {

    private String username;

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

    public UserRegisterEvent(Object source, String username) {
        super(source);
        this.username = username;
    }

    public String getUsername() {
        return username;
    }
}

然后定义用户注册服务类,实现 ApplicationEventPublisherAware 接口,从而将 ApplicationEventPublisher 注入进来。从下面代码可以看到,在执行完注册逻辑后,调用了 ApplicationEventPublisher的 publishEvent(ApplicationEvent event) 方法,发布了 UserRegisterEvent 事件。

import com.storyhasyou.boc.entity.User;
import com.storyhasyou.boc.event.UserRegisterEvent;
import com.storyhasyou.boc.mapper.UserMapper;
import com.storyhasyou.boc.service.UserService;
import com.storyhasyou.kratos.base.BaseServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;

/**
 * <p>
 * 用户表 服务实现类
 * </p>
 *
 * @author fangxi
 * @since 2020-12-14
 */
@Slf4j
@Service
public class UserServiceImpl extends BaseServiceImpl<UserMapper, User> implements UserService {

    @Autowired
    private ApplicationEventPublisher applicationEventPublisher;

    @Override
    public void register(String username) {
        log.info("[register][执行用户({}) 的注册逻辑]", username);
        applicationEventPublisher.publishEvent(new UserRegisterEvent(this, username));
    }
}

创建邮箱Service,实现 ApplicationListener 接口,通过 E 泛型设置感兴趣的事件,实现 onApplicationEvent(E event) 方法,针对监听的 UserRegisterEvent 事件,进行自定义处理。

import com.storyhasyou.boc.event.UserRegisterEvent;
import com.storyhasyou.boc.service.MailService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Service;

/**
 * @author 方曦
 */
@Slf4j
@Service
public class MailServiceImpl implements MailService, ApplicationListener<UserRegisterEvent> {


    @Override
    public void sendEmail(String username) {
        log.info("发送邮箱给:{}", username);
    }

    @Override
    public void onApplicationEvent(UserRegisterEvent event) {
        this.sendEmail(event.getUsername());
    }
}

创建优惠券Service,不同于上面的实现 ApplicationListener 接口方式,在方法上,添加 @EventListener 注解,并设置监听的事件为 UserRegisterEvent。这是另一种使用方式。

import com.storyhasyou.boc.event.UserRegisterEvent;
import com.storyhasyou.boc.service.CouponService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service;

/**
 * @author 方曦
 */
@Slf4j
@Service
public class CouponServiceImpl implements CouponService {

    @Override
    @EventListener
    public void sendCoupon(UserRegisterEvent event) {
        log.info("发送优惠券给:{}", event.getUsername());
    }
}

看到这里,细心的同学可能想到了发布订阅模式,其实观察者模式于发布订阅还是有区别的,简单来说,发布订阅模式属于广义上的观察者模式,在观察者模式的 Subject 和 Observer 的基础上,引入 Event Channel 这个中介,进一步解耦。图示如下,可以看出,观察者模式更加轻量,通常用于单机,而发布订阅模式相对而言更重一些,通常用于分布式环境下的消息通知场景。

事件对象,也可以不用继承ApplicationEvent

定义一个通用的Event

import lombok.Data;

/**
 * @author 方曦
 */
@Data
public class BaseEvent<E> {

    private E data;
    private Boolean success;

    public BaseEvent(E data) {
        this(data, true);
    }

    public BaseEvent(E data, Boolean success) {
        this.data = data;
        this.success = success;
    }
}
  • data:事件过程中的数据
  • success:是否触发

自定义一个事件,继承这个通用事件

import com.storyhasyou.kratos.base.BaseEvent;

/**
 * @author 方曦
 */
public class UserRegisterEvent extends BaseEvent<String> {

    public UserRegisterEvent(String data, Boolean success) {
        super(data, success);
    }
}

发布事件

/**
 * <p>
 * 用户表 服务实现类
 * </p>
 *
 * @author fangxi
 * @since 2020-12-14
 */
@Slf4j
@Service
public class UserServiceImpl extends BaseServiceImpl<UserMapper, User> implements UserService {

    @Autowired
    private ApplicationEventPublisher applicationEventPublisher;

    @Override
    public void register(String username) {
        log.info("[register][执行用户({}) 的注册逻辑]", username);
        applicationEventPublisher.publishEvent(new UserRegisterEvent(username, true));
    }
}

监听事件,只能用 @EventListener 注解

import com.storyhasyou.boc.event.UserRegisterEvent;
import com.storyhasyou.boc.service.MailService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service;

/**
 * @author 方曦
 */
@Slf4j
@Service
public class MailServiceImpl implements MailService {


    @Override
    public void sendEmail(String username) {
        log.info("发送邮箱给:{}", username);
    }

    @EventListener(condition = "#event.success")
    public void onApplicationEvent(UserRegisterEvent event) {
        this.sendEmail(event.getData());
    }
}
  • condition = "#event.success": 事件里面的success属性,如果是true则这行,false则不执行。
    这样效果和上面一样,只不过我们对事件进行了抽象。可以通过参数控制事件是否执行。

Author: Re:0
Reprint policy: All articles in this blog are used except for special statements CC BY 4.0 reprint policy. If reproduced, please indicate source Re:0 !
  TOC