观察者模式:多个观察者同时监听一个主题对象,当主题对象发生改变时,它的所有观察者都会收到通知。
例如微信公众号,当作者发文时,所有的订阅者都会收到。这样观察者模式就能实现广播,同时符合开闭原则,增加新的观察者不用改原有的代码。
观察者模式的UML图如下
Subject(主题):定义了观察者集合,提供注册,删除,通知观察者的方法 ConcreteSubject(具体主题) Observer(观察者):定义了收到主题通知时所做的改变 ConcreteObserver(具体观察者)
手写观察者模式以微信公众号为例写一个观察者模式
抽象主题
public interface MySubject { void registerObserver(MyObserver o); void removeObserver(MyObserver o); void notifyObserver(); }抽象观察者
public interface MyObserver { void update(String authorName, String articleName); }具体主题
public class WeChatServer implements MySubject { private List<MyObserver> myObservers; private String authorName; private String articleName; public WeChatServer(String authorName) { myObservers = new ArrayList<>(); this.authorName = authorName; } public void publishArticle(String articleName) { this.articleName = articleName; notifyObserver(); } @Override public void registerObserver(MyObserver o) { myObservers.add(o); } @Override public void removeObserver(MyObserver o) { if (myObservers.contains(o)) { myObservers.remove(o); } } @Override public void notifyObserver() { myObservers.forEach(item -> { item.update(authorName, articleName); }); } }具体观察者
public class WeChatClient implements MyObserver { private String username; public WeChatClient(String username) { this.username = username; } @Override public void update(String authorName, String articleName) { System.out.println(username + ": " + authorName + " 发了一篇文章 " + articleName); } }测试类
public class Main { public static void main(String[] args) { WeChatServer weChatServer = new WeChatServer("Java识堂"); WeChatClient user1 = new WeChatClient("张三"); WeChatClient user2 = new WeChatClient("李四"); weChatServer.registerObserver(user1); weChatServer.registerObserver(user2); weChatServer.publishArticle("《五分钟学会观察者模式》"); } }输出为
张三: Java识堂 发了一篇文章 《五分钟学会观察者模式》 李四: Java识堂 发了一篇文章 《五分钟学会观察者模式》 观察者模式的应用JDK提供了观察者模式接口Java在java.util包中有对观察者模式进行支持,它定义了2个接口
抽象观察者
public interface Observer { void update(Observable o, Object arg); }抽象主题
public class Observable { private boolean changed = false; private Vector<Observer> obs; public Observable() { obs = new Vector<>(); } public synchronized void addObserver(Observer o) { if (o == null) throw new NullPointerException(); if (!obs.contains(o)) { obs.addElement(o); } } public synchronized void deleteObserver(Observer o) { obs.removeElement(o); } public void notifyObservers() { notifyObservers(null); } public void notifyObservers(Object arg) { Object[] arrLocal; synchronized (this) { if (!changed) return; arrLocal = obs.toArray(); clearChanged(); } for (int i = arrLocal.length-1; i>=0; i--) ((Observer)arrLocal[i]).update(this, arg); } public synchronized void deleteObservers() { obs.removeAllElements(); } protected synchronized void setChanged() { changed = true; } protected synchronized void clearChanged() { changed = false; } public synchronized boolean hasChanged() { return changed; } public synchronized int countObservers() { return obs.size(); } }和我们之前定义的很相似哈,只是多了一个change的开关字段,并且保证了线程安全
我们来把之前的例子改写一下,定义事件对象
@Data @AllArgsConstructor public class NewArticleEvent { private String authorName; private String articleName; } public class WeChatServer extends Observable { private String authorName; private String articleName; public WeChatServer(String authorName) { this.authorName = authorName; } public void publishArticle(String articleName) { setChanged(); this.articleName = articleName; notifyObservers(new NewArticleEvent(authorName, articleName)); } } public class WeChatClient implements Observer { private String username; public WeChatClient(String username) { this.username = username; } @Override public void update(Observable o, Object arg) { NewArticleEvent event = (NewArticleEvent) arg; System.out.println(username + ": " + event.getAuthorName() + " 发了一篇文章 " + event.getAuthorName()); } } public class Main { public static void main(String[] args) { WeChatServer weChatServer = new WeChatServer("Java识堂"); WeChatClient user1 = new WeChatClient("张三"); WeChatClient user2 = new WeChatClient("李四"); weChatServer.addObserver(user1); weChatServer.addObserver(user2); weChatServer.publishArticle("《五分钟学会观察者模式》"); } }输出和上面一样
在spring中自定义事件Spring用观察者模式来实现事件监听,在spring中实现事件监听比较简单,还是把上面的例子改造一下
事件类型需要继承ApplicationEvent类
@Data public class NewArticleEvent extends ApplicationEvent { private String authorName; private String articleName; public NewArticleEvent(Object source, String authorName, String articleName) { super(source); this.authorName = authorName; this.articleName = articleName; } }我们可以通过实现ApplicationListener接口或者使用@EventListener注解来实现事件监听
@Component public class NewArticleEventListener implements ApplicationListener<NewArticleEvent> { @Override public void onApplicationEvent(NewArticleEvent event) { System.out.println(event.getAuthorName() + " 发了一篇文章 " + event.getArticleName()); } } @Component public class MyEventListener { @EventListener public void newArticleEventListener(NewArticleEvent event) { System.out.println(event.getAuthorName() + " 发了一篇文章 " + event.getArticleName()); } }上面的例子用两种方式写了2个观察者,下面开始写测试类。上面的类都在com.javashitang.part6包下哈
@Configuration @ComponentScan("com.javashitang.part6") public class AppConfig { } public class Main { public static void main(String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class); context.publishEvent(new NewArticleEvent(context, "Jva识堂", "《五分钟学会观察者模式》")); context.close(); } }此时可以看到控制台输出
Jva识堂 发了一篇文章 《五分钟学会观察者模式》 Jva识堂 发了一篇文章 《五分钟学会观察者模式》好了,到现在你已经会在spring中自定义事件了。spring中观察者模式的实现可能复杂一点,但基本思想就是上面提到的。
ApplicationEvent是Spring的事件接口ApplicationListener是Spring的事件监听器接口,所有的监听器都实现该接口ApplicationEventPublisher是Spring的事件发布接口,ApplicationContext实现了该接口spring提供了一些ApplicationEvent的实现类供我们使用
ContextStartedEvent:ApplicationContext容器在初始化的时候发布的事件类型ContextClosedEvent:ApplicationContext容器在即将关闭的时候发布的事件类型ContextRefreshedEvent:ApplicationContext容器在初始化或者刷新的时候发布的事件类型如果你想知道spring定义好的事件在项目中的应用。你可以看我之前的文章,看我们是如何利用注解+spring事件来实现策略模式的
IM系统中我们是如何用策略模式避免大量的if else?
自定义事件在spring-cloud-eureka中的应用我们都知道eureka是一个注册中心,保存了服务名->具体的ip地址的关系,它是用 ConcurrentHashMap来保存这种映射关系的。既然是注册中心肯定涉及到服务的注册和取消,eureka在register(注册),cancel(取消),renew(续约)发生时,都会发布一个事件,如下代码所示
InstanceRegistry.java
private void handleRegistration(InstanceInfo info, int leaseDuration, boolean isReplication) { log("register " + info.getAppName() + ", vip " + info.getVIPAddress() + ", leaseDuration " + leaseDuration + ", isReplication " + isReplication); publishEvent(new EurekaInstanceRegisteredEvent(this, info, leaseDuration, isReplication)); }如果你对服务信息比较感兴趣,如想对数据进行持久化,就可以监听这些事件,是不是可扩展性特别好?
---来自腾讯云社区的---Java识堂
微信扫一扫打赏
支付宝扫一扫打赏