当前位置: 首页 > 工具软件 > Spring-Wind > 使用案例 >

11. spring-容器: @Value, @PropertySource详解

田焕
2023-12-01

在使用Java config配置Bean以前,即xml配置时代,bean的属性值可以通过 <property name=“xxx” value=""/>的形式。 value既可以使用固定值,也可以使用占位符的形式${xxx}。 占位符中的配置的值由一个特殊的bean的解析(PropertyPlaceholderConfigurer)。

而到了java config时代,给bean属性配置值,可以使用的方式是@Value,本章就来重点讲下@Value如何使用。

@Value定义

先看源码:

package org.springframework.beans.factory.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * Annotation at the field or method/constructor parameter level
 * that indicates a default value expression for the affected argument.
 *
 * <p>Typically used for expression-driven dependency injection. Also supported
 * for dynamic resolution of handler method parameters, e.g. in Spring MVC.
 *
 * <p>A common use case is to assign default field values using
 * <code>#{systemProperties.myProp}</code> style expressions.
 *
 * <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 &mdash; for example, <code>#{systemProperties.myProp}</code>.
	 */
	String value();

}

从源码注释提取一下关键信息:

  • @Value可注解在Field, 方法,方法参数,或注解。通常用于表达式驱动的依赖注入,也支持动态方法参数的解析。
  • 一个通常的用法是SpEL, 如:#{systemProperties.myProp}。 (SpEL下一章单独讲解)。
  • 注意:事实上@Value注解是通过BeanPostProcessor实现的,所以BeanPostProcessor本身不能注入@Value的Field。
  • 确切的说@Value是通过AutowiredAnnotationBeanPostProcessor实现,即它既处理@Autowired注解,也处理@Value。

@Value注解的用法

  1. 不通过配置文件将外部的值动态注入到Bean中
  • 注入普通字符串
@Value("panda")
private String name;
  • 注入操作系统属性
@Value("#{systemProperties['os.name']}")
private String os;
  • 注入表达式结果
@Value("#{T(java.lang.Math).random() * 100.0}")
private double randomNumber;
  • 注入其他Bean的属性
@Value("#{animal.name}")
private String animalName;
  • 注入文件资源
@Value("classpath:application.properties")
private Resource classpathResource;
  • 注入URL资源
@Value("https://baidu.com")
private Resource urlResource;

示例:

  • 自定义Bean class, 演示了各种通过@Value的注入场景
package win.elegentjs.spring.ioc.value;

import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.Resource;

/**
 * 自定义Bean class, 演示了各种通过@Value的注入场景
 * 注:不使用外部配置属性
 */
@Data
public class CustomerValue {

    @Value("panda")
    private String name;

    @Value("#{systemProperties['os.name']}")
    private String os;

    @Value("#{T(java.lang.Math).random() * 100.0}")
    private double randomNumber;

    @Value("#{animal.name}")
    private String animalName;

    @Value("classpath:application.properties")
    private Resource classpathResource;

    @Value("https://baidu.com")
    private Resource urlResource;
}

  • java config 配置类,定义了两个bean,animal会被customerValue依赖
package win.elegentjs.spring.ioc.value;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * java config 配置类,定义了两个bean,animal会被customerValue依赖
 */
@Configuration
public class CustomerValueConfig {

    @Bean
   public CustomerValue customerValue() {
       return new CustomerValue();
   }

   @Bean
   public Animal animal() {
        Animal animal = new Animal();
        animal.setName("dog");
        return animal;
   }
}

  • 测试类,演示测试结果
package win.elegentjs.spring.ioc.value;

import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

@Slf4j
public class CustomerValueSample {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(CustomerValueConfig.class);

        CustomerValue customerValue = context.getBean(CustomerValue.class);

        log.info("==> customerValue: {}", customerValue);

    }
}


// result:
2021-05-31 19:36:06.919 [main] INFO  win.elegentjs.spring.ioc.value.CustomerValueSample-==> customerValue: CustomerValue(name=panda, os=Mac OS X, randomNumber=54.4727006010814, animalName=dog, classpathResource=class path resource [application.properties], urlResource=URL [https://baidu.com])


  1. 通过配置文件注入属性

先通过@PropertySource指定属性配置文件源,@PropertySource源码如下:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Repeatable(PropertySources.class)
public @interface PropertySource {

	/**
	 * Indicate the name of this property source. If omitted, a name will
	 * be generated based on the description of the underlying resource.
	 * @see org.springframework.core.env.PropertySource#getName()
	 * @see org.springframework.core.io.Resource#getDescription()
	 */
	String name() default "";

	/**
	 * Indicate the resource location(s) of the properties file to be loaded.
	 * <p>Both traditional and XML-based properties file formats are supported
	 * &mdash; for example, {@code "classpath:/com/myco/app.properties"}
	 * or {@code "file:/path/to/file.xml"}.
	 * <p>Resource location wildcards (e.g. *&#42;/*.properties) are not permitted;
	 * each location must evaluate to exactly one {@code .properties} resource.
	 * <p>${...} placeholders will be resolved against any/all property sources already
	 * registered with the {@code Environment}. See {@linkplain PropertySource above}
	 * for examples.
	 * <p>Each location will be added to the enclosing {@code Environment} as its own
	 * property source, and in the order declared.
	 */
	String[] value();

	/**
	 * Indicate if failure to find the a {@link #value() property resource} should be
	 * ignored.
	 * <p>{@code true} is appropriate if the properties file is completely optional.
	 * Default is {@code false}.
	 * @since 4.0
	 */
	boolean ignoreResourceNotFound() default false;

	/**
	 * A specific character encoding for the given resources, e.g. "UTF-8".
	 * @since 4.3
	 */
	String encoding() default "";

	/**
	 * Specify a custom {@link PropertySourceFactory}, if any.
	 * <p>By default, a default factory for standard resource files will be used.
	 * @since 4.3
	 * @see org.springframework.core.io.support.DefaultPropertySourceFactory
	 * @see org.springframework.core.io.support.ResourcePropertySource
	 */
	Class<? extends PropertySourceFactory> factory() default PropertySourceFactory.class;

}

原始的源码注释很长,这里没有贴出来,感兴趣的自己去看一下,总结一下有用信息如下:

  • @PropertySource是一个实用注解用于添加一个PropertySource至Spring的Environment对象:即通过@PropertySource将properties配置文件中的值存储到spring的Environment对象中。
  • 典型用法1:使用Environment对象取属性值
 @Configuration
 @PropertySource("classpath:/com/myco/app.properties")
 public class AppConfig {

     @Autowired
     private Environment env;

     @Bean
     public TestBean testBean() {
         TestBean testBean = new TestBean();
         testBean.setName(env.getProperty("testbean.name"));
         return testBean;
     }
 }
  • 典型用法2:使用@Value("${…}")解析
@Configuration
@PropertySource("classpath:/com/myco/app.properties")
public class AppConfig {

    @Value("${testbean.name}")
    private String beanName;

   
}
  • 在xml中能解析${…}占位符是因为配置了<context:property-placeholder>, 而@Configuration是因为配置了PropertySourcesPlaceholderConfigurer,一般情况下不需要自己配置,除非默认的规则不满足,需要自己定制。

  • 可配置缺省值。 如 “${testbean.name:zhangsan}”。 如果未设置缺省值,取不到的情况下会抛出IllegalArgumentException异常。

  • 同一个属性项,后注册的会覆盖前面的,优先级更高。

以下是一个简单示例:

  • 在classpath下新增一个配置文件:application.properties
weather=sunning
wind=5
  • propertySource java config配置示例
package win.elegentjs.spring.ioc.propertysource;

import lombok.ToString;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;

@Configuration
@PropertySource({"classpath:application.properties"})
@ToString
public class MyPropertySourceConfig {

    @Value("${weather}")
    private String weather;

    @Value("${wind}")
    private Integer wind;
}

  • 测试,查看结果
package win.elegentjs.spring.ioc.propertysource;

import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

@Slf4j
public class MyPropertySourceSample {

    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(MyPropertySourceConfig.class);
        MyPropertySourceConfig config = context.getBean(MyPropertySourceConfig.class);

        log.info("==> config: {}", config);
    }
}

// result:
2021-05-31 19:38:35.508 [main] INFO  w.e.s.ioc.propertysource.MyPropertySourceSample-==> config: MyPropertySourceConfig(weather=sunning, wind=5)

#{…} vs ${…}

${}用于从属性配置文件中获取属性值。
#{}里面可以写SpEL表达式, 如#{T(java.lang.Math.random}, #{‘Hello world’.bytes.length}。 类似于OGNL。

两者可以混用,如:#{${…}.split(’,’)}
但需要注意:必须#{}在外面,${}在里面, 因为${}会优先解析

小结

本节学习了如何使用@Value加载属性值,属性值可以来源于外部资源文件,也可以来自内部的属性,spEL。
学习了如何使用@PropertySource加载外部配置文件。至于背后的原理部分如Environment, PropertySourcesPlaceholderConfigurer等后面会开专门的章节分析。

 类似资料: