Spring环境下处理事件,标准的观察者模式

一、简单看一下观察者模式

有时候我们希望做了一件事,触发其他的一些行为动作,这时候可以通过代码强耦合的方式顺序编码,比如美女Liuer下班了,隔壁的小王喜欢她,来公司接她,代码如下:

public class Liuer {
	public void endWork() {
		//Liuer下班了,穿衣服穿鞋,出公司等一系列代码
		......
		//此处调用小王要来接小丽,小王的一系列动作
		......
	}
}

这种编码可以实现需求,看起来也没什么问题。这时候还有个小张也要来接她,好整,改一下Liuer代码:

public class Liuer {
	public void endWork() {
		//Liuer下班了,穿衣服穿鞋,出公司等一系列代码
		......
		//此处调用小王要来接小丽,小王的一系列动作
		......
		//此处调用小张要来接小丽,小张的一系列动作
		......
	}
}

可以,这么改也成,但以后可能还会加小李小刘,也有可能去掉小王小张,你会发现,这种编码方式不易扩展,需要频繁修改Liuer的代码,而且Liuer下班本就是自己的事儿,在Liuer下班的代码里大量调用别人的行为代码也不合适。
此时可以引入观察者模式,观察者监听着他们感兴趣的事件,当事件发生了,做出行为。
Liuer的endWork()只管自己下班,不用管有几个人来接她,Liuer的下班可以看成是一个事件,诸如小王小张之流,他们对丽丽下班感兴趣,他们就可以成为观察者,也可以说是监听者,他们观察着/监听着柳儿下班,那么我们伪代码可以改成下面这样:

public class Liuer {
	public void endWork() {
		//Liuer下班了,穿衣服穿鞋,出公司等一系列代码
		......
		//此处不在直接调用男性动物的代码,只需要发布一个事件,如下:
		Publisher.publish();
	}
}

OK,Liuer调用了一个事件发布的工具类代码,接下来是这个工具类假设代码:

public class Publisher {
	private static List<Object> listeners = new LinkedList();
	
	public static Object addListener(Object o) {
		return listeners.add(o);
	}
	public static void publish() {
		//调用了此方法证明Liuer下班了,那么就循环调用listeners,执行每一个监听器的动作代码
	}
}

通过以上的代码我们发现,Liuer下班的代码里,只需要调用一个发布器,然后剩下的事情由发布器来做,至于有没有人、有几个人来接她,她都不用管,再也不用修改Liuer的代码。
对Liuer下班感兴趣的人,我们都放在了发布器的列表容器中,循环调用。当然了,以上代码主要体验思想,并不完善,比如说列表的泛型是Object,这样肯定是没有意义的,因为Object并没有什么特殊的业务代码,并且事件发布器发送的只能是Liuer下班事件。没关系,主要体会这个流程设计思想,对此模式感兴趣可以专门去学一学这个模式,毕竟此篇的目的并不是观察者模式。

二、Spring环境下的事件支持

一句话:spring容器中给咱们提供了一个事件发布器,用它来发布事件。并且由于Spring IOC的天然优势,我们定义的观察者都可以被容器检测到,进而在发布器发布事件后被调用。
那么怎么用?

1.发布事件

首先是事件,我们的Liuer下班了就要发布一个下班事件,需要实现Spring的抽象类ApplicationEvent(此抽象类是对jdk中EventObject的一个扩展),那好,我们定义事件,为了方便观看,我们就定义为内部类:

@component
public class Liuer {
	public void endWork() {
		//Liuer下班了,穿衣服穿鞋,出公司等一系列代码
		......
	}
	public static class LiuerEndWorkEvent extends ApplicationEvent {
		/**
	     * @param source 也就是事件的详细信息,看我们业务情况,可能你这个发布了一个事件,观察者不但要知道你发生了,还要知道你一些数据
	     *               比如说Liuer下班这个事件,我们可以另外把她一天的工作状态封装到一个对象里,当作入参传入,然后观察者可以从此对象
	     *               中取出Liuer的一天工作状态
	     */
		public HelloEvent(Object source) {
	        super(source);
   		}
	}
}

然后就是把这个事件发布出去,我们得拿到Spring的时间发布器,好拿,它也是个Ioc中的bean,可以直接注入,也可以通过实现一个Aware接口来拿到(Spring的给我们提供了很多Aware接口,帮助我们在Spring的生命周期中注入一些组件,包括我们可以注入Bean工厂等等),传统方式注入我就不做演示了,实现Aware接口如下:

@component
public class Liuer implements ApplicationEventPublisherAware {
	private ApplicationEventPublisher publisher;

	@Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        this.publisher = applicationEventPublisher;
    }
	
	public void endWork() {
		//Liuer下班了,穿衣服穿鞋,出公司等一系列代码
		......
		//调用发布器
		publisher.publishEvent(new LiuerEndWorkEvent ("哥哥们,我下班了。"));
	}
	
	public static class LiuerEndWorkEvent extends ApplicationEvent {
		/**
	     * @param source 也就是事件的详细信息,看我们业务情况,可能你这个发布了一个事件,观察者不但要知道你发生了,还要知道你一些数据
	     *               比如说Liuer下班这个事件,我们可以另外把她一天的工作状态封装到一个对象里,当作入参传入,然后观察者可以从此对象
	     *               中取出Liuer的一天工作状态
	     */
		public LiuerEndWorkEvent (Object source) {
	        super(source);
   		}
	}
}

我把Liuer放到容器中了。上边我们通过构造函数获取到了发布器,并且发布了事件,事件发布的同时放了一条信息:哥哥们我下班了

2.监听事件

两种方式,一种是实现特定接口,另一种是标个注解

① 实现接口(我先拿小王举例):

@component
public class XiaowangListener implements ApplicationListener<LiuerEndWorkEvent> {
	@Override
    public void onApplicationEvent(LiuerEndWorkEvent event) {
    	// 会输出:哥哥们我下班了
        System.out.println(event.getSource());
    }
} 

ApplicationListener的泛型是你要监听的事件类型,必须是ApplicationEvent的子类。
这样,当发布器发布了一个LiuerEndWorkEvent事件的时候,就会触发这个监听器代码。并且我们也从事件源信息中取出了:哥哥们我下班了

②使用注解(我先拿小王举例):
从Spring4.2开始支持注解

@component
public class XiaowangListener {

	@EventListener({LiuerEndWorkEvent .class})
    public void onApplicationEvent(LiuerEndWorkEvent event) {
    	// 会输出:哥哥们我下班了
        System.out.println(event.getSource());
    }
} 

注解的方式有很多好处,比如说方法名称我们随便起,而且可以继续触发事件(有失败情况,稍后讲),比如我们可以这样:

@component
public class XiaowangListener {

	@EventListener({LiuerEndWorkEvent .class})
    public ApplicationEvent onApplicationEvent(LiuerEndWorkEvent event) {
    	// 会输出:哥哥们我下班了
        System.out.println(event.getSource());
        return new HelloEvent("hello......");
    }
} 

小王监听Liuer下班事件后,做了一些行为之后,又发布了一个HelloEvent,这样,对HelloEvent感兴趣的监听器将会被触发。
上边的两种实现方式,都可以监听多个事件,例如实现接口的方式,将泛型中的类型范围放大,将会监听所有这个类型的事件,比如说:

@component
public class XiaowangListener implements ApplicationListener<ApplicationEvent> {
	@Override
    public void onApplicationEvent(ApplicationEvent event) {
    	// 所有的ApplicationEvent被发布的时候,都会调用这段业务代码
        System.out.println(event.getSource());
    }
} 

注解的方式,那就是在注解中指定多个类型数组,或者也可以指定一个范围较大的类型。

3.在新线程中执行监听代码

默认调用发布器的发布和监听器的逻辑执行都是在一个线程中,所以是顺序同步的,如果想要异步多线程进行,官方建议加上@Async注解,启用异步方式,需要在配置类上指定@EnableAsync(其实Spring家族的每个enable…啥啥啥,都是往里边导入了新的配置类、新的BeanPostProcessor组件等等,我们也同样可以按照官方的路子扩展Spring)。
如下:

@component
public class XiaowangListener {

	@EventListener({LiuerEndWorkEvent .class})
	@Async
    public ApplicationEvent onApplicationEvent(LiuerEndWorkEvent event) {
    	// 会输出:哥哥们我下班了
        System.out.println(event.getSource());
        return new HelloEvent("hello......");
    }
} 

此时会在新线程异步执行代码,监听器中的逻辑不会阻塞Liuer中发布器之后的代码。
但是开启异步后,HelloEvent事件会失效,想要紧接着发布新的事件,需要手动调用发布器,返回值的方式是不行了。

写在最后:
  1. 这个事件是ApplicationContext级别的,在SpringBoot中,初始化ApplicationContext之前SpringBoot还有另外一套事件处理,SpringSecurity也有自己的一套事件处理等等。
  2. ApplicationContext有很多生命周期事件,我们都可以监听的到,比如说ContextRefreshedEvent。详细资料都可以在官网查阅:https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#context-functionality-events
  3. Spring Data Jpa中就用到了本文所讲的这种ApplicationContext集别的事件,通过Spring Jpa定义的特定注解发布事件,然后用我们本文所讲的方式监听事件,感兴趣可以参考:https://docs.spring.io/spring-data/jpa/docs/2.4.7/reference/html/#core.domain-events,但是由于异步新线程执行监听,所以会与发布者不在同一个事务中,若当你保存一个用户信息之后,马上异步监听然后从数据库读出此数据,可能会不存在。解决办法:要么改成同步监听,在一个事务中。要么在异步监听中sleep一段时间,等待事务提交。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值