乐趣区

关于程序员:SpringBoot中EventListener注解的使用

背景

在开发工作中,会遇到一种场景,做完某一件事件当前,须要播送一些音讯或者告诉,通知其余的模块进行一些事件处理,一般来说,能够一个一个发送申请去告诉,然而有一种更好的形式,那就是事件监听,事件监听也是设计模式中 公布 - 订阅模式、观察者模式的一种实现。

观察者模式:简略的来讲就是你在做事件的时候身边有人在盯着你,当你做的某一件事件是旁边察看的人感兴趣的事件的时候,他会依据这个事件做一些其余的事,然而盯着你看的人必须要到你这里来注销,否则你无奈告诉到他(或者说他没有资格来盯着你做事件)。

对于 Spring 容器的一些事件,能够监听并且触发相应的办法。通常的办法有 2 种,ApplicationListener 接口和@EventListener 注解。

简介

要想顺利的创立监听器,并起作用,这个过程中须要这样几个角色:
1、事件(event)能够封装和传递监听器中要解决的参数,如对象或字符串,并作为监听器中监听的指标。
2、监听器(listener)具体依据事件产生的业务解决模块,这里能够接管处理事件中封装的对象或字符串。
3、事件发布者(publisher)事件产生的触发者。

ApplicationListener 接口

ApplicationListener 接口的定义如下:

public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
 
/**
* Handle an application event.
* @param event the event to respond to
*/
void onApplicationEvent(E event);
}

它是一个泛型接口,泛型的类型必须是 ApplicationEvent 及其子类,只有实现了这个接口,那么当容器有相应的事件触发时,就能触发 onApplicationEvent 办法。ApplicationEvent 类的子类有很多,Spring 框架自带的如下几个。

简略应用

应用办法很简略,就是实现一个 ApplicationListener 接口,并且将退出到容器中就行。

@Component
public class MyApplicationListener implements ApplicationListener<ApplicationEvent> {
 
@Override
public void onApplicationEvent(ApplicationEvent event) {System.out.println("事件触发:"+event.getClass().getName());
}

而后启动本人的 springboot 我的项目:

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

能够看到控制台输入:

事件触发:org.springframework.context.event.ContextRefreshedEvent
2021-01-24 22:09:20.113  INFO 9228 --- [main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
事件触发:org.springframework.boot.web.servlet.context.ServletWebServerInitializedEvent
2021-01-24 22:09:20.116  INFO 9228 --- [main] c.n.ApplicationListenerDemoApplication   : Started ApplicationListenerDemoApplication in 1.221 seconds (JVM running for 1.903)
事件触发:org.springframework.boot.context.event.ApplicationStartedEvent
事件触发:org.springframework.boot.context.event.ApplicationReadyEvent

这样就触发了 spring 默认的一些事件。

自定义事件以及监听

定义事件

首先,咱们须要定义一个工夫(MyTestEvent),须要继承 Spring 的ApplicationEvent

public class MyTestEvent extends ApplicationEvent{
    /**
     * 
     */
    private static final long serialVersionUID = 1L;

    private String msg ;

    public MyTestEvent(Object source,String msg) {super(source);
        this.msg = msg;
    }

    public String getMsg() {return msg;}

    public void setMsg(String msg) {this.msg = msg;}
}

定义监听器

须要定义一下监听器,本人定义的监听器须要实现 ApplicationListener,同时泛型参数要加上本人要监听的事件 Class 名,在重写的办法 onApplicationEvent 中,增加本人的业务解决:

@Component
public class MyNoAnnotationListener implements ApplicationListener<MyTestEvent> {

    @Override
    public void onApplicationEvent(MyTestEvent event) {System.out.println("非注解监听器:" + event.getMsg());
    }

}

事件公布

有了事件,有了事件监听者,那么什么时候触发这个事件呢?每次想让监听器收到事件告诉的时候,就能够调用一下事件公布的操作。首先在类里主动注入了ApplicationEventPublisher,这个也就是咱们的ApplicationCOntext,它实现了这个接口。

@Component
public class MyTestEventPubLisher {
    @Autowired
    private ApplicationEventPublisher applicationEventPublisher;

    /**
     *  事件公布办法
      */
    public void pushListener(String msg) {applicationEventPublisher.publishEvent(new MyTestEvent(this, msg));
    }

}

测试

用一个 HTTP 申请来模仿:

@RestController
public class TestEventListenerController {

    @Autowired
    private MyTestEventPubLisher publisher;

    @RequestMapping(value = "/test/testPublishEvent1")
    public void testPublishEvent(){publisher.pushListener("我来了!");
    }
}

启动我的项目,能够看到控制台输入,测试实现:

事件触发:com.njit.personal.unannotation.MyTestEvent
非注解监听器:我来了!

@EventListener 注解

简略应用

除了通过实现接口,还能够应用 @EventListener 注解,实现对任意的办法都能监听事件。

在任意办法上标注 @EventListener 注解,指定 classes,即须要解决的事件类型,个别就是 ApplicationEven 及其子类,能够设置多项。

@Configuration
public class Config {@EventListener(classes = {ApplicationEvent.class})
    public void listen(ApplicationEvent event) {System.out.println("事件触发:" + event.getClass().getName());
    }
}

启动我的项目

能够看到控制台和之前的输入是一样的:

事件触发:org.springframework.context.event.ContextRefreshedEvent
2021-01-24 22:39:13.647  INFO 16072 --- [main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
事件触发:org.springframework.boot.web.servlet.context.ServletWebServerInitializedEvent
2021-01-24 22:39:13.650  INFO 16072 --- [main] c.n.ApplicationListenerDemoApplication   : Started ApplicationListenerDemoApplication in 1.316 seconds (JVM running for 2.504)
事件触发:org.springframework.boot.context.event.ApplicationStartedEvent
事件触发:org.springframework.boot.context.event.ApplicationReadyEvent

自定义事件以及监听

应用注解的益处是不必每次都去实现 ApplicationListener,能够在一个 class 中定义多个办法,用 @EventListener 来做办法级别的注解。

和下面相似,事件以及事件公布不须要扭转,只有这样定义监听器即可。

@Component
public class MyAnnotationListener {

    @EventListener
    public void listener1(MyTestEvent event) {System.out.println("注解监听器 1:" + event.getMsg());
    }
}

此时,就能够有一个公布,两个监听器监听到公布的音讯了,一个是注解形式,一个是非注解形式

后果:

事件触发:com.njit.personal.unannotation.MyTestEvent
注解监听器 1: 我来了!非注解监听器:我来了!

咱们能够发现,注解模式的监听器的执行走在了非注解的后面。

原理

其实下面增加 @EventListener 注解的办法被包装成了 ApplicationListener 对象,下面的相似于上面这种写法,这个应该比拟好了解。

@Component
public class MyAnnotationListener implements ApplicationListener<MyTestEvent> {
    
    @Override
    public void onApplicationEvent(MyTestEvent event) {System.out.println("注解监听器 1:" + event.getMsg());
    }
}

那么 Spring 是什么时候做这件事的呢?

查看 SpringBoot 的源码,找到上面的代码, 因为我是 Tomcat 环境,这里创立的 ApplicationContextorg.springframework.bootweb.servlet.context.AnnotationConfigServletWebServerApplicationContext

protected ConfigurableApplicationContext createApplicationContext() {
        Class<?> contextClass = this.applicationContextClass;
        if (contextClass == null) {
            try {switch (this.webApplicationType) {
                case SERVLET:
                    contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
                    break;
                case REACTIVE:
                    contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
                    break;
                default:
                    contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
                }
            }
            catch (ClassNotFoundException ex) {
                throw new IllegalStateException(
                        "Unable create a default ApplicationContext," + "please specify an ApplicationContextClass",
                        ex);
            }
        }
        return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
    }

他的构造方法如下:

public AnnotationConfigServletWebServerApplicationContext() {this.reader = new AnnotatedBeanDefinitionReader(this);
        this.scanner = new ClassPathBeanDefinitionScanner(this);
    }

进到 AnnotatedBeanDefinitionReader 外面

public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry, Environment environment) {Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
        Assert.notNull(environment, "Environment must not be null");
        this.registry = registry;
        this.conditionEvaluator = new ConditionEvaluator(registry, environment, null);
        AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
    }

再进到 AnnotationConfigUtils 的办法外面,省略了一部分代码,能够看到他注册了一个 EventListenerMethodProcessor 类到工厂了。这是一个 BeanFactory 的后置处理器。

public static Set<BeanDefinitionHolder> registerAnnotationConfigProcessors(BeanDefinitionRegistry registry, @Nullable Object source) {DefaultListableBeanFactory beanFactory = unwrapDefaultListableBeanFactory(registry);
    ......
    .....
    ......    

    if (!registry.containsBeanDefinition(EVENT_LISTENER_PROCESSOR_BEAN_NAME)) {RootBeanDefinition def = new RootBeanDefinition(EventListenerMethodProcessor.class);
            def.setSource(source);
            beanDefs.add(registerPostProcessor(registry, def, EVENT_LISTENER_PROCESSOR_BEAN_NAME));
        }
    
    ......
    ......

        return beanDefs;
    }

查看这个 BeanFactory 的后置处理器 EventListenerMethodProcessor,上面办法,他会遍历所有 bean,找到其中带有@EventListener 的办法,将它包装成ApplicationListenerMethodAdapter,注册到工厂里,这样就胜利注册到 Spring 的监听系统里了。

    @Override
    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) {if (!ScopedProxyUtils.isScopedTarget(beanName)) {
                Class<?> type = null;
                try {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) {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) {if (!this.nonAnnotatedClasses.contains(targetType) &&
                !targetType.getName().startsWith("java") &&
                !isSpringContainerClass(targetType)) {

            Map<Method, EventListener> annotatedMethods = null;
            try {
                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");
                List<EventListenerFactory> factories = this.eventListenerFactories;
                Assert.state(factories != null, "EventListenerFactory List not initialized");
                for (Method method : annotatedMethods.keySet()) {for (EventListenerFactory factory : factories) {if (factory.supportsMethod(method)) {Method methodToUse = AopUtils.selectInvocableMethod(method, context.getType(beanName));
                            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);
                }
            }
        }
    }

由办法生成 Listener 的逻辑由 EventListenerFactory 实现的,这又分为两种,一种是一般的@EventLintener 另一种是@TransactionalEventListener,是由两个工厂解决的。

总结

下面介绍了 @EventListener 的原理,其实下面办法里还有一个 @TransactionalEventListener 注解,其实原理是截然不同的,只是这个监听者能够抉择在事务实现后才会被执行,事务执行失败就不会被执行。

这两个注解的逻辑是截然不同的,并且 @TransactionalEventListener 自身就被标记有@EventListener

只是最初生成监听器时所用的工厂不一样而已。

参考

spring 中监听器的应用

@EventListener 注解原理

退出移动版