架构封装得思考何时调用开发者的方法,开发者的思想是如何处理
1.定义交换机的名字和事件常量
public interface EventConstact {
/**
* 交换机的名称
*/
String EXCHANGE_NAME = "event-exchange";
/**
* 事件类型常量
*/
String EVENT_LOGIN = "login";//用户登录事件
String EVENT_HOTAL_INSERT = "hotal_insert";//酒店新增事件
String EVENT_HOTAL_ROOM_INSERT = "hotal_room_insert";//酒店客房新增事件
String EVENT_HOTAL_PRICE_UPDATE = "hotal_price_update";//酒店价格对象修改事件
String EVENT_HOTAL_CLICK = "hotal_click";//酒店点击率事件
String EVENT_HOTAL_ROOM_UPDATE = "hotal_room_update";//酒店房间修改事件
//.....
}
2.创建交换机:消费者通常创建队列,提供者创建交换机 因为提供者创建交互机没有消费者顶多把消息丢失,而不会报错,消费者创建队列没有人给他发消息他也不会报错。
3.参数:把交换机的名字封装出去,设置为持久化,不要自动删除
@Configuration
public class EventPublishConfiguration {
/**
* 事件发布者需要创建
* 事件接收者也需要创建
* 创建了一个交换机
* @return
*/
@Bean
public DirectExchange getExchange(){
return new DirectExchange(EventConstact.EXCHANGE_NAME, true, false);
}
}
4.动态事件发布定义了一个类
//事件发布者
@Component
public class EventUtil {
@Autowired
private RabbitTemplate rabbitTemplate;
public void publishEvent(String eventType, Object msg){
rabbitTemplate.convertAndSend(EventConstact.EXCHANGE_NAME, eventType, msg);
}
}
5.事件开始发布
/**
* 根据id查询酒店详情信息
* @return
*/
@RequestMapping("/getInfo")
public ResultData<Hotal> getInfo(Integer hid, String devId){
//查询酒店信息
Hotal hotal = hotalService.getById(hid);
//TODO 发生了一次点击率 ????????????????
//boolmfilter
// if (!bloomUtil.isExists("djl", devId + "-" + hid)) {
System.out.println(devId + " - " + hid + "算一次点击率!!!");
eventUtil.publishEvent(EventConstact.EVENT_HOTAL_CLICK, hotal);
//不存在,添加一次
// bloomUtil.addBloom("djl", devId + "-" + hid);
// } else {
// System.out.println(devId + " - " + hid + "已经点击过了!!!");
// }
return new ResultData<Hotal>().setData(hotal);
}
}
6.动态的监听事件类型和事件处理方法定义一个接口
/**
* 事件监听器的接口
*/
public interface EventListener<T> {
/**
* 监听的事件类型
* @return
*/
String getEventType();
/**
* 事件处理方法 - 核心
* @param msg
*/
void eventHandler(T msg);
}
7.在消费者消费事件只需要调用事件监听器的接口就可以消费,队列,交换机,路由建全部封装到事件总线中
/**
* 酒店事件的监听处理器
*/
@Component
public class HotalEventListener implements EventListener<Hotal> {
@Autowired
private ICityService cityService;
/**
* 处理"酒店新增"事件
* @return
*/
@Override
public String getEventType() {
return EventConstact.EVENT_HOTAL_INSERT;
}
/**
* 实际的触发方法
* @param msg
*/
@Override
public void eventHandler(Hotal msg) {
System.out.println("城市服务,接收到酒店新增事件," + msg);
cityService.updateCityHNumber(msg.getCid(), 1);
}
}
8.上面的这个类完全没有作用,因为HotalEventListener 根本没有监听队列不会调用他,我们得做两件事
1.创建队列
2. 队列绑定到交换机
3. 有几个 但是所有的队列的消息都发布到同个交换机上面,但是事件的消费是动态的,他会消费很多不同的事件,路由建是消费者决定,所以在路由建和队列和交换机是动态的:
4. 解决:
4.1队列的名称要实现动态的,每个微服务都有独有的名称,作为队列的名称,不管什么微服务调用都区别开来
4.2获得当前的处理器需要处理的事件类型 - 路由键
4.3绑定队列和交换机,路由建也得绑定
4.4 路由建只能绑定一个with方法返回值是String,不是数组所以得动态绑定
4.5在RabbitMQListener和EventConsumerConfiguration都注入了
@Autowired
private List eventListeners;
因为发布者和消费者都依赖了封装的包但是消费者没有创建eventListeners的类,会报错@ConditionalOnBean(EventListener.class)这个注解,判断spring容器有没有EventListener类,有就会被管理起来也说明了有是消费者没有就是提供者
@Configuration
//判断spring容器有没有EventListener类有就是消费者没有就是提供者
@ConditionalOnBean(EventListener.class)
public class EventConsumerConfiguration {
@Value("${spring.application.name}")
private String applicatiName;
@Autowired
private SpringContextUtil springContextUtil;
@Autowired
private List<EventListener> eventListeners;
//每个服务都有一个独立的队列
@Bean
public Queue getQueue(){
return new Queue(applicatiName + "-queue", true, false, false);
}
/**
* 队列和交换机的绑定???????????? 所有的队列的消息都发布到同个交换机上面,但是事件的消费是动态的,他会消费很多不同的事件,所以在路由建和队列和交换机是动态的:路由建与开发者决定
* with的参数是string不能多次绑定所以只能多次绑定
* @param getQueue
* @param getExchange
* @return
*/
@Bean
public Binding getBinding(Queue getQueue, DirectExchange getExchange){
//循环所有的EventListener实现类
eventListeners.stream().forEach(event -> {
//获得当前的处理器需要处理的事件类型 - 路由键
String eventType = event.getEventType();
System.out.println("绑定的路由键:" + eventType);
Binding binding = BindingBuilder.bind(getQueue).to(getExchange).with(eventType);
//动态将binding对象注册到Spring容器中(万一有两个时间类型直接加hashCode)
springContextUtil.registerBean(eventType + event.hashCode(), binding);
});
return null;
}
}
@Component
public class SpringContextUtil implements BeanDefinitionRegistryPostProcessor {
//注册bean的核心对象
private BeanDefinitionRegistry beanDefinitionRegistry;
/**
* 自定义工具方法 - 注册Bean对象
*/
public void registerBean(String beanName, Object bean){
//将bean封装成BeanDefinition对象
BeanDefinitionBuilder beanDefition = BeanDefinitionBuilder.genericBeanDefinition(bean.getClass(), new Supplier() {
@Override
public Object get() {
return bean;
}
});
//将BeanDefintion注册到Spring容器中
beanDefinitionRegistry.registerBeanDefinition(beanName, beanDefition.getBeanDefinition());
}
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
this.beanDefinitionRegistry = registry;
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
}
}
- 把rabbitmq的消息传给eventHandler处理该消息;监听message对象,message所有电子邮件的的超类,属性包括
(1)消息地址
(2)消息接收方
(3)消息主题和主体等
@Component
@ConditionalOnBean(EventListener.class)
public class RabbitMQListener {
@Autowired
private List<EventListener> eventListeners;
/**
* 监听指定队列
*/
@RabbitListener(queues = "${spring.application.name}-queue")
public void msgHandler(Message message){
//获得发布消息的路由键 - 事件类型
String routingKey = message.getMessageProperties().getReceivedRoutingKey();
//交给对应的EventListener处理
eventListeners.stream().forEach(event -> {
//判断事件类型和路由键是否匹配
if (event.getEventType().equals(routingKey)) {
//直接调用当前event处理该消息
byte[] msgBytes = message.getBody();
event.eventHandler(SerializationUtils.deserialize(msgBytes));
}
});
}
}