当前位置: 首页 > article >正文

@EventListener底层原理(超详细)| @TransactionalEventListener底层原理 | 事务同步

    • 0. 举个栗子
      • 0.1. 事件监听方法
      • 0.2. 事件推送
    • 1. @EventListener注解
    • 2. @EventListener标注的监听方法解析
      • 2.1. 事件监听方法处理器EventListenerMethodProcessors
        • 2.1.1. AbstractApplicationContext.invokeBeanFactoryPostProcessors
        • 2.1.2. AbstractApplicationContext.initApplicationEventMulticaster
        • 2.1.3. AbstractApplicationContext.registerListeners
          • 2.1.3.1. AbstractApplicationEventMulticaster.addApplicationListener
          • 2.1.3.2. AbstractApplicationEventMulticaster.addApplicationListenerBean
        • 2.1.4. finishBeanFactoryInitialization
          • 2.1.4.1. EventListenerMethodProcessor.afterSingletonsInstantiated
          • 2.1.4.2. EventListenerMethodProcessor.processBean
          • 2.1.4.3. DefaultEventListenerFactory.createApplicationListener
          • 2.1.4.4. TransactionalEventListenerFactory.createApplicationListener
          • 2.1.4.5. AbstractApplicationContext.addApplicationListener
      • 2.2. 事件监听方法处理器EventListenerMethodProcessors小结
    • 3. publishEvent事件推送
      • 3.1. AbstractApplicationContext.publishEvent
      • 3.2. SimpleApplicationEventMulticaster.multicastEvent
        • 3.2.1. AbstractApplicationEventMulticaster.getApplicationListeners
        • 3.2.2. AbstractApplicationEventMulticaster.retrieveApplicationListeners
        • 3.2.3. AbstractApplicationEventMulticaster.CachedListenerRetriever.getApplicationListeners
      • 3.3. SimpleApplicationEventMulticaster.invokeListener
        • 3.3.1. ApplicationListenerMethodAdapter.onApplicationEvent
        • 3.3.2. ApplicationListenerMethodAdapter.doInvoke
      • 3.4. @TransactionalEventListener底层原理
        • 3.4.1. ApplicationListenerMethodTransactionalAdapter.onApplicationEvent
      • 3.5. publishEvent事件推送小结

0. 举个栗子

0.1. 事件监听方法

/*** 通用事件监听器*/
@Slf4j
@Component
public class CommonHandleListener {public static final String NOTIFY_OTHER_SYSTEM_EVENT = "onNotifyOtherSystemEvent";@Autowiredprivate CommonConfigService commonConfigService;@EventListener(condition = "#eventDTO.source == T(com.example.common.listener.CommonHandleListener).NOTIFY_OTHER_SYSTEM_EVENT")public void onNotifyOtherSystemEvent(CommonEventDTO eventDTO) {log.info("EventListener事务提交后执行");if (NOTIFY_OTHER_SYSTEM_EVENT.equals(eventDTO.getSource())) { ... }}@TransactionalEventListener(// 监听的阶段phase = TransactionPhase.AFTER_COMMIT,// 监听的条件condition = "#eventDTO.source == T(com.example.common.listener.CommonHandleListener).NOTIFY_OTHER_SYSTEM_EVENT")public void onNotifyOtherSystemTransactionalEvent(CommonEventDTO eventDTO) {log.info("TransactionalEventListener事务提交后执行");if (NOTIFY_OTHER_SYSTEM_EVENT.equals(eventDTO.getSource())) { ... }}
}

0.2. 事件推送

    @Override@Transactional(rollbackFor = Exception.class)public NotifySystemDTO submit(CommonConfigDTO commonConfigDTO) {Long id = this.dealMainInfo(commonConfigDTO);List<DetailVo> detailVos = this.dealMainDetailInfo(id);NotifySystemDTO dto = this.buildNotifySystemDTO(id, detailVos);publisher.publishEvent(new CommonEventDTO(CommonHandleListener.NOTIFY_OTHER_SYSTEM_EVENT, dto));log.info("事务提交前操作:{}", JSON.toJSONString(commonConfigDTO));return dto;}

1. @EventListener注解

注解有3个属性,主要是condition和classes(value同classes)

  • condition是监听条件,使用SpEL表达式,满足条件才会调用监听方法,使用栗子可以见@EventListener的使用的2.3.1
  • classes属性:指定监听的类,可以指定多个;监听的类和监听方法参数的约定
    • 如果为空,则监听方法参数有且只有一个,且为发布事件的事件类型(不然你发布事件,底层也反射调用不到监听方法)
    • 如果设置一个事件类,监听方法可以不设置参数;如果要设置参数,也是有且只有一个参数,且为发布事件的事件类型
    • 如果设置多个事件类,监听方法可以不设置参数;如果要设置参数,也是有且只有一个参数,最好是共同父类或接口
    • 正常来说不建议设置多个事件类,增加系统的复杂性和潜在的性能开销;建议根据实际需求合理设计事件监听机制,避免不必要的事件监听

2. @EventListener标注的监听方法解析

需要对Spring底层Bean容器注册有所了解

2.1. 事件监听方法处理器EventListenerMethodProcessors

先看一下这个类

  • 实现了SmartInitializingSingleton,就意味着初始化之后就会触发afterSingletonsInstantiated方法调用
  • 实现了BeanFactoryPostProcessor,就意味着会触发EventListenerMethodProcessor.postProcessBeanFactory
  • 下文会有说明,眼熟一下
public class EventListenerMethodProcessorimplements SmartInitializingSingleton, ApplicationContextAware, BeanFactoryPostProcessor{@Nullableprivate List<EventListenerFactory> eventListenerFactories;
}

在SpringBoot启动类run方法(SpringBoot的启动整体过程2.2节)中有四个方法

  • createApplicationContext创建容器上下文时注册EventListenerMethodProcessor的Bean定义
  • refreshContext会对IOC容器的Bean定义进行生命周期管理;
    • 该方法调用到refresh方法(容器管理的重要方法),里面主要有四个方法
      • invokeBeanFactoryPostProcessors:调用Bean工厂的后置处理器,会触发EventListenerMethodProcessor.postProcessBeanFactory初始化eventListenerFactories工厂
      • initApplicationEventMulticaster:初始化事件广播
      • registerListeners:检测并注册已有的事件监听器和Bean
      • finishBeanFactoryInitialization:实例化所有的(non-lazy-init)单例Bean,会触发EventListenerMethodProcessor对@EventListener标注的监听方法解析
public ConfigurableApplicationContext run(String... args) {... ... ...try {ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);// 上面设置一些环境吧应该,没怎么关注configureIgnoreBeanInfo(environment);// 这里控制台会打印输出那个Spring启动图Banner printedBanner = printBanner(environment);// 创建ApplicationContext即ioc容器context = createApplicationContext();// 会从缓存中获取SpringBootExceptionReporter类型实例exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,new Class[] { ConfigurableApplicationContext.class }, context);// 对context进行一些预处理,主要是一些赋值// 这里会将主配置类封装成BeanDefinition再注册到ApplicationContext中prepareContext(context, environment, listeners, applicationArguments, printedBanner);// 刷新context容器,重点方法refreshContext(context);... ...
}public void refresh() throws BeansException, IllegalStateException {synchronized (this.startupShutdownMonitor) {try{// 调用Bean工厂的后置处理器invokeBeanFactoryPostProcessors(beanFactory);// 初始化事件广播initApplicationEventMulticaster();// 检测并注册已有的事件监听器和BeanregisterListeners();// 实例化所有的(non-lazy-init)单例BeanfinishBeanFactoryInitialization(beanFactory);.........}}
2.1.1. AbstractApplicationContext.invokeBeanFactoryPostProcessors
  • 调用Bean工厂的后置处理器,会触发EventListenerMethodProcessor.postProcessBeanFactory初始化eventListenerFactories工厂
  • eventListenerFactories会被初始化两个事件监听工厂类DefaultEventListenerFactory和TransactionalEventListenerFactory
  • 后者比前者优先级高
2.1.2. AbstractApplicationContext.initApplicationEventMulticaster

初始化事件广播,主要是初始化事件监听组播者applicationEventMulticaster并往容器注册单例bean

protected void initApplicationEventMulticaster() {ConfigurableListableBeanFactory beanFactory = getBeanFactory();// 如果有自定义的组播器,即beanName为applicationEventMulticaster,则用自定义的组播器if (beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) {... ...}else {// 默认初始化组播器SimpleApplicationEventMulticaster,往bean公池注册单例beanthis.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);beanFactory.registerSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, this.applicationEventMulticaster);}
}
2.1.3. AbstractApplicationContext.registerListeners
  • 检测并注册已有的事件监听器,添加到组播器的监听器集合中
  • 获取实现的ApplicationListener接口的bean名称,添加到事件监听组播者的监听器Bean集合中
	protected void registerListeners() {// 将容器已有的监听器添加到事件监听组播者的监听器集合中for (ApplicationListener<?> listener : getApplicationListeners()) {getApplicationEventMulticaster().addApplicationListener(listener);}// 获取实现的ApplicationListener接口的bean名称,添加到事件监听组播者的监听器Bean集合中// 此时的bean还没实例化,所以存储beanName,以便于后续根据beanName获取对应监听器String[] listenerBeanNames = getBeanNamesForType(ApplicationListener.class, true, false);for (String listenerBeanName : listenerBeanNames) {getApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName);}// 早期事件调用,此时监听器已注册,就将早期的事件earlyApplicationEvents进行广播,然后置空Set<ApplicationEvent> earlyEventsToProcess = this.earlyApplicationEvents;this.earlyApplicationEvents = null;if (!CollectionUtils.isEmpty(earlyEventsToProcess)) {for (ApplicationEvent earlyEvent : earlyEventsToProcess) {getApplicationEventMulticaster().multicastEvent(earlyEvent);}}}
  • getApplicationEventMulticaster()获取到的组播者就是2.1.2初始化的SimpleApplicationEventMulticaster
  • SimpleApplicationEventMulticaster的父类AbstractApplicationEventMulticaster持有一个辅助的检索器defaultRetriever对象,内部有两个集合
    • applicationListeners:辅助持有的监听器集合
    • applicationListenerBeans:辅助持有的监听器beanName
public abstract class AbstractApplicationEventMulticasterimplements ApplicationEventMulticaster, BeanClassLoaderAware, BeanFactoryAware {// 持有的辅助检索器对象private final DefaultListenerRetriever defaultRetriever = new DefaultListenerRetriever();// 监听方法同步调用的监听器缓存final Map<ListenerCacheKey, CachedListenerRetriever> retrieverCache = new ConcurrentHashMap<>(64);......private class DefaultListenerRetriever {public final Set<ApplicationListener<?>> applicationListeners = new LinkedHashSet<>();public final Set<String> applicationListenerBeans = new LinkedHashSet<>();}
}
2.1.3.1. AbstractApplicationEventMulticaster.addApplicationListener

将创建的ApplicationListenerMethodAdapter加入组播者辅助持有的监听器集合

	@Override
public void addApplicationListener(ApplicationListener<?> listener) {synchronized (this.defaultRetriever) {// 如果当前监听器是代理对象,则先移除代理的target对象,避免重复调用Object singletonTarget = AopProxyUtils.getSingletonTarget(listener);if (singletonTarget instanceof ApplicationListener) {this.defaultRetriever.applicationListeners.remove(singletonTarget);}// 添加至defaultRetriever辅助持有的监听器this.defaultRetriever.applicationListeners.add(listener);this.retrieverCache.clear();}
}
2.1.3.2. AbstractApplicationEventMulticaster.addApplicationListenerBean

将实现的ApplicationListener接口的组件beanName,添加到组播者辅助持有的监听器beanName

	@Overridepublic void addApplicationListenerBean(String listenerBeanName) {synchronized (this.defaultRetriever) {this.defaultRetriever.applicationListenerBeans.add(listenerBeanName);this.retrieverCache.clear();}}
2.1.4. finishBeanFactoryInitialization
  • 主要在preInstantiateSingletons方法进行bean的生命周期以及bean初始化后的一些操作
  • EventListenerMethodProcessor初始化之后就会触发afterSingletonsInstantiated方法调用
    • EventListenerMethodProcessor是实现SmartInitializingSingleton接口的
protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {... ... ...// 省略一些环境或者其他调用的代码// 该方法会将非懒加载又是单例的bean进行生命周期调用beanFactory.preInstantiateSingletons();
}public void preInstantiateSingletons() throws BeansException {... ... ... //省略List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);// Trigger initialization of all non-lazy singleton beans...for (String beanName : beanNames) {RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);// 非抽象&& 单例 && 非懒加载if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {if (isFactoryBean(beanName)) {... ... ... // 大部分组件bean不会走这里,这是工厂bean的}else {// 最终调用getBean进行Bean的生命周期调用getBean(beanName);}}}// Trigger post-initialization callback for all applicable beans...for (String beanName : beanNames) {Object singletonInstance = getSingleton(beanName);if (singletonInstance instanceof SmartInitializingSingleton) {SmartInitializingSingleton smartSingleton = (SmartInitializingSingleton) singletonInstance;if (System.getSecurityManager() != null) {AccessController.doPrivileged((PrivilegedAction<Object>) () -> {smartSingleton.afterSingletonsInstantiated();return null;}, getAccessControlContext());}else {// 会触发EventListenerMethodProcessor.afterSingletonsInstantiated方法调用smartSingleton.afterSingletonsInstantiated();}}}
}
2.1.4.1. EventListenerMethodProcessor.afterSingletonsInstantiated

就是遍历所有容器bean去对@EventListener标注的监听方法解析

	public void afterSingletonsInstantiated() {ConfigurableListableBeanFactory beanFactory = this.beanFactory;Assert.state(this.beanFactory != null, "No ConfigurableListableBeanFactory set");String[] beanNames = beanFactory.getBeanNamesForType(Object.class);for (String beanName : beanNames) {... ...// 忽略一大堆的条件判断,最终通过该方法对@EventListener标注的监听方法解析processBean(beanName, type);}}
2.1.4.2. EventListenerMethodProcessor.processBean
  • 对@EventListener标注的监听方法解析,
  • 通过工厂对监听方法生成监听器方法适配器ApplicationListenerMethodAdapter
  • 将创建的ApplicationListenerMethodAdapter加入容器和组播者的监听器集合
	private void processBean(final String beanName, final Class<?> targetType) {if (!this.nonAnnotatedClasses.contains(targetType) &&AnnotationUtils.isCandidateClass(targetType, EventListener.class) &&!isSpringContainerClass(targetType)) {Map<Method, EventListener> annotatedMethods = null;try {// 这里获取目标bean上有@EventListener(包括嵌套引用有该注解,比如@TransactionalEventListener)注解标注的方法annotatedMethods = MethodIntrospector.selectMethods(targetType,(MethodIntrospector.MetadataLookup<EventListener>) method ->AnnotatedElementUtils.findMergedAnnotation(method, EventListener.class));}catch {...}if (CollectionUtils.isEmpty(annotatedMethods)) {...}else {// Non-empty set of methodsConfigurableApplicationContext context = this.applicationContext;Assert.state(context != null, "No ApplicationContext set");// 在上文2.1的refresh方法中invokeBeanFactoryPostProcessors就会触发EventListenerMethodProcessor.postProcessBeanFactory初始化eventListenerFactories工厂// 这里会拿到两个事件监听工厂类DefaultEventListenerFactory和TransactionalEventListenerFactory// 后者的优先级比前置高,后置针对事务有一些特殊处理List<EventListenerFactory> factories = this.eventListenerFactories;Assert.state(factories != null, "EventListenerFactory List not initialized");// 双重遍历方法和工厂,去匹配方法使用哪个工厂去创建ApplicationListenerMethodAdapter监听器方法适配器for (Method method : annotatedMethods.keySet()) {for (EventListenerFactory factory : factories) {// 判断当前工厂时候是否支持当前方法// 默认的工厂类DefaultEventListenerFactory会直接是true;TransactionalEventListenerFactory会判断方法是否有@TransactionalEventListener注解if (factory.supportsMethod(method)) {Method methodToUse = AopUtils.selectInvocableMethod(method, context.getType(beanName));// 通过当前工厂去创建监听器// 默认的工厂类DefaultEventListenerFactory会直接创建ApplicationListenerMethodAdapter监听器方法适配器;// TransactionalEventListenerFactory会创建ApplicationListenerMethodTransactionalAdapter监听器方法适配器,继承了ApplicationListenerMethodAdapterApplicationListener<?> applicationListener =factory.createApplicationListener(beanName, targetType, methodToUse);if (applicationListener instanceof ApplicationListenerMethodAdapter) {((ApplicationListenerMethodAdapter) applicationListener).init(context, this.evaluator);}// 将创建的ApplicationListenerMethodAdapter加入容器和组播者的监听器集合context.addApplicationListener(applicationListener);break;}}}if (logger.isDebugEnabled()) {...}}}}
2.1.4.3. DefaultEventListenerFactory.createApplicationListener
  • DefaultEventListenerFactory实现了Ordered,并且它定义的优先级是最低的
  • createApplicationListener方法就仅仅初始化创建ApplicationListenerMethodAdapter
public class DefaultEventListenerFactory implements EventListenerFactory, Ordered {private int order = LOWEST_PRECEDENCE;public void setOrder(int order) { this.order = order; }@Overridepublic int getOrder() { return this.order; }@Overridepublic boolean supportsMethod(Method method) { return true; }@Overridepublic ApplicationListener<?> createApplicationListener(String beanName, Class<?> type, Method method) {return new ApplicationListenerMethodAdapter(beanName, type, method);}}public ApplicationListenerMethodAdapter(String beanName, Class<?> targetClass, Method method) {this.beanName = beanName;this.method = BridgeMethodResolver.findBridgedMethod(method);this.targetMethod = (!Proxy.isProxyClass(targetClass) ?AopUtils.getMostSpecificMethod(method, targetClass) : this.method);this.methodKey = new AnnotatedElementKey(this.targetMethod, targetClass);EventListener ann = AnnotatedElementUtils.findMergedAnnotation(this.targetMethod, EventListener.class);this.declaredEventTypes = resolveDeclaredEventTypes(method, ann);// 获取当前监听方法上@EventListener注解的condition属性值this.condition = (ann != null ? ann.condition() : null);// 获取当前监听方法顺序值,如果有标注@Order就会获取改注解的value,无则默认0this.order = resolveOrder(this.targetMethod);}
2.1.4.4. TransactionalEventListenerFactory.createApplicationListener
  • DefaultEventListenerFactory也实现了Ordered,并且它定义的优先级是50,所以它比DefaultEventListenerFactory优先级高
  • createApplicationListener方法就创建的是ApplicationListenerMethodTransactionalAdapter
public class TransactionalEventListenerFactory implements EventListenerFactory, Ordered {private int order = 50;public TransactionalEventListenerFactory() {}public void setOrder(int order) { this.order = order; }public int getOrder() { return this.order;}public boolean supportsMethod(Method method) {return AnnotatedElementUtils.hasAnnotation(method, TransactionalEventListener.class);}public ApplicationListener<?> createApplicationListener(String beanName, Class<?> type, Method method) {return new ApplicationListenerMethodTransactionalAdapter(beanName, type, method);}
}
class ApplicationListenerMethodTransactionalAdapter extends ApplicationListenerMethodAdapter {private final TransactionalEventListener annotation;public ApplicationListenerMethodTransactionalAdapter(String beanName, Class<?> targetClass, Method method) {// 父级ApplicationListenerMethodAdapter初始化,复用@EventListener能力super(beanName, targetClass, method);TransactionalEventListener ann = AnnotatedElementUtils.findMergedAnnotation(method, TransactionalEventListener.class);if (ann == null) {throw new IllegalStateException("No TransactionalEventListener annotation found on method: " + method);}this.annotation = ann;}
}
2.1.4.5. AbstractApplicationContext.addApplicationListener
	@Overridepublic void addApplicationListener(ApplicationListener<?> listener) {Assert.notNull(listener, "ApplicationListener must not be null");// 这里是已经持有组播器了,先往组播器添加当前获取的监听器,再完容器的监听器集合添加if (this.applicationEventMulticaster != null) {// 上文2.1.3.1方法this.applicationEventMulticaster.addApplicationListener(listener);}this.applicationListeners.add(listener);}

2.2. 事件监听方法处理器EventListenerMethodProcessors小结

  • IOC容器管理时会注册EventListenerMethodProcessors组件
  • EventListenerMethodProcessors组件进行系列生命周期处理后,触发其afterSingletonsInstantiated方法
  • 对@EventListener标注的监听方法解析,再由工厂类生成监听器方法适配器ApplicationListenerMethodAdapter,加入容器和组播者的监听器集合
  • 对@TransactionalEventListener标注的监听方法解析,再由工厂类生成监听器方法适配器ApplicationListenerMethodTransactionalAdapter,加入容器和组播者的监听器集合

3. publishEvent事件推送

  • 我们由上述栗子,来看看该方法的调用,最终是调用到AbstractApplicationContext#publishEvent(java.lang.Object, org.springframework.core.ResolvableType)

3.1. AbstractApplicationContext.publishEvent

  • 声明事件为ApplicationEvent类型,如果不是就包装成PayloadApplicationEvent(其父类就是是ApplicationEvent)
  • 通过组播器multicastEvent方法进行推送
  • 如果有父上下文,也向父上下文递归推送该事件
	protected void publishEvent(Object event, @Nullable ResolvableType eventType) {// 声明事件为ApplicationEvent类型,如果不是就包装成PayloadApplicationEvent(其父类就是是ApplicationEvent)ApplicationEvent applicationEvent;if (event instanceof ApplicationEvent) {applicationEvent = (ApplicationEvent) event;} else {applicationEvent = new PayloadApplicationEvent<>(this, event);if (eventType == null) {eventType = ((PayloadApplicationEvent<?>) applicationEvent).getResolvableType();}}// 如果不为空,则还未注册监听器,则加入早期事件集合earlyApplicationEvents// 组播器在上文2.1.3. refresh->registerListeners方法,注册完并推送早期事件,会将其置空if (this.earlyApplicationEvents != null) {this.earlyApplicationEvents.add(applicationEvent);}else {// 我们应用推送的事件,都通过multicastEvent方法进行推送getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);}// 如果有父上下文,也向父上下文递归推送该事件,向应用栗子手动推送的事件,最后parent都是null的if (this.parent != null) {if (this.parent instanceof AbstractApplicationContext) {((AbstractApplicationContext) this.parent).publishEvent(event, eventType);}else {this.parent.publishEvent(event);}}}

3.2. SimpleApplicationEventMulticaster.multicastEvent

  • 上文2.1.2也说了,初始化的组播器就是SimpleApplicationEventMulticaster
  • 默认taskExecutor为空,即同步调用事件监听方法
    • 如果想要异步,最好还是在监听方法内进行异步处理,(也就是事件同步调用,监听方法的逻辑异步处理)
    • taskExecutor不为空则全局事件监听都是异步处理的
    • taskExecutor不为空,就是自定义组播器applicationEventMulticaster的时候给taskExecutor赋值(推荐使用线程池)
	@Overridepublic 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);}}}
3.2.1. AbstractApplicationEventMulticaster.getApplicationListeners
  • 组播器的父类AbstractApplicationEventMulticaster持有一个辅助的检索器defaultRetriever对象,还持有一个本地检索器缓存retrieverCache(上文2.1.3)
  • 命中缓存就直接从CachedListenerRetriever检索器获取监听器列表返回,否则retrieveApplicationListeners方法进行查找
    • 缓存key为事件类型eventType和来源标识类型sourceType构成的ListenerCacheKey对象
      • 提一嘴:这个对象重写了equals方法,只要eventType、sourceType都相等就是同一个对象
    • 缓存value是内部类对象CachedListenerRetriever
	protected Collection<ApplicationListener<?>> getApplicationListeners(ApplicationEvent event, ResolvableType eventType) {// 创建缓存key,只要eventType、sourceType都相等就是同一个对象Object source = event.getSource();Class<?> sourceType = (source != null ? source.getClass() : null);ListenerCacheKey cacheKey = new ListenerCacheKey(eventType, sourceType);// 准备新的缓存检索器CachedListenerRetriever newRetriever = null;// 尝试从缓存中获取已存在的检索器CachedListenerRetriever existingRetriever = this.retrieverCache.get(cacheKey);if (existingRetriever == null) {if (this.beanClassLoader == null ||(ClassUtils.isCacheSafe(event.getClass(), this.beanClassLoader) &&(sourceType == null || ClassUtils.isCacheSafe(sourceType, this.beanClassLoader)))) {//如果缓存不命中,且类加载器安全(防止类加载器泄漏),则创建新的检索器newRetriever = new CachedListenerRetriever();// 如果缓存中已经有cacheKey,则putIfAbsent返回之前的缓存对象,即existingRetriever不为空// 如果缓存中没有cacheKey,则putIfAbsent返回null,即existingRetriever为空existingRetriever = this.retrieverCache.putIfAbsent(cacheKey, newRetriever);// 这块什么时候会满足,有点不太理解??if (existingRetriever != null) {newRetriever = null;  // no need to populate it in retrieveApplicationListeners}}}// 如果存在缓存的检索器,尝试获取缓存的监听器返回if (existingRetriever != null) {Collection<ApplicationListener<?>> result = existingRetriever.getApplicationListeners();if (result != null) {return result;}// If result is null, the existing retriever is not fully populated yet by another thread.// Proceed like caching wasn't possible for this current local attempt.}// 缓存不命中时,通过retrieveApplicationListeners方法获取监听器返回return retrieveApplicationListeners(eventType, sourceType, newRetriever);}
3.2.2. AbstractApplicationEventMulticaster.retrieveApplicationListeners
  • 缓存不命中时,先写的缓存检索器对象,再通过该方法获取支持当前事件处理的监听器集合数据,再更新缓存的检索器对象持有的监听器集合
	private Collection<ApplicationListener<?>> retrieveApplicationListeners(ResolvableType eventType, @Nullable Class<?> sourceType, @Nullable CachedListenerRetriever retriever) {// 定义查找到所有的监听器列表List<ApplicationListener<?>> allListeners = new ArrayList<>();// 定义过滤后的支持当前事件的监听器列表和beanNameSet<ApplicationListener<?>> filteredListeners = (retriever != null ? new LinkedHashSet<>() : null);Set<String> filteredListenerBeans = (retriever != null ? new LinkedHashSet<>() : null);Set<ApplicationListener<?>> listeners;Set<String> listenerBeans;// 对组播器持有的默认检索器defaultRetriever加锁,将该检索器持有已注册的监听器和监听器beanName赋值到当前局部变量// 在上文2.1.3.registerListeners时,就会注册监听器到默认的检索器defaultRetriever里头synchronized (this.defaultRetriever) {listeners = new LinkedHashSet<>(this.defaultRetriever.applicationListeners);listenerBeans = new LinkedHashSet<>(this.defaultRetriever.applicationListenerBeans);}// 循环注册的监听器,通过supportsEvent方法来判断每个监听器是否支持当前事件(判断事件类型、来源类型),然后写入当前的局部变量里头// @EventListener注解的classes过滤就是在这里处理的for (ApplicationListener<?> listener : listeners) {if (supportsEvent(listener, eventType, sourceType)) {if (retriever != null) {filteredListeners.add(listener);}allListeners.add(listener);}}// 循环注册的监听器beanName,通过supportsEvent方法来判断每个监听器是否支持当前事件(判断事件类型、来源类型),支持就写入当前的局部变量里头,不支持就移除// 按beanName来检索监听器,可能与注册的监听器重合,还需要判断去重if (!listenerBeans.isEmpty()) {ConfigurableBeanFactory beanFactory = getBeanFactory();for (String listenerBeanName : listenerBeans) {try {if (supportsEvent(beanFactory, listenerBeanName, eventType)) { ... }else { ... }}catch (NoSuchBeanDefinitionException ex) { ... }}}// 将所有的监听器进行排序,order越小约优先// 如果是ApplicationListenerMethodAdapter,在上文2.1.4.3节新建的时候就会解析监听方法的@Order注解的value值AnnotationAwareOrderComparator.sort(allListeners);if (retriever != null) {// 更新缓存的检索器持有的监听器集合if (filteredListenerBeans.isEmpty()) {// filteredListenerBeans为空时allListeners和filteredListeners其实是一样的retriever.applicationListeners = new LinkedHashSet<>(allListeners);retriever.applicationListenerBeans = filteredListenerBeans;}else {retriever.applicationListeners = filteredListeners;retriever.applicationListenerBeans = filteredListenerBeans;}}return allListeners;}
3.2.3. AbstractApplicationEventMulticaster.CachedListenerRetriever.getApplicationListeners
  • 缓存命中时,直接从缓存的检索器对象获取监听器集合数据
	private class CachedListenerRetriever {@Nullablepublic volatile Set<ApplicationListener<?>> applicationListeners;@Nullablepublic volatile Set<String> applicationListenerBeans;@Nullablepublic Collection<ApplicationListener<?>> getApplicationListeners() {// 写缓存的时候,就已经把符合当前事件的监听器集合数据更新到当前缓存的检索器了Set<ApplicationListener<?>> applicationListeners = this.applicationListeners;Set<String> applicationListenerBeans = this.applicationListenerBeans;if (applicationListeners == null || applicationListenerBeans == null) {// Not fully populated yetreturn null;}// 从applicationListenerBeans和applicationListeners获取监听器数据,然后排序List<ApplicationListener<?>> allListeners = new ArrayList<>(applicationListeners.size() + applicationListenerBeans.size());allListeners.addAll(applicationListeners);if (!applicationListenerBeans.isEmpty()) {BeanFactory beanFactory = getBeanFactory();for (String listenerBeanName : applicationListenerBeans) {try {allListeners.add(beanFactory.getBean(listenerBeanName, ApplicationListener.class));}catch (NoSuchBeanDefinitionException ex) { ... }}}if (!applicationListenerBeans.isEmpty()) {AnnotationAwareOrderComparator.sort(allListeners);}return allListeners;}}

3.3. SimpleApplicationEventMulticaster.invokeListener

  • 如果有自定义的错误处理器,则在错误处理器中处理,没有则直接调用doInvokeListener(调用当前监听器onApplicationEvent方法),做监听方法的调用
	protected void invokeListener(ApplicationListener<?> listener, ApplicationEvent event) {// 默认为空,除非是自定义的组播器,手动定义了错误处理器ErrorHandler errorHandler = getErrorHandler();if (errorHandler != null) {try {doInvokeListener(listener, event);}catch (Throwable err) {errorHandler.handleError(err);}}else {doInvokeListener(listener, event);}}private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {try {// 调用当前监听器onApplicationEvent方法,做监听方法的调用listener.onApplicationEvent(event);}catch (ClassCastException ex) { ... }}
3.3.1. ApplicationListenerMethodAdapter.onApplicationEvent
  • 常规的事件监听方法,向上述栗子的onNotifyOtherSystemEvent,用@EventListener标注的
    • 在上文2.1.4.2节就讲到,在IOC容器管理时,就对@EventListener标注的监听方法解析,生成监听器方法适配器ApplicationListenerMethodAdapter
  • 最后通过processEvent对监听事件处理
	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");}}}private boolean shouldHandle(ApplicationEvent event, @Nullable Object[] args) {if (args == null) {return false;}// @EventListener的condition属性处理,返回true即当前适配器可处理当前事件String condition = getCondition();if (StringUtils.hasText(condition)) {Assert.notNull(this.evaluator, "EventExpressionEvaluator must not be null");return this.evaluator.condition(condition, event, this.targetMethod, this.methodKey, args, this.applicationContext);}return true;}
3.3.2. ApplicationListenerMethodAdapter.doInvoke
  • 就是通过反射调用监听方法,适配器里面的method,beanName等等,在创建ApplicationListenerMethodAdapter就已经赋值了
protected Object doInvoke(Object... args) {// 通过beanName获取目标对象Object bean = getTargetBean();if (bean.equals(null)) {return null;}ReflectionUtils.makeAccessible(this.method);try {// 通过反射调用目标对象方法return this.method.invoke(bean, args);}catch (IllegalArgumentException ex) { ... }}

3.4. @TransactionalEventListener底层原理

  • 底层原理和@EventListener差不多,区别在于,它使用的是ApplicationListenerMethodTransactionalAdapter监听方法适配器
  • 让我们看看它的onApplicationEvent方法
3.4.1. ApplicationListenerMethodTransactionalAdapter.onApplicationEvent
  • 创建一个事务同步的事件适配器TransactionSynchronizationEventAdapter,实现了TransactionSynchronization
    • 事务完成之后会获取所有的TransactionSynchronization,触发afterCompletion方法
  • 使用了事务同步管理器(TransactionSynchronizationManager.registerSynchronization)来注册同步任务
    • 就是将当前适配器添加到事务同步管理器TransactionSynchronizationManager的内部集合synchronizations里头
  • 在事务完成之后会触发TransactionSynchronizationEventAdapter的afterCompletion钩子方法
    • 会调用processEvent方法和@EventListener一样
class ApplicationListenerMethodTransactionalAdapter extends ApplicationListenerMethodAdapter {private final TransactionalEventListener annotation;@Overridepublic void onApplicationEvent(ApplicationEvent event) {// 事务活跃状态if (TransactionSynchronizationManager.isSynchronizationActive() &&TransactionSynchronizationManager.isActualTransactionActive()) {// 创建一个事务同步的事件适配器TransactionSynchronizationEventAdapterTransactionSynchronization transactionSynchronization = createTransactionSynchronization(event);// 通过事务同步管理器注册任务TransactionSynchronizationManager.registerSynchronization(transactionSynchronization);}... ...}private TransactionSynchronization createTransactionSynchronization(ApplicationEvent event) {return new TransactionSynchronizationEventAdapter(this, event, this.annotation.phase());}private static class TransactionSynchronizationEventAdapter extends TransactionSynchronizationAdapter {private final ApplicationListenerMethodAdapter listener;private final ApplicationEvent event;private final TransactionPhase phase;public TransactionSynchronizationEventAdapter(ApplicationListenerMethodAdapter listener,ApplicationEvent event, TransactionPhase phase) {this.listener = listener;this.event = event;this.phase = phase;}@Overridepublic int getOrder() {return this.listener.getOrder();}@Overridepublic void beforeCommit(boolean readOnly) {if (this.phase == TransactionPhase.BEFORE_COMMIT) {processEvent();}}@Overridepublic void afterCompletion(int status) {if (this.phase == TransactionPhase.AFTER_COMMIT && status == STATUS_COMMITTED) {processEvent();}else if (this.phase == TransactionPhase.AFTER_ROLLBACK && status == STATUS_ROLLED_BACK) {processEvent();}else if (this.phase == TransactionPhase.AFTER_COMPLETION) {processEvent();}}protected void processEvent() {this.listener.processEvent(this.event);}}}

3.5. publishEvent事件推送小结

  • 通过组播器进行推送,默认是同步推送
  • 会先从检索器缓存中获取检索器,再获取检索器持有的支持当前事件处理的监听器
    • 如果没命中缓存则,先写入检索器缓存,再查找支持当前事件处理的监听器,再更新缓存
    • 对于事件推送,获取的监听器其实就是监听器方法适配器
      • @EventListener标注的监听方法由TransactionSynchronizationEventAdapter进行处理调用
      • @TransactionalEventListener标注的监听方法由ApplicationListenerMethodAdapter进行处理调用
  • 循环遍历监听器列表
    • 通过shouldHandle方法判断当前监听器能否处理当前事件,主要处理condition属性的SPEL表达式条件
    • 能处理就反射调用监听器方法
  • 对于事务监听(@TransactionalEventListener标注的方法),
    • 事件推送时,通过事务同步管理器TransactionSynchronizationManager.registerSynchronization注册任务,
    • 事务完成时,事务同步的事件适配器TransactionSynchronizationEventAdapter.afterCompletion方法来进行监听方法调用

相关文章:

@EventListener底层原理(超详细)| @TransactionalEventListener底层原理 | 事务同步

0. 举个栗子0.1. 事件监听方法0.2. 事件推送 1. EventListener注解2. EventListener标注的监听方法解析2.1. 事件监听方法处理器EventListenerMethodProcessors2.1.1. AbstractApplicationContext.invokeBeanFactoryPostProcessors2.1.2. AbstractApplicationContext.initAppli…...

NX/UG二次开发—CAM—快速查找程序参数名称

使用UF_PARAM_XXX读取或设置参数时,会发现程序中有一个INT类型参数param_index,这个就是对应程序中的参数,比如读取程序余量,则param_index = UF_PARAM_STOCK_PART,读取程序的加工坐标系则param_index = UF_PARAM_MCS等等。 你需要读取什么参数,只要只能在uf_param_indic…...

X86路由搭配rtl8367s交换机

x86软路由&#xff0c;买双网口就好。或者单网口主板&#xff0c;外加一个pcie千兆。 华硕h81主板戴尔i350-T2双千兆&#xff0c;做bridge下载&#xff0c;速度忽高忽低。 今天交换机到货&#xff0c;poe供电&#xff0c;还是网管&#xff0c;支持Qvlan及IGMP Snooping&#xf…...

【C++语言】卡码网语言基础课系列----5. A+B问题VIII

文章目录 练习题目AB问题VIII具体代码实现 小白寄语诗词共勉 练习题目 AB问题VIII 题目描述&#xff1a; 你的任务是计算若干整数的和。 输入描述&#xff1a; 输入的第一行为一个整数N&#xff0c;接下来N行每行先输入一个整数M&#xff0c;然后在同一行内输入M个整数。 输出…...

【LLM-agent】(task1)简单客服和阅卷智能体

note 一个完整的agent有模型 (Model)、工具 (Tools)、编排层 (Orchestration Layer)一个好的结构化 Prompt 模板&#xff0c;某种意义上是构建了一个好的全局思维链。 如 LangGPT 中展示的模板设计时就考虑了如下思维链&#xff1a;Role (角色) -> Profile&#xff08;角色…...

CAP 定理的 P 是什么

分布式系统 CAP 定理 P 代表什么含义 作者之前在看 CAP 定理时抱有很大的疑惑&#xff0c;CAP 定理的定义是指在分布式系统中三者只能满足其二&#xff0c;也就是存在分布式 CA 系统的。作者在网络上查阅了很多关于 CAP 文章&#xff0c;虽然这些文章对于 P 的解释五花八门&am…...

RK3568使用opencv(使用摄像头捕获图像数据显示)

文章目录 一、opencv相关的类1. **cv::VideoCapture**2. **cv::Mat**3. **cv::cvtColor**4. **QImage**5. **QPixmap**总结二、代码实现一、opencv相关的类 1. cv::VideoCapture cv::VideoCapture 是 OpenCV 中用于视频捕捉的类,常用于从摄像头、视频文件、或者图像序列中捕…...

ZZNUOJ(C/C++)基础练习1021——1030(详解版)

目录 1021 : 三数求大值 C语言版 C版 代码逻辑解释 1022 : 三整数排序 C语言版 C版 代码逻辑解释 补充 &#xff08;C语言版&#xff0c;三目运算&#xff09;C类似 代码逻辑解释 1023 : 大小写转换 C语言版 C版 1024 : 计算字母序号 C语言版 C版 代码逻辑总结…...

2025 年,链上固定收益领域迈向新时代

“基于期限的债券市场崛起与 Secured Finance 的坚定承诺” 2025年&#xff0c;传统资产——尤其是股票和债券——大规模涌入区块链的浪潮将创造历史。BlackRock 首席执行官 Larry Fink 近期在彭博直播中表示&#xff0c;代币化股票和债券将逐步融入链上生态&#xff0c;将进一…...

使用where子句筛选记录

默认情况下,SearchCursor将返回一个表或要素类的所有行.然而在很多情况下,常常需要某些条件来限制返回行数. 操作方法: 1.打开IDLE,加载先前编写的SearchCursor.py脚本 2.添加where子句,更新SearchCursor()函数,查找记录中有<>文本的<>字段 with arcpy.da.Searc…...

基于互联网+智慧水务信息化整体解决方案

智慧水务的概述与发展背景 智慧水务是基于互联网、云计算、大数据、物联网等先进技术&#xff0c;对水务行业的工程建设、生产管理、管网运营、营销服务及企业综合管理等业务进行全面智慧化管理的创新模式。它旨在解决水务企业分散经营、管理水平不高、投资不足等问题。 水务…...

FIDL:Flutter与原生通讯的新姿势,不局限于基础数据类型

void initUser(User user); } 2、执行命令./gradlew assembleDebug&#xff0c;生成IUserServiceStub类和fidl.json文件 3、打开通道&#xff0c;向Flutter公开方法 FidlChannel.openChannel(getFlutterEngine().getDartExecutor(), new IUserServiceStub() { Override void…...

文件读写操作

写入文本文件 #include <iostream> #include <fstream>//ofstream类需要包含的头文件 using namespace std;void test01() {//1、包含头文件 fstream//2、创建流对象ofstream fout;/*3、指定打开方式&#xff1a;1.ios::out、ios::trunc 清除文件内容后打开2.ios:…...

cf1000(div.2)

Minimal Coprime最小公倍数 输入&#xff1a; 6 1 2 1 10 49 49 69 420 1 1 9982 44353 输出&#xff1a; 1 9 0 351 1 34371 代码...

【2025年数学建模美赛E题】(农业生态系统)完整解析+模型代码+论文

生态共生与数值模拟&#xff1a;生态系统模型的物种种群动态研究 摘要1Introduction1.1Problem Background1.2Restatement of the Problem1.3Our Work 2 Assumptions and Justifications3 Notations4 模型的建立与求解4.1 农业生态系统模型的建立与求解4.1.1 模型建立4.1.2求解…...

jhat命令详解

jhat 命令通常与 jmap 搭配使用&#xff0c;用来分析 jmap 生成的 dump 文件&#xff0c;jhat 内置了一个微型的HTTP/HTML服务器&#xff0c;生成 dump 的分析结果后&#xff0c;可以在浏览器中查看。 命令的使用格式如下。&#xff08;其中heap-dump-file为必填项&#xff09…...

FFmpeg(7.1版本)的基本组成

1. 前言 FFmpeg 是一个非常流行的开源项目,它提供了处理音频、视频以及其他多媒体内容的强大工具。FFmpeg 包含了大量的库,可以用来解码、编码、转码、处理和播放几乎所有类型的多媒体文件。它广泛用于视频和音频的录制、转换、流媒体传输等领域。 2. FFmpeg的组成 1. FFmp…...

DDD - 领域驱动设计分层架构:构建可演化的微服务架构

文章目录 引言1. 什么是DDD分层架构&#xff1f;1.1 DDD分层架构的演变1.2 四层架构的起源与问题1.3 依赖倒置和五层架构 2. DDD分层架构的核心层次2.1 用户接口层&#xff08;User Interface Layer&#xff09;2.2 应用层&#xff08;Application Layer&#xff09;2.3 领域层…...

大数据挖掘--两个角度理解相似度计算理论

文章目录 0 相似度计算可以转换成什么问题1 集合相似度的应用1.1 集合相似度1.1文档相似度1.2 协同过滤用户-用户协同过滤物品-物品协同过滤 1.2 文档的shingling--将文档表示成集合1.2.1 k-shingling1.2.2 基于停用词的 shingling 1.3 最小哈希签名1.4 局部敏感哈希算法&#…...

主流的AEB标准有哪些?

目录 1、AEB的技术构成与工作原理 2、典型应用场景举例 3、AEB的功能分类 4、AEB系统性能评估的关键因素 5、全球AEB技术标准概览 5.1、联合国欧洲经济委员会&#xff08;UN ECE&#xff09; 5.2、美国NHTSA法规 5.3、中国标准 5.4、印度AIS 185 5.5、澳大利亚ADR法规…...

开源智慧园区管理系统如何重塑企业管理模式与运营效率

内容概要 在如今快速发展的商业环境中&#xff0c;企业面临着日益复杂的管理挑战。开源智慧园区管理系统应运而生&#xff0c;旨在通过技术创新来应对这些挑战。它不仅是一个简单的软件工具&#xff0c;而是一个全面整合大数据、物联网和智能化功能的综合平台&#xff0c;为企…...

decison tree 决策树

熵 信息增益 信息增益描述的是在分叉过程中获得的熵减&#xff0c;信息增益即熵减。 熵减可以用来决定什么时候停止分叉&#xff0c;当熵减很小的时候你只是在不必要的增加树的深度&#xff0c;并且冒着过拟合的风险 决策树训练(构建)过程 离散值特征处理&#xff1a;One-Hot…...

Spring Data JPA 实战:构建高性能数据访问层

1 简介 1.1 Spring Data JPA 概述 1.1.1 什么是 Spring Data JPA? Spring Data JPA 是 Spring Data 项目的一部分,旨在简化对基于 JPA 的数据库访问操作。它通过提供一致的编程模型和接口,使得开发者可以更轻松地与关系型数据库进行交互,同时减少了样板代码的编写。Spri…...

11 Spark面试真题

11 Spark大厂面试真题 1. 通常来说&#xff0c;Spark与MapReduce相比&#xff0c;Spark运行效率更高。请说明效率更高来源于Spark内置的哪些机制&#xff1f;2. hadoop和spark使用场景&#xff1f;3. spark如何保证宕机迅速恢复?4. hadoop和spark的相同点和不同点&#xff1f;…...

【AI论文】VideoAuteur:迈向长叙事视频

摘要&#xff1a;近期的视频生成模型在制作持续数秒的高质量视频片段方面已展现出令人鼓舞的成果。然而&#xff0c;这些模型在生成能传达清晰且富有信息量的长序列时面临挑战&#xff0c;限制了它们支持连贯叙事的能力。在本文中&#xff0c;我们提出了一个大规模烹饪视频数据…...

循环神经网络(RNN)+pytorch实现情感分析

目录 一、背景引入 二、网络介绍 2.1 输入层 2.2 循环层 2.3 输出层 2.4 举例 2.5 深层网络 三、网络的训练 3.1 训练过程举例 1&#xff09;输出层 2&#xff09;循环层 3.2 BPTT 算法 1&#xff09;输出层 2&#xff09;循环层 3&#xff09;算法流程 四、循…...

css-background-color(transparent)

1.前言 在 CSS 中&#xff0c;background-color 属性用于设置元素的背景颜色。除了基本的颜色值&#xff08;如 red、blue 等&#xff09;和十六进制颜色值&#xff08;如 #FF0000、#0000FF 等&#xff09;&#xff0c;还有一些特殊的属性值可以用来设置背景颜色。 2.backgrou…...

【Leetcode 热题 100】32. 最长有效括号

问题背景 给你一个只包含 ‘(’ 和 ‘)’ 的字符串&#xff0c;找出最长有效&#xff08;格式正确且连续&#xff09;括号 子串 的长度。 数据约束 0 ≤ s . l e n g t h ≤ 3 1 0 4 0 \le s.length \le 3 \times 10 ^ 4 0≤s.length≤3104 s [ i ] s[i] s[i] 为 ‘(’ 或 ‘…...

Linux网络 | 网络层IP报文解析、认识网段划分与IP地址

前言&#xff1a;本节内容为网络层。 主要讲解IP协议报文字段以及分离有效载荷。 另外&#xff0c; 本节也会带领友友认识一下IP地址的划分。 那么现在废话不多说&#xff0c; 开始我们的学习吧&#xff01;&#xff01; ps&#xff1a;本节正式进入网络层喽&#xff0c; 友友们…...

Google 和 Meta 携手 FHE 应对隐私挑战

1. 引言 为什么世界上最大的广告商&#xff0c;如谷歌和 Meta 这样的超大规模公司都选择全同态加密 (FHE)。 2. 定向广告 谷歌和 Meta 是搜索引擎和社交网络领域的两大巨头&#xff0c;它们本质上从事的是同一业务——广告。它们最近公布的年度广告收入数据显示&#xff0c;…...