观察者模式
观察者监听被观察者,一旦被观察者发生一定行为,观察者会被通知对该行为作出响应。例如微博中的关注与被关注关系。一对多的关系。
涉及四类角色:抽象观察者、抽象被观察者、具体观察者、具体被观察者。
关键:被观察者对象中持有观察者对象的队列,在被观察者完成指定行为后,循环观察者队列,通知每个关注自己的观察者。观察者必须先注册到被观察者上,其实就是将自身添加在被观察者中的队列中。
/**
* 被观察者/主题
*/
public abstract class ISubject {
//维持一个观察者的队列
List<IObserver> observers = new ArrayList<>();
/**
* 观察者注册
*/
public void regiser(IObserver observer){
observers.add(observer);
}
public void remove(IObserver observer){
observers.remove(observer);
}
/**
* 通知观察者,具体内容由子类实现
*/
abstract void notifyObervers();
/**
* 被观察者自己的业务
*/
abstract void work();
}
/**
* 观察者接口
*/
public interface IObserver {
//对被观察者发生特定行为后的响应
void action();
}
/**
* 具体观察者/主题
*/
public class ConcreteSubjectA extends ISubject{
//主题名称
private String name;
public ConcreteSubjectA(String name) {
this.name = name;
}
/**
* 通知观察者
*/
@Override
void notifyObervers() {
//实际上此处不一定是去调用观察者的方法,也可以是主动向观察者推送消息等。
//例如消息中间件,在收到某消息后,将消息发送给指定的订阅者(观察者)。其中observer对象中有订阅者的地址等通信信息
for (IObserver observer:observers){
observer.action();
}
}
/**
* 观察者自己的业务方法。完成业务后通知观察者
*/
@Override
void work() {
System.out.println(name+"work");
//do something ...
//通知观察者
notifyObervers();
}
}
/**
* 具体观察者A
*/
public class ConcreteObserverA implements IObserver{
@Override
public void action() {
//do something
System.out.println("ConcreteObserverA收到通知");
}
}
/**
* 具体观察者B
*/
public class ConcreateObserverB implements IObserver {
@Override
public void action() {
//do something
System.out.println("ConcreateObserverB收到通知");
}
}
/**
* 客户端
*/
public class Test {
public static void main(String[] args) {
//观察者1
IObserver observer1 = new ConcreteObserverA();
//观察者1
IObserver observer2 = new ConcreateObserverB();
//创建主体
ISubject subject = new ConcreteSubjectA("复古主题");
//观察者注册到指定主体
subject.regiser(observer1);
subject.regiser(observer2);
//主题产生指定行为
subject.work();
}
}
监听者模式
监听者模式不是23中设计模式之一,这样只是方便称呼。个人认为,监听者模式和观察者模式本质是无区别的。监听者模式只是将从观察者模式多抽取出了一个事件角色。
监听器模式中涉及的角色:
事件源:指事件发生的主体,例如,一个人可以产生吃饭、睡觉、学习等事件。此处人为事件源
事件对象:对具体事件的封装
事件监听器:对某事件源的事件进行监听
//此处以人作为事件源
public class Person{
private List<Listener> list=new ArrayList<Listener>(); //同样持有监听者的队列
//添加事件监听器
public void addListener(Listener listener){
list.add(listener);
}
public void removeListener(Listener listener){
list.remove(listener);
}
//事件被触发,通知监听者
public void sendNotification(Event event){
for(Listener listener:list){
listener.onChange(event);
}
}
}
public interface Listener {
public void onChange(Event event);
}
//监听器
public class MyListener implements Listener {
@Override
public void onChange(Event event) {
switch(event.getType()){
case Event.EAT:
System.out.println("do eat...");
break;
case Event.SLEEP :
System.out.println("do sleep...");
break;
......
default:
throw new IllegalArgumentException();
}
}
}
//事件
public class Event {
public static final int EAT=1;
public static final int SLEEP=2;
public static final int STUDY=3;
public static final int RUN=4;
private int type ;
private Object source ;
//type:事件类型,source:事件源
public Event(int type, Object source) {
this.type = type;
this.source = source;
}
public int getType() {
return type;
}
public Object getSource() {
return source;
}
}
public class Test {
public static void main(String[] args) {
Person person = new Person();
Listener listener=new MyListener();
//添加监听者
person.addListener(listener);
//模拟人睡觉完成事件触发
person.sendNotification(new Event(Event.INSTALLED, person)); //将睡觉行为封装成事件对象,通知给监听者,监听者对该事件具体采取何种响应由自己决定
}
}
上例实际是对事件源做了监听,事件源会将发生的所有事件都通知给了事件监听器,可以将监听粒度缩小到事件,在事件源内部可以为每类事件创建不同的队列,某事件发生时只通知在该事件上注册的监听器。
以上都只是基本思想,可以根据业务需求做出不同的变形。
观察者模式与监听者模式关系
最明显就是,监听者模式中通知监听者时需要传递一个事件对象,而观察者模式不需要
观察者=事件监听器,被观察者=事件源+事件
图片来自:https://blog.csdn.net/axuan_k/article/details/78803382
监听者模式中的例子如果用观察者模式实现,则需要在被观察者对象完成特定业务后,调用观察者对象的对应方法,需要知道要调用被观察者的哪个方法。而监听者模式告诉监听者自己做了什么事(通过将行为封装成事件传递给监听者),监听者做出什么响应由其自身决定。
Spring中的监听器
几个角色:ApplicationEvent(事件类)、ApplicationListener(监听者类)、ApplicationContext(事件发布类)
ApplicationEvent:继承自JDK中的EventObject,EventObject,为JavaSE提供的事件类型基类,任何自定义的事件都继承自该类,例如上图中右侧灰色的各个事件。
public abstract class ApplicationEvent extends EventObject {
private static final long serialVersionUID = 7099057708183571937L;
private final long timestamp;
public ApplicationEvent(Object source) {
super(source);
this.timestamp = System.currentTimeMillis();
}
public final long getTimestamp() {
return this.timestamp;
}
}
ApplicationListener:继承自JDK中的EventListener。EventListener为JavaSE提供的事件监听者接口,任何自定义的事件监听者都实现了该接口。
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
void onApplicationEvent(E event);
}
ApplicationContext:JavaSE中未提供事件发布者这一角色类,由各个应用程序自行实现事件发布者这一角色。Spring中提供了ApplicationEventPublisher接口作为事件发布者,并且ApplicationContext实现了这个接口,担当起了事件发布者这一角色。可通过ApplicationContext.publishEvent(ApplicationEvent event)发布事件。可猜想ApplicationContext中维护了一个监听者的列表,事件发生时通知所有监听者。
具体用法,from:https://blog.csdn.net/erbao_2014/article/details/68924231?locationNum=9&fps=1
//自定义事件
public class MyTestEvent extends ApplicationEvent{
private String msg ;
public MyTestEvent(Object source,String msg) {
super(source);
this.msg = msg;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
//自定义监听器
@Component
public class MyNoAnnotationListener implements ApplicationListener<MyTestEvent>{
@Override
public void onApplicationEvent(MyTestEvent event) {
System.out.println("非注解监听器:" + event.getMsg());
}
}
//自定义发布类
@Component
public class MyTestEventPubLisher {
@Autowired
private ApplicationContext applicationContext;
// 事件发布方法
public void pushListener(String msg) {
applicationContext.publishEvent(new MyTestEvent(this, msg));
}
}
//测试类
@Controller
public class TestEventListenerController {
@Autowired
private MyTestEventPubLisher publisher;
@RequestMapping(value = "/test/testPublishEvent1" )
public void testPublishEvent(){
publisher.pushListener("我来了!");
}
}
注解方式:在监听器中用@EventListener来做方法级别的注解。其余不变
@Component
public class MyAnnotationListener {
@EventListener
public void listener1(MyTestEvent event) {
System.out.println("注解监听器1:" + event.getMsg());
}
}
补充:如何在自定义Listener(非实现spring的ApplicationListener)中使用Spring容器管理的bean。
form:https://www.cnblogs.com/fjdingsd/p/5731982.html
1.在java web项目中我们通常会有这样的需求:当项目启动(非spring启动)时执行一些初始化操作,例如从数据库加载全局配置文件等,通常情况下我们会用javaee规范中的Listener去实现。ServletContextListener是javaee下的类
1 public class ConfigListener implements ServletContextListener {
2 @Override
3 public void contextInitialized(ServletContextEvent sce) {
4 //执行初始化操作
5 }
6 @Override
7 public void contextDestroyed(ServletContextEvent sce) {
8 }
9 }
2.这样当servlet容器初始化完成后便会调用contextInitialized方法。但是通常我们在执行初始化的过程中会调用service和dao层提供的方法,service和dao是由spring框架来管理,我们想当然会像下面这么写,假设执行初始化的过程中需要调用ConfigService的initConfig方法,而ConfigService由spring容器管理
public class ConfigListener implements ServletContextListener {
@Autowired
private ConfigService configService;
@Override
public void contextInitialized(ServletContextEvent sce) {
configService.initConfig();
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
}
}
3.以上代码会在项目启动时抛出空指针异常。ConfigService实例并没有成功注入。这是为什么呢?要理解这个问题,首先要区分Listener的生命周期和spring管理的bean的生命周期。
(1)Listener的生命周期是由servlet容器(例如tomcat)管理的,项目启动时上例中的ConfigListener是由servlet容器实例化并调用其contextInitialized方法,而servlet容器并不认得@Autowired注解,因此导致ConfigService实例注入失败。
(2)而spring容器中的bean的生命周期是由spring容器管理的。
4.那么该如何在spring容器外面获取到spring容器bean实例的引用呢?这就需要用到spring为我们提供的WebApplicationContextUtils工具类,该工具类的作用是获取到spring容器的引用,进而获取到我们需要的bean实例。代码如下
public class ConfigListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
ConfigService configService = WebApplicationContextUtils.getWebApplicationContext(sce.getServletContext()).getBean(ConfigService.class);
configService.initConfig();
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
}
}
注意:以上代码有一个前提,那就是servlet容器在实例化ConfigListener并调用其方法之前,要确保spring容器已经初始化完毕!而spring容器的初始化也是由Listener(ContextLoaderListener)完成,因此只需在web.xml中先配置初始化spring容器的Listener,然后在配置自己的Listener,配置如下
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<listener>
<listener-class>example.ConfigListener</listener-class>
</listener>
若在ConfigListener上加上@Component注解会怎样?
ConfigService还是无法注入,因为ConfigListener实现的是ServletContextListener,是受web容器才管理,在web容器初始化完成后创建ConfigListener实例并调用其contextInitialized()方法的。web容器完全不认识@Autowired注解。(此时的ConfigListener实例由web容器创建)
但是如果当项目完全启动后,再另个一类中注入ConfigListener,会发现ConfigService已经被成功注入ConfigListener类,这是因为,此时用到的ConfigListener是spring管理下的bean,所以ConfigService也可以被注入了。(此时的ConfigListener实例由spring容器创建)