nacos动态配置刷新机制原理
项目里面许多业务场景以及灵活配置的需求经常要用到动态配置。一般就是apollo和nacos两种选型。
nacos动态刷新导致的bug
nacos一般为了实现动态配置一般会加入@RefreshScope注解进行实现,例如下面的代码加入了@RefreshScope想要实现跨域的动态配置,例如跨域白名单等。
@slf4j
@configuration
@RefreshScope
public class FilterConfiguration {
@value("${jlpay.business.filter.allowCredentials:true}")
private boolean allowCredentials;
@value("${jlpay.business.filter.allowedHeader:}")
private String allowedHeader;
@value("${jlpay.business.filter.allowedMethod:}")
private String allowedMethod;
@value("${jlpay.business.filter.allowedOrigin:*}")
private String allowedOrigin;
@value("${jlpay.business.filter.corsPath:/**}")
private String corsPath;
/**
* 跨域请求配置
* @return
*/
private CorsConfiguration buildCorsConfig() {
CorsConfiguration corsConfig = new CorsConfiguration();
corsConfig.setAllowCredentials(allowCredentials);
corsConfig.addAllowedHeader(allowedHeader);
corsConfig.addAllowedMethod(allowedMethod);
//指定域名拦截配置
if (!StringUtils.isEmpty(allowedOrigin) && !CorsConfiguration.ALL.equals(allowedOrigin)) {
String[] originArr = allowedOrigin.split(",");
for (String origin : originArr) {
corsConfig.addAllowedOrigin(origin);
}
} else {
corsConfig.addAllowedOrigin(CorsConfiguration.ALL);
}
return corsConfig;
}
/**
* 跨域请求过滤器配置
*/
@Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource configSource = new UrlBasedCorsConfigurationSource();
//注:暂定所有接口均允许跨域,建议针对具体接口配置允许跨域
configSource.registerCorsConfiguration(corsPath, buildCorsConfig());
log.info("跨域请求过滤器配置:{}", JSONObject.toJSONString(configSource.getCorsConfigurations()));
return new CorsFilter(configSource);
}
但是发现这个注解后直接导致了跨域配置失效,前端访问我接口的时候报了跨域错误。问题的根因在于serverletContext没有注入生成的filterbean,为什么没有注入,是serverletContext在初始化的时候去找符合的filer类,是通过getBeanNamesForType获取的注入的filter类名称,按道理根据filter实现统一接口类是可以获取到的,但是这个方法isFactoryBean 判定了CorsFilter是factorybean,因此,CorsFilter没有被获取到加入到filter链中。所以@RefreshScope注解影响了CorsFilter bean的生成 方式。下面仔细看下@RefreshScope的原理。
@Override
public boolean isFactoryBean(String name) throws NoSuchBeanDefinitionException {
String beanName = transformedBeanName(name);
Object beanInstance = getSingleton(beanName, false);
if (beanInstance != null) {
return (beanInstance instanceof FactoryBean);
}
// No singleton instance found -> check bean definition.
if (!containsBeanDefinition(beanName) && getParentBeanFactory() instanceof ConfigurableBeanFactory cbf) {
// No bean definition found in this factory -> delegate to parent.
return cbf.isFactoryBean(name);
}
return isFactoryBean(beanName, getMergedLocalBeanDefinition(beanName));
}
因为@RefreshScope 导致FilterConfiguration 变成了一个facotorybean, 导致无法加载了serverletContext的filter链中。 所以解决防范是建议使用ConfigurationProperties bean 方式使用,@RefreshScope并不是一个很好的使用方式。
RefreshScope动态刷新原理
RefreshScope注解的Bean可以在运行时刷新,并且使用它们的任何组件都将在下一个方法调用前获得一个新实例,该实例将完全初始化并注入所有依赖项。
public @interface RefreshScope {
/**
* @see Scope#proxyMode()
* @return proxy mode
*/
ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;
}
RefreshScope用到的是spring bean的scope模式能力。scope模式在获取bean时和单例的Bean以及多例的bean不太一样。scope 里自定义了一个scope范围来隔离不同的scope,在获取bean时会优先从实现了Scope接口的类中获取,简单来说,scope模式的bean的获取方式通过scope接口实现类获取,是一种代理模式。
//org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean
// Create bean instance.
if (mbd.isSingleton()) {
sharedInstance = getSingleton(beanName, () -> {
try {
return createBean(beanName, mbd, args);
}
//......
}
else if (mbd.isPrototype()) {
// It's a prototype -> create a new instance.
Object prototypeInstance = null;
try {
beforePrototypeCreation(beanName);
prototypeInstance = createBean(beanName, mbd, args);
}
//......
}
else {
String scopeName = mbd.getScope();
if (!StringUtils.hasLength(scopeName)) {
throw new IllegalStateException("No scope name defined for bean '" + beanName + "'");
}
Scope scope = this.scopes.get(scopeName);
if (scope == null) {
throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");
}
try {
Object scopedInstance = scope.get(beanName, () -> {
beforePrototypeCreation(beanName);
try {
return createBean(beanName, mbd, args);
}
finally {
afterPrototypeCreation(beanName);
}
});
beanInstance = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
}
catch (IllegalStateException ex) {
throw new ScopeNotActiveException(beanName, scopeName, ex);
}
}
}
catch (BeansException ex) {
//......
}
finally {
//......
}
RefreshScope 就是其中一个代理类实现,scope.get由genericScope实现。
GenericScope是一个beanFactoryPostprocess,在bean实列化前就会执行postProcessBeanFactory将自己注册放scopes中去,这样前面的Bean获取单例就会从对应scopeName中获取scope,而scope是一个保存了代理类的map,最终执行的代理类的Invoke方法。
//postProcessBeanFactory会在实例化执行
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
beanFactory.registerScope(this.name, this);
setSerializationId(beanFactory);
}
public void registerScope(String scopeName, Scope scope) {
Assert.notNull(scopeName, "Scope identifier must not be null");
Assert.notNull(scope, "Scope must not be null");
if (SCOPE_SINGLETON.equals(scopeName) || SCOPE_PROTOTYPE.equals(scopeName)) {
throw new IllegalArgumentException("Cannot replace existing scopes 'singleton' and 'prototype'");
}
Scope previous = this.scopes.put(scopeName, scope);
if (previous != null && previous != scope) {
if (logger.isDebugEnabled()) {
logger.debug("Replacing scope '" + scopeName + "' from [" + previous + "] to [" + scope + "]");
}
}
else {
if (logger.isTraceEnabled()) {
logger.trace("Registering scope '" + scopeName + "' with implementation [" + scope + "]");
}
}
}
这个代理类RefreshScope 注解生成的是LockedScopedProxyFactoryBean,下面看看代理类是怎么生成的以及invoke代理的方法。
RefreshScope 注解类代理类注册容器
RefreshScope 注解的类 在生成Bean 定义时会生成两份定义,一份是原来的类的定义,另一份时代理类LockedScopedProxyFactoryBean的定义,且LockedScopedProxyFactoryBean时一个factorybean,通过getObject方法获取实际的proxy对象,实际的proxy对象通过getBeanFactory().getBean(getTargetBeanName()),即ioc容器中获取原始bean。
代理类定义
springboot bean 加载过程是利用@ComponentScan会扫描并注册包下带有@Componet注解的类为Bean(@Configuration 实际上也是一个@Componet),达到一个注解驱动注册Bean的效果。扫描Bean并注册为BeanDefinition这一过程是由ClassPathBeanDefinitionScanner类的doScan方法去做的。
protected Set doScan(String... basePackages) {
Assert.notEmpty(basePackages, "At least one base package must be specified");
Set beanDefinitions = new LinkedHashSet();
// 扫描basePackages所在的包下的所有的类,带@Componet的类都会被注册为BeanDefinition
for (String basePackage : basePackages) {
//...
if (checkCandidate(beanName, candidate)) {
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
// 这里就是Scope的Bean的统一处理,是一个改变BeanDefinition的回调机会
// 调用createScopedProxy创建一个scope 代理
definitionHolder =
AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
beanDefinitions.add(definitionHolder);
// 注册BeanDefinition到IOC容器中
registerBeanDefinition(definitionHolder, this.registry);
}
}
}
return beanDefinitions;
}
注意AnnotationConfigUtils.applyScopedProxyMode方法,这里提供了一个代理bean定义的方式,若具体是在 创建bean时会判断类是否带@refreshScope注解,然后创建ScopedProxyFactoryBean bean工厂用来创建代理bean。
public static BeanDefinitionHolder createScopedProxy(BeanDefinitionHolder definition,
BeanDefinitionRegistry registry, boolean proxyTargetClass) {
// ...
// Create a scoped proxy definition for the original bean name,
// "hiding" the target bean in an internal target definition.
// 重点,这里构造函数中将beanClass设置为了ScopedProxyFactoryBean.class
RootBeanDefinition proxyDefinition = new RootBeanDefinition(ScopedProxyFactoryBean.class);
// targetDefinition是被代理的原生Bean
proxyDefinition.setDecoratedDefinition(new BeanDefinitionHolder(targetDefinition, targetBeanName));
// ...
// Return the scoped proxy definition as primary bean definition
// (potentially an inner bean).
return new BeanDefinitionHolder(proxyDefinition, originalBeanName, definition.getAliases());
}
postProcessBeanDefinitionRegistry回调方法中会针对刚刚那个beanClass为ScopedProxyFactoryBean.class的BeanDefinition进行一个增强处理,最终生成的代理类定义的是LockedScopedProxyFactoryBean。
// GenericScope.class
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry)
throws BeansException {
// 获取所有BeanDefinition的名称
for (String name : registry.getBeanDefinitionNames()) {
BeanDefinition definition = registry.getBeanDefinition(name);
// 针对RootBeanDefinition这个BeanDefinition来做,这和上面的逻辑吻合
if (definition instanceof RootBeanDefinition) {
RootBeanDefinition root = (RootBeanDefinition) definition;
// 判断BeanClass == ScopedProxyFactoryBean.class
if (root.getDecoratedDefinition() != null && root.hasBeanClass()
&& root.getBeanClass() == ScopedProxyFactoryBean.class) {
if (getName().equals(root.getDecoratedDefinition().getBeanDefinition()
.getScope())) {
// 将BeanClass换为LockedScopedProxyFactoryBean
root.setBeanClass(LockedScopedProxyFactoryBean.class);
root.getConstructorArgumentValues().addGenericArgumentValue(this);
// surprising that a scoped proxy bean definition is not already
// marked as synthetic?
root.setSynthetic(true);
}
}
}
}
}
代理类创建
LockedScopedProxyFactoryBean的父类ScopedProxyFactoryBean 实现了FactoryBean,FactoryBean在Spring中最为典型的一个应用就是用来创建AOP的代理对象。proxy就是生成的实际代理对象。
public Object getObject() {
if (this.proxy == null) {
throw new FactoryBeanNotInitializedException();
}
return this.proxy;
}
proxy是通过 ScopedProxyFactoryBean 生成的,该类实现了 BeanFactoryAware(该接口用于知道创建Bean的beanfactory,即ioc容器),因此setBeanFactory会在比较早的时机被回调用来生成proxy.
public void setBeanFactory(BeanFactory beanFactory) {
// ...
// 这里是一个比较关键的点,scopedTargetSource变量是一个SimpleBeanTargetSource
// scopedTargetSource中保存了IOC容器
this.scopedTargetSource.setBeanFactory(beanFactory);
// 创建动态代理前,将动态代理的信息都保存到ProxyFactory中
ProxyFactory pf = new ProxyFactory();
pf.copyFrom(this);
// 注意,这里的TargetSource就是刚刚说的scopedTargetSource
pf.setTargetSource(this.scopedTargetSource);
// ...
this.proxy = pf.getProxy(cbf.getBeanClassLoader());
}
proxy的代理方法如下, 代理实现是CGLib动态代理都会实现一个MethodInterceptor,被代理的类的每一个方法调用实质上都是在调用MethodInterceptor的intercept方法。
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
// 反复强调的TargetSource
TargetSource targetSource = this.advised.getTargetSource();
try {
// ...
// Get as late as possible to minimize the time we "own" the target, in case it comes from a pool...
// 重点,这里调用了targetSource的getTarget
// target变量就是被代理的类,调用实际方法的时候反射调用其对应方法
target = targetSource.getTarget();
// ...
if (chain.isEmpty() && Modifier.isPublic(method.getModifiers())) {
// ...
// target变量就是被代理的类,调用实际方法的时候反射调用其对应方法
retVal = methodProxy.invoke(target, argsToUse);
}
else {
// We need to create a method invocation...
// target变量就是被代理的类,调用实际方法的时候反射调用其对应方法
retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();
}
// target变量就是被代理的类,调用实际方法的时候反射调用其对应方法
retVal = processReturnType(proxy, target, method, retVal);
return retVal;
}
// ...
}
前面讲到TargetSource的实现类是SimpleBeanTargetSource:通过bean容器获取代理的bean.
public class SimpleBeanTargetSource extends AbstractBeanFactoryBasedTargetSource {
@Override
public Object getTarget() throws Exception {
// 从IOC中getBean
return getBeanFactory().getBean(getTargetBeanName());
}
}
bean注册
this.registry 的类是实现了BeanDefinitionRegistry 是一个接口,它定义了关于 BeanDefinition 的注册、移除、查询等一系列的操作。该接口有三个实现类:DefaultListableBeanFactory、GenericApplicationContext、SimpleBeanDefinitionRegistry。BeanDefinitionRegistry 集成了SingletonBeanRegistry ,接口的核心实现类是 DefaultSingletonBeanRegistry(该类存储 Bean 之间的依赖关系、存储 Bean 的包含关系(外部类包含内部类)、获取 Bean 所处的状态(正在创建、创建完毕等)、回调销毁 Bean 时触发的 destroy 方法等。)用来注册bean到ioc容器。
执行完 registerBeanDefinition 方法后,Bean 的名称和对应的 BeanDefinition 就被放入了容器中,后续获取 Bean 也是从这个容器中获取。
/**
* 注册Bean基于给定的bean factory.
* @param definitionHolder bean定义类
* @param registry bean factory注册
* @throws BeanDefinitionStoreException if registration failed
*/
public static void registerBeanDefinition(
BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
throws BeanDefinitionStoreException {
// 注册bean 定义
String beanName = definitionHolder.getBeanName();
registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());
// 证据Bean 别名
String[] aliases = definitionHolder.getAliases();
if (aliases != null) {
for (String alias : aliases) {
registry.registerAlias(beanName, alias);
}
}
}
@Override
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
throws BeanDefinitionStoreException {
//......
if (hasBeanCreationStarted()) {
// 双重判断,将bean定义放入beanDefinitionMap (ioc容器)
synchronized (this.beanDefinitionMap) {
this.beanDefinitionMap.put(beanName, beanDefinition);
List updatedDefinitions = new ArrayList(this.beanDefinitionNames.size() + 1);
updatedDefinitions.addAll(this.beanDefinitionNames);
updatedDefinitions.add(beanName);
this.beanDefinitionNames = updatedDefinitions;
removeManualSingletonName(beanName);
}
}
//......
动态配置刷新
前面讲到通过beanfactory会创建以及获取bean, 这里scope.get获取的Scope对象为RefreshScope,创建Bean还是由IOC来做(createBean方法),但是获取Bean,都由RefreshScope对象的get方法去获取,其get方法在父类GenericScope中实现。scop利用hash map管理的ioc创建的bean
Object scopedInstance = scope.get(beanName, () -> {
beforePrototypeCreation(beanName);
try {
return createBean(beanName, mbd, args);
}
finally {
afterPrototypeCreation(beanName);
}
});
public Object get(String name, ObjectFactory objectFactory) {
// 将原始Bean缓存下来
BeanLifecycleWrapper value = this.cache.put(name,
new BeanLifecycleWrapper(name, objectFactory));
this.locks.putIfAbsent(name, new ReentrantReadWriteLock());
try {
// 获取原始类bean
return value.getBean();
}
catch (RuntimeException e) {
this.errors.put(name, e);
throw e;
}
}
#BeanLifecycleWrapper getbean
public Object getBean() {
// 因爲是新的BeanLifecycleWrapper實例,這裏必定爲null
if (this.bean == null) {
synchronized (this.name) {
if (this.bean == null) {
// 調用IOC容器的createBean,再建立一個Bean出來
this.bean = this.objectFactory.getObject();
}
}
}
return this.bean;
}
BeanLifecycleWrapper生成了一个原始的包装类,其中getBean是返回前面说到的原始类的bean。那么原始类的beab和代理类的bean的关系就出来了。
代理类bean会被其他bean引用,代理原始bean进行执行,并且原始bean可以看出是由GenericScope来管理生命周期。所以当配置发生变化时,只需要将scop缓存的bean移除且将原始bean值设置为null,既可以让代理类重新生成新的原始bean,此时获取的配置也是最新的。
// 配置发生变化时调用
public synchronized Set refresh() {
Set keys = refreshEnvironment();
this.scope.refreshAll();
return keys;
}
@ManagedOperation(description = "Dispose of the current instance of all beans in this scope and force a refresh on next method execution.")
public void refreshAll() {
super.destroy();
this.context.publishEvent(new RefreshScopeRefreshedEvent());
}
//进行对象获取,如果没有就创建并放入缓存
@Override
public Object get(String name, ObjectFactory objectFactory) {
BeanLifecycleWrapper value = this.cache.put(name,
new BeanLifecycleWrapper(name, objectFactory));
locks.putIfAbsent(name, new ReentrantReadWriteLock());
try {
return value.getBean();
}
catch (RuntimeException e) {
this.errors.put(name, e);
throw e;
}
}
//进行缓存的数据清理
@Override
public void destroy() {
List errors = new ArrayList();
Collection wrappers = this.cache.clear();
for (BeanLifecycleWrapper wrapper : wrappers) {
try {
Lock lock = locks.get(wrapper.getName()).writeLock();
lock.lock();
try {
wrapper.destroy();
}
finally {
lock.unlock();
}
}
catch (RuntimeException e) {
errors.add(e);
}
}
if (!errors.isEmpty()) {
throw wrapIfNecessary(errors.get(0));
}
this.errors.clear();
}
总结
@refreshScope注解用了两种代理的模式去实现动态刷新bean,一个是scope代理原始bean的生成和销毁,另一个是aop模式代理原始bean,去获取修改配置后新配置值生成的bean。值得注意的是@RefreshScope会使得bean自动销毁和生成,可能会导致一些莫名其妙的问题,例如常见的@scheduled注解失效、线程池bean不断被创建等等,因此实际生产中最好控制动态变量用ConfigurationProperties 注解生产,该注解是通过设置变量值的方式进行配置的修改,更加的安全。