spring boot 整合 谷歌guava的EventBus 實現單機版的消息發佈訂閱
大型分佈式系統,直接用mq解耦,那麼單機系統怎麼辦,可以考慮用EventBus
用EventBus的好處也是異步解耦,和mq的類似,可以勉強認爲是單機版的mq
先解釋,後附示例代碼(jdk1.8)
EventBus 處理的事情類似觀察者模式,基於事件驅動,觀察者們監聽自己感興趣的特定事件,進行相應的處理。
流程是這樣的:
1、先自定義一個註解@EventBusListener,所有異步實現的類都加這個註解,代表自己是一個監聽器
2、再定義一個實現類,就用這個@EventBusListener註解,代表自己是監聽器
類中方法用@Subscribe註解,方法入參類和後面寫的發送異步或同步消息的類保持一致即可,這個類也叫消息體,是這個消息的注意內容,比如訂單號id,消息體最好是自己定義的不同的類
3、核心的類是EventBusCenter,負責將帶有@EventBusListener註解的bean註冊成一個監聽器,並提供發送異步或同步消息的方法入口
4、業務service中注入EventBusCenter,並調用發送異步或同步消息的方法,方法入參是消息體,不同的消息體對應不同的發佈訂閱,訂閱的時候也是根據這個消息體來區分不同類型的消息的
簡單的源碼解讀
核心類是:com.google.common.eventbus.SubscriberRegistry
裏面有個靜態變量:subscriberMethodsCache
類在初始化的時候,會給這個靜態變量複製,拿到所有的@Subscribe註釋的方法和對應類的映射關係
然後我們代碼中會註冊監聽器,就把方法和監聽器對應上了
至此,方法的入參類型和方法和類的對應關係就知道了,key就是方法參數類型,value就是一組對應的方法和類
用的時候,調用asyncEventBus.post(event);
入參event就是真正的消息體類型,然後會根據這個類型去上面找對應的方法和類,然後獲取bean,調用
所以說,這個發佈訂閱,就是通過消息體類型去唯一識別的方法的。
對比mq的主題概念,這個消息體類型,就可以看成是主題的意思。
下面附示例代碼
模擬OrderService中訂單創建後,發送訂單創建的異步事件,再發送訂單修改的異步事件,再發送訂單修改的同步事件
訂閱端是OrderChangeListener和OrderChangeListener02兩個訂閱
OrderChangeListener訂閱了訂單創建和訂單修改事件
OrderChangeListener02訂閱了訂單創建事件
執行流程是:
啓動springboot,註冊bean的時候遇到EventBusCenter,開始註冊OrderChangeListener和OrderChangeListener02爲監聽器
啓動springboot後立即執行FistRun類,裏面直接調用訂單創建方法,發佈訂單創建和修改的消息
OrderChangeListener和OrderChangeListener02消費消息,完
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>22.0</version>
</dependency>
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface EventBusListener {
}
@Component
public class EventBusCenter {
// 管理同步事件
private EventBus syncEventBus = new EventBus();
// 管理異步事件
private AsyncEventBus asyncEventBus = new AsyncEventBus(Executors.newCachedThreadPool());
public void postSync(Object event) {
syncEventBus.post(event);
}
public void postAsync(Object event) {
asyncEventBus.post(event);
}
@PostConstruct
public void init() {
// 獲取所有帶有 @EventBusListener 的 bean,將他們註冊爲監聽者
List<Object> listeners = SpringContextUtils.getBeansWithAnnotation(EventBusListener.class);
for (Object listener : listeners) {
asyncEventBus.register(listener);
syncEventBus.register(listener);
}
}
}
@Component
public class SpringContextUtils implements BeanFactoryPostProcessor {
private static ConfigurableListableBeanFactory beanFactory;
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
SpringContextUtils.beanFactory = configurableListableBeanFactory;
}
public static <T> T getBean(String name) throws BeansException {
return (T) beanFactory.getBean(name);
}
public static <T> T getBean(Class<T> clz) throws BeansException {
T result = beanFactory.getBean(clz);
return result;
}
public static <T> List<T> getBeansOfType(Class<T> type) {
return beanFactory.getBeansOfType(type).entrySet().stream().map(entry -> entry.getValue()).collect(Collectors.toList());
}
// 上面的例子用到了這個
public static List<Object> getBeansWithAnnotation(Class<? extends Annotation> annotationType) {
Map<String, Object> beansWithAnnotation = beanFactory.getBeansWithAnnotation(annotationType);
// java 8 的寫法,將 map 的 value 收集起來到一個 list 中
return beansWithAnnotation.entrySet().stream().map(entry -> entry.getValue()).collect(Collectors.toList());
// java 7
// List<Object> result = new ArrayList<>();
// for (Map.Entry<String, Object> entry : beansWithAnnotation.entrySet()) {
// result.add(entry.getValue());
// }
// return result;
}
}
@Data
public class OrderCreatedEvent {
private long orderId;
private long userId;
public OrderCreatedEvent(long orderId, long userId) {
this.setOrderId(orderId);
this.setUserId(userId);
}
}
@Data
public class OrderChangeEvent {
private long orderId;
private long userId;
public OrderChangeEvent(long orderId, long userId) {
this.setOrderId(orderId);
this.setUserId(userId);
}
}
@Component
@EventBusListener
@Slf4j
public class OrderChangeListener {
@Subscribe
public void created(OrderCreatedEvent event) throws InterruptedException {
long orderId = event.getOrderId();
Thread.sleep(300);
log.info("訂單創建監聽,發送短信,orderId=" + orderId);
}
@Subscribe
public void change(OrderChangeEvent event) throws InterruptedException {
long orderId = event.getOrderId();
Thread.sleep(200);
log.info("訂單修改監聽,物流變化,orderId=" + orderId);
}
}
@Component
@EventBusListener
@Slf4j
public class OrderChangeListener02 {
@Subscribe
public void created(OrderCreatedEvent event) {
long orderId = event.getOrderId();
long userId = event.getUserId();
// 訂單創建成功後的各種操作,如發短信、發郵件等等。
// 注意,事件可以被訂閱多次,也就是說可以有很多方法監聽 OrderCreatedEvent 事件,
// 所以沒必要在一個方法中處理發短信、發郵件、更新庫存等
log.info("訂單創建監聽02,修改庫存,orderId=" + orderId);
}
}
@Service
@Slf4j
public class OrderService {
@Autowired
private EventBusCenter eventBusCenter;
public void createOrder() throws InterruptedException {
// 創建訂單
// 發送異步事件
eventBusCenter.postAsync(new OrderCreatedEvent(1L, 1L));
System.out.println("發送異步事件,訂單創建");
eventBusCenter.postAsync(new OrderChangeEvent(1L, 1L));
System.out.println("發送異步事件,訂單修改");
//發送同步事件
Thread.sleep(500);
try {
System.out.println("發送同步事件,訂單修改,開始");
eventBusCenter.postSync(new OrderChangeEvent(1L, 1L));
System.out.println("發送同步事件,訂單修改,結束");
} catch (Exception e) {
log.error("發送同步事件,抓異常");
}
}
}
@Component
@Slf4j
@Order(1)
public class FistRun implements CommandLineRunner {
@Autowired
private OrderService orderService;
@Override
public void run(String... args) throws Exception {
log.info("FistRun start===============");
orderService.createOrder();
}
}