在Spring Cloud中,@RefreshScope是用来动态刷新配置的,在和配置中心集成后,要想不重启动态刷新配置,需要在类上面加上@RefreshScope,但是这个注解因为其机制是销毁现有被标记对象重新创建新的被标记对象,存在一些问题,比如会将动态数据源的连接给干掉,导致mq的listener失效。为了解决这个问题,写了下面的小案例,可以用来替代这个注解。
项目地址:https://gitee.com/xuwenjingrencai/microservice
1、注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyRefresh {
}
2、配置变更监听器以及动态刷新逻辑
@Component
@Slf4j
public class MyRefreshListener implements ApplicationRunner {
private Map<String, List<BeanField>> map = new HashMap<>();
@Autowired
private Environment environment;
@Autowired
private ApplicationContext applicationContext;
public void run(ApplicationArguments args) throws Exception {
Map<String, Object> beansWithAnnotation =
applicationContext.getBeansWithAnnotation(MyRefresh.class);
for (Map.Entry<String, Object> entry : beansWithAnnotation.entrySet()) {
String beanName = entry.getKey();
Object bean = entry.getValue();
Object target = AopTargetUtils.getTarget(bean);
Class<?> targetClass = target.getClass();
Field[] declaredFields = targetClass.getDeclaredFields();
if (declaredFields.length <= 0) continue;
for (Field field : declaredFields) {
if (field.isAnnotationPresent(Value.class)) {
Value declaredAnnotation = field.getDeclaredAnnotation(Value.class);
String valueKey = getPropertyName(declaredAnnotation.value());
BeanField beanField = new BeanField();
beanField.setPropertyKey(valueKey);
beanField.setBeanName(beanName);
beanField.setFileName(field.getName());
beanField.setBean(bean);
beanField.setProxyObject(target);
if (map.containsKey(valueKey)) {
map.get(valueKey).add(beanField);
} else {
ArrayList<BeanField> beanFields = new ArrayList<>();
beanFields.add(beanField);
map.put(valueKey, beanFields);
}
}
}
}
log.info("refresh map 初始化完成");
}
@EventListener(EnvironmentChangeEvent.class)
public void listener(EnvironmentChangeEvent event) throws Exception {
for (String key : event.getKeys()) {
String property = environment.getProperty(key);
log.info("refresh key: {}", key);
log.info("refresh value: {}", property);
try {
if (map.containsKey(key)) {
List<BeanField> beanFields = map.get(key);
for (BeanField beanField : beanFields) {
Object target = beanField.getProxyObject();
Field declaredField = target.getClass().getDeclaredField(beanField.getFileName());
declaredField.setAccessible(true);
declaredField.set(target, property);
}
}
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
}
private String getPropertyName(String propertyName) {
return propertyName.substring(2, propertyName.length()-1);
}
}
@Data
public class BeanField {
private String propertyKey;
private String beanName;
private String fileName;
private Object bean;
private Object proxyObject;
}
public class AopTargetUtils {
public static Object getTarget(Object proxy) throws Exception {
if(!AopUtils.isAopProxy(proxy)) {
return proxy;
}
if(AopUtils.isJdkDynamicProxy(proxy)) {
return getJdkDynamicProxyTargetObject(proxy);
} else {
return getCglibProxyTargetObject(proxy);
}
}
private static Object getCglibProxyTargetObject(Object proxy) throws Exception {
Field h = proxy.getClass().getDeclaredField("CGLIB$CALLBACK_0");
h.setAccessible(true);
Object dynamicAdvisedInterceptor = h.get(proxy);
Field advised = dynamicAdvisedInterceptor.getClass().getDeclaredField("advised");
advised.setAccessible(true);
Object target = ((AdvisedSupport)advised.get(dynamicAdvisedInterceptor)).getTargetSource().getTarget();
return target;
}
private static Object getJdkDynamicProxyTargetObject(Object proxy) throws Exception {
Field h = proxy.getClass().getSuperclass().getDeclaredField("h");
h.setAccessible(true);
AopProxy aopProxy = (AopProxy) h.get(proxy);
Field advised = aopProxy.getClass().getDeclaredField("advised");
advised.setAccessible(true);
Object target = ((AdvisedSupport)advised.get(aopProxy)).getTargetSource().getTarget();
return target;
}
}
3、原理
当EnvironmentChangeEvent监听到配置变更事件时,将缓存中的与配置相关的对象取出并通过反射给对应的属性赋值