009-Spring Boot 事件监听、监听器配置与方式、spring、Spring boot内置事件
一、概念
1.事件监听的流程
步骤一、自定义事件,一般是继承ApplicationEvent抽象类
步骤二、定义事件监听器,一般是实现ApplicationListener接口
步骤三、启动时,需要将监听器加入到Spring容器中
步骤四、发布事件
对于配置监听器的方式【即第三步】
方式一、app.addListeners(new MyApplicationListener());添加监听器
方式二、把监听器使用纳入Spring配置中管理如使用@Component标注
方式三、再application.properties中添加context.listener.classes配置项配置
方式四、使用注解@EventListener在方法上,且该类需要在Spring上管理
2、示例【方式一】:
步骤一、自定义事件MyApplicationEvent
package com.lhx.spring.springboot_event; import org.springframework.context.ApplicationEvent; /** * 定义事件 * @author Administrator * */ public class MyApplicationEvent extends ApplicationEvent { private static final long serialVersionUID = 1L; public MyApplicationEvent(Object source) { super(source); } }
步骤二、定义事件监听器MyApplicationListener
package com.lhx.spring.springboot_event; import org.springframework.context.ApplicationListener; public class MyApplicationListener implements ApplicationListener<MyApplicationEvent> { @Override public void onApplicationEvent(MyApplicationEvent event) { System.out.println("接收到事件:" + event.getClass()); } }
步骤三、在App启动程序中添加以及发布
@SpringBootApplication public class App { public static void main(String[] args) { // ConfigurableApplicationContext context = SpringApplication.run(App.class, // args); SpringApplication app = new SpringApplication(App.class); app.addListeners(new MyApplicationListener()); ConfigurableApplicationContext context = app.run(args); context.publishEvent(new MyApplicationEvent(new Object())); context.close(); } }
3、示例【方式二】:
注意:其实在示例步骤三,也可以用注解方式将时间监听器纳入Spring管理中
步骤一、与示例一一致
步骤二、将事件监听器添加@Component注解。
步骤三、启动类
@SpringBootApplication public class App { public static void main(String[] args) { SpringApplication app = new SpringApplication(App.class); //app.addListeners(new MyApplicationListener()); ConfigurableApplicationContext context = app.run(args); context.publishEvent(new MyApplicationEvent(new Object())); context.close(); } }
4、示例【方式四】:
步骤一、与示例一一致
步骤二、与示例一一致
步骤三、增加单独处理类
package com.lhx.spring.springboot_event; import org.springframework.context.event.ContextStoppedEvent; import org.springframework.context.event.EventListener; import org.springframework.stereotype.Component; @Component public class MyEventHandle { @EventListener public void event(MyApplicationEvent event) { System.out.println("MyEventHandle 接收到事件:" + event.getClass()); } @EventListener public void event2(ContextStoppedEvent event) { System.out.println("应用停止 接收到事件:" + event.getClass()); } }
步骤三、启动类
@SpringBootApplication public class App { public static void main(String[] args) { SpringApplication app = new SpringApplication(App.class);、 ConfigurableApplicationContext context = app.run(args); context.publishEvent(new MyApplicationEvent(new Object())); context.close(); } }
二、配置监听器的方式原理
1.方式三实现,在application.properties中添加context.listener.classes配置项配置
查看:DelegatingApplicationListener
public class DelegatingApplicationListener implements ApplicationListener<ApplicationEvent>, Ordered { // NOTE: Similar to org.springframework.web.context.ContextLoader private static final String PROPERTY_NAME = "context.listener.classes";
核心逻辑
@Override public void onApplicationEvent(ApplicationEvent event) { if (event instanceof ApplicationEnvironmentPreparedEvent) { List<ApplicationListener<ApplicationEvent>> delegates = getListeners( ((ApplicationEnvironmentPreparedEvent) event).getEnvironment()); if (delegates.isEmpty()) { return; } this.multicaster = new SimpleApplicationEventMulticaster(); for (ApplicationListener<ApplicationEvent> listener : delegates) { this.multicaster.addApplicationListener(listener); } } if (this.multicaster != null) { this.multicaster.multicastEvent(event); } } @SuppressWarnings("unchecked") private List<ApplicationListener<ApplicationEvent>> getListeners( ConfigurableEnvironment environment) { if (environment == null) { return Collections.emptyList(); } String classNames = environment.getProperty(PROPERTY_NAME); List<ApplicationListener<ApplicationEvent>> listeners = new ArrayList<ApplicationListener<ApplicationEvent>>(); if (StringUtils.hasLength(classNames)) { for (String className : StringUtils.commaDelimitedListToSet(classNames)) { try { Class<?> clazz = ClassUtils.forName(className, ClassUtils.getDefaultClassLoader()); Assert.isAssignable(ApplicationListener.class, clazz, "class [" + className + "] must implement ApplicationListener"); listeners.add((ApplicationListener<ApplicationEvent>) BeanUtils .instantiateClass(clazz)); } catch (Exception ex) { throw new ApplicationContextException( "Failed to load context listener class [" + className + "]", ex); } } } AnnotationAwareOrderComparator.sort(listeners); return listeners; }
2、方式四实现
查看:EventListenerMethodProcessor的processBean
protected void processBean(final List<EventListenerFactory> factories, final String beanName, final Class<?> targetType) { if (!this.nonAnnotatedClasses.contains(targetType)) { Map<Method, EventListener> annotatedMethods = null; try { annotatedMethods = MethodIntrospector.selectMethods(targetType, new MethodIntrospector.MetadataLookup<EventListener>() { @Override public EventListener inspect(Method method) { return 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 for (Method method : annotatedMethods.keySet()) { for (EventListenerFactory factory : factories) { if (factory.supportsMethod(method)) { Method methodToUse = AopUtils.selectInvocableMethod( method, this.applicationContext.getType(beanName)); ApplicationListener<?> applicationListener = factory.createApplicationListener(beanName, targetType, methodToUse); if (applicationListener instanceof ApplicationListenerMethodAdapter) { ((ApplicationListenerMethodAdapter) applicationListener) .init(this.applicationContext, this.evaluator); } this.applicationContext.addApplicationListener(applicationListener); break; } } } if (logger.isDebugEnabled()) { logger.debug(annotatedMethods.size() + " @EventListener methods processed on bean '" + beanName + "': " + annotatedMethods); } } } }
EventListener
annotatedMethods = MethodIntrospector.selectMethods(targetType, new MethodIntrospector.MetadataLookup<EventListener>() { @Override public EventListener inspect(Method method) { return AnnotatedElementUtils.findMergedAnnotation(method, EventListener.class); } });
查看for
for (Method method : annotatedMethods.keySet()) { for (EventListenerFactory factory : factories) { if (factory.supportsMethod(method)) { Method methodToUse = AopUtils.selectInvocableMethod( method, this.applicationContext.getType(beanName)); ApplicationListener<?> applicationListener = factory.createApplicationListener(beanName, targetType, methodToUse); if (applicationListener instanceof ApplicationListenerMethodAdapter) { ((ApplicationListenerMethodAdapter) applicationListener) .init(this.applicationContext, this.evaluator); } this.applicationContext.addApplicationListener(applicationListener); break; } } }
其中factory即EventListenerFactory factory
/* * Copyright 2002-2015 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.context.event; import java.lang.reflect.Method; import org.springframework.context.ApplicationListener; /** * Strategy interface for creating {@link ApplicationListener} for methods * annotated with {@link EventListener}. * * @author Stephane Nicoll * @since 4.2 */ public interface EventListenerFactory { /** * Specify if this factory supports the specified {@link Method}. * @param method an {@link EventListener} annotated method * @return {@code true} if this factory supports the specified method */ boolean supportsMethod(Method method); /** * Create an {@link ApplicationListener} for the specified method. * @param beanName the name of the bean * @param type the target type of the instance * @param method the {@link EventListener} annotated method * @return an application listener, suitable to invoke the specified method */ ApplicationListener<?> createApplicationListener(String beanName, Class<?> type, Method method); }
三、spring、Spring boot内置事件
1.Spring
jar包:Spring-context-4.3.13.RELEASE
包:org.springframwork.context.event;
常用:ContextClosedEvent、ContextStartedEvent、ContextStopedEvent
示例
@Component public class MyEventHandle { @EventListener public void event2(ContextStoppedEvent event) { System.out.println("应用停止 接收到事件:" + event.getClass()); } }
当然:app中Context要停止
@SpringBootApplication public class App { public static void main(String[] args) { SpringApplication app = new SpringApplication(App.class); ConfigurableApplicationContext context = app.run(args); context.publishEvent(new MyApplicationEvent(new Object())); context.stop(); } }
2.Spring-boot
jar包:Spring-boot-1.5.9.RELEASE
包:org.springframework.boot.context.event;
常用:ApplicationEnvironmentPreparedEvent、ApplicationFailedEvent等
内置时间执行时机:
Connected to the target VM, address: '127.0.0.1:55584', transport: 'socket' 0---------ApplicationEvent Type:org.springframework.boot.context.event.ApplicationStartingEvent 1---------ApplicationEvent Type:org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent . ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v2.7.8) 2023-03-27 09:48:16,136 --- INFO --- [background-preinit] --- [util.Version] --- [] --- HV000001: Hibernate Validator 6.2.5.Final 2---------ApplicationEvent Type:org.springframework.boot.context.event.ApplicationContextInitializedEvent 2023-03-27 09:48:16,254 --- INFO --- [main] --- [openapi.OpenapiApplication] --- [] --- Starting OpenapiApplication using Java 1.8.0_151 on B-92SYMD6R-2222.local with PID 32623 (/Users/lihongxu/work/IdeaProjects/gateway-demo/app/target/classes started by lihongxu in /Users/lihongxu/work/IdeaProjects/gateway-demo) 2023-03-27 09:48:16,257 --- INFO --- [main] --- [openapi.OpenapiApplication] --- [] --- The following 1 profile is active: "dev" 3---------ApplicationEvent Type:org.springframework.boot.context.event.ApplicationPreparedEvent 1 2023-03-27 09:48:17,049 --- INFO --- [main] --- [tomcat.TomcatWebServer] --- [] --- Tomcat initialized with port(s): 7003 (http) 7004 (http) 2023-03-27 09:48:17,064 --- INFO --- [main] --- [http11.Http11NioProtocol] --- [] --- Initializing ProtocolHandler ["http-nio-7003"] 2023-03-27 09:48:17,065 --- INFO --- [main] --- [http11.Http11NioProtocol] --- [] --- Initializing ProtocolHandler ["http-nio-7004"] 2023-03-27 09:48:17,078 --- INFO --- [main] --- [core.StandardService] --- [] --- Starting service [Tomcat] 2023-03-27 09:48:17,078 --- INFO --- [main] --- [core.StandardEngine] --- [] --- Starting Servlet engine: [Apache Tomcat/9.0.71] 2023-03-27 09:48:17,293 --- INFO --- [main] --- [[localhost].[/]] --- [] --- Initializing Spring embedded WebApplicationContext 2023-03-27 09:48:17,293 --- INFO --- [main] --- [context.ServletWebServerApplicationContext] --- [] --- Root WebApplicationContext: initialization completed in 1000 ms 4---------ApplicationEvent Type:PostConstruct 5---------ApplicationEvent Type:InitializingBean.afterPropertiesSet 2023-03-27 09:48:17,606 --- INFO --- [main] --- [http11.Http11NioProtocol] --- [] --- Starting ProtocolHandler ["http-nio-7003"] 2023-03-27 09:48:17,612 --- INFO --- [main] --- [http11.Http11NioProtocol] --- [] --- Starting ProtocolHandler ["http-nio-7004"] 2023-03-27 09:48:17,615 --- INFO --- [main] --- [tomcat.TomcatWebServer] --- [] --- Tomcat started on port(s): 7003 (http) 7004 (http) with context path '' 6---------ApplicationEvent Type:org.springframework.boot.web.servlet.context.ServletWebServerInitializedEvent 7---------ApplicationEvent Type:org.springframework.context.event.ContextRefreshedEvent 2023-03-27 09:48:17,623 --- INFO --- [main] --- [openapi.OpenapiApplication] --- [] --- Started OpenapiApplication in 1.881 seconds (JVM running for 3.536) 8---------ApplicationEvent Type:org.springframework.boot.context.event.ApplicationStartedEvent 9---------ApplicationEvent Type:org.springframework.boot.availability.AvailabilityChangeEvent 10---------ApplicationEvent Type:org.springframework.boot.context.event.ApplicationReadyEvent 11---------ApplicationEvent Type:org.springframework.boot.availability.AvailabilityChangeEvent