Spring参数动态绑定的原理

简介: 在Spring体系下,如果实现了ConfigurationProperties则会自动刷新。而如果只使用`@Value`的方法,要加上 `@RefreshScope` 才能实现。本篇文章我们来分别研究下他们的原理。然后在来看看其他的方案是如何做的吧。

一、如何实现动态配置

在Spring体系下,如果实现了ConfigurationProperties则会自动刷新。而如果只使用@Value的方法,要加上 @RefreshScope 才能实现。
本篇文章我们来分别研究下他们的原理。然后在来看看其他的方案是如何做的吧。

二、实现原理

2.1 @ConfigurationProperties

所有被@ConfigurationProperties修饰的类都会被ConfigurationPropertiesBeans处理

  1. 实现BeanPostProcessor处理器,初始化时候判断是否被@ConfigurationProperties修饰,如果是就保存到ConfigurationPropertiesBeans#beans属性中
    public Object postProcessBeforeInitialization(Object bean, String beanName)
            throws BeansException {
        // 1. 如果已经被RefreshScope修饰了,也会自动更新就不用在处理了。     
        if (isRefreshScoped(beanName)) {
            return bean;
        }
        ConfigurationProperties annotation = AnnotationUtils
                .findAnnotation(bean.getClass(), ConfigurationProperties.class);
        if (annotation != null) {
            this.beans.put(beanName, bean);
        }
        else if (this.metaData != null) {
            annotation = this.metaData.findFactoryAnnotation(beanName,
                    ConfigurationProperties.class);
            if (annotation != null) {
                this.beans.put(beanName, bean);
            }
        }
        return bean;
    }
  1. ConfigurationPropertiesRebinder 实现 EnvironmentChangeEvent 变更事件, 当收到EnvironmentChangeEvent事件

会重新触发绑定事件。需要绑定的bean就从ConfigurationPropertiesBeans#beans属性中获取。

具体的实现类 ConfigurationPropertiesRebinder

  1. 先调用销毁方法
  2. 然后重新初始化
    // 接受事件
    public void onApplicationEvent(EnvironmentChangeEvent event) {
        if (this.applicationContext.equals(event.getSource())
                // Backwards compatible
                || event.getKeys().equals(event.getSource())) {
            rebind();
        }
    }
    // 重新绑定
    public boolean rebind(String name) {
        if (!this.beans.getBeanNames().contains(name)) {
            return false;
        }
        if (this.applicationContext != null) {
            try {
                Object bean = this.applicationContext.getBean(name);
                if (AopUtils.isAopProxy(bean)) {
                    bean = ProxyUtils.getTargetObject(bean);
                }
                if (bean != null) {
                    this.applicationContext.getAutowireCapableBeanFactory()
                            .destroyBean(bean);
                    this.applicationContext.getAutowireCapableBeanFactory()
                            .initializeBean(bean, name);
                    return true;
                }
            }
            catch (RuntimeException e) {
                this.errors.put(name, e);
                throw e;
            }
            catch (Exception e) {
                this.errors.put(name, e);
                throw new IllegalStateException("Cannot rebind to " + name, e);
            }
        }
        return false;
    }

2.2 @RefreshScope

@RefreshScope 的原理相对流程较长,首先他需要你将类用 @RefreshScope来修饰。

  1. 首先明确那些是被修饰的AnnotatedBeanDefinitionReader#registerBean
<T> void doRegisterBean(Class<T> annotatedClass, @Nullable Supplier<T> instanceSupplier, @Nullable String name,
            @Nullable Class<? extends Annotation>[] qualifiers, BeanDefinitionCustomizer... definitionCustomizers) {

        AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(annotatedClass);
        if (this.conditionEvaluator.shouldSkip(abd.getMetadata())) {
            return;
        }

        abd.setInstanceSupplier(instanceSupplier);
        ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(abd);
        abd.setScope(scopeMetadata.getScopeName());
        ...
        BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(abd, beanName);
        // 创建bean描述信息 beanClass = ScopedProxyFactoryBean
        // ScopedProxyCreator#createScopedProxy->ScopedProxyUtils#createScopedProxy
        definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
        BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this.registry);

}        
  1. 被Scope修饰的beanClass都是ScopedProxyFactoryBean

    • GenericScope 实现BeanFactoryPostProcessor 会提前将RefreshScope注册到BeanFactory中
    • beanFactory.registerScope(this.name, this)
    • 当执行完上面 AbstractBeanFactory#scopes属性中就有值了。对于RefreshScope name = refresh
public class GenericScope implements Scope, BeanFactoryPostProcessor,
        BeanDefinitionRegistryPostProcessor, DisposableBean {

}
public class RefreshScope extends GenericScope implements ApplicationContextAware,
        ApplicationListener<ContextRefreshedEvent>, Ordered {    
}        
  1. 当getBean时候,对于域对象会有特殊的处理逻辑,会调用 Scope#get(String name, ObjectFactory<?> objectFactory)
    protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
            @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
            ...
                // 创建单例逻辑
                if (mbd.isSingleton()) {
                    ...
                    bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
                }
                // 创建原型逻辑
                else if (mbd.isPrototype()) {
                    ...
                    bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
                }
                else {
                    // 创建域对象
                    // refresh
                    String scopeName = mbd.getScope();
                    // RefreshScope
                    final 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);
                            }
                        });
                        bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
                    }
                }
            }
        }
        return (T) bean;
    }
public interface Scope {
    Object get(String name, ObjectFactory<?> objectFactory); 
}
public class GenericScope implements Scope, BeanFactoryPostProcessor,
        BeanDefinitionRegistryPostProcessor, DisposableBean {}
public class RefreshScope extends GenericScope implements ApplicationContextAware,
        ApplicationListener<ContextRefreshedEvent>, Ordered {}        
  1. RefreshEventListener 接受事件,触发刷新操作
public class RefreshEventListener implements SmartApplicationListener {
    private ContextRefresher refresh;
    @Override
    public void onApplicationEvent(ApplicationEvent event) {
        if (event instanceof ApplicationReadyEvent) {
            handle((ApplicationReadyEvent) event);
        }
        else if (event instanceof RefreshEvent) {
            handle((RefreshEvent) event);
        }
    }

    public void handle(ApplicationReadyEvent event) {
        this.ready.compareAndSet(false, true);
    }

    public void handle(RefreshEvent event) {
        if (this.ready.get()) { // don't handle events before app is ready
            log.debug("Event received " + event.getEventDesc());
            Set<String> keys = this.refresh.refresh();
            log.info("Refresh keys changed: " + keys);
        }
    }
}
  1. ContextRefresher#refresh

    1. refreshEnvironment刷新环境
    2. 调用RefreshScope#refreshAll
public class ContextRefresher {
   public synchronized Set<String> refresh() {
        Set<String> keys = refreshEnvironment();
        this.scope.refreshAll();
        return keys;
    }

    public synchronized Set<String> refreshEnvironment() {
        Map<String, Object> before = extract(
                this.context.getEnvironment().getPropertySources());
        addConfigFilesToEnvironment();
        Set<String> keys = changes(before,
                extract(this.context.getEnvironment().getPropertySources())).keySet();
        this.context.publishEvent(new EnvironmentChangeEvent(this.context, keys));
        return keys;
    }
}
  1. RefreshScope#refreshAll

会将容器中的bean给销毁。
而ScopedProxyFactoryBean中getObject是一个代理对象。带代理类每次都从容器中获取。而容器前面已经将被RefreshScope修饰的类给销毁了
测试拿到的对象就是重新从容器中生成的。

public class ScopedProxyFactoryBean extends ProxyConfig
        implements FactoryBean<Object>, BeanFactoryAware, AopInfrastructureBean {
    private Object proxy;    
    private final SimpleBeanTargetSource scopedTargetSource = new SimpleBeanTargetSource();
    @Override
    public void setBeanFactory(BeanFactory beanFactory) {
        ...
        ProxyFactory pf = new ProxyFactory();
        pf.copyFrom(this);
        pf.setTargetSource(this.scopedTargetSource);
        this.proxy = pf.getProxy(cbf.getBeanClassLoader());
    }
}        

public class SimpleBeanTargetSource extends AbstractBeanFactoryBasedTargetSource {
    @Override
    public Object getTarget() throws Exception {
        return getBeanFactory().getBean(getTargetBeanName());
    }
}

三、其他方案

因为我们项目中用的是阿波罗,那我们只看阿波罗是如何来做的吧。
在阿波罗只用使用@Value就行了

3.1 先扫描@Value注解

将被@Value修饰的Bean和配置key先生成一个SpringValue对象然后注册到SpringValueRegistry

public class SpringValueProcessor extends ApolloProcessor implements BeanFactoryPostProcessor, BeanFactoryAware {
   protected void processField(Object bean, String beanName, Field field) {
       // register @Value on field
       Value value = field.getAnnotation(Value.class);
       if (value == null) {
         return;
       }
       Set<String> keys = placeholderHelper.extractPlaceholderKeys(value.value());
   
       if (keys.isEmpty()) {
         return;
       }
   
       for (String key : keys) {
         SpringValue springValue = new SpringValue(key, value.value(), bean, beanName, field, false);
         springValueRegistry.register(beanFactory, key, springValue);
         logger.debug("Monitoring {}", springValue);
       }
  }
}

3.2 找到需要更新的Bean

接受到配置变更事件后,遍历本地变更的配置key,然后将本次key关联需要变更的Bean,从springValueRegistry中找到。

public class AutoUpdateConfigChangeListener implements ConfigChangeListener{
   @Override
  public void onChange(ConfigChangeEvent changeEvent) {
    Set<String> keys = changeEvent.changedKeys();
    if (CollectionUtils.isEmpty(keys)) {
      return;
    }
    for (String key : keys) {
      // 1. check whether the changed key is relevant
      Collection<SpringValue> targetValues = springValueRegistry.get(beanFactory, key);
      if (targetValues == null || targetValues.isEmpty()) {
        continue;
      }

      // 2. update the value
      for (SpringValue val : targetValues) {
        updateSpringValue(val);
      }
    }
  }
}

3.3 通过反射的方法注入

public class SpringValue {
   public void update(Object newVal) throws IllegalAccessException, InvocationTargetException {
       if (isField()) {
         injectField(newVal);
       } else {
         injectMethod(newVal);
       }
     }
    private void injectField(Object newVal) throws IllegalAccessException {
       Object bean = beanRef.get();
       if (bean == null) {
         return;
       }
       boolean accessible = field.isAccessible();
       field.setAccessible(true);
       field.set(bean, newVal);
       field.setAccessible(accessible);
     }
}

非常简单,高效。相比使用@RefreshScope是不是清爽多了呢?

相关文章
|
3天前
|
XML Java 开发者
Spring Boot开箱即用可插拔实现过程演练与原理剖析
【11月更文挑战第20天】Spring Boot是一个基于Spring框架的项目,其设计目的是简化Spring应用的初始搭建以及开发过程。Spring Boot通过提供约定优于配置的理念,减少了大量的XML配置和手动设置,使得开发者能够更专注于业务逻辑的实现。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,为开发者提供一个全面的理解。
9 0
|
3月前
|
安全 Java 数据库
一天十道Java面试题----第四天(线程池复用的原理------>spring事务的实现方式原理以及隔离级别)
这篇文章是关于Java面试题的笔记,涵盖了线程池复用原理、Spring框架基础、AOP和IOC概念、Bean生命周期和作用域、单例Bean的线程安全性、Spring中使用的设计模式、以及Spring事务的实现方式和隔离级别等知识点。
|
3月前
|
Java
Spring5入门到实战------9、AOP基本概念、底层原理、JDK动态代理实现
这篇文章是Spring5框架的实战教程,深入讲解了AOP的基本概念、如何利用动态代理实现AOP,特别是通过JDK动态代理机制在不修改源代码的情况下为业务逻辑添加新功能,降低代码耦合度,并通过具体代码示例演示了JDK动态代理的实现过程。
Spring5入门到实战------9、AOP基本概念、底层原理、JDK动态代理实现
|
1月前
|
JSON 前端开发 Java
Spring MVC——获取参数和响应
本文介绍了如何在Spring框架中通过不同的注解和方法获取URL参数、上传文件、处理cookie和session、以及响应不同类型的数据。具体内容包括使用`@PathVariable`获取URL中的参数,使用`MultipartFile`上传文件,通过`HttpServletRequest`和`@CookieValue`获取cookie,通过`HttpSession`和`@SessionAttribute`获取session,以及如何返回静态页面、HTML代码片段、JSON数据,并设置HTTP状态码和响应头。
49 1
Spring MVC——获取参数和响应
|
4月前
|
Java 应用服务中间件 开发者
Java面试题:解释Spring Boot的优势及其自动配置原理
Java面试题:解释Spring Boot的优势及其自动配置原理
118 0
|
4月前
|
设计模式 监控 Java
解析Spring Cloud中的断路器模式原理
解析Spring Cloud中的断路器模式原理
|
1月前
|
Java Spring 容器
Spring底层原理大致脉络
Spring底层原理大致脉络
|
1月前
|
Java Spring 容器
Spring IOC、AOP与事务管理底层原理及源码解析
【10月更文挑战第1天】Spring框架以其强大的控制反转(IOC)和面向切面编程(AOP)功能,成为Java企业级开发中的首选框架。本文将深入探讨Spring IOC和AOP的底层原理,并通过源码解析来揭示其实现机制。同时,我们还将探讨Spring事务管理的核心原理,并给出相应的源码示例。
126 9
|
1月前
|
前端开发 Java Spring
【Spring】“请求“ 之传递单个参数、传递多个参数和传递对象
【Spring】“请求“ 之传递单个参数、传递多个参数和传递对象
106 2
|
1月前
|
XML 前端开发 Java
拼多多1面:聊聊Spring MVC的工作原理!
本文详细剖析了Spring MVC的工作原理,涵盖其架构、工作流程及核心组件。Spring MVC采用MVC设计模式,通过DispatcherServlet、HandlerMapping、Controller和ViewResolver等组件高效处理Web请求。文章还探讨了DispatcherServlet的初始化和请求处理流程,以及HandlerMapping和Controller的角色。通过理解这些核心概念,开发者能更好地构建可维护、可扩展的Web应用。适合面试准备和技术深挖
42 0