Spring源码篇(十二)事件机制

news2024/10/6 1:42:38

文章目录

  • 前言
  • 应用示例
    • 第一种:@EventListener
    • 第二种:实现ApplicationListener
    • 第三种:@TransactionalEventListener
    • 补充:筛选条件
  • 源码
    • 初始化事件器
    • 注册监听器
    • 添加监听器
      • 添加1:应用启动前的监听器
        • SpringApplication实例化时
        • SpringApplication.run
        • refresh
      • 添加2:ApplicationListener实现类
      • 添加3:@EventListener
      • 添加4:@TransactionalEventListener
    • 发布事件/执行事件
    • @TransactionalEventListener与@EventListener
  • 总结

前言

事件的应用在开发中也是比较灵活的一个方式,可以做到代码解耦,这个篇章就探究事件的发布订阅的原理,本篇原理比较简单,但代码结构较为复杂,所以如果只想了解事件机制原理,添加监听器的前面部分可以简单了解就行。

应用示例

第一种:@EventListener

注解方式的监听事件

定义一个事件如下,但是需要注意的是不一定需要继承ApplicationEvent,任何对象都可以,并且@EventListener注解的方法可以private修饰

public class CusEvent extends ApplicationEvent {

    private String id;

    public CusEvent(Object source) {
        super(source);
    }

    public CusEvent(Object source, String id) {
        this(source);
        this.id = id;
    }

    public String getId() {
        return id;
    }
}

    

定义一个监听的方法

@Component
public class CusService {

    @EventListener
    public Object get(CusEvent event) {
        System.out.println("eventListener 监听到事件");
        return true;
    }
    
    // 或者也可以这样,和上面是一样的
    @EventListener(CusEvent.class)
    public Object get() {
        System.out.println("eventListener 监听到事件 2");
        return true;
    }
}

发布一个事件

@Component
public class EventAppRunner implements ApplicationRunner {

    @Autowired
    private ApplicationContext applicationContext;

    @Override
    public void run(ApplicationArguments args) throws Exception {
        applicationContext.publishEvent(new CusEvent(this, "1"));
    }
}

第二种:实现ApplicationListener

public class CusEvent extends ApplicationEvent {

    private String id;

    public CusEvent(Object source) {
        super(source);
    }

    public CusEvent(Object source, String id) {
        this(source);
        this.id = id;
    

继承ApplicationListener,这个是spring提供的监听类;

这里有一个注意点,它是泛型的,泛型类型是我们定义的事件类,如果没有这个泛型,那么就是默认的ApplicationEvent事件,那么发布的事件,都会被监听到

@Component
public class CusEventListener implements ApplicationListener<CusEvent> {
    @Override
    public void onApplicationEvent(CusEvent event) {
        System.out.println("自定义事件监听器 监听到事件");
    }
}

第三种:@TransactionalEventListener

这第三种方式是支持事务的,需要引入spring-data的依赖

  		<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jdbc</artifactId>
        </dependency>

使用就是注解不一样就行了

    @TransactionalEventListener
    public Object get2(CusEvent event) {
        System.out.println("TransactionalEventListener 监听到事件");
        return true;
    }

补充:筛选条件

除此之外,它还支持条件筛选,如下是EventListener的定义,它有一个属性condition它支持el表达式

@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface EventListener {

	@AliasFor("classes")
	Class<?>[] value() default {};


	@AliasFor("value")
	Class<?>[] classes() default {};

	String condition() default "";

}

那下面我们可以尝试以下:

// 这个是当我们发布的事件对象中的id='1'时,才会触发
	@EventListener(condition = "#event.id == '1'")
    public Object get3(CusEvent event) {
        System.out.println("eventListener条件 监听到事件 - ID=1");
        return true;
    }

    @EventListener(condition = "#event.id == '2'")
    public Object get4(CusEvent event) {
        System.out.println("eventListener条件 监听到事件 - ID=2");
        return true;
    }

也可以尝试枚举变量

    @EventListener(condition = "T(com.liry.event.CusConst).WORD.name()  == #event.id")
    public Object get5(CusEvent event) {
        System.out.println("eventListener条件 监听到事件 - ID=2");
        return true;
    }

如果你使用的是TransactionalEventListener,它额外提供了属性phase这个是用于事务传播机制的;

它提供了:

# 事务提交前执行
BEFORE_COMMIT
# 事务提交后执行
AFTER_COMMIT
# 事务回滚后执行
AFTER_ROLLBACK
# 事务完成后
AFTER_COMPLETION

用法如下:

    @TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT)
    public Object get6(CusEvent event) {
        System.out.println("TransactionalEventListener事务 监听到事件");
        return true;
    }

源码

事件机制的原理也算简单,它使用了观察者模式,可以理解为在spring容器中它保存着一个监听器的列表,当我们发布事件时,就遍历这个列表,然后逐个判断是否符合条件,符合条件就执行监听器方法。

我们先来看一段代码:

protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
		Assert.notNull(event, "Event must not be null");

    // 这里直接看的话,就是对事件的封装
		ApplicationEvent applicationEvent;
		if (event instanceof ApplicationEvent) {
			applicationEvent = (ApplicationEvent) event;
		}
		else {
			applicationEvent = new PayloadApplicationEvent<>(this, event);
			if (eventType == null) {
				eventType = ((PayloadApplicationEvent<?>) applicationEvent).getResolvableType();
			}
		}

    // 然后添加了事件
		if (this.earlyApplicationEvents != null) {
			this.earlyApplicationEvents.add(applicationEvent);
		}
		else {
            // 或者执行
			getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
		}

    // 以及父容器的执行
		if (this.parent != null) {
			if (this.parent instanceof AbstractApplicationContext) {
				((AbstractApplicationContext) this.parent).publishEvent(event, eventType);
			}
			else {
				this.parent.publishEvent(event);
			}
		}
	}

通过这段代码,我们就已经出现了几个问题:

  1. 为何要进行事件的封装?
  2. 为什么要把容器放到一个容器里(earlyApplicationEvents),不直接执行?
  3. 执行用的ApplicationEventMulticaster在哪里初始化?
  4. 以及ApplicationEventMulticaster它怎样执行事件?

初始化事件器

位置:org.springframework.context.support.AbstractApplicationContext#refresh

这里ApplicationEventMulticaster我这里叫做事件器,按照名字翻译叫事件广播器,我下面的都叫事件器;我们所使用的事件器是在spring初始化中做的:

image-20231012215019682

	protected void initApplicationEventMulticaster() {
		ConfigurableListableBeanFactory beanFactory = getBeanFactory();
        // 判断spring容器中是否已经有了事件器bean
        // 一般是不存在的
		if (beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) {
            // 若存在,则将spring容器中的事件器bean赋给当前的属性变量
			this.applicationEventMulticaster =
					beanFactory.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, ApplicationEventMulticaster.class);
			if (logger.isTraceEnabled()) {
				logger.trace("Using ApplicationEventMulticaster [" + this.applicationEventMulticaster + "]");
			}
		}
		else {
            // 如果在spring容器中不存在事件器bean,那么这里new一个
			this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);
            // 然后将这个new出来的事件器,注册到spring容器中
			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() + "]");
			}
		}
	} 

这一个步骤是在AbstractApplicationContext中做的,所以this.applicationEventMulticaster就是 Application Context的属性,我们可以直接通过ApplicationContext发布事件。

那为什么在方法中会先从bean容器中获取这个事件器呢?

这个我的理解是这样的:

这个init方法在bean容器之后、也在bean扫描之后,所以可能存在我们自定义的一个事件器,因为这个过程中,我们可以通过beanFactoryPostProcessor,或者是beanPostProcessor,也或者是beanRegister进行添加,所以这个就相当于是将我们自定义的bean优先级提高。

注册监听器

上一步是初始化了事件器,那么在执行事件前,它还需要添加监听器,才能执行监听器的方法。

image-20231012223059702

	protected void registerListeners() {
		// 1. 静态指定的监听器:显示声明,如add(new Listener())
		for (ApplicationListener<?> listener : getApplicationListeners()) {
			getApplicationEventMulticaster().addApplicationListener(listener);
		}

		// 2. spring扫描得到的bean,如import ApplicationListener
		String[] listenerBeanNames = getBeanNamesForType(ApplicationListener.class, true, false);
		for (String listenerBeanName : listenerBeanNames) {
            // 这里添加的是bean的名称,因为这里还并没有实例化
			getApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName);
		}

		// 3. 执行早期的事件,也就是容器准备好之前的事件
		Set<ApplicationEvent> earlyEventsToProcess = this.earlyApplicationEvents;
		this.earlyApplicationEvents = null;
		if (earlyEventsToProcess != null) {
			for (ApplicationEvent earlyEvent : earlyEventsToProcess) {
				getApplicationEventMulticaster().multicastEvent(earlyEvent);
			}
		}
	}

注册监听器这一段中,分了两种方式添加监听器:

  1. 静态指定:显示的声明添加,如add(new Listener())
  2. Spring扫描得到的监听器bean,如import ApplicationListener

这里要注意的一点是,这里添加的是bean的名称

第三点中,它执行了监听器:

image-20231021141728883

这里,他是先进行了复制,然后earlyApplicationEvents置空,这里的这个属性是作为一个判断条件来用的,当不为空时,表示存在应用准备前期的事件,需要执行,因为现在才完成初始化,所以现在执行。

添加监听器

上面说过监听器的添加有两种,一种是静态指定(显示声明),另一种是spring扫描添加。

我们还是先找到注册监听器的位置:

org.springframework.context.support.AbstractApplicationContext#registerListeners

这里getApplicationListeners()它获取的是当前实例对象(ApplicationContext)的属性this.applicationListeners,并且针对这个属性有add方法,也就是说,只要能拿到ApplicationContext对象都可以调用add方法:addApplicationListener

添加1:应用启动前的监听器

这里添加步骤:

  1. SpringApplication实例化时,获取spring.factories里的监听器,添加到listeners
  2. SpringApplication.run方法中:prepareContext时添加到applicationListeners
  3. SpringApplication.run方法中:refresh时添加到earlyApplicationListeners,在close时调用
SpringApplication实例化时

主程序如下:

@SpringBootApplication
public class EventApp {
    public static void main(String[] args) {
        SpringApplication.run(EventApp.class);
    }
}

那么它会默认读取spring.factories文件中key为org.springframework.context.ApplicationListener的监听器全类名,这里一般会有11个(一般简单的SpringBoot程序),这时添加的监听器是在application.listener

image-20231021095948788

SpringApplication.run

prepareContext中,存在很多ApplicationContextInitializer应用初始化器,他们都会注册一个自己的监听器进行事件发布

image-20231020212018227

image-20231021101400475

image-20231021095628177

image-20231021095538232

image-20231021102012975

这两个地方是应用初始化器自己添加监听器,和从spring.factories文件读取的监听器,需要注意的是,在这里这些获取到的监听器都只是保持在一个ApplicationContext的属性applicationListeners中。

refresh

这里就是将启动前所有的监听器(上一步)都添加到earlyApplicationListeners,表示这个是早期的监听器。

image-20231021102921526

添加2:ApplicationListener实现类

这里的步骤是:

  1. 添加一个bean后置处理器ApplicationListenerDetector

  2. 在bean生命周期之后,会执行后置处理器的回调方法,添加监听器的

  3. 添加后置处理器ApplicationListenerDetector

Spring在准备bean容器时,会添加默认的处理器和解析器,可以理解为这里添加的都是spring会执行,或者用到的必须的一些配置;

首先这在这里它会添加一个后置处理器·ApplicatonListenerDetector

位置:org.springframework.context.support.AbstractApplicationContext#prepareBeanFactory

image-20231020212953924

  1. 执行后置处理器的回调方法

后置处理器执行的位置:org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#applyBeanPostProcessorsAfterInitialization

image-20231020213300542

添加监听器的位置:

org.springframework.context.support.ApplicationListenerDetector#postProcessAfterInitialization

image-20231021140908103

可以看到,它这里判断是ApplicationListener类型,也就是说只要我们的bean实现了ApplicationListener接口,就会被添加到事件器。

添加3:@EventListener

相对于接口来说,注解方式的更为简便,其实就是将有注解的方法保存,然后在执行的时候进行反射执行,外部再套一个封装类就完成了,下面来看一下这个过程。

  1. 所有的bean初始化完毕
  2. 执行SmartInitializingSingleton接口回调
  3. 便利bean,查找含有@EventListener注解的类,并解析
  4. 通过bean对象,和解析出来的方法对象,通过监听器工厂类构建一个新的监听器封装类ApplicationListenerMethodAdapter

看这个位置:

org.springframework.beans.factory.support.DefaultListableBeanFactory#preInstantiateSingletons

image-20231021153456727

这个地方就是添加注解方式的监听器的地方,所有实现SmartInitializingSingleton的类都会被执行,而处理注解式监听器就是由实现了这个接口的类EventListenerMethodProcessor完成,

简单看下的代码:

@Override
	public void afterSingletonsInstantiated() {
        // 获取bean容器
		ConfigurableListableBeanFactory beanFactory = this.beanFactory;
		Assert.state(this.beanFactory != null, "No ConfigurableListableBeanFactory set");
        // 遍历所有的beanname
		String[] beanNames = beanFactory.getBeanNamesForType(Object.class);
		for (String beanName : beanNames) {
            // 判断是否是原型bean,只有单例bean才进行此操作
			if (!ScopedProxyUtils.isScopedTarget(beanName)) {
				Class<?> type = null;
				try {
                    // 获取bean的原始类型,因为存在动态代理,代理过的对象直接获取的类型并不是原始类型
					type = AutoProxyUtils.determineTargetClass(beanFactory, beanName);
				}
				catch (Throwable ex) {
					// An unresolvable bean type, probably from a lazy bean - let's ignore it.
					if (logger.isDebugEnabled()) {
						logger.debug("Could not resolve target class for bean with name '" + beanName + "'", ex);
					}
				}
				if (type != null) {
                    // 这里再次进行了校验,ScopedObject是spring内部类,作用域对象
                    // 它这里应该是在ScopedObject对象中不应该有监听器类,所以这里在获取了一次
					if (ScopedObject.class.isAssignableFrom(type)) {
						try {
							Class<?> targetClass = AutoProxyUtils.determineTargetClass(
									beanFactory, ScopedProxyUtils.getTargetBeanName(beanName));
							if (targetClass != null) {
								type = targetClass;
							}
						}
						catch (Throwable ex) {
							// An invalid scoped proxy arrangement - let's ignore it.
							if (logger.isDebugEnabled()) {
								logger.debug("Could not resolve target bean for scoped proxy '" + beanName + "'", ex);
							}
						}
					}
					try {
                        // 这里是真正构建监听器封装类的地方
						processBean(beanName, type);
					}
					catch (Throwable ex) {
						throw new BeanInitializationException("Failed to process @EventListener " +
								"annotation on bean with name '" + beanName + "'", ex);
					}
				}
			}
		}
	}
	private void processBean(final String beanName, final Class<?> targetType) {
        // 没有解析过,含有@EventListener注解,并且是一个bean(也就是有@Component注解)
		if (!this.nonAnnotatedClasses.contains(targetType) &&
				AnnotationUtils.isCandidateClass(targetType, EventListener.class) &&
				!isSpringContainerClass(targetType)) {

			Map<Method, EventListener> annotatedMethods = null;
			try {
                // 从指定class中获取到被注解@EventListener标注的方法
				annotatedMethods = MethodIntrospector.selectMethods(targetType,
						(MethodIntrospector.MetadataLookup<EventListener>) method ->
								AnnotatedElementUtils.findMergedAnnotation(method, EventListener.class));
			}
			catch (Throwable ex) {
				// An unresolvable type in a method signature, probably from a lazy bean - let's ignore it.
				if (logger.isDebugEnabled()) {
					logger.debug("Could not resolve methods for bean with name '" + beanName + "'", ex);
				}
			}

            // 如果没有获取到方法就结束
			if (CollectionUtils.isEmpty(annotatedMethods)) {
				this.nonAnnotatedClasses.add(targetType);
				if (logger.isTraceEnabled()) {
					logger.trace("No @EventListener annotations found on bean class: " + targetType.getName());
				}
			}
			else {
				// Non-empty set of methods
				ConfigurableApplicationContext context = this.applicationContext;
				Assert.state(context != null, "No ApplicationContext set");
                // 这里事件监听器工厂,这里也是指定为EventListenerFactory的实现类,
                // 可以看下面
				List<EventListenerFactory> factories = this.eventListenerFactories;
				Assert.state(factories != null, "EventListenerFactory List not initialized");
                // 便利@EventListener注解的方法
                // 每一个方法都应该创建一个监听器
				for (Method method : annotatedMethods.keySet()) {
					for (EventListenerFactory factory : factories) {
                        // 这里是工厂方法一般有的一个支持接口
                        // 用于判断当前这个工厂类是否支持这个方法
						if (factory.supportsMethod(method)) {
                            // 这里类似判断
                            // 里面做了校验: private修饰的实例方法,并且是SpringProxy实现类,就报异常
                            // 这说明了什么问题?
                            // 说明了@EventListener它支持private修饰的方法
							Method methodToUse = AopUtils.selectInvocableMethod(method, context.getType(beanName));
                            // 通过工厂方法创建一个监听器封装类,参数就是beanName,class,方法对象
							ApplicationListener<?> applicationListener =
									factory.createApplicationListener(beanName, targetType, methodToUse);
                            // 这里的这个判断,类似于适配扩展功能,因为我们可能会定义自己的一个工厂类
							if (applicationListener instanceof ApplicationListenerMethodAdapter) {
								((ApplicationListenerMethodAdapter) applicationListener).init(context, this.evaluator);
							}
                            // 将创建的监听器封装类添加到事件器中
							context.addApplicationListener(applicationListener);
							break;
						}
					}
				}
				if (logger.isDebugEnabled()) {
					logger.debug(annotatedMethods.size() + " @EventListener methods processed on bean '" +
							beanName + "': " + annotatedMethods);
				}
			}
		}
	}

添加事件监听器工厂类,如果你没有引入spring-boot-starter-jdbc依赖,那么只有一个DefaultEventListenerFactory这个就是创建@EventListener监听器的工厂类

image-20231021163720775

添加4:@TransactionalEventListener

@EventListener封装的地方一样,只是他们所使用工厂类不一样,工厂类TransactionalEventListenerFactory创建ApplicationListenerMethodTransactionalAdapter

位置:org.springframework.context.event.EventListenerMethodProcessor#processBean

image-20231021193011975

发布事件/执行事件

首先发布事件的类需要实现ApplicationEventPublisher接口,ApplicationContext也实现了这个一个接口,所以可以通过ApplicatoinContext进行事件发布。

protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
		Assert.notNull(event, "Event must not be null");

		// 这里做发布事件的判别
    // 当我们传入的事件event并非是ApplicationEvent时,它会进行一个事件对象的封装
		ApplicationEvent applicationEvent;
		if (event instanceof ApplicationEvent) {
			applicationEvent = (ApplicationEvent) event;
		}
		else {
            // 非applicationEvent类,进行封装
			applicationEvent = new PayloadApplicationEvent<>(this, event);
			if (eventType == null) {
				eventType = ((PayloadApplicationEvent<?>) applicationEvent).getResolvableType();
			}
		}

		// 那这里的判空,不难理解,在spring启动过程中,它会经过很多的步骤,每个步骤都有可能发布事件,但是在spring容器准备好之前都是不能发布事件的,
    // 而这里的意思就是在准备好之前,所发布的事件都会添加到earlyApplicationEvents这个容器中,待容器准备好后,在执行这些事件
		if (this.earlyApplicationEvents != null) {
			this.earlyApplicationEvents.add(applicationEvent);
		}
		else {
            // 没有暂存的事件,就执行执行事件
			getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
		}

		// 如果父容器存在,就会进行父容器的事件发布
		if (this.parent != null) {
			if (this.parent instanceof AbstractApplicationContext) {
				((AbstractApplicationContext) this.parent).publishEvent(event, eventType);
			}
			else {
				this.parent.publishEvent(event);
			}
		}
	}

这里我们讨论下这段代码:

		if (this.earlyApplicationEvents != null) {
			this.earlyApplicationEvents.add(applicationEvent);
		}
		else {
            // 没有暂存的事件,就执行执行事件
			getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
		}

这段就是为了处理在准备前发布的事件,但是在registerListener方法中已经清理过了,我们可以这样理解

  1. 所有的方法都会通过publishEvent进行事件发布,那么就是调用上面的方法,如果是在容器准备之前,那么就是执行this.earlyApplicationEvents.add(applicationEvent);
  2. 然后走到registerListener然后清空前期事件
  3. 之后spring启动过程中再发事件,就走getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);

image-20231021200435568

下面,那我们来看getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);

位置:org.springframework.context.event.SimpleApplicationEventMulticaster#multicastEvent(org.springframework.context.ApplicationEvent, org.springframework.core.ResolvableType)

	@Override
	public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
        // 获取事件类型
		ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
        // 这里是获取一个执行器,我们可以设置一个我们自定义的一个线城池
        // 这里默认是null
		Executor executor = getTaskExecutor();
        // getApplicationListeners 其实做了一个缓存,根据事件类型进行监听器的缓存
        // 第一次是从bean容器获取的,之后都是从缓存中获取
		for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
			if (executor != null) {
				executor.execute(() -> invokeListener(listener, event));
			}
			else {
				invokeListener(listener, event);
			}
		}
	}

@TransactionalEventListener与@EventListener

这两个的区别在于,一个可以保证同步执行,一个保证同步执行同时,保证事务的传播;

@TransactionalEventListener执行的代码,位置:org.springframework.transaction.event.ApplicationListenerMethodTransactionalAdapter#onApplicationEvent

	@Override
	public void onApplicationEvent(ApplicationEvent event) {
		if (TransactionSynchronizationManager.isSynchronizationActive() &&
				TransactionSynchronizationManager.isActualTransactionActive()) {
			TransactionSynchronization transactionSynchronization = createTransactionSynchronization(event);
			TransactionSynchronizationManager.registerSynchronization(transactionSynchronization);
		}
		else if (this.annotation.fallbackExecution()) {
			if (this.annotation.phase() == TransactionPhase.AFTER_ROLLBACK && logger.isWarnEnabled()) {
				logger.warn("Processing " + event + " as a fallback execution on AFTER_ROLLBACK phase");
			}
			processEvent(event);
		}
		else {
			// No transactional event execution at all
			if (logger.isDebugEnabled()) {
				logger.debug("No transaction is active - skipping " + event);
			}
		}
	}

之前在事务原理篇中解析过,spring保证事务的传播,是有一个事务管理器,它在线程缓存中保持了会话connection,通过dataSource获取,并且事务的提交回滚都是由TransactionSynchronizationManager这个类进行管理,我们看一段提交的代码:

	public static void triggerBeforeCommit(boolean readOnly) {
		for (TransactionSynchronization synchronization : TransactionSynchronizationManager.getSynchronizations()) {
			synchronization.beforeCommit(readOnly);
		}
	}

这里它其实是获取了所有的TransactionSynchronization类,在开启事务和提交事务时,都是获取到这个对象,然后通过这个对象进行开启事务和事务提交,所以这个类就类似一个事务的回调方法,在开启事务和提交事务这些操作,都会调用实现类。

image-20231021205552589

image-20231021205611033

image-20231021205634158

呐,这里的代码就和ApplicationListenerMethodTransactionalAdapter执行的相似。

总结

  1. 发布的事件对象可以是实现了ApplicationEvent接口的实现类,也可以是随意的一个对象
  2. 它的底层原理就是一个监听器集合,在发布事件后,遍历监听器,判别适合的监听器,然后执行
  3. 我们可以自定义自己的监听器工厂类:
    1. 自定义监听器注解,如:@CustomEventListener
    2. 实现一个监听器工厂类:CustomEventListenerFactory implement EventListenerFactory
    3. 构建一个ApplicationListenerMethodAdapter监听器对象
    4. 添加到事件器中
  4. 使用@EventListener不一定要public修饰,private也是生效的
  5. 事件对象不一定要实现ApplicationEvent
  6. 可以在spring.factories添加监听器类

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1125346.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Hadoop3教程(三十五):(生产调优篇)HDFS小文件优化与MR集群简单压测

文章目录 &#xff08;168&#xff09;HDFS小文件优化方法&#xff08;169&#xff09;MapReduce集群压测参考文献 &#xff08;168&#xff09;HDFS小文件优化方法 小文件的弊端&#xff0c;之前也讲过&#xff0c;一是大量占用NameNode的空间&#xff0c;二是会使得寻址速度…

【Linux】进程优先级|进程并发概念|在vim中批量化注释

文章目录 前言tips——如何在vim中批量化注释进程更深度理解一、什么是进程优先级二、 为什么要有优先级三、Linux怎么设置优先级查看进程优先级的命令PRI and NI用top命令更改已存在进程的nice&#xff1a; 如何根据优先级开展调度呢&#xff1f;五、其他概念并发&#xff08;…

使用Github.io创建自己的博客

文章目录 1.最终效果2.操作步骤2.1 前置操作2.2 按照自己需求修改内容2.2.1 基本修改2.2.2 额外添加知乎等社交网站链接 2.3 首页修改2.4 查看发布状态2.5 奇怪的错误(头像显示错误)2.6 本地调试2.7 后续修改 3. 项目设置为私密&#xff08;要付费升级账号才行❌&#xff09;3.…

【BA-BP分类】基于蝙蝠算法优化神经网络分类研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

RHCE---Shell基础 2

文章目录 目录 文章目录 前言 一.变量 概述 定义 自定义变量 环境变量 概述&#xff1a; 定义环境变量&#xff1a; 位置变量 "$*"会把所有位置参数当成一个整体&#xff08;或者说当成一个单词 变量的赋值和作用域 read 命令 变量和引号 变量的作用域 变…

Java SOAP 调用 C# 的WebService

Java SOAP 调用 C# 的WebService&#xff0c;C# 的WebService方法的创建可以参考上一篇文章。IntelliJ IDEA Community Edition 2021.2.3的idea64.exe新建项目&#xff0c;导入需要的jar&#xff0c;代码如下&#xff1a; import org.apache.axis.client.Service; import org.…

微信小程序 picker-view 组件构建一个上下拖动选择器

picker-view是官方的一个选择器组件 支持多级选择 当然也可以单项选择 我们先来看看是个什么东西吧 简单写一个 wxml代码 <view><picker-view bindchange"pickerChange" style"width: 300rpx; height: 200rpx; font-size: 20px;"><!-- pic…

1.顺序表-头插、头删、尾插、尾删

文章目录 简介1.头插功能2.头删功能3.尾插功能4.尾删功能5.此程序共包含4个文件&#xff0c;2个.c文件和2个.h文件5.1 SeqList.h文件5.2 SeqList.c文件5.3 test.h文件5.4 test.c文件 6.测试结果6.1 测试尾插和尾删的运行结果6.2 测试头插和头删的运行结果 7.温馨提示 简介 本文…

实战:打造一个开箱即用的超丝滑超漂亮hexo博客网站-v4-(通过百度网盘同步空间来同步source核心数据)

实战&#xff1a;打造一个开箱即用的超丝滑超漂亮hexo博客网站-v4-(通过百度网盘同步空间来同步source核心数据) 目录 文章目录 实战&#xff1a;打造一个开箱即用的超丝滑超漂亮hexo博客网站-v4-(通过百度网盘同步空间来同步source核心数据)目录写在前面本次更新方案背景方案官…

C语言每日一题(17)老人的数目

力扣 2678 老人的数目 给你一个下标从 0 开始的字符串 details 。details 中每个元素都是一位乘客的信息&#xff0c;信息用长度为 15 的字符串表示&#xff0c;表示方式如下&#xff1a; 前十个字符是乘客的手机号码。接下来的一个字符是乘客的性别。接下来两个字符是乘客的…

测开不得不会的python之re模块正则表达式匹配

学习目录 正则表达式介绍 正则表达式的常用符号 python的re模块 findall()函数 finditer()函数 match()函数 search()函数 split()函数 正则表达式的介绍 Python 通过标准库中的 re 模块来支持正则表达式。 正则表达式作为高级的文本模式匹配、抽取、和搜索。简单地说…

Python OpenCV将n×n的小图拼接成m×m的大图

Python OpenCV将nn的小图拼接成mm的大图 前言前提条件相关介绍实验环境n \times n的小图拼接成m \times m的大图代码实现 前言 由于本人水平有限&#xff0c;难免出现错漏&#xff0c;敬请批评改正。更多精彩内容&#xff0c;可点击进入Python日常小操作专栏、OpenCV-Python小…

独立企业签名和共享企业签名的区别

最近两天&#xff0c;小编注意到行业内的一则消息&#xff0c;市面上有好几本企业签名证书又被封了。毋庸置疑&#xff0c;这些肯定是共享的证书。理由很简单&#xff0c;市面上的用来做共享证书的企业签名&#xff0c;基本上都是不会限制应用类型以及签名的数量。据鲲分发平台…

计算机考研自命题(6)

1、C语言–奇数求和 1、使用函数求奇数和&#xff1a;输入一批正整数&#xff08;以零或负数为结束标志&#xff09;&#xff0c;求其中的奇数和。要求定义和调用函数 odd(n) 判断数的奇偶 性&#xff0c;当 n 为偶数时返回 0 &#xff0c;否则返回 1 。试编写相应程序。 /* 解…

yolov7改进优化之蒸馏(二)

续yolov7改进优化之蒸馏&#xff08;一&#xff09;-CSDN博客 上一篇已经基本写出来yolov7/v5蒸馏的整个过程&#xff0c;不过要真的训起来我们还需要进行一些修改。 Model修改 蒸馏需要对teacher和student网络的特征层进行loss计算&#xff0c;因此我们forward时要能够返回需…

Lua入门使用与基础语法

文章目录 目的基础说明开发环境基础语法注释数据类型变量流程控制函数 总结 目的 Lua是一种非常小巧的脚本语言&#xff0c;基于C构建并且完全开源&#xff0c;可以方便的嵌入到各种项目中&#xff0c;当然也可以单独使用。Lua经常被用在很多非脚本语言的项目中&#xff0c;用…

组件通信$refs | $parent |$root

父组件传值子组件用Props 子组件传值父组件用$emit 父组件直接还可以直接取子组件的值用$refs 父组件直接从子子组件中获取值$refs 不建议使用会增加组件之间的耦合度&#xff0c;一般用于第三方插件的封装 ref如果绑定在dom节点上&#xff0c;拿到的就是原生dom节点。 ref如…

【智能家居】

面向Apple developer学习&#xff1a;AirPlay | Apple Developer Documentation Airplay AirPlay允许人们将媒体内容从iOS、ipad、macOS和tvOS设备无线传输到支持AirPlay的Apple TV、HomePod以及电视和扬声器上。 网页链接的最佳实践 首选系统提供的媒体播放器。内置的媒体播…

VRPTW(MATLAB):蜘蛛蜂优化算法SWO求解带时间窗的车辆路径问题VRPTW(提供参考文献及MATLAB代码)

一、VRPTW简介 带时间窗的车辆路径问题(Vehicle Routing Problem with Time Windows, VRPTW)是车辆路径问题(VRP)的一种拓展类型。VRPTW一般指具有容量约束的车辆在客户指定的时间内提供配送或取货服务&#xff0c;在物流领域应用广泛&#xff0c;具有重要的实际意义。VRPTW常…

IPD集成产品开发TR技术评审详解

IPD&#xff08;Integrated Product Development&#xff09;集成产品开发是一种跨部门协同的、利用先进技术和管理方法来快速推出新产品并满足客户需求的开发模式。华为利用IPD也非常出名。在IPD集成产品开发的过程中&#xff0c;TR&#xff08;Technical Review&#xff09;技…