spring的bean管理中,每个bean都有对应的scope。在BeanDefinition中就已经指定scope,默认的RootBeanDefinition的scope是prototype类型. 使用@ComponentScan扫描出的BeanDefinition会指定是singleton. 最常使用的也是singleton。在web环境还有request,session,gloable_session可用。当这些都不满足的情况下也可用自定义scope;
步骤
1
自定义scope实现 scope接口
public class MyScopeX implements Scope {
/***
* 这里返回实例对象,
* objectFactory 一般情况下就是给到的BeanFactory
* 从objectFactory获取
* 从objectFactory获取对象后,可用自己维护对象的声明周期。如不维护就相当于prototype
* 如果声明为MyScopeX作用域的对象有对应的FactoryBean,objectFactory.getObject()降委托给
* 该FactoryBean创建对象
**/
@Override
public Object get(String name, ObjectFactory> objectFactory) {
return objectFactory.getObject();
}
// 忽略其他方法
}
2
注册scope
@Configuration
public class MyCustomeScopeConfigurer {
@Bean
public CustomScopeConfigurer customScopeConfigurer() {
CustomScopeConfigurer customScopeConfigurer = new CustomScopeConfigurer();
Map scopes = new HashMap<>();
scopes.put("myScope", MyScopeX.class);
customScopeConfigurer.setScopes(scopes);
return customScopeConfigurer;
}
}
3
使用scope
@Component
@Scope("myScope")
public class MyScopeComponent {
}
或者另外声明一个注解
@Scope("myScope")
public @interface MyScope {
}
这样只要在类上标识@MysScope就行
运行效果
public static void main(String[] args) {
AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext();
annotationConfigApplicationContext.scan("com.xie.java.scope");
annotationConfigApplicationContext.refresh();
MyScopeComponent myScopeComponent = annotationConfigApplicationContext.getBean(MyScopeComponent.class);
BeanDefinition beanDefinition = annotationConfigApplicationContext.getBeanDefinition("myScopeComponent");
System.out.println("myScopeComponent:" + myScopeComponent);
System.out.println("myScopeComponent scope:" + beanDefinition.getScope());
}
就可用看到, myScopeComponent的scope变成了myScope,不在是singleton
spring是怎么区分是不是singleton呢?是在AbstractBeanFactory的doGetBean方法里有判断逻辑
首先根据BeanDefinition判断scope是不是singleton,是不是prototype最后再按自定义scope处理。
那么读取BeanDefinition怎么知道scope的呢,已annotation scan为例
ClassPathBeanDefinitionScanner.doScan进行包扫描, 并且读取解析类上的注解。其中有一步
this.scopeMetadataResolver.resolveScopeMetadata(candidate);
@Override
public ScopeMetadata resolveScopeMetadata(BeanDefinition definition) {
// 默认是singlton
ScopeMetadata metadata = new ScopeMetadata();
if (definition instanceof AnnotatedBeanDefinition) {
AnnotatedBeanDefinition annDef = (AnnotatedBeanDefinition) definition;
AnnotationAttributes attributes = AnnotationConfigUtils.attributesFor(
annDef.getMetadata(), this.scopeAnnotationType);
if (attributes != null) {
// 设置scope
metadata.setScopeName(attributes.getString("value"));
ScopedProxyMode proxyMode = attributes.getEnum("proxyMode");
if (proxyMode == null || proxyMode == ScopedProxyMode.DEFAULT) {
proxyMode = this.defaultProxyMode;
}
// 设置代理模式
metadata.setScopedProxyMode(proxyMode);
}
}
return metadata;
}
看完以上,还没发现有什么特殊的用处
RefreshScope
RefreshScope属于spring cloud项目,主要作用是感知配置更新并刷新自身的值. RefreshScope也是实现自Scope。同时也有注解@RefreshScope
RefreshScope又有哪些增强呢
@RefreshScope指定了ScopedProxyMode,并且指定为TARGET_CLASS,即指定CGLIB代理方式;
ScopedProxyMode有3中可用指定
NO: 默认,不代理
INTERFACES: 接口方式,JDK动态代理
TARGET_CLASS: CGLIB代理方式.
需要代理就需要ScopedProxyFactoryBean,每一个声明了@RefreshScope的类对象都会被定义为是ScopedProxyFactoryBean. 这一步是再哪里做的呢。是在扫描注解的时候完成的,具体是AnnotaiionConfigUtils.applyScopedProxyMode静态代码块里,这里会查询ScopedProxyMode,如果不是NO则会给beanDefinition的beanClass指定为ScopedProxyFactoryBean;
由于ScopedProxyFactoryBean是FactoryBean的实现类,也即是这里生成代理对象。
RefreshScope的对象是怎么刷新的呢?
@ManagedResource
public class RefreshScope extends GenericScope implements ApplicationContextAware,
ApplicationListener, Ordered {
分别提供了两个方法
- refresh(name)
指定刷新bean, 刷新的方式是销毁bean,并发出RefreshScopeRefreshedEvent事件
@ManagedOperation(description = "Dispose of the current instance of bean name "
+ "provided and force a refresh on next method execution.")
public boolean refresh(String name) {
if (!name.startsWith(SCOPED_TARGET_PREFIX)) {
// User wants to refresh the bean with this name but that isn't the one in the
// cache...
name = SCOPED_TARGET_PREFIX + name;
}
// Ensure lifecycle is finished if bean was disposable
if (super.destroy(name)) {
this.context.publishEvent(new RefreshScopeRefreshedEvent(name));
return true;
}
return false;
}
- refreshAll()
销毁所有RefreshScope作用域的bean,并发出RefreshScopeRefreshedEvent事件
@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());
}
这样旧的bean被销毁[this.cache.clear()清除RefreshScope作用域下的所有bean], 等下次使用到这个bean时再从GenericScope.get(name,objectFactory)里取出新的bean. 新的bean也会触发自动注入等完整的bean实例化流程,自然就拿到新的配置了。
整理刷新流程如下:
- 声明为@RefreshScope的类会被生成代理对象
- 声明为@RefreshScope的实例不再是beanFacotry的单例,实际的作用域由GenericScope管理
- GenericScope声明一个map对象存储各个@RefreshSopce的实例对象
- @RefreshScope的代理对象,当触发bean上的方法[如toString时,会调用真实对象的toString]
- 真实对象的获取是直接Scope的get(name,objectFactory)方法(GenericScope重写了该方法)
- 当触发refreshScope.refresh(name)或者refreshScope.refreashAll()时,清理或者清空GenericScope的map集合或元素. 这里清理的是包装对象和真实的bean实例对象, 代理对象并没有被清除
- 当代理对象需要触发方法时,还是会走4,5俩个流程[代理对象持有beanFactory对象,当需要使用原始对象时,触发getBean方法获取,由于是自定义scope,所以会走到Scope.get(name,objectFactory)方法],这一步就判断GenericScope里是否由缓存对象,没有则新生成.
总结以上,代理对象需要请求原始对象,GenericScope把原始对象包装起来放在map里缓存。当发生refresh时,就清理缓存. 等下次代理对象来拿原始对象时重新走一便创建对象的流程,流程中就触发了属性拿到新的值。
代理对象,请求原始对象时
public class SimpleBeanTargetSource extends AbstractBeanFactoryBasedTargetSource {
@Override
public Object getTarget() throws Exception {
return getBeanFactory().getBean(getTargetBeanName());
}
}
代码示例
声明一个@RefreshScope的bean
@Component
@RefreshScope
public class TestBean {
@Value("${test.value:}")
private String testValue;
public String getTestValue() {
return testValue;
}
public void setTestValue(String testValue) {
this.testValue = testValue;
}
@Override
public String toString() {
return "TestBean{" +
"testValue='" + testValue + '\'' +
'}';
}
}
@SpringBootApplication
public class StartUpApplication {
public static void main(String[] args) {
ConfigurableApplicationContext applicationContext = SpringApplication.run(StartUpApplication.class, args);
TestBean oldBean = applicationContext.getBean(TestBean.class);
System.out.println(oldBean);
// 设置属性
applicationContext.getEnvironment().getSystemProperties().put("test.value", "new_value");
RefreshScope refreshScope = applicationContext.getBean(RefreshScope.class);
// 手动刷新
refreshScope.refreshAll();
TestBean newBean = applicationContext.getBean(TestBean.class);
System.out.println(newBean);
System.out.println("newBean == oldBean ? " + (newBean == oldBean));
System.out.println("old bean " + oldBean);
}
}
那么怎么知道有属性刷新呢?下次再写
配置中心-阿波罗
阿波罗和spring配合,属性刷新于spring的RefreshScope实现方式不一样.
阿波罗不需要@RefreshScope也可以实现属性值刷新
- ApolloProcessor实现BeanProcessor接口,在bean初始化前处理bean对象上的有@Value的属性或方法,如果有@Value, 则保存@Value的key, 属性名称,以及bean的关系构建成一个StringValue对象,注册到SpringValueRegistry对象。
- AutoUpdateConfigChangeListener 监听到有配置值变更, 根据变更的key查找到对应的StringValue, 从配置拿到新值,并根据通过反射直接更新bean的属性。
总结
- 自定义scope,可以自由管理bean的生命周期。
- @RefreshScope,被@RefreshScope的类,生成的bean是代理对象,有配置变更会使代理和原始对象脱离关系,从而重新初始化原始bean.
- 阿波罗, @Value刷新不依赖@Refresh, 可以指定刷新某个key
比较两种刷新方式,@RefreshScope无法做到只对某个key刷新,最小刷新范围是bean,且会销毁bean对象。 而阿波罗可以针对某个key刷新值。