前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >一文读懂内部组件解耦神器 Spring Event(Spring 事件)

一文读懂内部组件解耦神器 Spring Event(Spring 事件)

原创
作者头像
Lorin 洛林
发布2023-11-03 19:03:22
5770
发布2023-11-03 19:03:22
举报
文章被收录于专栏:Java 技术小屋Java 技术小屋

Spring Event

  • 大家好,我是 Lorin,Spring事件(Spring Event)是Spring框架的一项功能,它允许不同组件之间通过发布-订阅机制进行解耦的通信。在Spring中,事件是表示应用程序中特定事件的对象,例如用户注册、订单创建、数据更新等。当这些事件发生时,可以通知其他组件来执行相应的操作。
  • 下面我们将学习 Spring Event 的使用方式和底层原理,帮助我们在实际项目开发中正确使用 Spring Event:

Spring Event 简单小例子

  • 下面是一个使用Spring事件处理用户登录的简单示例。在此示例中,我们将创建一个Spring Boot应用程序,演示如何使用Spring事件来处理用户登录事件。

创建一个登录事件

  • 创建一个自定义的事件类,用于表示用户登录事件,例如LogonEvent:
代码语言:java
复制
public class LogonEvent extends ApplicationEvent {

    private final String userName;

    public LogonEvent(Object source, String username) {
        super(source);
        this.userName = username;
    }

    public String getUserName() {
        return userName;
    }
}

创建事件发布者

  • 创建一个事件发布者,用于发布用户登录事件:
代码语言:java
复制
@Service
public class LoginEventPublisher {

    private final ApplicationEventPublisher applicationEventPublisher;

    public LoginEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        this.applicationEventPublisher = applicationEventPublisher;
    }

    public void publishLoginEvent(String username) {
        LogonEvent loginEvent = new LogonEvent(this, username);
        applicationEventPublisher.publishEvent(loginEvent);
    }
}

创建事件监听器

  • 创建事件监听器,用于处理用户登录事件,支持创建一个或者多个类似发布订阅模式,本示例中创建了两个时间监听器:
代码语言:java
复制
// 日志处理事件监听器
@Component
public class LoginEventPrintLogListener {

    @EventListener
    public void handleUserLoginEvent(LogonEvent event) {
        String username = event.getUserName();
        // 在这里执行处理用户登录事件的逻辑,例如记录日志或触发其他操作
        System.out.println("User logged in: " + username);
    }
}

// 登录消息通知事件监听器
@Component
public class LoginEventMessageNoticeListener {

    @EventListener
    public void LoginEventMessageNoticeListener(LogonEvent event) {
        String username = event.getUserName();
        // 发送消息通知用户
        System.out.println("message send User logged in: " + username);
    }
}

模拟用户登录

  • 这里为了方便测试,使用CommandLineRunner启动时模拟登录:
代码语言:java
复制
@Component
public class MyCommandLineRunner implements CommandLineRunner {

    private final LoginEventPublisher loginEventPublisher;

    public MyCommandLineRunner(LoginEventPublisher loginEventPublisher) {
        this.loginEventPublisher = loginEventPublisher;
    }

    @Override
    public void run(String... args) {
        // 在应用程序启动后执行的自定义逻辑
        System.out.println("MyCommandLineRunner executed!");

        // 登录成功
        // 登录执行后逻辑
        loginEventPublisher.publishLoginEvent("小王");
    }
}

运行结果

代码语言:txt
复制
2023-10-13 16:04:02.021  INFO 5356 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1250 ms
2023-10-13 16:04:02.382  INFO 5356 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2023-10-13 16:05:31.792  INFO 5356 --- [           main] c.e.s.SpringBootTestMavenApplication     : Started SpringBootTestMavenApplication in 200.49 seconds (JVM running for 201.165)

MyCommandLineRunner executed!
message send User logged in: 小王
User logged in: 小王

特性

事件驱动

  • 基于事件驱动的机制,它允许组件在特定事件发生时被通知并响应。

解耦

  • 帮助在应用内不同组件之间实现解耦。发布事件的组件不需要了解事件的具体监听者,监听者只需要订阅感兴趣的事件即可。

同步处理和异步处理

  • 支持同步或异步方式处理事件,通过@Async注解或配置异步事件处理器。
  • 即没有配置异步的情况下,由当前线程同步处理事件。

使用建议

  • 适用于内部组件之间的通信和解耦,本身不提供强制的可靠性保证机制,可靠性通常由应用开发者来保证。常用于用户注册事件、库存更新事件等轻量级事件的处理。
  • 而消息队列更适用于跨应用、跨服务、分布式系统以及异步处理、或者高并发场景,同时提供了一些现成的可靠性方案供我们选择。

监听器异步实现 @Async

  • 默认使用同步处理的方式,若需实现监听器异步处理我们可以结合 @EnableAsync 注解使用,我们将上文的 LoginEventPrintLogListener 使用异步线程处理:

示例

代码语言:java
复制
@SpringBootApplication
// 开启 Async
@EnableAsync
public class SpringBootTestMavenApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringBootTestMavenApplication.class, args);
    }
}


@Component
public class LoginEventPrintLogListener {

    @EventListener
    // 配置任务异步
    @Async
    public void handleUserLoginEvent(LogonEvent event) throws Exception {
        String username = event.getUserName();
        // 在这里执行处理用户登录事件的逻辑,例如记录日志或触发其他操作
        System.out.println("User logged in: " + username);
    }
}

深入了解 Spring Event

背后的观察者模式(Observer Pattern)

  • Spring事件(Spring Events)实际上是一种基于观察者模式(Observer Pattern)的实现。

简单回顾一下观察者模式

  • 观察者模式是一种设计模式,用于定义对象之间的一对多依赖关系,当一个对象的状态发生变化时,其所有依赖对象都会收到通知并自动更新。
四个关键元素
  • 主题(Subject):也称为被观察者,它维护了一个观察者列表并提供方法来添加、删除和通知观察者。主题的状态变化会通知观察者。
  • 观察者(Observer):观察者是依赖于主题的对象,它们注册自己以接收主题的通知。当主题状态发生变化时,观察者会收到通知并执行相应的操作。
  • 具体主题(Concrete Subject):具体主题是主题的具体实现,它通常包括状态变化、观察者列表、以及通知观察者的方法。
  • 具体观察者(Concrete Observer):具体观察者是观察者的具体实现,它实现了更新操作以响应主题的通知。
类图
优点

松耦合

  • 主题和观察者之间的耦合度较低,主题无需了解观察者的具体实现。可维护性
  • 可以轻松添加、删除和更改观察者,而无需修改主题。分布式通知
  • 允许将通知传递给多个观察者,以便它们可以独立地响应通知,即支持发布订阅模式。

源码解析

  • 三个重要的类

ApplicationEventPublisher

  • 事件发布器,负责发布事件,可以看到我们自定义的发布器 LoginEventPublisher 持有一个 ApplicationEventPublisher 发布器用于发布事件。
代码语言:java
复制
@FunctionalInterface
public interface ApplicationEventPublisher {

	default void publishEvent(ApplicationEvent event) {
		publishEvent((Object) event);
	}

	void publishEvent(Object event);
}

ApplicationEventMulticaster

  • 负责管理我们的监听者列表,并提供给发布器使用。
代码语言:java
复制
public interface ApplicationEventMulticaster {

	void addApplicationListener(ApplicationListener<?> listener);

	void addApplicationListenerBean(String listenerBeanName);

	void removeApplicationListener(ApplicationListener<?> listener);

	void removeApplicationListenerBean(String listenerBeanName);

	void removeApplicationListeners(Predicate<ApplicationListener<?>> predicate);

	void removeApplicationListenerBeans(Predicate<String> predicate);

	void multicastEvent(ApplicationEvent event);

	void multicastEvent(ApplicationEvent event, @Nullable ResolvableType eventType);
}

ApplicationEvent

  • 事件抽象类,用于实现我们的事件。
代码语言:java
复制
public abstract class ApplicationEvent extends EventObject {

	/** use serialVersionUID from Spring 1.2 for interoperability. */
	private static final long serialVersionUID = 7099057708183571937L;

	/** System time when the event happened. */
	private final long timestamp;


	public ApplicationEvent(Object source) {
		super(source);
		this.timestamp = System.currentTimeMillis();
	}
}

执行流程

  • 源码版本:JDK 1.8

发布事件

  • 从 publishEvent() 方法出发,一直到 AbstractApplicationContext 类中:
代码语言:java
复制
// org.springframework.context.support.AbstractApplicationContext#publishEvent
	protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
		Assert.notNull(event, "Event must not be null");

		// Decorate event as an ApplicationEvent if necessary
		ApplicationEvent applicationEvent;
		if (event instanceof ApplicationEvent) {
			applicationEvent = (ApplicationEvent) event;
		}
		else {
			applicationEvent = new PayloadApplicationEvent<>(this, event);
			if (eventType == null) {
				eventType = ((PayloadApplicationEvent<?>) applicationEvent).getResolvableType();
			}
		}

		// Multicast right now if possible - or lazily once the multicaster is initialized
		// ApplicationEventMulticaster 未初始化完成时将 applicationEvent 暂存
		if (this.earlyApplicationEvents != null) {
			this.earlyApplicationEvents.add(applicationEvent);
		}
		else {
		    // 获取监听管理器并进行广播
			getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
		}

		// Publish event via parent context as well...
		if (this.parent != null) {
			if (this.parent instanceof AbstractApplicationContext) {
				((AbstractApplicationContext) this.parent).publishEvent(event, eventType);
			}
			else {
				this.parent.publishEvent(event);
			}
		}
	}

广播执行监听器

代码语言:java
复制
// org.springframework.context.event.SimpleApplicationEventMulticaster#multicastEvent

	@Override
	public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
		ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
		Executor executor = getTaskExecutor();
		// 循环遍历调用监听器
		for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
		    // 是否存在线程池 异步执行逻辑
			if (executor != null) {
				executor.execute(() -> invokeListener(listener, event));
			}
			else {
			    // 非异步线程处理
				invokeListener(listener, event);
			}
		}
	}
	
	protected void invokeListener(ApplicationListener<?> listener, ApplicationEvent event) {
		ErrorHandler errorHandler = getErrorHandler();
		// 是否存在 ErrorHandler 
		if (errorHandler != null) {
			try {
				doInvokeListener(listener, event);
			}
			catch (Throwable err) {
				errorHandler.handleError(err);
			}
		}
		else {
			doInvokeListener(listener, event);
		}
	}

// org.springframework.context.event.ApplicationListenerMethodAdapter#onApplicationEvent
	@Override
	public void onApplicationEvent(ApplicationEvent event) {
		processEvent(event);
	}

	public void processEvent(ApplicationEvent event) {
		Object[] args = resolveArguments(event);
		if (shouldHandle(event, args)) {
		    // 调用监听器
			Object result = doInvoke(args);
			if (result != null) {
				handleResult(result);
			}
			else {
				logger.trace("No result object given - no result to handle");
			}
		}
	}

	protected Object doInvoke(Object... args) {
	    // 从 spring 容器中通过 getBean() 方法获取监听器Bean
	    // 因此支持监听器动态加载
		Object bean = getTargetBean();
		// Detect package-protected NullBean instance through equals(null) check
		if (bean.equals(null)) {
			return null;
		}

		ReflectionUtils.makeAccessible(this.method);
		try {
			return this.method.invoke(bean, args);
		}
		catch (IllegalArgumentException ex) {
			assertTargetBean(this.method, bean, args);
			throw new IllegalStateException(getInvocationErrorMessage(bean, ex.getMessage(), args), ex);
		}
		catch (IllegalAccessException ex) {
			throw new IllegalStateException(getInvocationErrorMessage(bean, ex.getMessage(), args), ex);
		}
		catch (InvocationTargetException ex) {
			// Throw underlying exception
			Throwable targetException = ex.getTargetException();
			if (targetException instanceof RuntimeException) {
				throw (RuntimeException) targetException;
			}
			else {
				String msg = getInvocationErrorMessage(bean, "Failed to invoke event listener method", args);
				throw new UndeclaredThrowableException(targetException, msg);
			}
		}
	}

ApplicationEventMulticaster 构建过程

  • ApplicationEventMulticaster 在 Spring 容器初始化过程中被创建:
代码语言:java
复制
# 在核心方法refresh中建立applicationEventMulticaster
// Initialize message source for this context.
initMessageSource();

// Initialize event multicaster for this context.
// 在Spring容器中初始化事件广播器,事件广播器用于事件的发布
initApplicationEventMulticaster();

// Initialize other special beans in specific context subclasses.
onRefresh();

// Check for listener beans and register them.
// 把Spring容器内的事件监听器和BeanFactory中的事件监听器都添加的事件广播器中。
registerListeners();

// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);

// Last step: publish corresponding event.
finishrefresh();

# 主要逻辑在 initApplicationEventMulticaster 和 registerListeners
// 初始化事件广播器
protected void initApplicationEventMulticaster() {
    ConfigurableListableBeanFactory beanFactory = getBeanFactory();
    // 如果用户手动新建了一个名为applicationEventMulticaster类型为ApplicationEventMulticaster的bean,则将这个bean作为事件广播器
    if (beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) {
        this.applicationEventMulticaster =
                beanFactory.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, ApplicationEventMulticaster.class);
        if (logger.isTraceEnabled()) {
            logger.trace("Using ApplicationEventMulticaster [" + this.applicationEventMulticaster + "]");
        }
    }
    else {
        // 否则新建一个SimpleApplicationEventMulticaster作为默认的事件广播器
        this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);
        beanFactory.registerSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, this.applicationEventMulticaster);
        if (logger.isTraceEnabled()) {
            logger.trace("No '" + APPLICATION_EVENT_MULTICASTER_BEAN_NAME + "' bean, using " +
                    "[" + this.applicationEventMulticaster.getClass().getSimpleName() + "]");
        }
    }
}

// 注册监听器
protected void registerListeners() {
    // Register statically specified listeners first.
    // 把提前存储好的监听器添加到监听器容器中
    for (ApplicationListener<?> listener : getApplicationListeners()) {
        getApplicationEventMulticaster().addApplicationListener(listener);
    }

    // Do not initialize FactoryBeans here: We need to leave all regular beans
    // uninitialized to let post-processors apply to them!
    // 获取类型是ApplicationListener的beanName集合,此处不会去实例化bean
    String[] listenerBeanNames = getBeanNamesForType(ApplicationListener.class, true, false);
    for (String listenerBeanName : listenerBeanNames) {
        getApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName);
    }

    // Publish early application events now that we finally have a multicaster...
    Set<ApplicationEvent> earlyEventsToProcess = this.earlyApplicationEvents;
    this.earlyApplicationEvents = null;
    // 如果存在earlyEventsToProcess,提前处理这些事件
    if (earlyEventsToProcess != null) {
        for (ApplicationEvent earlyEvent : earlyEventsToProcess) {
            getApplicationEventMulticaster().multicastEvent(earlyEvent);
        }
    }
}

总结

  • Spring事件(Spring Events)是Spring框架的一项功能,基于观察者模式实现,适用于内部组件之间的通信和解耦,常用于用户注册事件、库存更新事件等轻量级事件的处理。
  • Spring事件本身不提供强制的可靠性保证机制,可靠性需要由应用开发者来保证。
  • 跨应用、跨服务、分布式系统以及异步处理、或者高并发场景,不适合Spring事件,此时考虑消息队列更加合适。

个人简介

? 你好,我是 Lorin 洛林,一位 Java 后端技术开发者!座右铭:Technology has the power to make the world a better place.

? 我对技术的热情是我不断学习和分享的动力。我的博客是一个关于Java生态系统、后端开发和最新技术趋势的地方。

? 作为一个 Java 后端技术爱好者,我不仅热衷于探索语言的新特性和技术的深度,还热衷于分享我的见解和最佳实践。我相信知识的分享和社区合作可以帮助我们共同成长。

? 在我的博客上,你将找到关于Java核心概念、JVM 底层技术、常用框架如Spring和Mybatis 、MySQL等数据库管理、RabbitMQ、Rocketmq等消息中间件、性能优化等内容的深入文章。我也将分享一些编程技巧和解决问题的方法,以帮助你更好地掌握Java编程。

? 我鼓励互动和建立社区,因此请留下你的问题、建议或主题请求,让我知道你感兴趣的内容。此外,我将分享最新的互联网和技术资讯,以确保你与技术世界的最新发展保持联系。我期待与你一起在技术之路上前进,一起探讨技术世界的无限可能性。

? 保持关注我的博客,让我们共同追求技术卓越。

我正在参与2023腾讯技术创作特训营第三期有奖征文,组队打卡瓜分大奖!

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Spring Event
  • Spring Event 简单小例子
    • 创建一个登录事件
      • 创建事件发布者
        • 创建事件监听器
          • 模拟用户登录
            • 运行结果
            • 特性
              • 事件驱动
                • 解耦
                  • 同步处理和异步处理
                  • 使用建议
                  • 监听器异步实现 @Async
                    • 示例
                    • 深入了解 Spring Event
                      • 背后的观察者模式(Observer Pattern)
                        • 简单回顾一下观察者模式
                        • 松耦合
                      • 源码解析
                        • ApplicationEventPublisher
                        • ApplicationEventMulticaster
                        • ApplicationEvent
                      • 执行流程
                        • 发布事件
                        • 广播执行监听器
                        • ApplicationEventMulticaster 构建过程
                    • 总结
                    • 个人简介
                    相关产品与服务
                    容器服务
                    腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
                    领券
                    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
                    http://www.vxiaotou.com