在spring cloud alibaba nacos的配置中,我们为了能够实现配置的动态刷新,@RefreshScope
则是其中的一个解决方案。这篇文章将会从被@RefreshScope
标记的bean从类信息搜集、创建、使用、刷新、销毁这样一个过程入手,记录Scope在spring中的工作原理。
1. @RefreshScope使用方式
@Getter @Service @RefreshScope public class NacosValueService { @Value("${config.age}") private Integer age; private volatile boolean isStop = false; @PreDestroy public void destroy() { System.out.println("执行destroy方法"); } }
通过在类上使用@RefreshScope
注解,就能够实现当配置config.age
在发生变化之后,bean中的配置信息也会发生变化。
2. @RefreshScope类信息BeanDefinition创建
2.1 RefreshScope注解定义
@Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Scope("refresh") @Documented public @interface RefreshScope { /** * @see Scope#proxyMode() * @return proxy mode */ ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS; }
在该注解定义中,包含了两种信息:
- 该注解上除了定义注解的使用方式外,还加了一个
@Scope
注解,该注解定义了@Scope
的名称,常用的有:- singleton
- prototype
- 另外就是代理的方式,因此我们可以推断,最终被
@RefreshScope
标记的bean, 最终是一个代理对象。
2.2 获取BeanDefinition
在spring-boot环境中,当执行Application
的时候,回去扫描当前classpath
下的所有class文件,并将对应的文件解析为对应的BeanDefinition
对象。
这里只考虑默认的扫描路径,不考虑通过注解或者配置方式扫描其他路径的情况
2.2.1 ClassPathBeanDefinitionScanner
该类主要为了扫描classpath
下的类列表,并将对应的class文件解析为BeanDefinition
对象,我们主要查看针对Scope
注解对BeanDefinition
影响的这部分。其他的不在讨论范围。
doScan()
protected Set<BeanDefinitionHolder> doScan(String... basePackages) { Assert.notEmpty(basePackages, "At least one base package must be specified"); Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>(); for (String basePackage : basePackages) { // 获取路径下的所有BeanDefinition列表 Set<BeanDefinition> candidates = findCandidateComponents(basePackage); // 遍历所有的BeanDefinition for (BeanDefinition candidate : candidates) { // 解析@Scope注解中的内容, 以ScopeMetadata表示 ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate); // 更新BeanDefinition的scope值,默认为singleton candidate.setScope(scopeMetadata.getScopeName()); // 生成beanName, 这里的beanName主要有两种获取方式: // 1. 判断是否有注解,其中包含了Component, Service, Named, Indexed等注解,如果有,则判断是否设置value值,如果设置,则以value的值为beanName // 2. 如果以上条件不满足,则去class的名成,只取类名,并将首字母转换为小写 String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry); // 这里判断是否为AbstractBeanDefinition类型,如果是,则为BeanDefinition设置必要的参数, 包括初始化方法名称等。 // 不过在默认中,使用的都没有名缺的指定 if (candidate instanceof AbstractBeanDefinition) { postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName); } // 判断是否为AnnotatedBeanDefinition对象,如果是,则执行通用的注解解析和使用: // 主要包含了以下几类注解: // Lazy, Role, Primary, DependsOn, Description if (candidate instanceof AnnotatedBeanDefinition) { AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate); } // 检查BeanDefinition是否已经被注册,如果被注册,则检查两者 if (checkCandidate(beanName, candidate)) { BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName); // 处理ScopeProxyMode属性配置, 并生成新的DefintionHolder对象 definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry); // 加入beanDefinition列表 beanDefinitions.add(definitionHolder); // 注册当前FactoryBean的Definition到Regsitry中 registerBeanDefinition(definitionHolder, this.registry); } } } return beanDefinitions; }
在处理BeanDefinition
数据中,最后通过AnnotationConfigUtils.applyScopedProxyMode()
方法对BeanDefinition
进行处理,在这个过程中,如果当前的BeanDefinition
并非为NO
时,这个时候在BeanDefinitionRegsitry
中包含了两个BeanDefinition
, 因此需要特别注意。
- 当
ProxyMode=TARGET_CLASS
时,这个时候被代理的BeanDefintion
的名称为scopedTarget.${origianlBeanName}
- 另一个就是本身的
BeanDefinition
, 这时是一个FactoryBean
的实现类,则对应的名称为originalBeanName
.
所以这两者其实是一个替代关系,代理的FactoryBean
指向了被代理的Bean
.
2.2.2 AnnotationConfigUtils
该类主要是对注解配置类的解析,其中applyScopedMode()
方法解决了不同代理的处理方式,具体源码如下:
createScopedProxy()
public static BeanDefinitionHolder createScopedProxy(BeanDefinitionHolder definition, BeanDefinitionRegistry registry, boolean proxyTargetClass) { // 获取初始beanName String originalBeanName = definition.getBeanName(); // 获取BeanDefinition BeanDefinition targetDefinition = definition.getBeanDefinition(); // 获取targetBeanName, 这里格式为: ``scopedTarget.${originalBeanName}`` String targetBeanName = getTargetBeanName(originalBeanName); // Create a scoped proxy definition for the original bean name, // "hiding" the target bean in an internal target definition. // 这里是为初始的BeanDefinition创建一个代理,隐藏目标的BeanDefinition RootBeanDefinition proxyDefinition = new RootBeanDefinition(ScopedProxyFactoryBean.class); proxyDefinition.setDecoratedDefinition(new BeanDefinitionHolder(targetDefinition, targetBeanName)); proxyDefinition.setOriginatingBeanDefinition(targetDefinition); proxyDefinition.setSource(definition.getSource()); proxyDefinition.setRole(targetDefinition.getRole()); // 设置TargetBeanName属性,则指向了``scopedTarget.${originalBeanName}``BeanDefinition proxyDefinition.getPropertyValues().add("targetBeanName", targetBeanName); if (proxyTargetClass) { // 如果ProxMode为TARGET_CLASS, 则设置属性'org.springframework.aop.framework.autoproxy.AutoProxyUtils.preserveTargetClass=true' targetDefinition.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE); // ScopedProxyFactoryBean's "proxyTargetClass" default is TRUE, so we don't need to set it explicitly here. } else { // 如果是其他的,则设置proxyTargetClass为false proxyDefinition.getPropertyValues().add("proxyTargetClass", Boolean.FALSE); } // Copy autowire settings from original bean definition. // 从源BeanDefinition中拷贝属性 proxyDefinition.setAutowireCandidate(targetDefinition.isAutowireCandidate()); proxyDefinition.setPrimary(targetDefinition.isPrimary()); if (targetDefinition instanceof AbstractBeanDefinition) { proxyDefinition.copyQualifiersFrom((AbstractBeanDefinition) targetDefinition); } // The target bean should be ignored in favor of the scoped proxy. targetDefinition.setAutowireCandidate(false); targetDefinition.setPrimary(false); // Register the target bean as separate bean in the factory. // 这里需要注意,我们实际的beanName名称是自定义或者指定,但是我们这里处理完成了之后 // 这里就变成了``scopedTarget.${originalBeanName}``, 因此注册的bean名称发生了改变 registry.registerBeanDefinition(targetBeanName, targetDefinition); // Return the scoped proxy definition as primary bean definition // (potentially an inner bean). // 返回代理之后的BeanDefinition信息 return new BeanDefinitionHolder(proxyDefinition, originalBeanName, definition.getAliases()); }
3. RefreshScope管理Bean
在讲到对scope=refresh
这类的BeanDefinition
进行实例化的时候,需要讲到的一个知识点就是对这类Bean的管理。我们知道, Spring默认管理两类bean, 分别为scope=singleton
和scope=prototype
两类,我们从bean的实例化过程就可以看出。而针对这两类,其他的scope
的bean则通过scope
类型进行管理,因此,针对scope=refresh
的管理,有专门的RefreshScope
来实现。
我们查看spring创建bean的过程中,scope的使用如下:
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, () -> { ...
从代码中,可以看出,主要包含以下步骤:
- 获取当前
BeanDefinition
原信息的scope
的值,并根据scope
的值获取对应的Scope
对象 - 如果
Scope
对象不存在,则抛出异常 - 根据
Scope
对象的get
方法创建bean
对象实例。
3.1 类结构
3.2 注册时机
从以上的类结构图可以知道,因为GenericScope本身实现了BeanFactoryPostProcessor
类,因此在BeanFactory准备完毕后,就会实例化该类并回调函数。因此我们查看对应的实现方法。
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { this.beanFactory = beanFactory; beanFactory.registerScope(this.name, this); setSerializationId(beanFactory); }
可以得知,在postProcessBeanFacotry()
方法回调方法中,通过regsiterScope()
方法将RefreshScope
对象注册到BeanFactory
对象中,因此在创建Bean
的时候能够通过RefreshScope
进行管理。
4. 创建Bean
在上面我们讲到了,只要对应的BeanDefintion的scope
不是singleton或者prototype
时,都会通过Scope
对象来管理bean,具体代码如下:
// 获取scope String scopeName = mbd.getScope(); if (!StringUtils.hasLength(scopeName)) { throw new IllegalStateException("No scope name defined for bean '" + beanName + "'"); } // 根据scope,获取对应的Scope对象 Scope scope = this.scopes.get(scopeName); // 如果没有注册Scope类型,将抛出错误 if (scope == null) { throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'"); } try { // 从Scope中获取bean对象,如果bean对象不存在,则通过函数创建bean 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); }
4.1 工厂类(FactoryBean)初始化
在上面我们谈到,当我们的类需要做类型代理的时候,则会生成两个BeanDefinition
对象,因此originalBeanName
则被FactoryBean所代替。在代理中,实际上使用的是ScopedProxyFactoryBean
进行代替。
ScopedProxyFactoryBean
这个类实现是很简单的,首先该类的类结构如下:
该类的实现是比较简单的,主要包含了两个元素:
targetBeanName
: 被代理的bean名称,之前我们知道,实在bean名称前多加了一个targetBeanName.
的前缀proxy
:被代理bean对象的实例。
从初始化角度来说,该FactoryBean并没有太多需要设置的内容。
4.2 代理对象proxy何时创建?
我们都知道,代理对象在最终获取被代理对象的时候,都是通过getObject()
方法来获取,那么我们通过查看源码,ScopedProxyFactoryBean
的 proxy
对象的创建,则是放在了setBeanFacotory()
方法中。
public void setBeanFactory(BeanFactory beanFactory) { // 判断beanFactory是否为ConfigurableBeanFacotry的实例 if (!(beanFactory instanceof ConfigurableBeanFactory)) { throw new IllegalStateException("Not running in a ConfigurableBeanFactory: " + beanFactory); } ConfigurableBeanFactory cbf = (ConfigurableBeanFactory) beanFactory; // scopedTargetSource默认为SimpleBeanTargetSource, 这里绑定了BeanFactory this.scopedTargetSource.setBeanFactory(beanFactory); // 创建ProxyFactory实例 ProxyFactory pf = new ProxyFactory(); // 从当前的ProxyConfig拷贝属性值到ProxyFactory中 pf.copyFrom(this); // 设置被代理对象的信息 pf.setTargetSource(this.scopedTargetSource); Assert.notNull(this.targetBeanName, "Property 'targetBeanName' is required"); // 获取被代理对象的Class类型 Class<?> beanType = beanFactory.getType(this.targetBeanName); // 如果beanType为空, 那么对应的bean的class没有被加载到BeanFactory中,则抛出异常 if (beanType == null) { throw new IllegalStateException("Cannot create scoped proxy for bean '" + this.targetBeanName + "': Target type could not be determined at the time of proxy creation."); } // 判断是否为为代理目标对象,即判断proxyTargetClass属性值是否为false // 判断代理对象的类型是否为接口 // 判断代理对象的类型定义是否为private // 当以上条件满足一个时,那么就获取代理对象class的所有接口,并绑定到ProxyFactory对象中 if (!isProxyTargetClass() || beanType.isInterface() || Modifier.isPrivate(beanType.getModifiers())) { pf.setInterfaces(ClassUtils.getAllInterfacesForClass(beanType, cbf.getBeanClassLoader())); } // Add an introduction that implements only the methods on ScopedObject. // 创建DefaultScopedObject对象, ScopedObject scopedObject = new DefaultScopedObject(cbf, this.scopedTargetSource.getTargetBeanName()); // 向ProxyFacotry中新增DelegatingIntroductionInterceptor拦截器 pf.addAdvice(new DelegatingIntroductionInterceptor(scopedObject)); // Add the AopInfrastructureBean marker to indicate that the scoped proxy // itself is not subject to auto-proxying! Only its target bean is. pf.addInterface(AopInfrastructureBean.class); // 创建代理对象并绑定到proxy属性中 this.proxy = pf.getProxy(cbf.getBeanClassLoader()); }
通过代码可知,创建代理对象是通过ProxyFactory
来完成的。
4.3 代理对象proxy创建过程
代理对象的创建主要通过ProxyFactory对象来完成,则查看对应的源码:
getProxy()
public Object getProxy(@Nullable ClassLoader classLoader) { return createAopProxy().getProxy(classLoader); }
该创建proxy主要通过createAopProxy()
方法创建的类来实现。
createAopProxy()
protected final synchronized AopProxy createAopProxy() { if (!this.active) { activate(); } return getAopProxyFactory().createAopProxy(this); }
getAopProxyFactory()
方法创建了工厂类对象,这里使用了DefaultAopProxyFactory
类来作为默认的工厂类对象。我们知道,spring有两种代理方式:
- JDK Proxy
- 代理class是接口
- 是Proxy生成的代理class
- 是lambda表达式class
- Cglib
这两种方式有不同的选择场景,则对应createAopProxy()
代码如下:
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException { if (!NativeDetector.inNativeImage() && (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config))) { // 获取被代理对象的class Class<?> targetClass = config.getTargetClass(); if (targetClass == null) { throw new AopConfigException("TargetSource cannot determine target class: " + "Either an interface or a target is required for proxy creation."); } // 使用jdk的代理方式需要满足一下条件: // 1. 是接口 // 2. 是代理的class // 3. 是lambada表达式的class对象 if (targetClass.isInterface() || Proxy.isProxyClass(targetClass) || ClassUtils.isLambdaClass(targetClass)) { return new JdkDynamicAopProxy(config); } // 其他情况使用Cglib return new ObjenesisCglibAopProxy(config); } else { return new JdkDynamicAopProxy(config); } }
这里我们主要关注下cglib的创建方式,因为对于被代理对象是接口的情况,其实我感觉在日常中使用的是比较少的,所以我们关注cglib创建方法。
cglib创建代理对象主要使用的asm生成字节码,这部分确实要对jvm有很高的认知的才会明白,cglib直接通过asm输入字节码指令生成对应的class文件。因此这里就不做过多介绍。因为我也是云里雾里。。。。
CglibAopProxy
getProxy()
public Object getProxy(@Nullable ClassLoader classLoader) { if (logger.isTraceEnabled()) { logger.trace("Creating CGLIB proxy: " + this.advised.getTargetSource()); } try { // 获取被代理对象的类型 Class<?> rootClass = this.advised.getTargetClass(); Assert.state(rootClass != null, "Target class must be available for creating a CGLIB proxy"); // 被代理对象 Class<?> proxySuperClass = rootClass; // 判断对象的名称中是否包含了$$, 因此判断是否本身为代理生成的class if (rootClass.getName().contains(ClassUtils.CGLIB_CLASS_SEPARATOR)) { // 获取超类 proxySuperClass = rootClass.getSuperclass(); // 获取接口列表 Class<?>[] additionalInterfaces = rootClass.getInterfaces(); // 将接口列表加入到advised对象中 for (Class<?> additionalInterface : additionalInterfaces) { this.advised.addInterface(additionalInterface); } } // Validate the class, writing log messages as necessary. // 验证class validateClassIfNecessary(proxySuperClass, classLoader); // Configure CGLIB Enhancer... Enhancer enhancer = createEnhancer(); if (classLoader != null) { // 设置classloader对象 enhancer.setClassLoader(classLoader); if (classLoader instanceof SmartClassLoader && ((SmartClassLoader) classLoader).isClassReloadable(proxySuperClass)) { enhancer.setUseCache(false); } } // 设置被代理的class对象 enhancer.setSuperclass(proxySuperClass); // 设置代理对象class的接口列表 enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised)); // 设置命名策略 enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE); // 设置生成策略 enhancer.setStrategy(new ClassLoaderAwareGeneratorStrategy(classLoader)); // 获取回调类列表 Callback[] callbacks = getCallbacks(rootClass); Class<?>[] types = new Class<?>[callbacks.length]; for (int x = 0; x < types.length; x++) { types[x] = callbacks[x].getClass(); } // 设置filter, 在代理的过程中,用于判断那些方法不被代理 // fixedInterceptorMap only populated at this point, after getCallbacks call above enhancer.setCallbackFilter(new ProxyCallbackFilter( this.advised.getConfigurationOnlyCopy(), this.fixedInterceptorMap, this.fixedInterceptorOffset)); enhancer.setCallbackTypes(types); // Generate the proxy class and create a proxy instance. // 创建代理对象 return createProxyClassAndInstance(enhancer, callbacks); } catch (CodeGenerationException | IllegalArgumentException ex) { throw new AopConfigException("Could not generate CGLIB subclass of " + this.advised.getTargetClass() + ": Common causes of this problem include using a final class or a non-visible class", ex); } catch (Throwable ex) { // TargetSource.getTarget() failed throw new AopConfigException("Unexpected AOP exception", ex); } }
createProxyClassAndInstance()
protected Object createProxyClassAndInstance(Enhancer enhancer, Callback[] callbacks) { // 生成代理的class并加载class Class<?> proxyClass = enhancer.createClass(); Object proxyInstance = null; if (objenesis.isWorthTrying()) { try { // 创建代理对象 proxyInstance = objenesis.newInstance(proxyClass, enhancer.getUseCache()); } catch (Throwable ex) { logger.debug("Unable to instantiate proxy using Objenesis, " + "falling back to regular proxy construction", ex); } } // 如果代理对象创建失败, 则通过构造器模式创建 if (proxyInstance == null) { // Regular instantiation via default constructor... try { Constructor<?> ctor = (this.constructorArgs != null ? proxyClass.getDeclaredConstructor(this.constructorArgTypes) : proxyClass.getDeclaredConstructor()); ReflectionUtils.makeAccessible(ctor); proxyInstance = (this.constructorArgs != null ? ctor.newInstance(this.constructorArgs) : ctor.newInstance()); } catch (Throwable ex) { throw new AopConfigException("Unable to instantiate proxy using Objenesis, " + "and regular proxy instantiation via default constructor fails as well", ex); } } // 为对象设置回调对象列表 ((Factory) proxyInstance).setCallbacks(callbacks); return proxyInstance;
这里通过截图可以看出,对于callback而言,他的内部不是存储的列表,而是将列表拆分成单个成员变量。具体源码可以参考cglib..
到此,对于FactoryBean
的创建就到此结束。
4.4 Scope管理被代理对象
也许会有人会问,为什么本身的bean
初始化过程为什么不讲,其实很简单,因为本身的bean就是一个普通对象的实例化过程,跟spring初始化一个bean的过程一样,则没有必要单独再讲一遍。
所以我们只需要关注Scope
在拿到了需要被Scope
管的bean
是如何存储的即可。我们还是回到最开始的代码,通过Scope
获取到Bean
的代码。
Object scopedInstance = scope.get(beanName, () -> { beforePrototypeCreation(beanName); try { return createBean(beanName, mbd, args); } finally { afterPrototypeCreation(beanName); } });
GenericScope
在通过get方法获取的时候,主要源码如下:
public Object get(String name, ObjectFactory<?> objectFactory) { // 加入缓存 BeanLifecycleWrapper value = this.cache.put(name, new BeanLifecycleWrapper(name, objectFactory)); // 加入锁信息 this.locks.putIfAbsent(name, new ReentrantReadWriteLock()); try { return value.getBean(); } catch (RuntimeException e) { this.errors.put(name, e); throw e; } }
这里设计到两个缓存信息,主要缓存与bean有关的信息。这里主要关注BeanLifecycleWrapper
类型的getBean()
方法.
这里有个点需要关注:
- 虽然cache类在每次都是put一个新的对象进入,但是内层的逻辑是会判断当前缓存中是否包含了名称为name的key,如果包含,是不会插入成功的,返回的依然是之前已经缓存过的对象
- 其次就是在刷新的时候,会清空缓存,这个时候才会将新的缓存对象放置到缓存中。
BeanLifecycleWrapper
public Object getBean() { // 判断bean是否已经创建, 如果没有创建,则直接创建 if (this.bean == null) { // 锁定名称,让统一时间只有一个线程创建bean synchronized (this.name) { // 如果bean为空,则调用ObjectFactory创建bean if (this.bean == null) { this.bean = this.objectFactory.getObject(); } } } return this.bean; }
因此,在BeanLifecycleWrapper
类型中,实际上是会缓存bean对象,避免重复创建。
5. 清空bean
当bean中的配置在发生变更之后,spring主要通过事件机制判断是否需要刷新bean, 当接受需要刷新事件之后,则会将scope中所有的缓存清空。
RefreshEventListener
@Override public void onApplicationEvent(ApplicationEvent event) { if (event instanceof ApplicationReadyEvent) { handle((ApplicationReadyEvent) event); } // 接受到刷新事件 else if (event instanceof RefreshEvent) { handle((RefreshEvent) event); } } public void handle(RefreshEvent event) { if (this.ready.get()) { // don't handle events before app is ready log.debug("Event received " + event.getEventDesc()); // 执行refresh操作 Set<String> keys = this.refresh.refresh(); log.info("Refresh keys changed: " + keys); } }
从代码中可以知道,在当前类中主要处理两类事件,一类是ApplicationReadyEvent
,另一类就是RefreshEvent
. 当接受到刷新的时候,会执行ContextRefresher
类的刷新方法。
ContextRefresher
public synchronized Set<String> refresh() { Set<String> keys = refreshEnvironment(); this.scope.refreshAll(); return keys; }
在刷新的过程中,主要包含了两个步骤:
- 刷新当前的环境配置,这个在之前的文章中@ConfigurationProperties自动刷新配置原理
- 刷新
Scope
中的缓存。
我们可以看下刷新缓存的实现逻辑:
RefreshScope
public void refreshAll() { super.destroy(); this.context.publishEvent(new RefreshScopeRefreshedEvent()); }
这里主要包含了两个步骤:
- 第一个就是执行
destroty
方法,在执行destroy()
方法的时候,会将保存在scope
中的bean进行遍历,然后执行销毁bean
的逻辑 - 再者就是发送一个
RefreshScopeRefreshedEvent
事件,用于通知刷新scope
成功
以上就是@RefreshScope
的原理介绍,希望对大家有所帮助。