设计模式——观察者模式

1,083 阅读5分钟

设计模式——观察者模式

1. 场景

  1. 用户登录成功后,需要发送邮件或推送消息;
  2. 订单状态变化时,需要异步执行推送消息等。

2. 什么是观察者模式

以上场景,可以总结为如下需求:

用户登录成功、订单状态改变等可以理解为一个事件,当触发了事件时,需要做多种互不相关的操作,如发送邮件、推送消息、调用某个方法逻辑。这里假设一个需求,当订单状态发生变化时,需要发送邮件和站内信提醒用户,并写入日志表中。

观察者模式需求.png

那我们可以通过观察者模式来解决这个问题

定义事件

/**
 * 事件
 * @author Tarzan写bug
 * @since 2022/08/02
 */
public class Event {

    private Object source;

    public Event(Object source) {
        this.source = source;
    }

    public Object getSource() {
        return source;
    }
}

定义触发器

/**
 * 触发器接口
 * @author Tarzan写bug
 * @since 2022/08/02
 */
public interface Trigger {

    /**
     * 添加执行者
     * @param executor
     */
    void addExecutor(Executor executor);

    /**
     * 事件触发
     * @param event
     */
    void touchOff(Event event);
}

订单状态改变触发器

/**
 * 订单状态改变触发器
 * @author Tarzan写bug
 * @since 2022/08/02
 */
public class OrderStatusChangeTrigger implements Trigger {

    private List<Executor> executorList = new ArrayList<>();

    @Override
    public void addExecutor(Executor executor) {
        executorList.add(executor);
    }

    @Override
    public void touchOff(Event event) {
        for (Executor executor : executorList) {
            executor.execute(event);
        }
    }
}

定义执行器

/**
 * 执行器接口
 * @author Tarzan写bug
 * @since 2022/08/02
 */
public interface Executor {

    /**
     * 执行方法
     * @param event
     */
    void execute(Event event);
}

邮件执行器

/**
 * 邮件执行器
 * @author Tarzan写bug
 * @since 2022/08/02
 */
public class EmailExecutor implements Executor {

    @Override
    public void execute(Event event) {
        System.out.println("邮件服务执行:" + event.getSource());
    }
}

日志执行器

/**
 * 日志执行器
 * @author Tarzan写bug
 * @since 2022/08/02
 */
public class LogExecutor implements Executor {

    @Override
    public void execute(Event event) {
        System.out.println("日志服务执行:" + event.getSource());
    }
}

站内信执行器

/**
 * 站内信执行器
 * @author Tarzan写bug
 * @since 2022/08/02
 */
public class StationLetterExecutor implements Executor {

    @Override
    public void execute(Event event) {
        System.out.println("站内信服务执行:" + event.getSource());
    }
}

主类

/**
 * @author Tarzan写bug
 * @since 2022/08/02
 */
public class ObserverMain {

    public static void main(String[] args) {
        // 创建触发器
        OrderStatusChangeTrigger trigger = new OrderStatusChangeTrigger();
        // 加入执行器
        trigger.addExecutor(new EmailExecutor());
        trigger.addExecutor(new LogExecutor());
        trigger.addExecutor(new StationLetterExecutor());
        // 发布订单状态变化事件
        trigger.touchOff(new Event("订单状态变化了"));
    }
}

类图:

Package order.png

总结:观察者模式主要是触发事件和处理事件进行解耦,当一个事件需要新的处理逻辑时,只需要实现执行器接口即可,符合开闭原则。而在Spring中的观察者模式除了解耦外,还能各个执行器异步执行。

3. Spring中的观察者模式

上面的需求场景用Spring中的观察者模式来实现:

订单状态改变事件:继承ApplicationEvent

public class OrderStatusChangeEvent extends ApplicationEvent {

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

邮件执行器:实现ApplicationListener接口,并将需要监听的事件类做为泛型加入;用@Component或其他注解加入到Spring容器中

@Component
public class EmailListener implements ApplicationListener<OrderStatusChangeEvent> {

    @Override
    public void onApplicationEvent(OrderStatusChangeEvent orderStatusChangeEvent) {
        System.out.println("邮件监听事件:" + orderStatusChangeEvent.getSource());
    }
}

日志订阅者

@Component
public class LogListener implements ApplicationListener<OrderStatusChangeEvent> {

    @Override
    public void onApplicationEvent(OrderStatusChangeEvent orderStatusChangeEvent) {
        System.out.println("日志监听事件:" + orderStatusChangeEvent.getSource());
    }
}

站内信订阅者

@Component
public class StationLetterListener implements ApplicationListener<OrderStatusChangeEvent> {

    @Override
    public void onApplicationEvent(OrderStatusChangeEvent orderStatusChangeEvent) {
        System.out.println("站内信监听事件:" + orderStatusChangeEvent.getSource());
    }
}

事件发布:实现ApplicationEventPublisherAware接口,获取ApplicationEventPublisher,用该类发布事件;并@Component加入到Spring容器中

@Component
public class OrderStatusChangePublisher implements ApplicationEventPublisherAware {

    private ApplicationEventPublisher publisher;

    public void publish() {
        publisher.publishEvent(new OrderStatusChangeEvent("订单状态变化"));
    }

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

4. Spring观察者模式原理

  1. 可以从发布者代码入手,即ApplicationEventPublisher.publishEvent()开始,会调用到实现类AbstractApplicationContext.publishEvent()

    protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
            Assert.notNull(event, "Event must not be null");
            Object applicationEvent;
            if (event instanceof ApplicationEvent) {
                applicationEvent = (ApplicationEvent)event;
            } else {
                applicationEvent = new PayloadApplicationEvent(this, event);
                if (eventType == null) {
                    eventType = ((PayloadApplicationEvent)applicationEvent).getResolvableType();
                }
            }
    
            if (this.earlyApplicationEvents != null) {
                this.earlyApplicationEvents.add(applicationEvent);
            } else {
                this.getApplicationEventMulticaster().multicastEvent((ApplicationEvent)applicationEvent, eventType);
            }
    
            if (this.parent != null) {
                if (this.parent instanceof AbstractApplicationContext) {
                    ((AbstractApplicationContext)this.parent).publishEvent(event, eventType);
                } else {
                    this.parent.publishEvent(event);
                }
            }
    
        }
    
  2. 重点在this.getApplicationEventMulticaster().multicastEvent((ApplicationEvent)applicationEvent, eventType);中,该方法会调用实现类SimpleApplicationEventMulticaster.multicastEvent()

    public void multicastEvent(ApplicationEvent event, @Nullable ResolvableType eventType) {
            ResolvableType type = eventType != null ? eventType : this.resolveDefaultEventType(event);
            Executor executor = this.getTaskExecutor();
            Iterator var5 = this.getApplicationListeners(event, type).iterator();
    
            while(var5.hasNext()) {
                ApplicationListener<?> listener = (ApplicationListener)var5.next();
                if (executor != null) {
                    executor.execute(() -> {
                        this.invokeListener(listener, event);
                    });
                } else {
                    this.invokeListener(listener, event);
                }
            }
    
        }
    

    方法的作用:获取对应事件的执行者(事件监听者),然后在线程池中执行每一个观察者的逻辑

  3. 分析getApplicationListeners()如何获取事件监听者

    protected Collection<ApplicationListener<?>> getApplicationListeners(ApplicationEvent event, ResolvableType eventType) {
            Object source = event.getSource();
            Class<?> sourceType = source != null ? source.getClass() : null;
            AbstractApplicationEventMulticaster.ListenerCacheKey cacheKey = new AbstractApplicationEventMulticaster.ListenerCacheKey(eventType, sourceType);
            AbstractApplicationEventMulticaster.ListenerRetriever retriever = (AbstractApplicationEventMulticaster.ListenerRetriever)this.retrieverCache.get(cacheKey);
            if (retriever != null) {
                return retriever.getApplicationListeners();
            } else if (this.beanClassLoader == null || ClassUtils.isCacheSafe(event.getClass(), this.beanClassLoader) && (sourceType == null || ClassUtils.isCacheSafe(sourceType, this.beanClassLoader))) {
                synchronized(this.retrievalMutex) {
                    retriever = (AbstractApplicationEventMulticaster.ListenerRetriever)this.retrieverCache.get(cacheKey);
                    if (retriever != null) {
                        return retriever.getApplicationListeners();
                    } else {
                        retriever = new AbstractApplicationEventMulticaster.ListenerRetriever(true);
                        Collection<ApplicationListener<?>> listeners = this.retrieveApplicationListeners(eventType, sourceType, retriever);
                        this.retrieverCache.put(cacheKey, retriever);
                        return listeners;
                    }
                }
            } else {
                return this.retrieveApplicationListeners(eventType, sourceType, (AbstractApplicationEventMulticaster.ListenerRetriever)null);
            }
        }
    
  4. retrieveApplicationListeners()

    private Collection<ApplicationListener<?>> retrieveApplicationListeners(ResolvableType eventType, @Nullable Class<?> sourceType, @Nullable AbstractApplicationEventMulticaster.ListenerRetriever retriever) {
            List<ApplicationListener<?>> allListeners = new ArrayList();
            LinkedHashSet listeners;
            LinkedHashSet listenerBeans;
            synchronized(this.retrievalMutex) {
                listeners = new LinkedHashSet(this.defaultRetriever.applicationListeners);
                listenerBeans = new LinkedHashSet(this.defaultRetriever.applicationListenerBeans);
            }
    
            Iterator var7 = listeners.iterator();
    
            while(var7.hasNext()) {
                ApplicationListener<?> listener = (ApplicationListener)var7.next();
                if (this.supportsEvent(listener, eventType, sourceType)) {
                    if (retriever != null) {
                        retriever.applicationListeners.add(listener);
                    }
    
                    allListeners.add(listener);
                }
            }
    
            if (!listenerBeans.isEmpty()) {
                ConfigurableBeanFactory beanFactory = this.getBeanFactory();
                Iterator var15 = listenerBeans.iterator();
    
                while(var15.hasNext()) {
                    String listenerBeanName = (String)var15.next();
    
                    try {
                        if (this.supportsEvent(beanFactory, listenerBeanName, eventType)) {
                            ApplicationListener<?> listener = (ApplicationListener)beanFactory.getBean(listenerBeanName, ApplicationListener.class);
                            if (!allListeners.contains(listener) && this.supportsEvent(listener, eventType, sourceType)) {
                                if (retriever != null) {
                                    if (beanFactory.isSingleton(listenerBeanName)) {
                                        retriever.applicationListeners.add(listener);
                                    } else {
                                        retriever.applicationListenerBeans.add(listenerBeanName);
                                    }
                                }
    
                                allListeners.add(listener);
                            }
                        } else {
                            Object listener = beanFactory.getSingleton(listenerBeanName);
                            if (retriever != null) {
                                retriever.applicationListeners.remove(listener);
                            }
    
                            allListeners.remove(listener);
                        }
                    } catch (NoSuchBeanDefinitionException var11) {
                    }
                }
            }
    
            AnnotationAwareOrderComparator.sort(allListeners);
            if (retriever != null && retriever.applicationListenerBeans.isEmpty()) {
                retriever.applicationListeners.clear();
                retriever.applicationListeners.addAll(allListeners);
            }
    
            return allListeners;
        }
    
    private boolean supportsEvent(ConfigurableBeanFactory beanFactory, String listenerBeanName, ResolvableType eventType) {
            Class<?> listenerType = beanFactory.getType(listenerBeanName);
            if (listenerType != null && !GenericApplicationListener.class.isAssignableFrom(listenerType) && !SmartApplicationListener.class.isAssignableFrom(listenerType)) {
                if (!this.supportsEvent(listenerType, eventType)) {
                    return false;
                } else {
                    try {
                        BeanDefinition bd = beanFactory.getMergedBeanDefinition(listenerBeanName);
                        ResolvableType genericEventType = bd.getResolvableType().as(ApplicationListener.class).getGeneric(new int[0]);
                        return genericEventType == ResolvableType.NONE || genericEventType.isAssignableFrom(eventType);
                    } catch (NoSuchBeanDefinitionException var7) {
                        return true;
                    }
                }
            } else {
                return true;
            }
        }
    

    方法作用:通过Bean容器中获取符合事件类的Bean

5. Spring Security中的观察者模式应用

由于最近在使用Spring Security,这个框架中也有使用到观察者模式

在资源服务器校验token过滤器中,OAuth2AuthenticationProcessingFilter

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
        boolean debug = logger.isDebugEnabled();
        HttpServletRequest request = (HttpServletRequest)req;
        HttpServletResponse response = (HttpServletResponse)res;

        try {
            Authentication authentication = this.tokenExtractor.extract(request);
            if (authentication == null) {
                if (this.stateless && this.isAuthenticated()) {
                    if (debug) {
                        logger.debug("Clearing security context.");
                    }

                    SecurityContextHolder.clearContext();
                }

                if (debug) {
                    logger.debug("No token in request, will continue chain.");
                }
            } else {
                request.setAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_VALUE, authentication.getPrincipal());
                if (authentication instanceof AbstractAuthenticationToken) {
                    AbstractAuthenticationToken needsDetails = (AbstractAuthenticationToken)authentication;
                    needsDetails.setDetails(this.authenticationDetailsSource.buildDetails(request));
                }

                Authentication authResult = this.authenticationManager.authenticate(authentication);
                if (debug) {
                    logger.debug("Authentication success: " + authResult);
                }

                this.eventPublisher.publishAuthenticationSuccess(authResult);
                SecurityContextHolder.getContext().setAuthentication(authResult);
            }
        } catch (OAuth2Exception var9) {
            SecurityContextHolder.clearContext();
            if (debug) {
                logger.debug("Authentication request failed: " + var9);
            }

            this.eventPublisher.publishAuthenticationFailure(new BadCredentialsException(var9.getMessage(), var9), new PreAuthenticatedAuthenticationToken("access-token", "N/A"));
            this.authenticationEntryPoint.commence(request, response, new InsufficientAuthenticationException(var9.getMessage(), var9));
            return;
        }

        chain.doFilter(request, response);
    }

在校验成功后会调用this.eventPublisher.publishAuthenticationSuccess(),在校验失败后调用this.eventPublisher.publishAuthenticationFailure()

在接口类AuthenticationEventPublisher中有多个实现,拿实现类DefaultAuthenticationEventPublisher为例,典型地应用了Spring的观察者模式的实现,实现了ApplicationEventPublisherAware,在publishAuthenticationSuccess()中调用this.applicationEventPublisher.publishEvent

public void publishAuthenticationSuccess(Authentication authentication) {
        if (this.applicationEventPublisher != null) {
            this.applicationEventPublisher.publishEvent(new AuthenticationSuccessEvent(authentication));
        }

    }

6. 委派模式+观察者模式

回到假设的场景代码,可以发现执行器都是实现相同的一个接口,如果一个执行器没有实现该接口是不是不能对事件进行处理了?即执行器处理的方法名不一样时,这时应该怎么办呢?这时可以通过委派模式,及Java8中Consumer传参类型实现

事件

/**
 * @author Tarzan写bug
 * @since 2022/08/02
 */
public class Event {

    private Object source;

    public Event(Object source) {
        this.source = source;
    }

    public Object getSource() {
        return source;
    }
}

邮件执行器

/**
 * @author Tarzan写bug
 * @since 2022/08/02
 */
public class EmailExecutor {

    public void emailExecute(Object event) {
        System.out.println("邮件服务执行:" + ((Event)event).getSource());
    }
}

日志执行器

/**
 * @author Tarzan写bug
 * @since 2022/08/02
 */
public class LogExecutor {

    public void logExecute(Object event) {
        System.out.println("日志服务执行:" + ((Event)event).getSource());
    }
}

站内信执行器

/**
 * @author Tarzan写bug
 * @since 2022/08/02
 */
public class StationLetterExecutor {

    public void stationLetterExecute(Object event) {
        System.out.println("站内信服务执行:" + ((Event)event).getSource());
    }
}

触发器接口

/**
 * @author Tarzan写bug
 * @since 2022/08/02
 */
public interface Trigger {

    /**
     * 添加执行者
     * @param executor
     */
    void addExecutor(EventHandler handler);

    /**
     * 事件触发
     * @param event
     */
    void touchOff(Event event);
}

订单状态改变触发器

/**
 * @author Tarzan写bug
 * @since 2022/08/02
 */
public class OrderStatusChangeTrigger implements Trigger {

    private List<EventHandler> eventHandlerList = new ArrayList<>();

    @Override
    public void addExecutor(EventHandler handler) {
        eventHandlerList.add(handler);
    }

    @Override
    public void touchOff(Event event) {
        for (EventHandler handler : eventHandlerList) {
            handler.doHandler(event);
        }
    }
}

主类

/**
 * @author Tarzan写bug
 * @since 2022/08/02
 */
public class ObserverMain {

    public static void main(String[] args) {
        // 创建触发器
        OrderStatusChangeTrigger trigger = new OrderStatusChangeTrigger();
        // 执行器
        EmailExecutor emailExecutor = new EmailExecutor();
        LogExecutor logExecutor = new LogExecutor();
        StationLetterExecutor stationLetterExecutor = new StationLetterExecutor();
        // 加入执行器
        trigger.addExecutor(new EventHandler(emailExecutor::emailExecute));
        trigger.addExecutor(new EventHandler(logExecutor::logExecute));
        trigger.addExecutor(new EventHandler(stationLetterExecutor::stationLetterExecute));
        // 发布订单状态变化事件
        trigger.touchOff(new Event("订单状态变化了"));
    }
}

可以看到每个执行器的处理方法名不一样时,通过EventHandler这个委派类来处理不同执行器的不同方法名。


谢谢阅读,就分享到这,未完待续...

欢迎同频共振的那一部分人

作者公众号:Tarzan写bug