目录导航
前言
前面的章节我们讲了Spring Cloud Stream (上)
本节,继续微服务专题的内容分享,共计16小节,分别是:
- 微服务专题01-Spring Application
- 微服务专题02-Spring Web MVC 视图技术
- 微服务专题03-REST
- 微服务专题04-Spring WebFlux 原理
- 微服务专题05-Spring WebFlux 运用
- 微服务专题06-云原生应用(Cloud Native Applications)
- 微服务专题07-Spring Cloud 配置管理
- 微服务专题08-Spring Cloud 服务发现
- 微服务专题09-Spring Cloud 负载均衡
- 微服务专题10-Spring Cloud 服务熔断
- 微服务专题11-Spring Cloud 服务调用
- 微服务专题12-Spring Cloud Gateway
- 微服务专题13-Spring Cloud Stream (上)
- 微服务专题14-Spring Cloud Bus
- 微服务专题15-Spring Cloud Stream 实现
- 微服务专题16-Spring Cloud 整体回顾
本节内容重点为:
- Spring 事件/监听器机制
- Spring Cloud Bus 实现
知识回顾和发散
Java 事件/监听者模式
事件
- 所有事件类型扩展
java.util.Event
- 每个事件对象都有事件源
事件监听器
-
事件监听器接口扩展
java.util.EventListener
-
方法参数类型
java.util.Event
对象(子类)- 事件源
- JPA
EntityListener
@Entity
EntityManager
persist(Object)
- JPA
-
监听方法访问限定符
public
-
监听方法是没有返回值(
void
)- 例外:Spring
@EventListener
- 例外:Spring
-
监听方法不会
throws
Throwable
多事件监听器接口
限制:Java 8 之前,接口没有 default
method,当出现多个监听方法时,需要 Adapter
抽象类提供空实现。
- Adapter
- 接口空实现
- A 和 B 两种没有层次关系,A 适配为 B
注意:事件/监听者器,尤其在扩展时,注意事件(语法)时态。
举例说明:GUI 桌面程序
public class GUIEvent {
public static void main(String[] args) throws Exception {
final JFrame frame = new JFrame("简单 GUI 程序 - Java 事件/监听机制");
frame.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent event) {
System.out.printf("[%s] 事件 : %s\n", Thread.currentThread().getName(), event);
}
});
frame.setBounds(300, 300, 400, 300);
frame.setVisible(true);
frame.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
frame.dispose();
}
@Override
public void windowClosed(WindowEvent event) {
System.exit(0);
}
});
}
}
Spring 事件
Spring 事件
Spring 事件基类 ApplicationEvent
- 相对于
java.util.EventObject
增加事件发生时间戳timestamp
内建事件
ContextRefreshedEvent
ConfigurableApplicationContext#refresh()
ContextClosedEvent
ConfigurableApplicationContext#close()
ContextStartedEvent
ConfigurableApplicationContext#start()
ContextStoppedEvent
ConfigurableApplicationContext#stop()
RequestHandledEvent
FrameworkServlet#publishRequestHandledEvent
注意:事件/监听者器,注意事件(语法)时态。
public class SpringEvent {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(Object.class);
// 增加 监听
context.addApplicationListener(e -> {
System.err.println("监听 : " + e.getClass().getSimpleName());
});
context.refresh();
context.start();
context.stop();
context.close();
}
}
打印如下:
Spring 事件监听器
所有 Spring 事件监听器实现
ApplicationListener
public class SpringAnnotationDrivenEvent {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
// SpringAnnotationDrivenEvent 注册为 Spring Bean
context.register(SpringAnnotationDrivenEvent.class);
context.refresh(); // 启动上下文
// 确保上下文启动完毕后,再发送事件
context.publishEvent(new MyApplicationEvent("Hello,World"));
context.close(); // 关闭上下文
}
private static class MyApplicationEvent extends ApplicationEvent {
/**
* Create a new ApplicationEvent.
*
* @param source the object on which the event initially occurred (never {@code null})
*/
public MyApplicationEvent(Object source) {
super(source);
}
}
@EventListener
public void onMessage(Object eventSource) {
System.err.println("监听到 MyApplicationEvent 事件源 : " + eventSource);
}
}
运行结果如下:
Spring Boot 事件
@RestController
public class SpringEventController implements ApplicationEventPublisherAware {
private ApplicationEventPublisher publisher;
@GetMapping("/send/event")
public String sendEvent(@RequestParam String message) {
publisher.publishEvent(message);
return "Sent";
}
@EventListener
public void onMessage(PayloadApplicationEvent event) {
System.out.println("接受事件:" + event.getPayload());
}
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.publisher = applicationEventPublisher;
}
}
启动 spring-cloud-client-application 应用后,访问http://localhost:8888/send/event?message=hello
打印结果:
Spring Cloud 事件
Event Bus(事件总线)
主要内容
Spring cloud bus通过轻量消息代理连接各个分布的节点。这会用在广播状态的变化(例如配置变化)或者其他的消息指令。Spring bus的一个核心思想是通过分布式的启动器对spring boot应用进行扩展,也可以用来建立一个多个应用之间的通信频道。目前唯一实现的方式是用AMQP消息代理作为通道,同样特性的设置(有些取决于通道的设置)在更多通道的文档中。
总体架构
Git 配置更新 Bus 架构
Spring Cloud Bus 事件
RemoteApplicationEvent
客户端
前面我们提到的单机版事件监听,这里我们通过远程的形式实现监听器,将事件数据发送 HTTP 请求到目标机器。
public class HttpRemoteAppEventListener implements SmartApplicationListener {
private RestTemplate restTemplate = new RestTemplate();
// 得到 DiscoveryClient Bean
private DiscoveryClient discoveryClient;
public String currentAppName;
public void onApplicationEvent(RemoteAppEvent event) {
Object source = event.getSource();
String appName = event.getAppName();
List<ServiceInstance> serviceInstances = discoveryClient.getInstances(appName);
for (ServiceInstance s : serviceInstances) {
String rootURL = s.isSecure() ?
"https://" + s.getHost() + ":" + s.getPort() :
"http://" + s.getHost() + ":" + s.getPort();
String url = rootURL + "/receive/remote/event/";
Map<String, Object> data = new HashMap<>();
data.put("sender", currentAppName);
data.put("value", source);
data.put("type", RemoteAppEvent.class.getName());
// 发送HTTP 请求到
String responseContent = restTemplate.postForObject(url, data, String.class);
}
}
@Override
public boolean supportsEventType(Class<? extends ApplicationEvent> eventType) {
return RemoteAppEvent.class.isAssignableFrom(eventType)
|| ContextRefreshedEvent.class.isAssignableFrom(eventType);
}
@Override
public boolean supportsSourceType(@Nullable Class<?> sourceType) {
return true;
}
@Override
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof RemoteAppEvent) {
onApplicationEvent((RemoteAppEvent) event);
} else if (event instanceof ContextRefreshedEvent) {
onContextRefreshedEvent((ContextRefreshedEvent) event);
}
}
private void onContextRefreshedEvent(ContextRefreshedEvent event) {
ApplicationContext applicationContext = event.getApplicationContext();
this.discoveryClient = applicationContext.getBean(DiscoveryClient.class);
this.currentAppName = applicationContext.getEnvironment().getProperty("spring.application.name");
}
@Override
public int getOrder() {
return 0;
}
}
远程事件:
public class RemoteAppEvent extends ApplicationEvent {
/**
* 事件传输类型 HTTP、RPC、MQ
*/
private String type;
/**
* 应用名称
*/
private final String appName;
/**
* 是否广播到集群
*/
private final boolean isCluster;
/***
* @param source POJO 事件源,JSON 格式
* @param appName
* @param isCluster
*/
public RemoteAppEvent(Object source, String appName, boolean isCluster) {
super(source);
this.appName = appName;
this.isCluster = isCluster;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getAppName() {
return appName;
}
}
编写远程应用事件控制器:
@RestController
public class RemoteAppEventSenderController implements
ApplicationEventPublisherAware {
@Value("${spring.application.name}")
public String currentAppName;
private ApplicationEventPublisher publisher;
@Autowired
private DiscoveryClient discoveryClient;
@GetMapping("/send/remote/event")
public String sendEvent(@RequestParam String message) {
publisher.publishEvent(message);
return "Sent";
}
@PostMapping("/send/remote/event/{appName}")
public String sendAppCluster(@PathVariable String appName, @RequestBody Object data){
RemoteAppEvent remoteAppEvent = new RemoteAppEvent(data, appName, true);
// 发送事件当前上下文
publisher.publishEvent(remoteAppEvent);
return "Ok";
}
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.publisher = applicationEventPublisher;
}
}
启动类注入Listener:
public static void main(String[] args) {
new SpringApplicationBuilder(SpringCloudClientApplication.class)
.web(WebApplicationType.SERVLET)
.listeners(new HttpRemoteAppEventListener())
.run(args);
}
服务端
编写远程事件接收器 控制器:
/**
* 远程事件接收器 控制器
*/
@RestController
public class RemoteAppEventReceiverController implements
ApplicationEventPublisherAware {
private Logger logger = LoggerFactory.getLogger(getClass());
private ApplicationEventPublisher publisher;
@PostMapping("/receive/remote/event/")
public String receive(@RequestBody Map<String, Object> data) { // REST 请求不需要具体类型
// 事件的发送者
String sender = (String) data.get("sender");
// 事件的数据内容
Object value = data.get("value");
// 事件类型
String type = (String) data.get("type");
logger.info("接受到事件");
// 接受到对象内容,同样也要发送事件到本地,做处理
publisher.publishEvent(new SenderRemoteAppEvent(sender, value));
return "received";
}
private static class SenderRemoteAppEvent extends ApplicationEvent {
private final String sender;
private SenderRemoteAppEvent(String sender, Object value) {
super(value);
this.sender = sender;
}
public String getSender() {
return sender;
}
}
@EventListener
public void onEvent(SenderRemoteAppEvent event) {
logger.info("接受到事件源:" + event.getClass().getSimpleName() + " , 来自应用 : " + event.getSender());
}
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.publisher = applicationEventPublisher;
}
}
整体架构:
接下来我们调用客户端远程接口,发送post方式的接口:
curl -X POST http://localhost:8888/send/remote/event/spring-cloud-server-application -d ‘{“name”:“zhangsan”}’ -H “content-type:application/json;charset=UTF-8”
当然,你也可以用postman工具请求,最终控制台打印效果:
我们发现接收到事件以后,在发送事件到本地的过程中,控制台为同一个线程处理,即同步执行。
现在我们通过多线程的方式,去处理发送事件的逻辑:
- 启动类引入@EnableAsync注解
- 在处理多线程的入口加入@Async标示为开启多线程
@EventListener
@Async
public void onEvent(SenderRemoteAppEvent event) {
logger.info("接受到事件源:" + event.getClass().getSimpleName() + " , 来自应用 : " + event.getSender());
}
最终控制台打印效果::
我们发现[nio-9090-exec-3] 、[ask-scheduler-1] 两个线程去分别处理业务逻辑,达到了异步的效果。
后记
通过 Spring Cloud Bus 的实现事件编程模型(数据源转换)的逻辑如下:
通过 Spring 事件/监听者编程模型,屏蔽了事件源传输的细节,可能是 MQ、RPC 或 HTTP 等。
本节代码地址:Spring Cloud Bus
更多架构知识,欢迎关注本套Java系列文章:Java架构师成长之路