发布订阅模式就是发布者发送消息,订阅者接收到消息后进行处理,降低了耦合度,很多业务场景都应用到了这样的场景,比如说下单完成后,可能要还有消息提醒,给予抽奖资格等后续处理,此时就可以使用发布订阅模式去进行解耦合处理;
目前对于发布订阅模式大多已经使用mq进行处理了,至于java应用内部的发布订阅模式使用的并不多,博主最近因为工作需要,需要写一个发布订阅模式,现在完成了最基础的部分,支持同步异步推送消息。
设计图如下:
可以看到主要分为三部分:发布者,消息订阅器和订阅者,发布者推送消息到订阅器,订阅器将消息派发到订阅者,发布者根据消息类型分类,一个发布者默认只发布一种消息类型(可通过重写接口方法,发布多种消息),订阅者也根据消息类型分类,一个订阅者只处理一种消息类型。
代码如下:
订阅者接口
/**
* 订阅者接口
*
* @author zeng wenbin
* @date Created in 2019/8/12
*/
public interface ISubscriber {
/**
* 获取此订阅对象需求的消息类型
* 需要子类实现
*
* @return 消息类型
*/
String getMessageType();
/**
* 注册到订阅器
* 默认实现
*/
default void registry(){
SubscribePublish.getSubscribePublish().subcribe(this);
}
/**
* 退订
* 默认实现
*/
default void unSubcribe(){
SubscribePublish.getSubscribePublish().unSubcribe(this);
}
/**
* 接收消息并处理
* 需要子类实现
*
* @param message 消息实例
*/
void receiveMessage(Object message) throws Exception;
}
发布者接口
/**
* 发布者接口
*
* @author zeng wenbin
* @date Created in 2019/8/12
*/
public interface IPublisher {
/**
* 获取消息类型
* 需要子类实现
*
* @return 获取消息类型
*/
String getMessageType();
/**
* 生产消息 (同步处理)
* 默认实现
*
* @param message 消息实体
*/
default void publishMessageBySync(Object message){
if (getMessageType() == null){
throw new IllegalArgumentException("消息发送失败!消息类型为null.");
}
SubscribePublish.getSubscribePublish().publishMessageBySync(getMessageType(), message);
}
/**
* 生产消息 (异步处理)
* 默认实现
*
* @param message 消息实体
*/
default void publishMessageByAsyn(Object message){
if (getMessageType() == null){
throw new IllegalArgumentException("消息发送失败!消息类型为null.");
}
SubscribePublish.getSubscribePublish().publicMessageByAsyn(getMessageType(), message);
}
}
消息订阅器
/**
* 订阅器类
*
* @author zeng wenbin
* @date Created in 2019/8/12
*/
public class SubscribePublish {
/**
* 订阅者map,key为消息类型
*/
private Map<String, List<ISubscriber>> subcriberMap = new HashMap<>();
/**
* 获取计算机有几个核
*/
private int processors = Runtime.getRuntime().availableProcessors();
/**
* 创建线程池:
* 参数:
* 核心线程数:计算机内核数
* 最大线程数:计算机内核数*5
* 空闲时间:60s,超过60s超过核心线程数的空闲线程被杀死
* 任务队列长度:200
* 线程池工厂:使用了jdk默认工厂
* handler(队列满时的任务拒绝策略):让提交任务的线程去执行
*/
private ExecutorService threadPool = new ThreadPoolExecutor(processors, processors * 5, 60, TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>(200), Executors.defaultThreadFactory(),
new ThreadPoolExecutor.CallerRunsPolicy());
/**
* 本类实例
*/
private static SubscribePublish subscribePublish = new SubscribePublish();
/**
* 私有此类的构造
* 使其不允许被外界实例化,仅类内部存在一个实例
*/
private SubscribePublish() {
}
/**
* 注册订阅者
*
* @param subscriber 订阅对象
*/
public void subcribe(ISubscriber subscriber) {
if (subscriber == null) {
throw new IllegalArgumentException("订阅失败!订阅对象不能为null.");
}
//获取消息类型
String messageType = subscriber.getMessageType();
if (messageType == null) {
throw new IllegalArgumentException("订阅失败!订阅对象的消息类型不能为null.");
}
//绑定订阅对象
if (subcriberMap.get(messageType) == null) {
List<ISubscriber> subcribers = new ArrayList<>();
subcribers.add(subscriber);
subcriberMap.put(messageType, subcribers);
} else {
subcriberMap.get(messageType).add(subscriber);
}
}
/**
* 退订
*
* @param subscriber 订阅对象
*/
public void unSubcribe(ISubscriber subscriber) {
if (subscriber != null && subscriber.getMessageType() != null) {
List<ISubscriber> subscribers = subcriberMap.get(subscriber.getMessageType());
if (subscribers != null) {
subscribers.remove(subscriber);
}
}
}
/**
* 生产同步消息
*
* @param messageType 消息类型
* @param message 消息
*/
public void publishMessageBySync(String messageType, Object message) {
sendMessage(messageType, message);
}
/**
* 生产异步消息
*
* @param messageType 消息类型
* @param message 消息
*/
public void publicMessageByAsyn(String messageType, Object message) {
/**
* 此处把for循环写在threadPool.submit方法外面代表每个订阅者对消息的处理都是由一个线程执行
* 如果把for循环写在threadPool.submit方法里面则代表一组相同类型的订阅者对同一个消息的处理是由一个线程处理
*/
subcriberMap.get(messageType).forEach(subscriber -> {
this.threadPool.submit(() -> {
try {
subscriber.receiveMessage(message);
} catch (Exception e) {
e.printStackTrace();
}
});
});
}
private void sendMessage(String messageType, Object message) {
subcriberMap.get(messageType).forEach(subscriber -> {
try {
subscriber.receiveMessage(message);
} catch (Exception e) {
e.printStackTrace();
}
});
}
/**
* 获取本类实例
*
* @return SubscribePublish对象
*/
public static SubscribePublish getSubscribePublish() {
return subscribePublish;
}
}
发布者实现类
/**
* 发布者实现类
*
* @author zeng wenbin
* @date Created in 2019/8/12
*/
public class PublisherImp implements IPublisher {
/**
* 消息类型
*/
private String messageType;
public PublisherImp(String messageType) {
this.messageType = messageType;
}
@Override
public String getMessageType() {
return messageType;
}
}
订阅者实现类
/**
* 订阅者实现类
*
* @author zeng wenbin
* @date Created in 2019/8/12
*/
public class SubscriberImp implements ISubscriber {
private String messageType;
public SubscriberImp(String messageType) {
this.messageType = messageType;
//注册到订阅器
registry();
}
@Override
public String getMessageType() {
return messageType;
}
@Override
public void receiveMessage(Object message) {
//此处做一些计算,增加订阅者处理消息耗时
for (int i = 0; i < 1000; i++) {
for (int j = 0; j < 1000; j++) {
int i1 = i + j;
}
}
System.out.println("消息类型:"+this.messageType+" 消息:"+message.toString() + ",线程名称:" + Thread.currentThread().getName());
}
}
测试代码
public static void main(String[] args) {
IPublisher musciPublisher = new PublisherImp("music");
new SubscriberImp("music");
new SubscriberImp("book");
//第一次调用线程池执行任务时,线程池需要消耗一定时间预热(我这大概40毫秒)
musciPublisher.publishMessageByAsyn("线程池预热");
long start = System.currentTimeMillis();
for (int i = 0; i < 100; i++) {
// musciPublisher.publishMessageByAsyn("来自musicPublisher的异步消息");
musciPublisher.publishMessageBySync("来自musicPublisher的同步消息");
}
long end = System.currentTimeMillis();
System.out.println("用时:"+(end-start)+"毫秒");
}
结果(同步执行):
...
消息类型:music 消息:来自musicPublisher的同步消息,线程名称:main
消息类型:music 消息:来自musicPublisher的同步消息,线程名称:main
消息类型:music 消息:来自musicPublisher的同步消息,线程名称:main
消息类型:music 消息:来自musicPublisher的同步消息,线程名称:main
消息类型:music 消息:来自musicPublisher的同步消息,线程名称:main
消息类型:music 消息:来自musicPublisher的同步消息,线程名称:main
消息类型:music 消息:来自musicPublisher的同步消息,线程名称:main
消息类型:music 消息:来自musicPublisher的同步消息,线程名称:main
消息类型:music 消息:来自musicPublisher的同步消息,线程名称:main
消息类型:music 消息:来自musicPublisher的同步消息,线程名称:main
消息类型:music 消息:来自musicPublisher的同步消息,线程名称:main
用时:40毫秒
结果(异步执行):
用时:1毫秒
消息类型:music 消息:来自musicPublisher的异步消息,线程名称:pool-1-thread-3
消息类型:music 消息:来自musicPublisher的异步消息,线程名称:pool-1-thread-5
消息类型:music 消息:来自musicPublisher的异步消息,线程名称:pool-1-thread-5
消息类型:music 消息:来自musicPublisher的异步消息,线程名称:pool-1-thread-2
消息类型:music 消息:来自musicPublisher的异步消息,线程名称:pool-1-thread-4
消息类型:music 消息:来自musicPublisher的异步消息,线程名称:pool-1-thread-6
消息类型:music 消息:来自musicPublisher的异步消息,线程名称:pool-1-thread-5
消息类型:music 消息:来自musicPublisher的异步消息,线程名称:pool-1-thread-7
消息类型:music 消息:来自musicPublisher的异步消息,线程名称:pool-1-thread-2
...