写点什么

Spring 配置加载

用户头像
樊江。
关注
发布于: 4 小时前

前言

文中软件版本及官方文档:


如何使用 Spring 配置

@Value 声明所需的配置 key

@Component public class MovieRecommender {     private final String catalog;     public MovieRecommender(@Value("${catalog.name}") String catalog) {         this.catalog = catalog;     } }
复制代码

@PropertySource 声明配置来源

@Configuration @PropertySource("classpath:application.properties") public class AppConfig { }
复制代码


如上述的一个实例。在 MovieRecommender 中声明需要注入一个 key 为 catalog.name 的配置。而该配置的值在 AppConfig 中通过 @PropertySource("classpath:application.properties") 声明,这里的 classpath:application.properties 指向 classpath 下的 application.properties 配置文件,也就是该配置文件配置了 catalog.name 这个 key 的具体指。

Spring 配置源

配置源集合

org.springframework.core.env.PropertySources

Spring 中的配置集合,该接口实现了 Iterable<PropertySource<?>> 也就是内部其实是多个 PropertySource<?>,该接口提供的主要方法:


  • PropertySource<?> get(String name); - 可以通过 name 获取到一个具体的 PropertySource

注解 - org.springframework.context.annotation.PropertySources

在 Spring 4.0 以后提供 @PropertySources 注解,可以通过注解的方式使用 org.springframework.core.env.PropertySources

主要实现 - org.springframework.core.env.MutablePropertySources

PropertySources 的主要实现有 org.springframework.core.env.MutablePropertySources


public class MutablePropertySources implements PropertySources {
private final Log logger;
private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>(); ... 省略部分代码}
复制代码


该方法内部维护了一个 List<PropertySource<?>> 数组,底层采用 CopyOnWriteArrayList;List<PropertySource<?>> 是可以有序的:


相对顺序:


  • org.springframework.core.env.MutablePropertySources#addBefore

  • org.springframework.core.env.MutablePropertySources#addAfter


绝对顺序:


  • org.springframework.core.env.MutablePropertySources#addFirst

  • org.springframework.core.env.MutablePropertySources#addLast

单个配置 org.springframework.core.env.PropertySource

public abstract class PropertySource<T> {
protected final Log logger = LogFactory.getLog(getClass());
protected final String name;
protected final T source; ... 省略部分代码}
复制代码

注解 @org.springframework.context.annotation.PropertySources

配置源中主要有两个属性:


  • name: 配置源名称,建议唯一命名

  • source: 配置的实际来源,可能是 map,也可能是仓储对象

  • Map: org.springframework.core.env.MapPropertySource


    public MapPropertySource(String name, Map<String, Object> source) {                super(name, source);        }
复制代码


* Properties: `org.springframework.core.env.PropertiesPropertySource`
复制代码


    public PropertiesPropertySource(String name, Properties source) {    super(name, (Map) source);  }
复制代码


* 命令行参数: `org.springframework.core.env.JOptCommandLinePropertySource`
复制代码


    public JOptCommandLinePropertySource(String name, OptionSet options) {        super(name, options);    }
复制代码


配置源的主要行为:getProperty(String name)


这里以 Map 为例,在调用接口的 PropertySource 接口的 getProperty(String name) 方式时,调用到具体实现 org.springframework.core.env.MapPropertySource:


public MapPropertySource(String name, Map<String, Object> source) {    super(name, source);}
@Override@Nullablepublic Object getProperty(String name) { return this.source.get(name);}
复制代码


这里我们看到 MapPropertySource 中的 getProperty 直接返回了 this.source.get(name) ,而这里的 source 是在构造方法中传入的一个 Map<String, Object> source。 其实这里就是根据配置项的 key (如例子中的 catalog.name) 从 map 中获取对应的 value。

配置解析

org.springframework.core.env.Environment

Spring Framework 3.1 开始引入 Environment 抽象,它统一 Spring 配 置属性的存储,包括占位符处理和类型转换,不仅完整地替换 PropertyPlaceholderConfigurer,而且还支持更丰富的配置属性源 (PropertySource)。使用场景:


  • 用于属性占位符的处理

  • 用于转换 Spring 配置属性类型

  • 用于存储 Spring 配置属性源(PropertySource)

  • 用于 Profiles 状态的维护

核心接口及实现

  • 核心接口

  • org.springframework.core.env.Environment

  • org.springframework.core.env.ConfigurableEnvironment

  • 标准实现

  • org.springframework.core.env.StandardEnvironment


    @Override    protected void customizePropertySources(MutablePropertySources propertySources) {        propertySources.addLast(                        new PropertiesPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));        propertySources.addLast(                        new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));    }
复制代码


StandardEnvironment 类的 customizePropertySources 方法中,会添加系统相关的配置源到 propertySources 配置源集合中。而 customizePropertySources 方法是在父类的构造方法中创建的,在构造方法中,一起创建的还有 propertyResolver


protected AbstractEnvironment(MutablePropertySources propertySources) {    this.propertySources = propertySources;    // 新建一个 配置解析器。方法返回: new PropertySourcesPropertyResolver(propertySources);    this.propertyResolver = createPropertyResolver(propertySources);    customizePropertySources(propertySources);}
复制代码

Environment 的初始化

Environment 基本是伴随了 Spring 容器的整个生命周期,这块也是在 Spring 容器初始化阶段完成 Environment 的初始化。已 main 方法启动 Spring 容器为例:


public static void main(String[] args) {    SpringApplication.run(NacosConsumerApplication.class, args);}
复制代码


一般是通过 SpringApplication.run 方法完成对 Spring 容器的初始化操作,最终会调用 org.springframework.boot.SpringApplication#run(java.lang.String...) :


public ConfigurableApplicationContext run(String... args) {    StopWatch stopWatch = new StopWatch();    stopWatch.start();    ConfigurableApplicationContext context = null;    Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();    configureHeadlessProperty();    SpringApplicationRunListeners listeners = getRunListeners(args);    listeners.starting();    try {            ApplicationArguments applicationArguments = new DefaultApplicationArguments(                            args);            // 预处理 Environment,如果没有的话,会新建一个。            ConfigurableEnvironment environment = prepareEnvironment(listeners,                            applicationArguments);            configureIgnoreBeanInfo(environment);            Banner printedBanner = printBanner(environment);            context = createApplicationContext();            exceptionReporters = getSpringFactoriesInstances(                            SpringBootExceptionReporter.class,                            new Class[] { ConfigurableApplicationContext.class }, context);            prepareContext(context, environment, listeners, applicationArguments,                            printedBanner);            refreshContext(context);            afterRefresh(context, applicationArguments);            stopWatch.stop();            if (this.logStartupInfo) {                    new StartupInfoLogger(this.mainApplicationClass)                                    .logStarted(getApplicationLog(), stopWatch);            }            listeners.started(context);            callRunners(context, applicationArguments);    }    catch (Throwable ex) {            handleRunFailure(context, ex, exceptionReporters, listeners);            throw new IllegalStateException(ex);    }
try { listeners.running(context); } catch (Throwable ex) { handleRunFailure(context, ex, exceptionReporters, null); throw new IllegalStateException(ex); } return context;}
复制代码


org.springframework.boot.SpringApplication#prepareEnvironment


private ConfigurableEnvironment prepareEnvironment(            SpringApplicationRunListeners listeners,            ApplicationArguments applicationArguments) {    // Create and configure the environment    ConfigurableEnvironment environment = getOrCreateEnvironment();    configureEnvironment(environment, applicationArguments.getSourceArgs());    listeners.environmentPrepared(environment);    bindToSpringApplication(environment);    if (this.webApplicationType == WebApplicationType.NONE) {            environment = new EnvironmentConverter(getClassLoader())                            .convertToStandardEnvironmentIfNecessary(environment);    }    ConfigurationPropertySources.attach(environment);    return environment;}
复制代码


其中的 getOrCreateEnvironment 方法则是根据当前的环境,返回对应的 Environment,一般为 org.springframework.core.env.StandardEnvironment;


看到这里,我们大搞有了一个流程:


  • 通过 @PropertySource("classpath:application.properties") 获取 classpath 下的 application.properties 文件,并存储在 PropertySources 中。

  • PropertySources 注入到 Environment 中。

  • @Value 需要注入某个 key 的时候,调用 org.springframework.core.env.PropertySourcesPropertyResolver#getProperty(java.lang.String) 解析 @Value 中的表达式并返回正确的值。

@Value 的实现原理

@org.springframework.beans.factory.annotation.Value

/** * Annotation used at the field or method/constructor parameter level * that indicates a default value expression for the annotated element. * * <p>Typically used for expression-driven or property-driven dependency injection. * Also supported for dynamic resolution of handler method arguments &mdash; for * example, in Spring MVC. * * <p>A common use case is to inject values using * <code>#{systemProperties.myProp}</code> style SpEL (Spring Expression Language) * expressions. Alternatively, values may be injected using * <code>${my.app.myProp}</code> style property placeholders. * * <p>Note that actual processing of the {@code @Value} annotation is performed * by a {@link org.springframework.beans.factory.config.BeanPostProcessor * BeanPostProcessor} which in turn means that you <em>cannot</em> use * {@code @Value} within * {@link org.springframework.beans.factory.config.BeanPostProcessor * BeanPostProcessor} or * {@link org.springframework.beans.factory.config.BeanFactoryPostProcessor BeanFactoryPostProcessor} * types. Please consult the javadoc for the {@link AutowiredAnnotationBeanPostProcessor} * class (which, by default, checks for the presence of this annotation). * * @author Juergen Hoeller * @since 3.0 * @see AutowiredAnnotationBeanPostProcessor * @see Autowired * @see org.springframework.beans.factory.config.BeanExpressionResolver * @see org.springframework.beans.factory.support.AutowireCandidateResolver#getSuggestedValue */@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE})@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface Value {
/** * The actual value expression such as <code>#{systemProperties.myProp}</code> * or property placeholder such as <code>${my.app.myProp}</code>. */ String value();
}
复制代码


通过 @Value 注解的 javadoc 文档,我们大概知道。这个注解的主要实现是在 org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor 类中。

org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor

public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationAwareBeanPostProcessor,    MergedBeanDefinitionPostProcessor, PriorityOrdered, BeanFactoryAware {}
复制代码


而在类的定义上,我们看到他实现了 org.springframework.beans.factory.config.SmartInstantiationAwareBeanPostProcessor 接口,并复写了 org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor#postProcessProperties 方法:


@Overridepublic PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {    InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);    try {            metadata.inject(bean, beanName, pvs);    }    catch (BeanCreationException ex) {            throw ex;    }    catch (Throwable ex) {            throw new BeanCreationException(beanName, "Injection of autowired dependencies failed", ex);    }    return pvs;}
复制代码

org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor#findAutowiringMetadata

private InjectionMetadata findAutowiringMetadata(String beanName, Class<?> clazz, @Nullable PropertyValues pvs) {        // Fall back to class name as cache key, for backwards compatibility with custom callers.        String cacheKey = (StringUtils.hasLength(beanName) ? beanName : clazz.getName());        // Quick check on the concurrent map first, with minimal locking.        InjectionMetadata metadata = this.injectionMetadataCache.get(cacheKey);        if (InjectionMetadata.needsRefresh(metadata, clazz)) {                synchronized (this.injectionMetadataCache) {                        metadata = this.injectionMetadataCache.get(cacheKey);                        if (InjectionMetadata.needsRefresh(metadata, clazz)) {                                if (metadata != null) {                                        metadata.clear(pvs);                                }                                metadata = buildAutowiringMetadata(clazz);                                this.injectionMetadataCache.put(cacheKey, metadata);                        }                }        }        return metadata;}
复制代码


这里大概就是两个步骤:


  • injectionMetadataCache 缓存中获取 InjectionMetadata.

  • 如果缓存中没有,则调用 metadata = buildAutowiringMetadata(clazz) 生成一个 InjectionMetadata 并添加到缓存中。

org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor#buildAutowiringMetadata

private InjectionMetadata buildAutowiringMetadata(final Class<?> clazz) {    // 判断 clazz 是否标注有待处理的注解    if (!AnnotationUtils.isCandidateClass(clazz, this.autowiredAnnotationTypes)) {            return InjectionMetadata.EMPTY;    }
List<InjectionMetadata.InjectedElement> elements = new ArrayList<>(); Class<?> targetClass = clazz;
// 循环对当前类及父类的字段和方法做处理。 do { final List<InjectionMetadata.InjectedElement> currElements = new ArrayList<>();
ReflectionUtils.doWithLocalFields(targetClass, field -> { MergedAnnotation<?> ann = findAutowiredAnnotation(field); if (ann != null) { if (Modifier.isStatic(field.getModifiers())) { if (logger.isInfoEnabled()) { logger.info("Autowired annotation is not supported on static fields: " + field); } return; } boolean required = determineRequiredStatus(ann); currElements.add(new AutowiredFieldElement(field, required)); } });
ReflectionUtils.doWithLocalMethods(targetClass, method -> { Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method); if (!BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod)) { return; } MergedAnnotation<?> ann = findAutowiredAnnotation(bridgedMethod); if (ann != null && method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) { if (Modifier.isStatic(method.getModifiers())) { if (logger.isInfoEnabled()) { logger.info("Autowired annotation is not supported on static methods: " + method); } return; } if (method.getParameterCount() == 0) { if (logger.isInfoEnabled()) { logger.info("Autowired annotation should only be used on methods with parameters: " + method); } } boolean required = determineRequiredStatus(ann); PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz); currElements.add(new AutowiredMethodElement(method, required, pd)); } });
elements.addAll(0, currElements); targetClass = targetClass.getSuperclass(); } while (targetClass != null && targetClass != Object.class);
// 返回最终的 InjectionMetadata return InjectionMetadata.forElements(elements, clazz);}
复制代码


这个方法的大概逻辑:


  • 确认 clazz 对象是否需要处理。

  • 递归解析子类及所有父类的方法和字段。

  • 生成对应的 AutowiredFieldElementAutowiredMethodElement 并添加到 currElements 中,用于生成 InjectionMetadata;


而解析方法及字段的方法:


ReflectionUtils.doWithLocalFields(targetClass, field -> {    // 寻找 autowire 的注解    MergedAnnotation<?> ann = findAutowiredAnnotation(field);    // 如果没有对应注解则不处理    if (ann != null) {            // 如果是静态字段则不处理            if (Modifier.isStatic(field.getModifiers())) {                    if (logger.isInfoEnabled()) {                            logger.info("Autowired annotation is not supported on static fields: " + field);                    }                    return;            }            boolean required = determineRequiredStatus(ann);            currElements.add(new AutowiredFieldElement(field, required));    }});
复制代码

org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor#findAutowiredAnnotation

@Nullableprivate MergedAnnotation<?> findAutowiredAnnotation(AccessibleObject ao) {    MergedAnnotations annotations = MergedAnnotations.from(ao);    for (Class<? extends Annotation> type : this.autowiredAnnotationTypes) {            MergedAnnotation<?> annotation = annotations.get(type);            if (annotation.isPresent()) {                    return annotation;            }    }    return null;}
复制代码


这里就是确认类型中是否有 this.autowiredAnnotationTypes 中的注解

org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor#AutowiredAnnotationBeanPostProcessor

public AutowiredAnnotationBeanPostProcessor() {    this.autowiredAnnotationTypes.add(Autowired.class);    this.autowiredAnnotationTypes.add(Value.class);    try {            this.autowiredAnnotationTypes.add((Class<? extends Annotation>)                            ClassUtils.forName("javax.inject.Inject", AutowiredAnnotationBeanPostProcessor.class.getClassLoader()));            logger.trace("JSR-330 'javax.inject.Inject' annotation found and supported for autowiring");    }    catch (ClassNotFoundException ex) {            // JSR-330 API not available - simply skip.    }}
复制代码


AutowiredAnnotationBeanPostProcessor 的构造方法中可以看到,this.autowiredAnnotationTypes 中添加了三种类型的注解,分别是 @Autowired@Value 以及支持 JSR-330 规范的 @Inject


再回到 org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor#postProcessProperties 方法,这里已经获取到了 InjectionMetadata 了,之后就是对 metadata 进行依赖注入:metadata.inject(bean, beanName, pvs);


最终的调用其实就是通过反射,给对应的字段或者方法赋值。

@Value 注入特性

由此,我们可以看出 @Value 依赖的一些特性:


  • 递归遍历所有父类,父类可通过 @Value 注入。

  • 静态字段无法注入。

  • 支持 @Autowired@Value 以及支持 JSR-330 规范的 @Inject 三种方式注入。

  • @Autowired 优先级最高(this.autowiredAnnotationTypes 先加入的 @Autowired 注解)。

Spring 配置图解

@PropertySource

@Value

总结

本小节主要探讨了 Spring @Value 的配置依赖注入。通过源码的方式了解 Spring 核心的配置实现 PropertySourceEnvironment。 以及 @PropertySource@Value 的原理。


@PropertySource 简单的说,是将注解的 value 内容,转换为 PropertySources 并存储到 Environment 中。


@Value 是在 bean 初始化的阶段,解析到指定的注解 @Autowired@Value 以及支持 JSR-330 规范的 @Inject 后,从 Environment 获取到对应的配置,并通过反射注入到 bean 的方法或属性中。

用户头像

樊江。

关注

还未添加个人签名 2019.12.03 加入

还未添加个人简介

评论

发布
暂无评论
Spring 配置加载