后端思维篇:如何抽一个观察者模板

基于角色的提示设计:让AI扮演专家程序员 最简单的角色提示仅包含身份定义,如"你是一位经验丰富的Python开发者"。这种基础提示比没有角色提示要好,但效果有限。示例你是一位资深Python开发者。请编写一个高效的文本处理函数,用于从大型日志文件中提取特定模式的数据。优点:简单直接,比无角色提示好局限:缺乏具体专业背景和思维框架问题:过于冗长的角色定义可能导致关键要素被淹没,AI无法抓住核心专业特征。案例:一位开发者创建了包含500字的详细角色描述,但AI主要关注了前几点,忽略了后面的关键专业特征。规避策略。 阅读详情

前言

今天跟大家聊聊什么是观察者模式,如何应用到工作实践中,以及如何抽取一个观察者模板。

1. 观察者模式定义

观察者模式,也可以称之为发布订阅模式,它在GoF 的《设计模式》中,是这么定义的:

Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically。

翻译过来就是:观察者模式定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被完成业务的更新。

观察者模式属于行为模式,一个对象(被观察者)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知,进行广播通知。它的主要成员就是观察者和被观察者。

被观察者(Observerable):目标对象,状态发生变化时,将通知所有的观察者。

观察者(observer):接受被观察者的状态变化通知,执行预先定义的业务。

2. 观察者模式的应用场景

哪些场景我们可以考虑使用观察者模式呢?

我们日常生活中,其实就有观察者模式类似的例子。比如,我们订阅了报社一年的报纸。每天报社印刷好报纸,就送到我们手中。我们就是观察者,报社就是被观察者。

而日常开发中,观察者模式的使用场景主要表现在:完成一件事情后,通知处理某个逻辑。如,登陆成功发个IM消息,支付成功发个邮件消息或者发个抽奖消息,用户评论成功给他发个积分等等。

举个详细点的例子吧,登陆注册应该是最常见的业务场景了,我们就拿注册来说事,大家经常会遇到类似的场景,就是用户注册成功后,我们给用户发一条IM消息,又或者发个邮件等等,因此经常有如下的代码:

void register(User user){
  insertRegisterUser(user);
  sendIMMessage();
  sendEmail();
}

这块代码会有什么问题呢?如果产品又加需求:现在注册成功的用户,再给用户发一条短信通知。于是你又得改register方法的代码了。。。这是不是违反了开闭原则啦。

void register(User user){
  insertRegisterUser(user);
  sendIMMessage();
  sendMobileMessage();
  sendEmail();
}

并且,如果调发短信的接口失败了,是不是又影响到用户注册了?!这时候,是不是得加个异步方法,异步发通知消息才好??其实这种场景,我们可以使用异步非阻塞的观察者模式优化的。

3. 如何实现一个简单的观察者模式

我们先来看下,简单的观察者模式如何实现。可以这么定义

  • 一个主题接口Subject(声明添加、删除、通知观察者方法)。
  • 一个Observer观察者接口。
  • 一个创建主题的类ObserverableImpl​(即被观察者),实现了Subject接口。
  • 各个观察者的差异化实现。

为了通俗易懂,可以这样理解观察者模式:就是被观察者(ObserverableImpl)做了一件事情,或者说发布了一个主题(Subject),然后这件事情通知到各个相关的不同的人(不同的观察者,Observer的差异化实现者)。

一个主题接口。

public interface Subject {

    /**
     * 添加观察者
     * @param observer
     */
    void addServer(Observer observer);

    /**
     * 移除观察者
     * @param observer
     */
    void removeServer(Observer observer);

    /**
     * 通知观察者
     * @param msg
     */
    void notifyAllObservers(String msg);

}

一个Observer接口。

/**
 * 观察者
 *
 */
public interface Observer {
    /**
     * 更新消息
     * @param msg
     */
    void update(String msg);
}

一个创建主题的类ObserverableImpl(即被观察者),同时有观察者列表的属性(其实就是说观察者要事先注册到被观察者)。

public class ObserverableImpl implements Subject {

    /**
     * 存储被观察者
     */
    private List<Observer> observers = new ArrayList<Observer>();

    @Override
    public void addServer(Observer observer) {
        observers.add(observer);
    }

    @Override
    public void removeServer(Observer observer) {
        observers.remove(observer);
    }

    @Override
    public void notifyAllObservers(String msg) {
        for (Observer observer : observers) {
            observer.update(msg);
        }
    }
}

观察者的差异化实现,以及使用。

public class ObserverOneImpl implements Observer {
    @Override
    public void update(String msg) {
        System.out.println("ObserverOne is notified,"+msg);
    }
}

public class ObserverTwoImpl implements Observer {

    @Override
    public void update(String msg) {
        System.out.println("ObserverTwo is notified,"+msg);
    }
}

public class ObserverDemoTest {
    public static void main(String[] args) {
        Subject subject = new ObserverableImpl();
        //添加观察者
        subject.addObserver(new ObserverOneImpl());
        subject.addObserver(new ObserverTwoImpl());
        //通知
        subject.notifyAllObservers("关注公众号:捡田螺的小男孩");
    }
}
//输出
ObserverOne is notified,关注公众号:捡田螺的小男孩
ObserverTwo is notified,关注公众号:捡田螺的小男孩

就这样,我们实现了观察者模式啦,是不是很简单?不过上面的代码,只能算是观察者模式的模板代码,只能反映大体的设计思路。接下来,我们看下在工作中,是如何使用观察者模式的。

4. 工作中,如何使用观察者模式的

观察者模式的实现有两种方式,同步阻塞方式和异步非阻塞方式。第3小节就是一个同步阻塞方式的观察者模式。我们来看下,日常工作的例子:用户注册成功发消息的例子,如何实现。本小节分同步阻塞、异步阻塞、spring观察者模式三个方向探讨。

  • 同步阻塞方式的观察模式
  • 异步非阻塞方式的观察者模式
  • spring观察者模式应用

4.1 同步阻塞方式的观察模式

我们可以把用户注册,当做被观察者实现的逻辑,然后发消息就是观察者的实现逻辑。

假设有两个观察者,分  别是发QQ消息和手机消息,于是有以下代码:

public interface RegisterObserver {
    void sendMsg(String msg);
}
@Service
public class ObserverMobileImpl implements RegisterObserver {
    @Override
    public void sendMsg(String msg) {
        System.out.println("发送手机短信消息"+msg);
    }
}
@Service
public class ObserverQQImpl implements RegisterObserver {
    @Override
    public void sendMsg(String msg) {
        System.out.println("发送QQ消息"+msg);
    }
}

直接可以通过spring的ApplicationContextAware,初始化观察者列表,然后用户注册成功,通知观察者即可。代码如下:

@RestController
public class UserController implements ApplicationContextAware{

     @Autowired
     private UserService userService;

     //观察者列表
     private Collection<RegisterObserver> regObservers;

     @RequestMapping("register")
     public String register(UserParam userParam) {
          //注册成功过(类似于被观察者,做了某件事)
          userService.addUser(userParam);
          //然后就开始通知各个观察者。
          for(RegisterObserver temp:regObservers){
               temp.sendMsg("注冊成功");
          }
          return "SUCCESS";
     }

     //利用spring的ApplicationContextAware,初始化所有观察者
     @Override
     public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
          regObservers = new ArrayList<>(applicationContext.getBeansOfType(RegisterObserver.class).values());
     }
}

==可以发现,观察者模式,就是将不同的行为代码解耦,也就是说将观察者和被观察者代码解耦。但是这里大家会发现,这是同步阻塞式的观察者模式,是有缺点的,比如发QQ消息异常,就会影响用户注册,或者发消息因为某些原因耗时,就影响了用户注册,所以可以考虑异步非阻塞的观察者模式。

4.2 异步非阻塞方式的观察者模式

如何实现异步非阻塞,最简单就是另开个线程嘛,即新开个线程或者线程池异步跑观察者通知。代码如下:

@RestController
public class UserController implements ApplicationContextAware{

     @Autowired
     private UserService userService;

     private Collection<RegisterObserver> regObservers;

     private Executor executor = Executors.newFixedThreadPool(10);

     @RequestMapping("register")
     public String register(UserParam userParam) {
          userService.addUser(userParam);
          //异步通知每个观察者
          for (RegisterObserver temp : regObservers) {
               executor.execute(() -> {
                    temp.sendMsg("注冊成功");
               });
          }

          return "SUCCESS";
     }

     @Override
     public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
          regObservers = new ArrayList<>(applicationContext.getBeansOfType(RegisterObserver.class).values());
     }
}

线程池实现的异步非阻塞方式,还是可以的,但是异步执行逻辑都耦合在了register()函数中,不是很优雅,也增加了这部分业务代码的维护成本。一般日常工作中,我们会用spring那一套观察者模式等。

4.3 spring观察者模式应用

spring的观察者模式使用也是比较简单的,就是先定义个事件,继承于ApplicationEvent:

public class MessageEvent extends ApplicationEvent {

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

然后定义一个事件监听器MessageListener,类似于观察者,它实现ApplicationListener接口。

@Component
public class MessageListener implements ApplicationListener<MessageEvent> {
    @Override
    public void onApplicationEvent(MessageEvent messageEvent) {
       System.out.println("用户注册成功,执行监听事件"+messageEvent.getSource());
    }
}

用户注册成功后,applicationEventPublisher(类似于被观察者)发布事件即可,代码如下:

@RestController
public class UserController implements ApplicationContextAware{

     @Autowired
     private UserService userService;
     
     @Autowired
     private ApplicationEventPublisher applicationEventPublisher;

    @RequestMapping("springListenRegister")
    public String springListenRegister(UserParam userParam) {
        System.out.println("开始注册");
        userService.addUser(userParam);
        //用户注册成功,发布事件
        applicationEventPublisher.publishEvent(new MessageEvent("666"));
        return "SUCCESS";
    }

运行结果:

开始注册
用户注册成功,执行监听事件666

这个也是同步阻塞的方式实现的,等下下个小节先介绍完spring观察者模式的原理,田螺哥再来教大家如何抽取一个通用的异步非阻塞观察者模式哈。

5. Spring观察者模式原理

Spring 中实现的观察者模式包含三部分:分别是Event事件(相当于消息)、Listener监听者(相当于观察者)、Publisher发送者(相当于被观察者)。用个图表示就是这样:

这个ApplicationEvent是放到哪里的,监听者AppliactionListener是如何监听到的。接下来,我们来看下spring框架的观察者原理是怎样哈。

我们先来看下ApplicationEventPublisher源代码(被观察者/发布者)

@FunctionalInterface
public interface ApplicationEventPublisher {

 default void publishEvent(ApplicationEvent event) {
  publishEvent((Object) event);
 }

 void publishEvent(Object event);

}

ApplicationEventPublisher它只是一个函数式接口,我们再看下它接口方法的实现。它的具体实现类是AbstractApplicationContext,这个类代码有点多,我把关键部分代码贴出来了:

public abstract class AbstractApplicationContext extends ... {
  //监听者(观察者列表)
  private final Set<ApplicationListener<?>> applicationListeners;
  
  //构造器,初始化观察者列表
  public AbstractApplicationContext() {
    this.applicationListeners = new LinkedHashSet();
    //...
  }
  
  //发布事件
  public void publishEvent(ApplicationEvent event) {
    this.publishEvent(event, (ResolvableType)null);
  }

  public void publishEvent(Object event) {
    this.publishEvent(event, (ResolvableType)null);
  }

  //发布事件接口实现
  protected void publishEvent(Object event, ResolvableType eventType) {
    //...
    Object applicationEvent;
    if (event instanceof ApplicationEvent) {
      //如果event是ApplicationEvent对象,或者是它的子类
      applicationEvent = (ApplicationEvent)event;
    } else {
      // 如果不是ApplicationEvent对象或者它的子类,则将其包装成PayloadApplicationEvent事件,并获取对应的事件类型
      applicationEvent = new PayloadApplicationEvent(this, event);
      if (eventType == null) {
        eventType = ((PayloadApplicationEvent)applicationEvent).getResolvableType();
      }
    }

    if (this.earlyApplicationEvents != null) {
      this.earlyApplicationEvents.add(applicationEvent);
    } else {
      //真正的消息发送,是通过它。获取ApplicationEventMulticaster,调用multicastEvent方法广播事件
      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);
      }
    }
  }
  
  //添加观察者(监听者)
  public void addApplicationListener(ApplicationListener<?> listener) {
    Assert.notNull(listener, "ApplicationListener must not be null");
    if (this.applicationEventMulticaster != null) {
    this.applicationEventMulticaster.addApplicationListener(listener);
    } else {
      this.applicationListeners.add(listener);
    }  
  }
  
  //观察者列表
  public Collection<ApplicationListener<?>> getApplicationListeners() {
    return this.applicationListeners;
  }
  
  // 注册监听器
  protected void registerListeners() {
  //把提前存储好的监听器添加到监听器容器中到ApplicationEventMulticaster
  for (ApplicationListener<?> listener : getApplicationListeners()) {
   getApplicationEventMulticaster().addApplicationListener(listener);
  }
     //获取类型是ApplicationListener的beanName集合,此处不会去实例化bean
  String[] listenerBeanNames = getBeanNamesForType(ApplicationListener.class, true, false);
  for (String listenerBeanName : listenerBeanNames) {
   getApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName);
  }


  Set<ApplicationEvent> earlyEventsToProcess = this.earlyApplicationEvents;
  this.earlyApplicationEvents = null;
    //如果存在earlyEventsToProcess,提前处理这些事件
  if (!CollectionUtils.isEmpty(earlyEventsToProcess)) {
   for (ApplicationEvent earlyEvent : earlyEventsToProcess) {
    getApplicationEventMulticaster().multicastEvent(earlyEvent);
   }
  }
 }
}

通过以上代码,我们可以发现,真正的消息发送,实际上是通过事件广播器ApplicationEventMulticaster 这个接口来完成的。multicastEvent是主要方法,这个方法的实现在类SimpleApplicationEventMulticaster中,我们一起来看下源码:

public class SimpleApplicationEventMulticaster extends AbstractApplicationEventMulticaster {
  ...
  
  //线程池
  @Nullable
 protected Executor getTaskExecutor() {
  return this.taskExecutor;
 }
  
  public void setTaskExecutor(@Nullable Executor taskExecutor) {
  this.taskExecutor = taskExecutor;
 }

  
 @Override
 public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
  ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
    // 根据event类型获取适合的监听器
  Executor executor = getTaskExecutor();
  for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
   if (executor != null) {
        //如果executor不为空,异步调用执行监听器中的方法
    executor.execute(() -> invokeListener(listener, event));
   }
   else {
        //调用监听器的方法
    invokeListener(listener, event);
   }
  }
 }
  
 protected void invokeListener(ApplicationListener<?> listener, ApplicationEvent event) {
  ErrorHandler errorHandler = getErrorHandler();
    //如果存在ErrorHandler,调用监听器方法(会用try...catch包一下)
  if (errorHandler != null) {
   try {
    doInvokeListener(listener, event);
   }
   catch (Throwable err) {
        //如果抛出异常则调用ErrorHandler来处理异常。
    errorHandler.handleError(err);
   }
  }
  else {
      否则直接调用监听器方法
   doInvokeListener(listener, event);
  }
 }
  ...
  }

可以发现,默认情况下,spring实现的观察者模式,同步阻塞的。如果想异步执行事件,可以自定义SimpleApplicationEventMulticaster,然后构造一下executor线程池就好啦。代码如下:

/**
 * 公众号:捡田螺的小男孩
 */
@Component
public class ListenerConfig {

    //把线程池赋值进去
    @Bean
    public SimpleApplicationEventMulticaster applicationEventMulticaster() {
        SimpleApplicationEventMulticaster simpleApplicationEventMulticaster = new SimpleApplicationEventMulticaster();
        simpleApplicationEventMulticaster.setTaskExecutor(simpleAsyncTaskExecutor());
        return simpleApplicationEventMulticaster;
    }

    @Bean
    public SimpleAsyncTaskExecutor simpleAsyncTaskExecutor() {
        return new SimpleAsyncTaskExecutor();
    } 
}

demo跑一下,运行结果:

注册开始
当前线程名称http-nio-8080-exec-1
注册结束
用户注册成功2,执行监听事件666Sat Jun 18 11:44:07 GMT+08:00 2022
当前线程名称:SimpleAsyncTaskExecutor-20
当前线程名称:SimpleAsyncTaskExecutor-19
用户注册成功,执行监听事件666Sat Jun 18 11:44:12 GMT+08:00 2022

如果手动新建SimpleApplicationEventMulticaster,并设置taskExecutor的话,所有的监听响应事件,都是异步执行的哦。而有些有些场景我们希望同步执行的,这时候这种实现方式就不好了。

其实spring提供了@Async注解,可以用来实现异步。具体怎么实现呢?其实很简单,只需要在配置类加上@EnableAsync,接着在需要异步执行的监听实现方法。加上@Async即可。代码实现如下:

/**
 * 关注公众号:捡田螺的小男孩
 * 更多实战干货
 */
@Component
@EnableAsync //配置类加上```@EnableAsync```
public class ListenerConfig2 {

    @Bean
    public SimpleApplicationEventMulticaster applicationEventMulticaster() {
        SimpleApplicationEventMulticaster simpleApplicationEventMulticaster = new SimpleApplicationEventMulticaster();
        return simpleApplicationEventMulticaster;
    }
    
}

@Component
public class MessageAsyncListener3 implements ApplicationListener<MessageEvent> {

    @Async //方法异步注解
    @Override
    public void onApplicationEvent(MessageEvent messageEvent) {
        System.out.println("用户注册成功3,执行监听事件" + messageEvent.getSource() + new Date());
        System.out.println("当前线程名称:"+Thread.currentThread().getName());
    }
}

日常开发中,异步执行也可以自己手动通过线程池来开启啦。回到我们本文的后端思维主题,如果每个开发,都自己定义观察者模式的实现,这种代码会很混乱,所以最好是实现一个可扩展,通用的观察者模板。

6. 基于spring观察者模式,抽取一个模板

接下来的最后小节,跟大家一起基于spring的观察者模式,一步一步实现并抽取个模板哈。

我们要基于spring实现观察者模式的话,就包括这三步:

定义Event事件(相当于消息),一般定义一个Event对象,继承ApplicationEvent。

定义Listener监听者(相当于观察者),实现接口ApplicationListener。

Publisher发送者(相当于被观察者),通过ApplicationEventPublisher发布。

6.1 定义Event事件对象

既然我们要抽取观察者模板,那肯定不是每个人自己写自己的Event,然后都去继承ApplicationEvent。

我们可以自己定义一个项目相关的,通用的BaseEvent类,然后一些相关通用的信息属性可以放进去,比如eventId或者流水号bizSeq什么的,都可以,看你们项目需要哈。以下代码,我定义一个空空如也的BaseEvent

/**
 * 关注公众号:捡田螺的小男孩
 * 更多实战干货
 * @desc : 事件基础对象
 */
public class BaseEvent extends ApplicationEvent {

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

    public BaseEvent() {
        this("");
    }
}

如果你的观察者模式,是注册成功之后,发个消息的,你就可以声明一个消息类事件对象RegisterMessageEvent,继承通用的BaseEvent即可。然后属性可以自定义就好,比如messageId。

public class RegisterMessageEvent  extends BaseEvent{

    private String msgId;

    public RegisterMessageEvent(String msgId) {
        super();
        this.msgId = msgId;
    }

    public String getMsgId() {
        return msgId;
    }

    public void setMsgId(String msgId) {
        this.msgId = msgId;
    }
    
}

同理,如果你想定义一个用户送礼物成功,然后发个广播,可以定义一个GiftSendEvent。

public class GiftSendEvent extends BaseEvent {

    private String giftId;

    public GiftSendEvent(String giftId) {
        this.giftId = giftId;
    }

    public String getGiftId() {
        return giftId;
    }

    public void setGiftId(String giftId) {
        this.giftId = giftId;
    }
}

其他业务场景类似,只要想接入你的观察者模板,只需要自己定义事件对象,继承于你的BaseEvent即可,是不是成就感满满啦。

6.2  定义Listener监听者(观察者啦)

定义完Event事件,我们就可以开始定义监听者了。我们定义的监听者,只需要实现接口ApplicationListener接口即可。如果每个人也是各写各的,这些就很乱,毫无模板规范可言了。

我们可以封装一下嘛,如何封装呢?很简单,可以先声明一个IEventListener,让它继承ApplicationListener接口,我们都知道,接口也是可以继承的哈。如下:

public interface IEventListener extends ApplicationListener {

}

监听者的实现,关键在于实现ApplicationListener的onApplicationEvent的接口方法即可。又因为未来别的业务场景接入观察者模式,都是按你的模板来,所以各个Event事件对象,都是继承于你的BaseEvent的,所以我们可以把<T extends BaseEvent>的泛型加进去,如下:

/**
 * 关注公众号:捡田螺的小男孩
 * @desc : 更多干货
 */
public interface IEventListener<T extends BaseEvent> extends ApplicationListener <T>{
    void onApplicationEvent(T event);
}

有些时候,可能会有这种场景,就是执行监听逻辑,只对部分数据(或者说部分特殊用户才执行)。既然我们是抽取监听模板,考虑到可扩展性,我们可以优化下IEventListener的代码。我们可以声明一个support的方法,默认是执行的,子类可以覆盖重写(让子类去控制是否执行这个监听逻辑),如下:

public interface IEventListener<T extends BaseEvent> extends ApplicationListener <T> {

    void onApplicationEvent(T event);

    //接口里面,加了default,就可以写方法实现
    default boolean support(T event) {
        return true;
    }
}

然后呢,只有support方法返回true,才执行监听的逻辑,我们还可以定义一个handler方法,给子类去实现自己的业务逻辑,代码如下:

/**
 * 关注公众号:捡田螺的小男孩
 * @desc : 更多干货
 */
public interface IEventListener<T extends BaseEvent> extends ApplicationListener <T> {

    default void onApplicationEvent(T event){
        if (support(event)) {
            handler(event);
        }
    }

    default boolean support(T event) {
        return true;
    }

    //真正实现业务逻辑的接口,给子类去实现。
    void handler(T event);

}

对着以上的代码模板,小伙伴们是否还有一些优化的想法呢?

如果方法产生了异常,我们是不是可以注意一下异常处理呢?以上小节,SimpleApplicationEventMulticaster源码分析的时候,不知道大家有没有细看,其实它就用了一个很巧妙的异常处理,我们可以借鉴一下,哈哈哈,这就是看源码的一个小小的好处了。

我们可以给onApplicationEvent的实现try...catch...一下,如果catch住异常的话,可以定义一个handlerException异常处理方法,给子类自定义去实现,当然,异常可以默认不处理嘛,代码如下:

/**
 * 关注公众号:捡田螺的小男孩
 * @desc : 更多干货
 */
public interface IEventListener<T extends BaseEvent> extends ApplicationListener <T> {

    /**
     * 观察者的业务逻辑处理
     * @param event
     */
    default void onApplicationEvent(T event){
        try {
            if (support(event)) {
                handler(event);
            }
        } catch (Throwable e) {
            /**
             * 
             */
            handleException(e);
        }
    }

    /**
     * 默认执行观察者的逻辑的
     * @param event
     * @return
     */
    default boolean support(T event) {
        return true;
    }

    /**
     *  观察者的逻辑,交给不同子类自定义实现
     * @param event
     */
    void handler(T event);

    /**
     * 异常默认不处理
     * @param exception
     */
    default void handleException(Throwable exception) {
    }

}

最后呢,不同业务不同的监听者(观察者),直接实现你的IEventListener就好啦,比如注册成功那个,我们声明一个RegisterMessageListenerImpl类,如下:

@Service
public class RegisterMessageListenerImpl implements IEventListener<RegisterMessageEvent> {
    
    @Override
    public void handler(RegisterMessageEvent event) {
        System.out.println("用户注册成功register,执行监听事件" + event.getSource() + new Date());
    }
}

6.3 定义Publisher发送者模板

我们的观察者模板,最后一步就是定义发送者模板。最简单的发送,就是利用ApplicationContext直接发送就好。

@Component
public class EventPublish {

    @Autowired
    private ApplicationContext applicationContext;

    void publish(BaseEvent event) {
        applicationContext.publishEvent(event);
    }
    
}

为什么可以直接使用applicationContext来发送,文章开始介绍,我们不是用ApplicationEventPublisher来发送嘛?其实是因为applicationContext继承了ApplicationEventPublisher接口

这个只是同步阻塞方式的观察者模式,一般来说,一个通用的观察者模板。也需要提供异步非阻塞方式的观察者模板。本文第5小节,我们知道了,在配置类加上@EnableAsync,在需要异步执行的监听加上@Async,即可实现异步。

为了方便管理,和API语义更明确,我们可以手动设置线程池,给我们的模板发布类,提供异步发送的接口。我们先自定义一个线程池,一般不建议直接使用JDK的线程池。

如何自定义线程池呢?

我们在application.properties配置文件,定义线程池一些属性(核心线程数、最大线程数等等)

executor.corePoolSize=50
executor.maxPoolSize=100
executor.queueCapacity=200
executor.keepAliveSeconds=120
executor.threadNamePrefix=threadPoolExecutor

然后声明一个线程配置类TianLuoExecutorsConfig:

/**
 * 关注公众号:捡田螺的小男孩
 * 更多实战干货
 */

@Configuration
@ConfigurationProperties("executor")//读取配置文件的线程池属性
public class TianLuoExecutorsConfig {

    private int corePoolSize;

    private int maxPoolSize;

    private int queueCapacity;

    private int keepAliveSeconds;

    private String threadNamePrefix;
    
    //省略get和set的方法
}

通过线程配置类TianLuoExecutorsConfig,初始化线程池TianLuoExecutorPool,代码如下:

/**
 * 关注公众号:捡田螺的小男孩
 * 更多实战干货
 */
@Configuration
public class TianLuoExecutorPool {

    @Autowired
    private TianLuoExecutorsConfig tianLuoExecutorsConfig;

    @Bean
    public ThreadPoolTaskExecutor eventExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(tianLuoExecutorsConfig.getCorePoolSize());
        executor.setMaxPoolSize(tianLuoExecutorsConfig.getMaxPoolSize());
        executor.setKeepAliveSeconds(tianLuoExecutorsConfig.getKeepAliveSeconds());
        executor.setQueueCapacity(tianLuoExecutorsConfig.getQueueCapacity());
        executor.setThreadNamePrefix(tianLuoExecutorsConfig.getThreadNamePrefix());
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.initialize();
        return executor;
    }
}

然后回到EventPublish,把异步发布的接口加上,如下:

/**
 * 观察者模式,事件发布通用接口
 **/
@Component
public class EventPublish {

    @Autowired
    private ApplicationContext applicationContext;

    @Autowired
    private TianLuoExecutorPool tianLuoExecutorPool;
 
    //同步阻塞
    public void publish(BaseEvent event) {
        applicationContext.publishEvent(event);
    }
    
    //异步发布(异步非阻塞)
    public void asyncPublish(BaseEvent event) {
        tianLuoExecutorPool.eventExecutor().execute(()->{
            publish(event);
        });
    }
}
RapidScada在windows下的安装(详细) 1.下载地址:RapidScada官网 2.版本说明 有Linux版本和Windows版本,看自己的需求下载,推荐下载最新稳定版本。 3.系统要求 4.安装前的准备(windows10) 首先打开Windows功能: 先关闭 Microsoft .NET Framework3.5的 Windows Communication Foundation 子组件。如下图: Microsoft .NET Framework配置按下图勾选: iis安装的功能: 5.安装 解压.. 阅读详情

相关推荐

保姆级教程:手把手教你为裕泰微YT8521 PHY芯片编写Linux设备树(DTS)节点与驱动

本文提供了一份详细的保姆级教程,手把手教你为裕泰微YT8521 PHY芯片编写Linux设备树(DTS)节点与驱动。从基础配置到RGMII接口的特殊设置,再到驱动开发的关键步骤和调试技巧,帮助开发者快速掌握PHY芯片的移植与开发。

weixin_33734785的博客 335

高级Spring之设计模式(模板方法)

高级Spring之设计模式(模板方法)

qq_61313896的博客 274

芯片设计中的“隐形杀手”:手把手教你理解并优化互连寄生参数(以SPEF/DSPF为例)

本文深入探讨了芯片设计中互连寄生参数的影响及优化策略,特别关注SPEF/DSPF文件解析与静态时序分析。通过实际案例展示了耦合电容对时序收敛的关键影响,并提供从定位到优化的完整解决方案,助力工程师应对先进工艺下的设计挑战。

weixin_30687051的博客 599

[C/C++后端开发学习] 5 设计模式:创建型

设计模式:创建型设计模式定义如何学习设计模式?设计原则依赖倒置开放封闭面向接口封装变化点单一职责理氏替换接口隔离组合优于继承原则的层次关系模板方法本质观察者模式补充:互斥锁和自旋锁策略模式 设计模式定义 设计模式是指在软件开发中,经过验证的,用于解决在特定环境下,重复出现的,特定问题的解决方案 如何学习设计模式? 找到稳定点和变化点,把变化点隔离出来(解耦合-把耦合限定在最小范围) 先满足设计原则,慢慢迭代出设计模式(分治思维) 设计原则 依赖倒置 高层模块不应该依赖低层模块,两者都应该依赖抽象

jiang_T的博客 1426

Linux -- Tomcat 8.5.55 版本的下载与升级

安全扫描出现如下漏洞: Apache Tomcat 7.0.100之前的7.*版本、8.5.51之前的8.*版本和9.0.31之前的9.*版本中的Tomcat AJP协议存在文件包含漏洞。攻击者可利用该漏洞读取或包含Tomcat上所有 webapp目录下的任意文件,如:webapp 配置文件或源代码等 Apache Tomcat 9.0.0.M1版本至9.0.29版本、8.5.0版本至8.5.49版本和7.0.0版本至7.0.98版本中存在授权问题漏洞。该漏洞源于网络系统或产品中缺少身份验证措施或身份验

支棱起来 4458

【银行测试】手机银行APP专项项目+测试点汇总(一)

1、手机银行APP转账项目讲解面试官:请介绍一下你所负责的模块如何进行的测试?求职者:好的,我最近测试的是XX银行的手机银行App项目。这个项目里面包含了转账、转账额度调整、网银转账、交易明细、添加账户、存款、投资理财、生活缴费等功能模块。我主要负责了转账模块,这个大模块中包含了智能转账、手机转账、语音转账、预约转账、收款人管理等5个子模块,我首先介绍一下我们生活中使用最多的手机转账子模块。对于转账需要关注的是转账前、转账中、转账后这三个状态下的测试。

m0_70102063的博客 1607

Java后端成神之路

最近项目完成的差不多了,抽时间整理下Java后端的知识。 知识点是个人整理的,大家也可以根据自己的具体需求进行补充, 最后提供学习路线的思维导图。 1, java语言 java基础是十分重要的,java需要掌握的知识包括: (1)基础知识 a,基础的语法; b,面向对象的思想; c,接口; d,容器 e,异常; f,泛型; g,反射 h,注解 i,IO j,图形化(例如swing) (2)JVM a,类加载机制; b,字节码执行机制; c,jvm内存模型; d,.

程序员springmeng 1420

ESP32水墨屏日历实战:低功耗自动更新与HTTPS数据解析

本文详细介绍了ESP32水墨屏日历项目的开发实践,重点讲解了低功耗自动更新与HTTPS数据解析的实现方法。通过ESP32的自动休眠功能和水墨屏的零功耗特性,结合HTTPS请求安全获取网络数据,实现了高效节能的桌面日历应用。项目代码开源,适合物联网开发者参考。

xx56789的博客 778

COSMIC的后端学习之路——1.4 + 1.5 设计模式

1.4 + 1.5 设计模式

qq_62045230的博客 1265

MIT液态神经网络(LNN)深度研究:原理、应用与未来

液态神经网络(Liquid Neural Networks, LNNs)代表了人工智能领域,特别是神经网络架构设计中的一次范式转移。它从根本上挑战了传统深度学习模型依赖大规模参数和静态架构的惯例,转而寻求一种更为动态、高效且受生物神经系统启发的计算范式。LNN的核心创新在于其将神经网络的计算过程从离散的、基于时间步的更新,转变为连续的、由微分方程驱动的动态系统演化。这种转变不仅赋予了模型前所未有的适应性和鲁棒性,还使其在处理复杂的时序数据和因果推断任务时表现出色。本章节将深入剖析LNN的技术原理,从其核心的

步子哥的博客 2591

18、未来医疗信息学中的太赫兹天线技术

本文探讨了太赫兹天线技术在医疗信息学中的关键作用,重点分析了分裂环谐振器和纳米结构光导天线(PCA)的设计与性能提升方法,介绍了相干与非相干太赫兹检测技术,并通过等效电路模型和数值模拟深入解析PCA工作机理。文章还展示了太赫兹技术在无创血糖监测、肿瘤检测等医疗诊断中的实验应用,总结了其非侵入性、高分辨率成像和多参数分析等优势,同时指出信号检测难、成本高和技术复杂等挑战。未来趋势包括设备小型化、效率提升及与人工智能融合,拓展至药物研发、皮肤与神经疾病诊断等领域,展现出变革医疗健康的巨大潜力。

milk8的博客 146

Java后端面试高效复习:构建知识体系与实战思维

在Java后端开发领域,构建扎实的知识体系是应对技术面试和解决实际工程问题的基石。其核心原理在于将Java基础、并发编程、JVM、MySQL、Spring等核心技术模块进行有机串联,形成可解释、可推导的逻辑链条,而非孤立记忆知识点。这种体系化思维的技术价值在于,它能帮助开发者从底层原理出发,理解技术选型背后的设计考量,并具备从问题现象推导至解决方案的能力。典型的应用场景包括高并发场景下的系统设计、线上性能问题排查以及复杂业务逻辑的实现。例如,在应对“秒杀系统”这类高频面试题时,就需要综合运用并发控制、数据库

weixin_30372371的博客 302

【Pix4d精品教程】Pix4d空三后处理:点云分类与过滤、DSM精编生成DEM、生成等高线案例详解

本文详细介绍了利用Pix4D软件进行无人机摄影测量后处理的关键步骤,主要包括点云分类与分配、DEM精编生成和等高线绘制三个核心环节。首先通过点云分类区分地面、植被等7种地物类型,然后通过点云分配将地面点云归并到Ground类别,剔除植被等干扰要素。随后重新运行DSM生成步骤获取精确DEM,最后在附加输出中设置参数生成标准等高线。文中配有详细的操作截图和注意事项,如处理前需备份原始DSM/DOM数据,并解释了DEM与DSM的本质区别。该教程为无人机摄影测量内业处理提供了完整的解决方案。

智慧空间实验室 1万+

2026年Go后端求职市场洞察:从600场面试看工程师核心竞争力构建

Go语言作为云原生和微服务架构的核心技术栈,其生态发展正推动工程师能力模型快速演进。从技术原理层面看,Goroutine并发模型、内存管理和GC机制构成了高性能后端服务的基石;而工程实践价值则体现在如何将这些原理应用于分布式系统设计,解决高并发场景下的实际挑战。当前市场对Go工程师的需求已从基础CRUD开发转向具备云原生技术栈深度、系统设计能力和工程化思维的复合型人才。应用场景覆盖了从基础设施层到业务架构层的全链路技术实践,要求开发者不仅要掌握Kubernetes、Docker、gRPC等工具链,更要理解其

weixin_34378969的博客 369

蓝牙模块HM-10手册

蓝牙模块HM-10手册,该手册对蓝牙模块HM-10进行了详细的介绍和使用方法以及一些AT指令。

最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版

Part-E1-SDIO-Specification-Ver4.00-Final-120220.pdf:最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版,包括SDIO 接口的外设,SDIO 接口官方协议等。

Python设计模式退化:从GOF到协议思维的工程演进

设计模式本质是语言表达力不足时的结构补偿机制。在Java、C#等静态类型语言中,Strategy、Observer、Factory等模式通过类继承与接口约束显式建模行为契约;而Python凭借函数即对象、鸭子类型、装饰器和Protocol协议等原生特性,将大量模式逻辑下沉为运行时语义或数据结构,实现‘隐式契约’替代‘显式接口’。这种范式迁移提升了代码可读性、可测试性与迭代效率,尤其适用于微服务快速验证、跨语言重构及动态系统集成场景。本文聚焦Python中设计模式的自然退化现象,结合真实生产案例解析协议驱动、

didi9310的博客 375

efm32 入门.pdf

本PDF共计22页,介绍的是操作驱动API,下载与否自己斟酌。

C++进阶必由之路:拆解开源项目源码,掌握工程思维与设计模式

在软件工程领域,深入理解系统底层原理和架构设计是开发者进阶的关键。通过分析优秀开源项目的源码,可以直观学习到设计模式、内存管理、并发模型等核心概念的实际应用。例如,模板元编程和RAII(资源获取即初始化)等C++高级特性,在工业级代码中如何被用于提升性能与安全性。这种学习方式的价值在于,它将抽象的理论转化为具体的工程实践,帮助开发者掌握模块解耦、性能优化和异常处理等关键技术。无论是构建高性能网络服务还是设计可维护的基础设施,源码阅读都能提供从原理到实现的完整视角,最终自然收敛到通过拆解spdlog、Asio

weixin_33796177的博客 428

IEM模型,地表粗糙度模拟

利用微波反演土壤水分时候,利用IEM模型可以实现对随机地表的模拟

java后端开发面试java程序员面试书籍

分享一些资料给大家,我觉得这些都是很有用的东西,大家也可以跟着来学习,查漏补缺。《Java高级面试》《Java高级架构知识》《算法知识》分享一些资料给大家,我觉得这些都是很有用的东西,大家也可以跟着来学习,查漏补缺。《Java高级面试》[外链图片转存中…(img-MgoKNule-1710267648158)]《Java高级架构知识》[外链图片转存中…(img-m1kyaDGC-1710267648159)]《算法知识》[外链图片转存中…(img-o1mcjDbl-1710267648161)]

m0_60635609的博客 894

【面经】2024春招-软件开发工程师-后端方向1

面经整理系列: 【面经】软件开发工程师-后端方向1 文章目录 岗位与面经 基础1:数据库 & 网络 基础2:系统 & 语法 模板3:算法 & 项目 岗位与面经 岗位介绍

小哈里的博客 2357

Java面试通关⑰:设计模式全集

摘要:设计模式是代码架构思维、框架源码设计、高质量代码必备能力,大厂面试高频问模式原理、落地场景、源码应用。多数开发者只会CRUD,代码冗余、耦合严重、扩展性差,不懂设计模式思想,无法写出高复用、高扩展、低耦合代码,面试架构思维题失分严重。本篇全覆盖23种设计模式核心、常用模式原理、场景、源码案例、手写实现,掌握架构设计思维,写出高质量工程代码。

折哥|智能物流与Java实战,副高数据库工程师。深耕出版社物流WMS/OMS(线体/立库/VGA),也分享Java进阶与OMS电商对接。Oracle→PG实战派。相信沉淀可迁移能力,分享通用技术,不藏不虚。 489

Web基础总结、思维导图Web、JS、JQuery

前端开发入门学习有:HTML、CSS、JavaScript(简称JS)这三个部分。所以在学习之前我们需要先明确三个概念: HTML——内容层,它的作用是表示一个HTML标签在页面里是个什么角色。 CSS——样式层,它的作用是表示一块内容以什么样的样式(字体、大小、颜色、宽高等)显示。 JS——行为层,它的作用是当用户触发某些行为时,会给内容和样式带来什么样的改变。 ![在这里插入图片描述](h...

qq_43707633的博客 1162

编程思维四大核心与八种实战方法:从代码搬运工到系统设计者

编程思维是程序员解决复杂问题的核心心智模型,其本质是一套系统化的问题解决方法论。它建立在计算机科学基础之上,通过将复杂问题分解为可管理的子问题,并抽象出通用模型,从而构建出清晰、可维护的软件结构。这种思维模式的价值在于,它能将模糊的业务需求转化为精确的技术实现,并指导开发者识别和应用成熟的设计模式与算法,最终提升软件的质量、可扩展性与开发效率。在实际应用场景中,无论是设计一个电商系统,还是实现一个智能推荐引擎,编程思维都贯穿于从需求分析、架构设计到代码实现的每一个环节。本文深入探讨了分解、抽象、模式识别和算

weixin_30684743的博客 336

图解23种设计模式完整知识体系(Java后端面试完整版)

本文系统梳理了23种设计模式及其在Java开发中的应用,从底层原理到框架源码实现进行全面解析,主要内容包括: 一、设计模式基础 四大核心特性:封装(隔离细节)、继承(代码复用)、多态(动态扩展)、抽象(规范统一) 六大设计原则:单一职责、开闭原则、里氏替换、依赖倒置、接口隔离、迪米特法则 二、创建型模式(5种) 单例模式:全局唯一实例(Spring单例Bean) 工厂方法:单一产品扩展(Spring BeanFactory) 抽象工厂:产品族扩展(JDBC驱动) 建造者:复杂对象分步构建(StringBui

专注 Java 后端开发,分享基础语法、JVM、并发、Spring 全家桶、微服务、分布式、性能优化等实战干货。坚持输出高质量技术笔记,用简单语言讲清复杂原理,助力每一位 Java 开发者稳步进阶。 429

后端开发学习路径:从编程语言到分布式系统架构的完整指南

在软件开发领域,后端开发是构建稳定、可靠、可扩展的数据处理与业务逻辑中枢的核心技术。其本质在于将计算机科学基础原理应用于系统设计,通过编程语言实现业务逻辑,并利用数据库、中间件等组件构建高效的数据处理管道。从数据结构与算法优化代码性能,到操作系统进程管理与网络通信原理,再到数据库事务与索引设计,这些底层知识共同支撑起高可用的服务架构。在技术价值层面,后端系统通过并发编程、分布式中间件和容器化技术,实现了系统的高性能、高可用与弹性伸缩,为电商、社交、金融等各类互联网应用提供坚实的技术底座。具体到工程实践,开发

weixin_30883311的博客 373

从通宵写代码到三思而后行:资深开发者的工程思维进化之路

在软件工程领域,从实现功能到构建可持续系统的思维转变至关重要。其核心原理在于,随着项目规模扩大和团队协作需求增长,仅关注代码运行(编程)已不足够,必须引入工程化思想。这包括模块化设计、设计模式应用、自动化工具链和代码审查等实践,其技术价值在于显著提升代码的可维护性、可扩展性和团队协作效率。应用场景广泛覆盖从大型在线系统到长期运营的产品开发。本文以资深开发者的经验为例,探讨了如何通过重视接口设计、警惕技术债务等具体实践,实现从“写代码”到“设计代码”的思维进化,这正是应对现代软件复杂性的关键内功。

weixin_33912453的博客 335

程序员进阶指南:掌握编程思维四大核心与八种实战方法

编程思维是软件工程师解决复杂问题的核心能力,其本质是一套系统化的思考框架。从概念上讲,它包含分解、抽象、模式识别和算法四大核心环节,构成了从问题分析到方案设计的完整路径。在原理层面,分解通过分而治之降低认知负荷,抽象通过隐藏细节提升复用性,模式识别借助经验库加速问题归类,算法则确保解决方案的正确性与效率。这种思维模式的技术价值在于,它能帮助开发者超越对特定框架或语法的依赖,从被需求驱动的执行者转变为能自主设计和创造价值的工程师。在实际应用场景中,无论是进行系统架构设计、代码重构优化,还是解决具体的性能瓶颈和

weixin_34185512的博客 489

90篇JAVA高级深度分析与实践文章:做AI的主人,代码直接可用,附完整目录

以AI盛行的当下,我们该充实自己,还是当AI的附庸——这几乎是每个技术人都在深夜叩问过自己的问题。当IDE插件能自动补全整段代码,当ChatGPT可以解释任何源码,当"10倍效率工程师"的口号响彻云霄,我们似乎站在一个十字路口:是深度钻研底层原理,还是学会更优雅地"调用API"?这套《90篇JAVA高级深度分析与实践文章,代码直接可用》系列,给出的答案出乎意料地笃定——真正的危机不是被AI取代,而是止步于"会用AI",却不懂"AI在做什么"。

雨1986的博客 376
上一篇: 普通本科五面阿里艰苦经历,心路历程回馈一波
下一篇: 微服务之服务注册和服务发现篇
JavaMonsterr
博客等级 码龄4年 211粉丝 481原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值