在使用Java config配置Bean以前,即xml配置时代,bean的属性值可以通过 <property name=“xxx” value=""/>的形式。 value既可以使用固定值,也可以使用占位符的形式${xxx}。 占位符中的配置的值由一个特殊的bean的解析(PropertyPlaceholderConfigurer)。
而到了java config时代,给bean属性配置值,可以使用的方式是@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 — for example, <code>#{systemProperties.myProp}</code>.
*/
String value();
}
从源码注释提取一下关键信息:
@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;
示例:
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;
}
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])
先通过@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
* — for example, {@code "classpath:/com/myco/app.properties"}
* or {@code "file:/path/to/file.xml"}.
* <p>Resource location wildcards (e.g. **/*.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;
}
原始的源码注释很长,这里没有贴出来,感兴趣的自己去看一下,总结一下有用信息如下:
@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;
}
}
@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异常。
同一个属性项,后注册的会覆盖前面的,优先级更高。
以下是一个简单示例:
weather=sunning
wind=5
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)
${}用于从属性配置文件中获取属性值。
#{}里面可以写SpEL表达式, 如#{T(java.lang.Math.random}, #{‘Hello world’.bytes.length}。 类似于OGNL。
两者可以混用,如:#{${…}.split(’,’)}
但需要注意:必须#{}在外面,${}在里面, 因为${}会优先解析
本节学习了如何使用@Value加载属性值,属性值可以来源于外部资源文件,也可以来自内部的属性,spEL。
学习了如何使用@PropertySource加载外部配置文件。至于背后的原理部分如Environment, PropertySourcesPlaceholderConfigurer等后面会开专门的章节分析。