Spring Events--观察者模式在spring中的应用

Spring Events--观察者模式在spring中的应用

参考:

1. 观察者模式

  • 当对象间存在一对多关系时,则使用观察者模式(Observer Pattern)。比如,当一个对象被修改时,则会自动通知依赖它的对象。观察者模式属于行为型模式。

1.1 介绍

  • 意图:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。

  • 主要解决:一个对象状态改变给其他对象通知的问题,而且要考虑到易用和低耦合,保证高度的协作。

  • 何时使用:一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知,进行广播通知。

  • 关键代码:在抽象类里有一个 ArrayList 存放观察者们。

使用场景:

  • 一个抽象模型有两个方面,其中一个方面依赖于另一个方面。将这些方面封装在独立的对象中使它们可以各自独立地改变和复用。
  • 一个对象的改变将导致其他一个或多个对象也发生改变,而不知道具体有多少对象将发生改变,可以降低对象之间的耦合度。
  • 一个对象必须通知其他对象,而并不知道这些对象是谁。
  • 需要在系统中创建一个触发链,A对象的行为将影响B对象,B对象的行为将影响C对象……,可以使用观察者模式创建一种链式触发机制。
  • 注意事项: 1、JAVA 中已经有了对观察者模式的支持类。 2、避免循环引用。 3、如果顺序执行,某一观察者错误会导致系统卡壳,一般采用异步方式。

1.2 观察者模式简单实践

  • Observer
/**
 * @title 
 * @description 
 * @author xingzhou 
 * @updateTime 2022/4/5 7:59 上午 
 * @throws 
 */
interface Observer {
    /**
     *  当主题状态改变时,会将一个String类型字符传入该方法的参数,
     *  每个观察者都需要实现该方法
     */
    public void update(String info);
}
  • Subject

/**
 * @author xingzhou
 * @version 1.0.0
 * @ClassName Subject.java
 * @Description 主题接口
 * @createTime 2022年04月05日 07:42:00
 */
interface Subject {
    /**
     * 添加观察者
     * @param obj
     */
    void addObserver(Observer obj);

    /**
     * 移除观察者
     * @param obj
     */
    void deleteObserver(Observer obj);

    /**
     * 通知观察者
     */
    void notifyObserver();
}
  • StudentObserver
/**
 * @author xingzhou
 * @version 1.0.0
 * @ClassName StudentObserver.java
 * @Description TODO
 * @createTime 2022年04月05日 07:49:00
 */
public class StudentObserver implements Observer {

    /**
     *  保存一个Subject的引用,以后如果可以想取消订阅,有了这个引用会比较方便
     */
    private TeacherSubject t;
    /**
     * 学生的姓名,用来标识不同的学生对象
     */
    private String name;

    /**
     *  构造器用来注册观察者
     */
    public StudentObserver(String name, TeacherSubject t) {
        this.name = name;
        this.t = t;
        //每新建一个学生对象,默认添加到观察者的行列
        t.addObserver(this);
    }


    @Override
    public void update(String info) {
        System.out.println(name + "得到作业:" + info);

    }
}
  • TeacherSubject
/**
 * @author xingzhou
 * @version 1.0.0
 * @ClassName TeacherSubject.java
 * @Description TODO
 * @createTime 2022年04月05日 07:46:00
 */
public class TeacherSubject implements Subject {
    private List<Observer> observers=new ArrayList<Observer>();
    //记录状态的字符串
    private String info;

    @Override
    public void addObserver(Observer obj) {
        observers.add(obj);
    }

    @Override
    public void deleteObserver(Observer obj) {
        int i = observers.indexOf(obj);
        if(i>=0){
            observers.remove(obj);
        }
    }

    @Override
    public void notifyObserver() {
        for(int i=0;i<observers.size();i++){
            Observer o=(Observer)observers.get(i);
            o.update(info);
        }
    }
    //布置作业的方法,在方法最后,需要调用notifyObserver()方法,通知所有观察者更新状态
    public void setHomework(String info){
        this.info=info;
        System.out.println("今天的作业是"+info);
        this.notifyObserver();
    }

}
  • TestObservable
/**
 * @author xingzhou
 * @version 1.0.0
 * @ClassName TestObservable.java
 * @Description TODO
 * @createTime 2022年04月05日 07:52:00
 */
public class TestObservable {

    public static void main(String[] args) {

        TeacherSubject teacher=new TeacherSubject();
        StudentObserver zhangSan=new StudentObserver("张三", teacher);
        StudentObserver LiSi=new StudentObserver("李四", teacher);
        StudentObserver WangWu=new StudentObserver("王五", teacher);

        teacher.setHomework("第二页第六题");
        teacher.setHomework("第三页第七题");
        teacher.setHomework("第五页第八题");
    }
}
  • 输出结果

2. spring监听机制——观察者模式的应用

 spring中的事件驱动模型也叫作发布订阅模式,是观察者模式的一个典型的应用。

2.1 使用方法

spring监听模式需要三个组件:

    1. 事件,需要继承ApplicationEvent,即观察者模式中的"主题",可以看做一个普通的bean类,用于保存在事件监听器的业务逻辑中需要的一些字段;
    1. 事件监听器,需要实现ApplicationListener,即观察者模式中的"观察者",在主题发生变化时收到通知,并作出相应的更新,加泛型表示只监听某种类型的事件;
    1. 事件发布器,需要实现ApplicationEventPublisherAware,获取spring底层组件ApplicationEventPublisher,并调用其方法发布事件,即"通知"观察者。

其中,事件监听器和事件发布器需要在springIOC容器中注册。

2.2 简单实践

  • Event

/**
 * @author xingzhou
 * @version 1.0.0
 * @ClassName Event.java
 * @Description TODO
 * @createTime 2022年04月05日 11:00:00
 */
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Event {
    private Long id;
    private String name;
    private Integer age;
}
  • BusinessEvent
/**
 * @author xingzhou
 * @version 1.0.0
 * @ClassName BusinessEvent.java
 * @Description spring监听机制中的"事件"
 * @createTime 2022年04月05日 08:21:00
 */
public class BusinessEvent extends ApplicationEvent {

    /**
     * 该类型事件携带的信息
     */
    private Event eventData;

    public BusinessEvent( Object source) {
        super(source);
    }
    /**
     *
     * @param source 最初触发该事件的对象
     * @param eventData 该类型事件携带的信息
     */
    public BusinessEvent(Object source, Event eventData) {
        super(source);
        this.eventData = eventData;
    }

    public Event getEventData(){
        return eventData;
    }
}
  • BusinessPublisher
/**
 * @author xingzhou
 * @version 1.0.0
 * @ClassName BusinessPublisher.java
 * @Description spring事件监听机制中的"事件发布器"
 * @createTime 2022年04月05日 10:49:00
 */
@Component
public class BusinessPublisher implements ApplicationEventPublisherAware {

    /**
     * spring提供的事件发布组件
     */
    private ApplicationEventPublisher applicationEventPublisher;

    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        this.applicationEventPublisher = applicationEventPublisher;
    }

    /**
     * 发布事件
     */
    public void publishEvent(BusinessEvent businessEvent) {
        System.out.println("发布事件:" + businessEvent.getEventData().getId());
        this.applicationEventPublisher.publishEvent(businessEvent);
    }
}
  • BusinessListener
/**
 * @author xingzhou
 * @version 1.0.0
 * @ClassName BusinessListener.java
 * @Description TODO
 * @createTime 2022年04月05日 10:48:00
 */
@Component
public class BusinessListener implements ApplicationListener<BusinessEvent> {

    /**
     * 监听到事件后做的处理
     * @param event
     */
    @Override
    public void onApplicationEvent(BusinessEvent event) {
        System.out.println("监听到事件:" + event.getEventData().getId());
    }
}
  • AsyncConfig
/**
 * @author xingzhou
 * @version 1.0.0
 * @ClassName AsyncConfig.java
 * @Description TODO
 * @createTime 2022年04月04日 23:32:00
 */
@Configuration
@EnableAsync
@Slf4j
public class AsyncConfig implements AsyncConfigurer {

/**
     * 自定义异步线程池,若不重写会使用默认的线程池
     */
    @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);
            }
        }
    }
}
  • SyncListener
/**
 * @author xingzhou
 * @version 1.0.0
 * @ClassName SyncListener.java
 * @Description TODO
 * @createTime 2022年03月31日 15:23:00
 */
@Slf4j
@Component
@Order(1)
public class SyncListener {


    @Async
    @EventListener(BusinessEvent.class)
    public void createSuccess(BusinessEvent businessEvent) {
        Event eventData = businessEvent.getEventData();
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("f data" + eventData.getName());
        log.info("receive event data --> {}", eventData.getName());
    }

}

2.3 源码分析

在观察者模式中,主题发生改变时,会"通知"观察者作出相应的操作,实现方式是获取观察者列表,然后遍历、分别执行一遍其更新方法。那么,在spring事件监听中,事件发生变化时,是如何"通知"到观察者的呢?如上面的demo所述,我们是通过spring的组件ApplicationEventListener接口执行publishEvent方法发布事件的,而这个抽象方法在spring中只有一个实现,就是AbstractrApplicationContext,这是一个容器类。我们来跟进一下这个容器类对于发布事件的实现方法源码:


protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
        Assert.notNull(event, "Event must not be null");

        // 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 {       //获取事件广播器、然后广播事件
            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);
            }
        }
    }

粗体部分语句:首先获取事件广播器、然后广播事件。

所以问题分为两部分:如何获取事件广播器、怎样广播事件。

  1. 获取事件广播器

直接跟进上述语句——getApplicationEventMulticaster(),似乎找不到答案,因为这个方法是直接返回了AbstractApplicationContext类的属性。问题转化为:AbstractApplicationContext类中的事件广播器属性是什么时候被赋值的?这就要从容器创建说起了。springIOC容器创建有一个重要步骤——刷新容器refresh(),就是在AbstractApplicationContext中定义的,这个refresh()中包含了容器创建、初始化的诸多操作,其中两个步骤与事件监听有关,看一下源码

public void refresh() throws BeansException, IllegalStateException {
        synchronized (this.startupShutdownMonitor) {
            // Prepare this context for refreshing.
            prepareRefresh();

            // Tell the subclass to refresh the internal bean factory.
            ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

            // Prepare the bean factory for use in this context.
            prepareBeanFactory(beanFactory);

            try {
                // Allows post-processing of the bean factory in context subclasses.
                postProcessBeanFactory(beanFactory);

                // Invoke factory processors registered as beans in the context.
                invokeBeanFactoryPostProcessors(beanFactory);

                // Register bean processors that intercept bean creation.
                registerBeanPostProcessors(beanFactory);

                // Initialize message source for this context.
                initMessageSource();

                // Initialize event multicaster for this context.
                initApplicationEventMulticaster();

                // Initialize other special beans in specific context subclasses.
                onRefresh();

                // Check for listener beans and register them.
                registerListeners();

                // Instantiate all remaining (non-lazy-init) singletons.
                finishBeanFactoryInitialization(beanFactory);

                // Last step: publish corresponding event.
                finishRefresh();
            }

            catch (BeansException ex) {
                if (logger.isWarnEnabled()) {
                    logger.warn("Exception encountered during context initialization - " +
                            "cancelling refresh attempt: " + ex);
                }

                // Destroy already created singletons to avoid dangling resources.
                destroyBeans();

                // Reset 'active' flag.
                cancelRefresh(ex);

                // Propagate exception to caller.
                throw ex;
            }

            finally {
                // Reset common introspection caches in Spring's core, since we
                // might not ever need metadata for singleton beans anymore...
                resetCommonCaches();
            }
        }
    }

第一个步骤是initApplicationEventMulticaster,即初始化事件广播器,继续跟进源码会发现,是先从BeanFactory中获取,如果不存在,就新建一个。第二个步骤是registerListeners,即注册监听器,从容器中获取所有ApplicationEventListener类型的组件,添加进事件广播器。

  1. 广播事件

广播事件的方法是写在事件广播器的实现类——SimpleApplicationEventMulticater中的。

@Override
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
    ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));   //遍历监听器,分别执行invokeListener
    for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) {
        Executor executor = getTaskExecutor();
        if (executor != null) {
            executor.execute(() -> invokeListener(listener, event));
        }
        else {
            invokeListener(listener, event);
        }
    }
}

从源码中可以看出,SimpleApplicationEventMulticater从容器中获取所有的监听器列表,遍历列表,对每个监听器分别执行invokeListener方法,继续跟进invokeListener方法,它会调用一个doInvokeListener,在这个doInvokeListner中:

@SuppressWarnings({"unchecked", "rawtypes"})
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;
        }
    }
}

终于看到我们熟悉的:onApplicationEvent方法,这就是暴露在外层、供我们使用的事件监听方法;

也就是在这里,实现了观察者模式中的——"通知"观察者进行更新的操作。

posted @ 2022-04-05 11:23  行舟QAQ  阅读(481)  评论(0编辑  收藏  举报