概述
经常会在业务中遇到需要在项目启动后刷新/预热一些数据的要求。
常见可以监听ApplicationReadyEvent
和ContextRefreshedEvent
.
但是因为常见的springboot
项目都依赖的springmvc
,所以实际上有2个容器,spring
的ioc
容器是springmvc
的父容器。
而且ContextRefreshedEvent
实际中会发布多次,如果业务只需要执行一次的情况话是不太适合监听这个的。
发布多次的原因是每加载完一次context
,就会执行一次ContextRefreshedEvent
而且每次执行,都会再执行一次parent
的ContextRefreshedEvent
.上面提到父子容器都会触发这个事件
org.springframework.context.support.AbstractApplicationContext#publishEvent(java.lang.Object, org.springframework.core.ResolvableType)
只想在启动后执行一次demo
可以监听ApplicationReadyEvent
事件
package com.xxx.xxx.xxx.xxx.xxx;
import cn.hutool.extra.spring.SpringUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.util.Objects;
@Slf4j
@Component
public class xxxRefreshEventListener implements ApplicationListener<ApplicationReadyEvent> {
@Override
public void onApplicationEvent(ApplicationReadyEvent contextRefreshedEvent) {
//刷新配置
refreshTargetProperties();
}
private <T> T refreshObjProperties(Class<T> objClass){
try {
T bean = SpringUtil.getBean(objClass);
BusinessProperties annotation = AnnotationUtils.findAnnotation(objClass, BusinessProperties.class);
if(Objects.isNull(annotation)){
return bean;
}
//配置前缀
String prefix = annotation.prefix();
BeanInfo beanInfo = Introspector.getBeanInfo(bean.getClass());
for (PropertyDescriptor propertyDescriptor : beanInfo.getPropertyDescriptors()) {
String name = propertyDescriptor.getName();
Class<?> propertyType = propertyDescriptor.getPropertyType();
Method writeMethod = propertyDescriptor.getWriteMethod();
//完成的config key
String configKey = String.join(".", prefix, name);
//获取数据
xxx info = xxx.xxx(configKey);
if(Objects.isNull(info )){
continue;
}
//db中存放的值
String configValue = info.getConfigValue();
//给属性填充值
if(Objects.equals(propertyType, String.class)){
writeMethod.invoke(bean, configValue);
}else {
Object propertyValue = objectMapper.readValue(configValue, propertyType);
writeMethod.invoke(bean, propertyValue);
}
}
return bean;
} catch (Exception e) {
log.error("刷新配置异常,class:{}", objClass, e);
return null;
}
}
}
spring关键的几个事件
ContextClosedEvent
spring
容器关闭事件ContextRefreshedEvent
spring
容器的初始化后或者刷新完成事件;ContextStoppedEvent
spring
容器停止事件ContextStartedEvent
spring
容器初始化开始事件
3
和4
其实是spring
生命周期相关的事件,1
是整个spring
容器销毁的事件。
springboot对spring容器周期事件的扩展
springboot
对于spring
的事件又有自己的扩展.
ApplicationEnvironmentPreparedEvent
容器环境对象初始化后的事件ApplicationPreparedEvent
容器初始化前的事件,主要是在做refresh
动作之前做触发的事件ApplicationStartedEvent
容器已经完成 refresh 动作后所触发的事件ApplicationReadyEvent
容器已经运行中的事件ApplicationFailedEvent
容器初始化失败所触发的事件ApplicationStartingEvent
容器开始时所触发的事件
触发顺序如下
ApplicationStartingEvent ->
ApplicationEnvironmentPreparedEvent ->
ApplicationPreparedEvent ->
ContextStartedEvent ->
ContextRefreshedEvent ->
ApplicationStartedEvent ->
ApplicationReadyEvent