一、观察者模型
观察者模式,就是生产者消费者模式。下面我们来简单的建立一个生产者消费者模型。
1、建立生产者类
package com.dgw.hostel.observer;
import org.apache.commons.collections.CollectionUtils;
import java.util.ArrayList;
import java.util.List;
/**
* 生产者,发送消息
*/
public class ProductServer {
private List<ObServer> list;
public ProductServer(){
list=new ArrayList<>();
}
public void sendMessage(Object message){
if (CollectionUtils.isNotEmpty(list)) {
for (ObServer obServer : list) {
obServer.receiverMessage(message);
}
}
}
public void addObServer(ObServer obServer) {
list.add(obServer);
}
}
2、建立生产者对应的消费者(观察者)接口
package com.dgw.hostel.observer;
/**
* 观察者,接收消息
*/
public interface ObServer {
public void receiverMessage(Object message);
}
3、让具体需要接受消息的spring bean类,去实现该接口。
package com.dgw.hostel.observer;
import lombok.Data;
import org.springframework.stereotype.Component;
@Component
public class ObServerImpl implements ObServer {
@Override
public void receiverMessage(Object message) {
System.out.println("观察者1"+message);
}
}
package com.dgw.hostel.observer;
import org.springframework.stereotype.Component;
@Component
public class ReceiverMessage implements ObServer{
@Override
public void receiverMessage(Object message) {
System.out.println("观察者2:"+message);
}
}
4、在具体的项目中取使用,生产消息
package com.dgw.hostel;
import com.dgw.hostel.observer.ObServerBuider;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest
public class HostelApplicationTests {
@Test
public void contextLoads() {
ObServerBuider.buider().sendMessage("发送消息测试");
}
}
5、测试结果
观察者1发送消息测试
观察者2:发送消息测试
二、存在问题
1、假设我要建立另外一个消息主题,会员推送:只给会员推送非会员不进行推送,如何扩展?
2、能否进一步让消费者变得更简洁,进一步解耦让消费者不用关心有观察者这个东西,只用关心某个消息主题即可?
三、解决存在问题
针对第一个问题,我们采用了建造者模式,为某一个主题单独创建一个建造者方法。来构建这一个主题的生产者-消费者。这个也复合开闭原则。
如果有另外一主题的消息需要发送时,在生产者和消费者里面加特定方法来发送这一主题的消息。
1、观察者改造
package com.dgw.hostel.observer;
/**
* 观察者,接收消息
*/
public interface ObServer {
public void receiverMessage(Object message);
//会员充值,发送消息
public void sendMessage(Object message);
}
2、生产者改造
package com.dgw.hostel.observer;
import org.apache.commons.collections.CollectionUtils;
import java.util.ArrayList;
import java.util.List;
/**
* 生产者,发送消息
*/
public class ProductServer {
private List<ObServer> list;
public ProductServer(){
list=new ArrayList<>();
}
//接收消息测试
public void receiverMessage(Object message){
if (CollectionUtils.isNotEmpty(list)) {
for (ObServer obServer : list) {
obServer.receiverMessage(message);
}
}
}
//发送消息测试
public void sendMessage(Object message){
if (CollectionUtils.isNotEmpty(list)) {
for (ObServer obServer : list) {
obServer.sendMessage(message);
}
}
}
public void addObServer(ObServer obServer) {
list.add(obServer);
}
}
3、spring bean具体消费者类改造
package com.dgw.hostel.observer;
import lombok.Data;
import org.springframework.stereotype.Component;
@Component
public class ObServerImpl implements ObServer {
@Override
public void receiverMessage(Object message) {
System.out.println("观察者1"+message);
}
@Override
public void sendMessage(Object message) {
System.out.println("观察者1"+message);
}
}
package com.dgw.hostel.observer;
import org.springframework.stereotype.Component;
@Component
public class ReceiverMessage implements ObServer{
@Override
public void receiverMessage(Object message) {
System.out.println("观察者2:"+message);
}
@Override
public void sendMessage(Object message) {
System.out.println("观察者2:"+message);
}
}
虽然复合开闭原则,但是我们发现,我们已发出消息,所有观察者的子类都必须去实现观察者接口的所有方法,而另外一个主题的方法是我们不需要的。这个迪米特法则不复合。
我们继续改造,为生产者添加消息类别:区分消息的主题。
package com.dgw.hostel.observer;
import org.apache.commons.collections.CollectionUtils;
import java.util.ArrayList;
import java.util.List;
/**
* 生产者,发送消息
*/
public class ProductServer {
private String msgType;
private List<ObServer> list;
public ProductServer(){
list=new ArrayList<>();
}
public ProductServer(String msgType){
list=new ArrayList<>();
this.msgType=msgType;
}
//接收消息测试
public void receiverMessage(Object message){
if (CollectionUtils.isNotEmpty(list)) {
for (ObServer obServer : list) {
obServer.receiverMessage(message);
}
}
}
//发送消息测试
public void sendMessage(Object message) {
if (CollectionUtils.isNotEmpty(list)) {
for (ObServer obServer : list) {
try {
MessageListener sendMessage = obServer.getClass().getMethod("sendMessage", Object.class).getAnnotation(MessageListener.class);
if (sendMessage!=null && sendMessage.value().equals(this.msgType)) {
obServer.sendMessage(message);
}
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
}
}
public void addObServer(ObServer obServer) {
list.add(obServer);
}
}
在具体的观察者(消费者)方法上添加 @MessageListener 注解,在生产者发送消息时,首先获取@MessageListener注解的值。判断注解的值是否是该生产者的消息主题,如果是,则发送消息,不是则 不发送。
package com.dgw.hostel.observer;
import lombok.Data;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
@Component
public class ObServerImpl implements ObServer {
@Override
public void receiverMessage(Object message) {
System.out.println("观察者1"+message);
}
@MessageListener(value = "sendMessage")
@Override
public void sendMessage(Object message) {
System.out.println("观察者1"+message);
}
}
添加过后之后,测试发现,只有加了@MessageListener注解,并且值为sendMessage时,才会接收到该消息。
上面获取注解的时候我们依赖了消费者的方法名,我们继续改造,使其不依赖该方法名获取注解。
//发送消息测试
public void sendMessage(Object message) {
if (CollectionUtils.isNotEmpty(list)) {
for (ObServer obServer : list) {
try {
Class<? extends ObServer> clazz = obServer.getClass();
Method[] methods = clazz.getMethods();
for (Method method : methods) {
MessageListener messageListener = method.getAnnotation(MessageListener.class);
if (messageListener!=null && messageListener.value().equals(this.msgType)) {
obServer.sendMessage(message);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
我们再来看,加入我们具体的实现类里面有两个方法都有@MessageListener注解,可现在调用还是依赖以ObServer接口的sendMessage()方法,我想一个具体的类里面定义两个带@MessageListener注解的方法。怎么实现呢?再来改造通过反射调用,同时删除观察者接口中的方法
//发送消息测试
public void sendMessage(Object message) {
if (CollectionUtils.isNotEmpty(list)) {
for (ObServer obServer : list) {
try {
Class<? extends ObServer> clazz = obServer.getClass();
Method[] methods = clazz.getMethods();
for (Method method : methods) {
MessageListener messageListener = method.getAnnotation(MessageListener.class);
if (messageListener!=null && messageListener.value().equals(this.msgType)) {
// obServer.sendMessage(message);
method.invoke(obServer,message);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
package com.dgw.hostel.observer;
/**
* 观察者,接收消息
*/
public interface ObServer {
//会员充值,发送消息
// public void sendMessage(Object message);
}
再来看,我们现在生产者调用,消费者已经不依赖消费者中的方法名称。也实现了消息分组,可我们实现了观察者接口。那这个接口看似有点多余,我们继续进行改造。直接从spring 容器中获取bean,然后在调用bean中带有该注解的方法。
1、建造者改造,通过某个注解获取beans
package com.dgw.hostel.observer;
import com.dgw.hostel.util.SpringContextUtil;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
/**
* 用建造者模式,创建观察者对象
*/
public class ObServerBuider {
/**
* 建造者方法
*/
public static ProductServer buider(String msgType) {
//发送者
ProductServer productServer = new ProductServer(msgType);
//获取观察者
Map<String, Object> beans = SpringContextUtil.getBeansWithAnnotation(MessageListener.class);
Set<String> strings = beans.keySet();
for (String key : strings) {
Object object=beans.get(key);
//将观察者添加到发送者集合中
productServer.addObServer(object);
}
return productServer;
}
}
2、生产者改造,将观察者直接提升为Object
package com.dgw.hostel.observer;
import org.apache.commons.collections.CollectionUtils;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
/**
* 生产者,发送消息
*/
public class ProductServer {
private String msgType;
private List<Object> list;
public ProductServer(){
list=new ArrayList<>();
}
public ProductServer(String msgType){
list=new ArrayList<>();
this.msgType=msgType;
}
public void addObServer(Object object) {
list.add(object);
}
//发送消息测试
public void sendMessage(Object message) {
if (CollectionUtils.isNotEmpty(list)) {
for (Object object : list) {
try {
Class<?> clazz = object.getClass();
Method[] methods = clazz.getMethods();
for (Method method : methods) {
MessageListener messageListener = method.getAnnotation(MessageListener.class);
if (messageListener!=null && messageListener.value().equals(this.msgType)) {
method.invoke(object,message);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
3、具体的消费者改造
package com.dgw.hostel.observer;
import lombok.Data;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
@Component
@MessageListener
public class ObServerImpl {
@MessageListener(value = "sendMessage")
public void sendMessage(Object message) {
System.out.println("观察者1"+message);
}
@MessageListener(value = "sendMessage")
public void sendMessage2(Object message) {
System.out.println("sendMessage2观察者1"+message);
}
public void sendMessage3(Object message) {
System.out.println("观察者1"+message);
}
@MessageListener(value = "love")
public void sendMessage4(Object message) {
System.out.println("观察者1---love==="+message);
}
}
package com.dgw.hostel.observer;
import org.springframework.stereotype.Component;
@Component
@MessageListener
public class ReceiverMessage {
@MessageListener(value = "love")
public void sendMessage(Object message) {
System.out.println("观察者2--love===:"+message);
}
}
4、测试代码、发送两个不同的主题
package com.dgw.hostel;
import com.dgw.hostel.observer.ObServerBuider;
import com.dgw.hostel.observer.ProductServer;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest
public class HostelApplicationTests {
@Test
public void contextLoads() {
ObServerBuider.buider("sendMessage").sendMessage("发送消息测试");
ObServerBuider.buider("love").sendMessage("鲁静恩");
}
}
5、接收到的消息
观察者1发送消息测试
sendMessage2观察者1发送消息测试
观察者1---love===鲁静恩
观察者2--love===:鲁静恩
我们能不能直接不需要在类上加该注解,直接只用在方法上加注解就行呢?答案是行的,把spring ioc中的所有beans都取出来遍历一遍,但是效率太低了,我个人很不推荐,相比加个注解在类上,我个人还是觉得直接去实现某个接口调用该接口的方法,是个值得考虑的方案。
四、完整代码
package com.dgw.hostel.observer;
import org.springframework.stereotype.Indexed;
import java.lang.annotation.*;
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Indexed
public @interface MessageListener {
//消息主题
String value() default "";
}
package com.dgw.hostel.observer;
/**
* 观察者,接收消息
*/
public interface ObServer {
//会员充值,发送消息
// public void sendMessage(Object message);
}
package com.dgw.hostel.observer;
import com.dgw.hostel.util.SpringContextUtil;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
/**
* 用建造者模式,创建观察者对象
*/
public class ObServerBuider {
/**
* 建造者方法
*/
public static ProductServer buider(String msgType) {
//发送者
ProductServer productServer = new ProductServer(msgType);
//获取观察者
Map<String, Object> beans = SpringContextUtil.getBeansWithAnnotation(MessageListener.class);
Set<String> strings = beans.keySet();
for (String key : strings) {
Object object=beans.get(key);
//将观察者添加到发送者集合中
productServer.addObServer(object);
}
return productServer;
}
}
package com.dgw.hostel.observer;
import lombok.Data;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
@Component
@MessageListener
public class ObServerImpl {
@MessageListener(value = "sendMessage")
public void sendMessage(Object message) {
System.out.println("观察者1"+message);
}
@MessageListener(value = "sendMessage")
public void sendMessage2(Object message) {
System.out.println("sendMessage2观察者1"+message);
}
public void sendMessage3(Object message) {
System.out.println("观察者1"+message);
}
@MessageListener(value = "love")
public void sendMessage4(Object message) {
System.out.println("观察者1---love==="+message);
}
}
package com.dgw.hostel.observer;
import org.apache.commons.collections.CollectionUtils;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
/**
* 生产者,发送消息
*/
public class ProductServer {
private String msgType;
private List<Object> list;
public ProductServer(){
list=new ArrayList<>();
}
public ProductServer(String msgType){
list=new ArrayList<>();
this.msgType=msgType;
}
public void addObServer(Object object) {
list.add(object);
}
//发送消息测试
public void sendMessage(Object message) {
if (CollectionUtils.isNotEmpty(list)) {
for (Object object : list) {
try {
Class<?> clazz = object.getClass();
Method[] methods = clazz.getMethods();
for (Method method : methods) {
MessageListener messageListener = method.getAnnotation(MessageListener.class);
if (messageListener!=null && messageListener.value().equals(this.msgType)) {
method.invoke(object,message);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
package com.dgw.hostel.observer;
import org.springframework.stereotype.Component;
@Component
@MessageListener
public class ReceiverMessage {
@MessageListener(value = "love")
public void sendMessage(Object message) {
System.out.println("观察者2--love===:"+message);
}
}
package com.dgw.hostel.util;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
import java.util.Map;
@Component
public class SpringContextUtil implements ApplicationContextAware {
// Spring应用上下文环境
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
SpringContextUtil.applicationContext=applicationContext;
}
//获取spring上下文
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
//获取spring容器中的bean
@SuppressWarnings("unchecked")
public static <T>T getBean(String beanId) throws BeansException {
return (T)applicationContext.getBean(beanId);
}
public static <T>T getBean(Class type) throws BeansException {
return (T)applicationContext.getBean(type);
}
//从容器中获取所有接口的实现类
public static <T> Map<String, T> getBeansOfType(Class type){
return applicationContext.getBeansOfType(type);
}
//获取带有指定注解的beans
public static Map<String, Object> getBeansWithAnnotation(Class type){
return applicationContext.getBeansWithAnnotation(type);
}
}
package com.dgw.hostel;
import com.dgw.hostel.observer.ObServerBuider;
import com.dgw.hostel.observer.ProductServer;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest
public class HostelApplicationTests {
@Test
public void contextLoads() {
ObServerBuider.buider("sendMessage").sendMessage("发送消息测试");
ObServerBuider.buider("love").sendMessage("鲁静恩");
}
}
前面写这个例子的时候没有放到正式环境下使用过,今天放上去之后发现了,在service层存在aop代理对象时,代理对象的方法上取不到目标对象上的注解,所以对ProductService做了些修改。同时加入了按顺序发送消息。
package com.dgw.hostel.observer;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.aop.support.AopUtils;
import java.lang.reflect.Method;
import java.util.*;
/**
* 生产者,发送消息
*/
public class ProductServer {
private String msgType;
private List<Object> list;
public ProductServer(String msgType){
list=new ArrayList<>();
this.msgType=msgType;
}
public void addObServer(Object object) {
list.add(object);
}
//发送消息测试
public void sendMessage(Object ... message) {
if (CollectionUtils.isNotEmpty(list)) {
for (Object object : list) {
try {
Class<?> clazz = getTargetClass(object);
Method[] methods = clazz.getMethods();
for (Method method : methods) {
MessageListener messageListener = method.getAnnotation(MessageListener.class);
if (messageListener!=null && messageListener.value().equals(this.msgType)) {
object.getClass().getMethod(method.getName(),Object[].class).invoke(object,message);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
//发送消息,有顺序的执行
public void sendOrderMessage(Object ... message) {
if (CollectionUtils.isNotEmpty(list)) {
Set<Integer> orders = new TreeSet<>();
Map<Integer,Object> targetObj=new HashMap<>();
Map<Integer,Method> targetMethods=new HashMap<>();
for (Object object : list) {
try {
Class<?> clazz = getTargetClass(object);
Method[] methods = clazz.getMethods();
for (Method method : methods) {
MessageListener messageListener = method.getAnnotation(MessageListener.class);
if (messageListener!=null && messageListener.value().equals(this.msgType) && messageListener.order() > -1) {
int order = messageListener.order();
orders.add(order);
targetObj.put(order,object);
targetMethods.put(order,method);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
if (orders.size() > 0) {
for (Integer order : orders) {
try {
Object obj = targetObj.get(order);
Method method = targetMethods.get(order);
obj.getClass().getMethod(method.getName(),method.getParameterTypes()).invoke(obj,message);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
/**
* 根据代理对象获取目标对象的类类型(处理有aop代理对象时,拿不到方法上的注解)
* @param proxyObj
* @return
*/
private Class<?> getTargetClass(Object proxyObj){
return AopUtils.isAopProxy(proxyObj)? AopUtils.getTargetClass(proxyObj) : proxyObj.getClass();
}
}
添加了个观察者注解@Observer 来标识观察者类。对@MessageListener 的value属性去除默认值,强制性限制value必填。
package com.dgw.hostel.observer;
import java.lang.annotation.*;
/**
* 观察者,用来标识接收消息的类
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ObServer {
}
package com.dgw.hostel.observer;
import org.springframework.stereotype.Indexed;
import java.lang.annotation.*;
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Indexed
public @interface MessageListener {
//消息主题
String value();
//执行顺序
int order() default -1;
}
对建造者类稍作修改,改为Observe来获取
package com.dgw.hostel.observer;
import com.dgw.hostel.util.SpringContextUtil;
import java.util.Map;
import java.util.Set;
/**
* 用建造者模式,创建观察者对象
*/
public class ObServerBuider {
/**
* 建造者方法
*/
public static ProductServer buider(String msgType) {
//发送者
ProductServer productServer = new ProductServer(msgType);
//获取观察者
Map<String, Object> beans = SpringContextUtil.getBeansWithAnnotation(ObServer.class);
Set<String> strings = beans.keySet();
for (String key : strings) {
Object object=beans.get(key);
//将观察者添加到发送者集合中
productServer.addObServer(object);
}
return productServer;
}
}
对生产者类改为可变参数,只要接收端和发送端参数列表一致。就可以发送多个参数。
package com.dgw.hostel.observer;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.aop.support.AopUtils;
import java.lang.reflect.Method;
import java.util.*;
/**
* 生产者,发送消息
*/
public class ProductServer {
private String msgType;
private List<Object> list;
public ProductServer(String msgType){
list=new ArrayList<>();
this.msgType=msgType;
}
public void addObServer(Object object) {
list.add(object);
}
//发送消息测试
public void sendMessage(Object ... message) {
if (CollectionUtils.isNotEmpty(list)) {
for (Object object : list) {
try {
Class<?> clazz = getTargetClass(object);
Method[] methods = clazz.getMethods();
for (Method method : methods) {
MessageListener messageListener = method.getAnnotation(MessageListener.class);
if (messageListener!=null && messageListener.value().equals(this.msgType)) {
object.getClass().getMethod(method.getName(),method.getParameterTypes()).invoke(object,message);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
//发送消息,有顺序的执行
public void sendOrderMessage(Object ... message) {
if (CollectionUtils.isNotEmpty(list)) {
Set<Integer> orders = new TreeSet<>();
Map<Integer,Object> targetObj=new HashMap<>();
Map<Integer,Method> targetMethods=new HashMap<>();
for (Object object : list) {
try {
Class<?> clazz = getTargetClass(object);
Method[] methods = clazz.getMethods();
for (Method method : methods) {
MessageListener messageListener = method.getAnnotation(MessageListener.class);
if (messageListener!=null && messageListener.value().equals(this.msgType) && messageListener.order() > -1) {
int order = messageListener.order();
orders.add(order);
targetObj.put(order,object);
targetMethods.put(order,method);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
if (orders.size() > 0) {
for (Integer order : orders) {
try {
Object obj = targetObj.get(order);
Method method = targetMethods.get(order);
obj.getClass().getMethod(method.getName(),method.getParameterTypes()).invoke(obj,message);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
/**
* 根据代理对象获取目标对象的类类型(处理有aop代理对象时,拿不到方法上的注解)
* @param proxyObj
* @return
*/
private Class<?> getTargetClass(Object proxyObj){
return AopUtils.isAopProxy(proxyObj)? AopUtils.getTargetClass(proxyObj) : proxyObj.getClass();
}
}