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

前言

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

应用示例

第一种:@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添加监听器类
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值