自定义Scope与RefreshScope

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实例化流程,自然就拿到新的配置了。
整理刷新流程如下:

  1. 声明为@RefreshScope的类会被生成代理对象
  2. 声明为@RefreshScope的实例不再是beanFacotry的单例,实际的作用域由GenericScope管理
  3. GenericScope声明一个map对象存储各个@RefreshSopce的实例对象
  4. @RefreshScope的代理对象,当触发bean上的方法[如toString时,会调用真实对象的toString]
  5. 真实对象的获取是直接Scope的get(name,objectFactory)方法(GenericScope重写了该方法)
  6. 当触发refreshScope.refresh(name)或者refreshScope.refreashAll()时,清理或者清空GenericScope的map集合或元素. 这里清理的是包装对象和真实的bean实例对象, 代理对象并没有被清除
  7. 当代理对象需要触发方法时,还是会走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刷新值。

你可能感兴趣的:(自定义Scope与RefreshScope)