• Spring进阶(二十)之事件处理


    目录

    为什么需要使用事件这种模式

    事件模式中的几个概念

    使用事件模式实现上面用户注册的业务

    事件对象

    事件监听器

    事件广播器

    事件广播默认实现

    自定义用户注册成功事件类

    用户注册服务

    下面我们使用spring来将上面的对象组装起来

    测试用例模拟用户注册 

    添加注册成功发送邮件功能

    小结

    Spring中实现事件模式

    事件相关的几个类

    硬编码的方式使用spring事件3步骤 

    案例

    小结

    面向接口的方式

    面向@EventListener注解方式

    监听器支持排序功能

    通过接口实现监听器的情况

    通过@EventListener注解实现监听器的情况

    监听器异步模式

    事件使用的建议


    为什么需要使用事件这种模式

    先来看一个业务场景:

    产品经理:路人,这两天你帮我实现一个注册的功能 我:注册功能比较简单,将用户信息入库就可以了,伪代码如下:

    1. public void registerUser(UserModel user){
    2. //插入用户信息到db,完成注册
    3. this.insertUser(user);
    4. }

    过了几天,产品经理:路人,注册成功之后,给用户发送一封注册成功的邮件 我:修改了一下上面注册代码,如下:

    1. public void registerUser(UserModel user){
    2. //插入用户信息到db,完成注册
    3. this.insertUser(user);
    4. //发送邮件
    5. this.sendEmailToUser(user);
    6. }

    由于修改了注册接口,所以所有调用这个方法的地方都需要重新测试一遍,让测试的兄弟们帮忙跑了一 遍。 又过了几天,产品经理:路人,注册成功之后,给用户发一下优惠券 我:好的,又调整了一下代码:

    1. public void registerUser(UserModel user){
    2. //插入用户信息到db,完成注册
    3. this.insertUser(user);
    4. //发送邮件
    5. this.sendEmailToUser(user);
    6. //发送优惠券
    7. this.sendCouponToUser(user);
    8. }

    我:测试的兄弟们,辛苦一下大家,注册接口又修改了,帮忙再过一遍。 过了一段时间,公司效益太好,产品经理:路人,注册的时候,取消给用户发送优惠券的功能。 我:又跑去调整了一下上面代码,将发送优惠券的功能干掉了,如下

    1. public void registerUser(UserModel user){
    2. //插入用户信息到db,完成注册
    3. this.insertUser(user);
    4. //发送邮件
    5. this.sendEmailToUser(user);
    6. }

    由于调整了代码,而注册功能又属于核心业务,所以需要让测试再次帮忙过一遍,又要麻烦测试来一遍了。

    突然有一天,产品经理:路人,注册接口怎么这么慢啊,并且还经常失败?你这让公司要损失多少用户 啊 我:赶紧跑去查看了一下运行日志,发现注册的时候给用户发送邮件不稳定,依赖于第三方邮件服务 器,耗时比较长,并且容易失败。 跑去给产品经理说:由于邮件服务器不稳定的原因,导致注册不稳定。 产品经理:邮件你可以不发,但是你得确保注册功能必须可以用啊。 我想了想,将上面代码改成了下面这样,发送邮件放在了子线程中执行:

    1. public void registerUser(UserModel user){
    2. //插入用户信息到db,完成注册
    3. this.insertUser(user);
    4. //发送邮件,放在子线程中执行,邮件的发送结果对注册逻辑不会有干扰作用
    5. new Thread(()->{
    6. this.sendEmailToUser(user);
    7. }).start();
    8. }

    又过了几天,产品经理又跑来了说:路人,最近效益不好,需要刺激用户消费,注册的时候继续发送优 惠券。 我:倒,这是玩我么,反反复复让我调整注册的代码,让我改还好,让测试也反反复复来回搞,这是要 玩死我们啊。 花了点时间,好好复盘整理了一下:发现问题不在于产品经理,从业务上来看,产品提的这些需求都是 需求合理的,而结果代码反复调整、测试反复测试,以及一些次要的功能导致注册接口不稳定,这些问 题归根到底,主要还是我的设计不合理导致的,将注册功能中的一些次要的功能耦合到注册的方法中 了,并且这些功能可能会经常调整,导致了注册接口的不稳定性。 其实上面代码可以这么做:

    找3个人:注册器、路人A、路人B。

    注册器:负责将用户信息落库,落库成功之后,喊一声:用户XXX注册成功了。

    路人A和路人B,竖起耳朵,当听到有人喊:XXX注册成功 的声音之后,立即行动做出下面反应: 路人A:负责给XXX发送一封注册邮件 路人B:负责给XXX发送优惠券

    我们来看一下: 注册器只负责将用户信息落库,及广播一条用户注册成功的消息。 A和B相当于一个监听者,只负责监听用户注册成功的消息,当听到有这个消息产生的时候,A和B就去做 自己的事情。

    这里面注册器是感知不到A/B存在的,A和B也不用感知注册器的存在,A/B只用关注是否有人广播: XXX 注册成功了 的消息,当AB听到有人广播注册成功的消息,他们才做出反应,其他时间闲着休息。

    这种方式就非常好: 当不想给用户发送优惠券的时候,只需要将B去掉就行了,此时基本上也不用测试,注册一下B的代码就行了。 若注册成功之后需要更多业务,比如还需要给用户增加积分,只需新增一个监听者C,监听到注册成功 消息后,负责给用户添加积分,此时根本不用去调整注册的代码,开发者和测试人员只需要确保监听者 C中的正确性就可以了。

    上面这种模式就是事件模式。

    事件模式中的几个概念

    事件源:事件的触发者,比如上面的注册器就是事件源。

    事件:描述发生了什么事情的对象,比如上面的:xxx注册成功的事件

    事件监听器:监听到事件发生的时候,做一些处理,比如上面的:路人A、路人B

    使用事件模式实现上面用户注册的业务

    事件对象

    表示所有事件的父类,内部有个source字段,表示事件源;我们自定义的事件需要继承这个类。

    1. public abstract class AbstractEvent {
    2. private Object source;
    3. public AbstractEvent(Object source) {
    4. this.source = source;
    5. }
    6. public Object getSource() {
    7. return source;
    8. }
    9. public void setSource(Object source) {
    10. this.source = source;
    11. }
    12. }

    事件监听器

    我们使用一个接口来表示事件监听器,是个泛型接口,后面的类型 E 表示当前监听器需要监听的 事件类型,此接口中只有一个方法,用来实现处理事件的业务;其定义的监听器需要实现这个接 口。

    1. public interface EventListenerextends AbstractEvent> {
    2. void onEvent(E e);
    3. }

    事件广播器

    负责事件监听器的管理(注册监听器&移除监听器,将事件和监听器关联起来)

    负责事件的广播(将事件广播给所有的监听器,对该事件感兴趣的监听器会处理该事件)

    1. public interface EventMulticaster {
    2. /**
    3. * 广播事件给所有的监听器,对该事件感兴趣的监听器会处理该事件
    4. *
    5. * @param event
    6. */
    7. void multicastEvent(AbstractEvent event);
    8. /**
    9. * 添加一个事件监听器(监听器中包含了监听器中能够处理的事件)
    10. *
    11. * @param listener 需要添加监听器
    12. */
    13. void addEventListener(EventListener listener);
    14. /**
    15. * 将事件监听器移除
    16. *
    17. * @param listener 需要移除的监听器
    18. */
    19. void removeEventListener(EventListener listener);
    20. }

    事件广播默认实现

    1. /**
    2. * 事件广播器简单实现
    3. */
    4. public class SimpleEventMulticaster implements EventMulticaster {
    5. private Map, List> eventObjectEventListenerMap = new HashMap<>();
    6. @Override
    7. public void multicastEvent(AbstractEvent event) {
    8. List eventListeners = this.eventObjectEventListenerMap.get(event.getClass());
    9. if (eventListeners != null) {
    10. for (EventListener eventListener : eventListeners) {
    11. eventListener.onEvent(event);
    12. }
    13. }
    14. }
    15. @Override
    16. public void addEventListener(EventListener listener) {
    17. Class eventType = this.getEventType(listener);
    18. List eventListeners = this.eventObjectEventListenerMap.get(eventType);
    19. if (eventListeners == null) {
    20. eventListeners = new ArrayList<>();
    21. this.eventObjectEventListenerMap.put(eventType, eventListeners);
    22. }
    23. eventListeners.add(listener);
    24. }
    25. @Override
    26. public void removeEventListener(EventListener listener) {
    27. Class eventType = this.getEventType(listener);
    28. List eventListeners =
    29. this.eventObjectEventListenerMap.get(eventType);
    30. if (eventListeners != null) {
    31. eventListeners.remove(listener);
    32. }
    33. }
    34. /**
    35. * 获取事件监听器需要监听的事件类型
    36. *
    37. * @param listener
    38. * @return
    39. */
    40. protected Class getEventType(EventListener listener) {
    41. ParameterizedType parameterizedType = (ParameterizedType) listener.getClass().getGenericInterfaces()[0];
    42. Type eventType = parameterizedType.getActualTypeArguments()[0];
    43. return (Class) eventType;
    44. }
    45. }

    上面3个类支撑了整个事件模型,下面我们使用上面三个类来实现注册的功能,目标是:高内聚低耦合, 让注册逻辑方便扩展。

    自定义用户注册成功事件类

    1. /**
    2. * 用户注册成功事件
    3. */
    4. public class UserRegisterSuccessEvent extends AbstractEvent {
    5. //用户名
    6. private String userName;
    7. /**
    8. * 创建用户注册成功事件对象
    9. *
    10. * @param source 事件源
    11. * @param userName 当前注册的用户名
    12. */
    13. public UserRegisterSuccessEvent(Object source, String userName) {
    14. super(source);
    15. this.userName = userName;
    16. }
    17. public String getUserName() {
    18. return userName;
    19. }
    20. public void setUserName(String userName) {
    21. this.userName = userName;
    22. }
    23. }

    用户注册服务

    1. /**
    2. * 用户注册服务
    3. */
    4. public class UserRegisterService {
    5. //事件发布者
    6. private EventMulticaster eventMulticaster; //@0
    7. /**
    8. * 注册用户
    9. *
    10. * @param userName 用户名
    11. */
    12. public void registerUser(String userName) { //@1
    13. //用户注册(将用户信息入库等操作)
    14. System.out.println(String.format("用户【%s】注册成功", userName)); //@2
    15. //广播事件
    16. this.eventMulticaster.multicastEvent(new UserRegisterSuccessEvent(this, userName)); //@3
    17. }
    18. public EventMulticaster getEventMulticaster() {
    19. return eventMulticaster;
    20. }
    21. public void setEventMulticaster(EventMulticaster eventMulticaster) {
    22. this.eventMulticaster = eventMulticaster;
    23. }
    24. }

    @0:事件发布者

    @1:registerUser这个方法负责用户注册,内部主要做了2个事情

    @2:模拟将用户信息落库

    @3:使用事件发布者eventPublisher发布用户注册成功的消息:

    下面我们使用spring来将上面的对象组装起来

    1. @Configuration
    2. @ComponentScan
    3. public class MainConfig {
    4. /**
    5. * 注册一个bean:事件发布者
    6. *
    7. * @param eventListeners
    8. * @return
    9. */
    10. @Bean
    11. @Autowired(required = false)
    12. public EventMulticaster eventMulticaster(List eventListeners) { //@1
    13. EventMulticaster eventPublisher = new SimpleEventMulticaster();
    14. if (eventListeners != null) {
    15. eventListeners.forEach(eventPublisher::addEventListener);
    16. }
    17. return eventPublisher;
    18. }
    19. /**
    20. * 注册一个bean:用户注册服务
    21. *
    22. * @param eventMulticaster
    23. * @return
    24. */
    25. @Bean
    26. public UserRegisterService userRegisterService(EventMulticaster eventMulticaster) { //@2
    27. UserRegisterService userRegisterService = new UserRegisterService();
    28. userRegisterService.setEventMulticaster(eventMulticaster);
    29. return userRegisterService;
    30. }
    31. }

    上面有2个方法,负责向spring容器中注册2个bean。

    @1:向spring容器中注册了一个bean: 事件发布者 ,方法传入了 EventListener 类型的List,这 个地方会将容器中所有的事件监听器注入进来,丢到 EventMulticaster 中。

    @2:向spring容器中注册了一个bean: 用户注册服务

    测试用例模拟用户注册 

    1. public class EventTest {
    2. @Test
    3. public void test0() {
    4. AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig.class);
    5. //获取用户注册服务
    6. UserRegisterService userRegisterService = context.getBean(UserRegisterService.class);
    7. //模拟用户注册
    8. userRegisterService.registerUser("测试用户1");
    9. }
    10. }

    运行输出:

    用户【测试用户1】注册成功

    添加注册成功发送邮件功能

    下面添加一个注册成功发送邮件的功能,只需要自定义一个监听用户注册成功事件的监听器就可以了, 其他代码不需要任何改动,如下

    1. @Component
    2. public class SendEmailOnUserRegisterSuccessListener implements EventListener {
    3. @Override
    4. public void onEvent(UserRegisterSuccessEvent event) {
    5. System.out.println(String.format("给用户【%s】发送注册成功邮件!", event.getUserName()));
    6. }
    7. }

    运行输出:

    用户【测试用户1】注册成功
    给用户【测试用户1】发送注册成功邮件!

    小结

    上面将注册的主要逻辑(用户信息落库)和次要的业务逻辑(发送邮件)通过事件的方式解耦了。次要的业务做成了可插拔的方式,比如不想发送邮件了,只需要将邮件监听器上面的 @Component 注释就可 以了,非常方便扩展。 上面用到的和事件相关的几个类,都是我们自己实现的,其实这些功能在spring中已经帮我们实现好 了,用起来更容易一些,下面带大家来体验一下。

    Spring中实现事件模式

    事件相关的几个类

    Spring中事件相关的几个类需要先了解一下,下面来个表格,将spring中事件相关的类和我们上面自定 义的类做个对比,方便大家理解

    硬编码的方式使用spring事件3步骤 

    步骤1:定义事件

    自定义事件,需要继承 ApplicationEvent 类

    步骤2:定义监听器

    自定义事件监听器,需要实现 ApplicationListener 接口,这个接口有个方法 onApplicationEvent 需要实现,用来处理感兴趣的事件。

    1. @FunctionalInterface
    2. public interface ApplicationListenerextends ApplicationEvent> extends
    3. EventListener {
    4. /**
    5. * Handle an application event.
    6. * @param event the event to respond to
    7. */
    8. void onApplicationEvent(E event);
    9. }

    步骤3:创建事件广播器

    创建事件广播器 ApplicationEventMulticaster ,这是个接口,你可以自己实现这个接口,也可以直 接使用系统给我们提供的 SimpleApplicationEventMulticaster ,如下:

    ApplicationEventMulticaster applicationEventMulticaster = new SimpleApplicationEventMulticaster();

    步骤4:向广播器中注册事件监听器

    将事件监听器注册到广播器 ApplicationEventMulticaster 中,如:

    1. ApplicationEventMulticaster applicationEventMulticaster = new SimpleApplicationEventMulticaster();
    2. applicationEventMulticaster.addApplicationListener(new SendEmailOnOrderCreateListener());

    步骤5:通过广播器发布事件

    广播事件,调用 ApplicationEventMulticaster#multicastEvent方法,此时广播器中对这 个事件感兴趣的监听器会处理这个事件。

    applicationEventMulticaster.multicastEvent(new OrderCreateEvent(applicationEventMulticaster, 1L));

    案例

    实现功能:电商中订单创建成功之后,给下单人发送一封邮件,发送邮件的功能放在监听器中实现。

    事件类:订单创建成功事件

    1. /**
    2. * 订单创建事件
    3. */
    4. public class OrderCreateEvent extends ApplicationEvent {
    5. //订单id
    6. private Long orderId;
    7. /**
    8. * @param source 事件源
    9. * @param orderId 订单id
    10. */
    11. public OrderCreateEvent(Object source, Long orderId) {
    12. super(source);
    13. this.orderId = orderId;
    14. }
    15. public Long getOrderId() {
    16. return orderId;
    17. }
    18. public void setOrderId(Long orderId) {
    19. this.orderId = orderId;
    20. }
    21. }

    监听器:负责监听订单成功事件,发送邮件

    1. /**
    2. * 订单创建成功给用户发送邮件
    3. */
    4. @Component
    5. public class SendEmailOnOrderCreateListener implements ApplicationListener {
    6. @Override
    7. public void onApplicationEvent(OrderCreateEvent event) {
    8. System.out.println(String.format("订单【%d】创建成功,给下单人发送邮件通知!", event.getOrderId()));
    9. }
    10. }

    测试

    1. @Test
    2. public void test2() throws InterruptedException {
    3. //创建事件广播器
    4. ApplicationEventMulticaster applicationEventMulticaster = new SimpleApplicationEventMulticaster();
    5. //注册事件监听器
    6. applicationEventMulticaster.addApplicationListener(new SendEmailOnOrderCreateListener());
    7. //广播事件订单创建事件
    8. applicationEventMulticaster.multicastEvent(new OrderCreateEvent(applicationEventMulticaster, 1L));
    9. }

    运行输出:

    订单【1】创建成功,给下单人发送邮件通知!

    小结

    用上面spring集成好的事件处理的方式,简化了不少。但是,我们还可以进一步简化,另外两种方式来实现,面向接口的方式和面向@EventListener注解的方式

    面向接口的方式

    来一个案例:实现用户注册成功后发布事件,然后在监听器中发送邮件的功能,更好的展现怎么使用。

    用户注册事件

    需要继承 ApplicationEvent

    1. /**
    2. * 用户注册事件
    3. */
    4. public class UserRegisterEvent extends ApplicationEvent {
    5. //用户名
    6. private String userName;
    7. public UserRegisterEvent(Object source, String userName) {
    8. super(source);
    9. this.userName = userName;
    10. }
    11. public String getUserName() {
    12. return userName;
    13. }
    14. }

    发送邮件监听器

    需实现 ApplicationListener 接口

    1. /**
    2. * 用户注册成功发送邮件
    3. */
    4. @Component
    5. public class SendEmailListener implements ApplicationListener {
    6. @Override
    7. public void onApplicationEvent(UserRegisterEvent event) {
    8. System.out.println(String.format("给用户【%s】发送注册成功邮件!", event.getUserName()));
    9. }
    10. }

    用户注册服务

    内部提供用户注册的功能,并发布用户注册事件

    1. /**
    2. * 用户注册服务
    3. */
    4. @Component
    5. public class UserRegisterService implements ApplicationEventPublisherAware {
    6. private ApplicationEventPublisher applicationEventPublisher;
    7. /**
    8. * 负责用户注册及发布事件的功能
    9. *
    10. * @param userName 用户名
    11. */
    12. public void registerUser(String userName) {
    13. //用户注册(将用户信息入库等操作)
    14. System.out.println(String.format("用户【%s】注册成功", userName));
    15. //发布注册成功事件
    16. this.applicationEventPublisher.publishEvent(new UserRegisterEvent(this, userName));
    17. }
    18. @Override
    19. public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { //@1
    20. this.applicationEventPublisher = applicationEventPublisher;
    21. }
    22. }

    测试

    1. @Test
    2. public void test3() throws InterruptedException {
    3. AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    4. context.register(MainConfig1.class);
    5. context.refresh();
    6. //获取用户注册服务
    7. com.example.Way1.UserRegisterService userRegisterService = context.getBean(com.example.Way1.UserRegisterService.class);
    8. //模拟用户注册
    9. userRegisterService.registerUser("test");
    10. }

    运行输出

    用户【test】注册成功
    给用户【test】发送注册成功邮件!

    面向@EventListener注解方式

    其实和上面的面向接口的差不多,唯一的区别就是监听器不用实现ApplicationListener接口了,而是用@EventListener注解来代替,其它部分都一样。

    1. /**
    2. * 用户注册事件
    3. */
    4. public class UserRegisterEvent extends ApplicationEvent {
    5. //用户名
    6. private String userName;
    7. public UserRegisterEvent(Object source, String userName) {
    8. super(source);
    9. this.userName = userName;
    10. }
    11. public String getUserName() {
    12. return userName;
    13. }
    14. }

    1. /**
    2. * 用户注册监听器
    3. */
    4. @Component
    5. public class UserRegisterListener {
    6. @EventListener
    7. public void sendMail(UserRegisterEvent event) {
    8. System.out.println(String.format("给用户【%s】发送注册成功邮件!", event.getUserName()));
    9. }
    10. @EventListener
    11. public void sendCompon(UserRegisterEvent event) {
    12. System.out.println(String.format("给用户【%s】发送优惠券!", event.getUserName()));
    13. }
    14. }

    1. /**
    2. * 用户注册服务
    3. */
    4. @Component
    5. public class UserRegisterService implements ApplicationEventPublisherAware {
    6. private ApplicationEventPublisher applicationEventPublisher;
    7. /**
    8. * 负责用户注册及发布事件的功能
    9. *
    10. * @param userName 用户名
    11. */
    12. public void registerUser(String userName) {
    13. //用户注册(将用户信息入库等操作)
    14. System.out.println(String.format("用户【%s】注册成功", userName));
    15. //发布注册成功事件
    16. this.applicationEventPublisher.publishEvent(new UserRegisterEvent(this, userName));
    17. }
    18. @Override
    19. public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { //@1
    20. this.applicationEventPublisher = applicationEventPublisher;
    21. }
    22. }

    测试:

    1. @Test
    2. public void test4() throws InterruptedException {
    3. AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    4. context.register(MainConfig2.class);
    5. context.refresh();
    6. //获取用户注册服务
    7. com.example.Way2.UserRegisterService userRegisterService = context.getBean(com.example.Way2.UserRegisterService.class);
    8. //模拟用户注册
    9. userRegisterService.registerUser("test");
    10. }

    用户【test】注册成功
    给用户【test】发送注册成功邮件!
    给用户【test】发送优惠券!

    监听器支持排序功能

    如果某个事件有多个监听器,默认情况下,监听器执行顺序是无序的,不过我们可以为监听器指定顺序。

    通过接口实现监听器的情况

    指定监听器的顺序有三种方式

    方式一:

    实现org.springframework.core.Ordered接口,需要实现一个getOrder方法,返回顺序值,值越小,顺序越高。

    方式二:

    实现org.springframework.core.PriorityOrdered接口

    PriorityOrdered接口继承了方式一中的Ordered接口,所以如果你实现PriorityOrdered接口,也需要实 现getOrder方法。

    方式三:

    类上使用@org.springframework.core.annotation.Order注解

    通过@EventListener注解实现监听器的情况

    在标注 @EventListener 的方法上面使用 @Order(顺序值) 注解来标注顺序

    监听器异步模式

    具体实现如下:
     

    1. @ComponentScan
    2. @Configuration
    3. public class MainConfig5 {
    4. @Bean
    5. public ApplicationEventMulticaster applicationEventMulticaster() { //@1
    6. //创建一个事件广播器
    7. SimpleApplicationEventMulticaster result = new SimpleApplicationEventMulticaster();
    8. //给广播器提供一个线程池,通过这个线程池来调用事件监听器
    9. Executor executor = this.applicationEventMulticasterThreadPool().getObject();
    10. //设置异步执行器
    11. result.setTaskExecutor(executor);//@1
    12. return result;
    13. }
    14. @Bean
    15. public ThreadPoolExecutorFactoryBean applicationEventMulticasterThreadPool(){
    16. ThreadPoolExecutorFactoryBean result = new ThreadPoolExecutorFactoryBean();
    17. result.setThreadNamePrefix("applicationEventMulticasterThreadPool-");
    18. result.setCorePoolSize(5);
    19. return result;
    20. }
    21. }

    @1:定义了一个名称为 applicationEventMulticaster 的事件广播器,内部设置了一个线程池 用来异步调用监听器

    事件使用的建议

    1. spring中事件是使用接口的方式还是使用注解的方式,具体使用哪种方式都可以,不过在公司内部 最好大家都统一使用一种方式
    2. 异步事件的模式,通常将一些非主要的业务放在监听器中执行,因为监听器中存在失败的风险,所 以使用的时候需要注意。如果只是为了解耦,但是被解耦的次要业务也是必须要成功的,可以使用消息中间件的方式来解决这些问题。

  • 相关阅读:
    企业营销策略之积分营销
    基于Rook+Ceph的云原生存储架构剖析
    Flutter最新稳定版3.16 新特性介绍
    linux-配置服务器之间 ssh免密登录
    Leetcode75颜色分类
    改进的PSO-BP算法在工业机器人末端位姿误差补偿中的应用
    wireshark抓包解密TLS,解决个人环境看不到明文流量
    Zabbix之部署代理服务器
    ez_pz_hackover_2016
    Spring Boot项目误将Integer类型写成int来进行传参
  • 原文地址:https://blog.csdn.net/weixin_56644618/article/details/127908636