Spring 配置

242 阅读3分钟

前言

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

如何使用 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
@Nullable
public 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)
@Documented
public @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 方法:

@Override
public 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

@Nullable
private 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

image.png

@Value

image.png

总结

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

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

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