Spring @Value注解
一、概述
在使用spring
框架的项目中,@Value
是经常使用的注解之一。其功能是将与配置文件中的键对应的值分配给其带注解的属性。在日常使用中,我们常用的功能相对简单。
@Value
注解可以用来将外部的值动态注入到Bean
中,在@Value
注解中,可以使用${}
或#{}
。
${}与#{}的区别如下:
(1)@Value("${}")
:可以获取对应属性文件中定义的属性值。
(2)@Value("#{}")
:表示SpEl
表达式通常用来获取bean
的属性,或者调用bean
的某个方法。
二、使用方式
2.1 注入普通属性
@Value
注解可以注入一些字段的普通属性,并且会自动进行类型转换
A Spring BeanPostProcessor uses a ConversionService behind the scene to handle the process for converting the String value in @Value to the target type. If you want to provide conversion support for your own custom type, you can provide your own ConversionService bean instance as the following example shows:
Spring BeanPostProcessor
在后台使用一个ConversionService
来处理将@Value
中的字符串值转换为目标类型的过程。如果你想为自己的自定义类型提供转换支持,你可以提供自己的ConversionService bean
实例,如下例所示
@Configuration
public class AppConfig {
@Bean
public ConversionService conversionService() {
DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService();
conversionService.addConverter(new MyCustomConverter());
return conversionService;
}
}
注入普通属性如下:
@Component
public class ConnectionPool {
@Value("jdbc:mysql://localhost:3306/test")
private String url;
@Value("com.mysql.jdbc.Driver")
private String driveName;
//重写下toString方法
}
2.2 注入配置文件
可以通过@Value("${}")
来注入配置文件里面的信息
@Component
public class ConnectionPool {
@Value("${mysql.pwd}")
private String pwd;
//重写下toString方法
}
2.3 注入表达式并运算
When @Value contains a SpEL expression the value will be dynamically computed at runtime as the following example shows:
当@Value
包含SpEL
表达式时,该值将在运行时动态计算,如下例所示:
@Component
public class MovieRecommender {
private final String catalog;
@Value("#{systemProperties['user.catalog'] + 'Catalog' }")
public MovieRecommender(String catalog) {
this.catalog = catalog;
}
}
2.4 注入复杂结构
SpEL also enables the use of more complex data structures:
SpEL
还支持使用更复杂的数据结构:
@Component
public class MovieRecommender {
private final Map<String, Integer> countOfMoviesPerCatalog;
@Value("#{{'Thriller': 100, 'Comedy': 300}}")
public MovieRecommender(Map<String, Integer> countOfMoviesPerCatalog) {
this.countOfMoviesPerCatalog = countOfMoviesPerCatalog;
}
}
三、使用案例
- 系统中需要连接
db
,连接db
有很多配置信息。 - 系统中需要发送邮件,发送邮件需要配置邮件服务器的信息。
- 还有其他的一些配置信息。
我们可以将这些配置信息统一放在一个配置文件中,上线的时候由运维统一修改。那么系统中如何使用这些配置信息呢,spring
中提供了@Value
注解来解决这个问题。通常我们会将配置信息以key=value
的形式存储在properties
配置文件中。通过@Value("${配置文件中的key}")
来引用指定的key
对应的value
。
3.1 步骤一:使用@PropertySource
注解引入配置文件
将@PropertySource
放在类上面,如下
@PropertySource({"配置文件路径1","配置文件路径2", "..."})
@PropertySource
注解有个value
属性,字符串数组类型,可以用来指定多个配置文件的路径。
如:
@Component
@PropertySource({"classpath:com/test/db.properties"})
public class DbConfig {
}
3.2 步骤二:使用@Value注解引用配置文件的值
通过@Value
引用上面配置文件中的值:
语法
@Value("${配置文件中的key:默认值}")
@Value("${配置文件中的key}")
如:
@Value("${password:123}")
上面如果
password
不存在,将123
作为值
@Value("${password}")
上面如果
password
不存在,值为$
假如配置文件如下
jdbc.url=jdbc:mysql://localhost:3306/test?characterEncoding=UTF-8
jdbc.username=root
jdbc.password=root
使用方式如下:
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
下面来看案例
3.3 案例
1. 配置文件db.properties
jdbc.url=jdbc:mysql://localhost:3306/test?characterEncoding=UTF-8
jdbc.username=root
jdbc.password=root
2. 使用@PropertySource引入上面的配置文件
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.PropertySource;
@Configurable
@ComponentScan
@PropertySource({"classpath:com/test/db.properties"})
public class MainConfig1 {
}
3. 使用@Value来使用配置文件中的信息
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
@Data
public class DbConfig {
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
}
上面重点在于注解
@Value
注解,注意@Value
注解中的
4. 测试用例
import com.test.DbConfig;
import com.test.MainConfig1;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class ValueTest {
@Test
public void test1() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(MainConfig1.class);
context.refresh();
DbConfig dbConfig = context.getBean(DbConfig.class);
System.out.println(dbConfig);
}
}
5. 运行输出
DbConfig{url='jdbc:mysql://localhost:3306/test?characterEncoding=UTF-8',
username='root', password='root'}
上面用起来比较简单,很多用过的人看一眼就懂了,这也是第一个问题,多数人都是ok
的,下面来看@Value
中数据来源除了配置文件的方式,是否还有其他方式。
四、@Value数据来源
通常情况下我们@Value
的数据来源于配置文件,不过,还可以用其他方式,比如我们可以将配置文件的内容放在数据库,这样修改起来更容易一些。
我们需要先了解一下@Value
中数据来源于spring
的什么地方。
spring
中有个类org.springframework.core.env.PropertySource
可以将其理解为一个配置源,里面包含了key->value
的配置信息,可以通过这个类中提供的方法获取key
对应的value
信息
内部有个方法:
public abstract Object getProperty(String name);
通过name
获取对应的配置信息。
系统有个比较重要的接口org.springframework.core.env.Environment
用来表示环境配置信息,这个接口有几个方法比较重要
String resolvePlaceholders(String text);
MutablePropertySources getPropertySources();
resolvePlaceholders
用来解析${text}
的,@Value
注解最后就是调用这个方法来解析的。
getPropertySources
返回MutablePropertySources
对象,来看一下这个类
public class MutablePropertySources implements PropertySources {
private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>();
}
内部包含一个propertySourceList
列表。
spring
容器中会有一个Environment
对象,最后会调用这个对象的resolvePlaceholders
方法解析@Value
。
大家可以捋一下,最终解析@Value
的过程:
1. 将@Value注解的value参数值作为Environment.resolvePlaceholders方法参数进行解析
2. Environment内部会访问MutablePropertySources来解析
3. MutablePropertySources内部有多个PropertySource,此时会遍历PropertySource列表,
调用PropertySource.getProperty方法来解析key对应的值
通过上面过程,如果我们想改变@Value
数据的来源,只需要将配置信息包装为PropertySource
对象,丢到Environment
中的MutablePropertySources
内部就可以了。
下面我们就按照这个思路来一个。
邮件配置信息类,内部使用@Value
注入邮件配置信息
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
/**
* 邮件配置信息
*/
@Component
@Data
public class MailConfig {
@Value("${mail.host}")
private String host;
@Value("${mail.username}")
private String username;
@Value("${mail.password}")
private String password;
}
再来个类DbUtil
,getMailInfoFromDb
方法模拟从db
中获取邮件配置信息,存放在map
中
import java.util.HashMap;
import java.util.Map;
public class DbUtil {
/**
* 模拟从db中获取邮件配置信息
*
* @return
*/
public static Map<String, Object> getMailInfoFromDb() {
Map<String, Object> result = new HashMap<>();
result.put("mail.host", "smtp.qq.com");
result.put("mail.username", "路人");
result.put("mail.password", "123");
return result;
}
}
来个spring
配置类
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan
public class MainConfig2 {
}
下面是重点代码
@Test
public void test2() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
/*下面这段是关键 start*/
//模拟从db中获取配置信息
Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();
//将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)
MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);
//将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高
context.getEnvironment().getPropertySources().addFirst(mailPropertySource);
/*上面这段是关键 end*/
context.register(MainConfig2.class);
context.refresh();
MailConfig mailConfig = context.getBean(MailConfig.class);
System.out.println(mailConfig);
}
注释比较详细,就不详细解释了。
直接运行,看效果
MailConfig{host='smtp.qq.com', username='路人', password='123'}
可以随意修改DbUtil.getMailInfoFromDb
,具体数据是从db
、redis
或者其他文件中来,可以自己选择。
如果我们将配置信息放在db
中,可能我们会通过一个界面来修改这些配置信息,然后保存之后,希望系统在不重启的情况下,让这些值在spring
容器中立即生效。
@Value
动态刷新的问题的问题,springboot
中使用@RefreshScope
实现了。
五、实现@Value动态刷新
5.1 自定义bean作用域
对这块不了解的先看一下这篇文章:Spring Bean作用域与并发安全
bean
作用域中有个地方没有讲,来看一下@Scope
这个注解的源码,有个参数是:
ScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT;
这个参数的值是个ScopedProxyMode
类型的枚举,值有下面4中
public enum ScopedProxyMode {
DEFAULT,
NO,
INTERFACES,
TARGET_CLASS;
}
前面3个,不讲了,直接讲最后一个值是干什么的。
当@Scope
中proxyMode
为TARGET_CLASS
的时候,会给当前创建的bean
通过cglib
生成一个代理对象,通过这个代理对象来访问目标bean
对象。
理解起来比较晦涩,还是来看代码吧,容易理解一些,来个自定义的Scope
案例。
自定义一个bean
作用域的注解
import org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.ScopedProxyMode;
import java.lang.annotation.*;
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Scope(BeanMyScope.SCOPE_MY) //@1
public @interface MyScope {
/**
* @see Scope#proxyMode()
*/
ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;//@2
}
@1:使用了
@Scope
注解,value
为引用了一个常量,值为my
,一会下面可以看到。
@2:注意这个地方,参数名称也是proxyMode
,类型也是ScopedProxyMode
,而@Scope
注解中有个和这个同样类型的参数,spring
容器解析的时候,会将这个参数的值赋给@MyScope
注解上面的@Scope
注解的proxyMode
参数,所以此处我们设置proxyMode
值,最后的效果就是直接改变了@Scope
中proxyMode
参数的值。此处默认值取的是ScopedProxyMode.TARGET_CLASS
@MyScope注解对应的Scope实现如下
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.config.Scope;
import org.springframework.lang.Nullable;
/**
* @see MyScope 作用域的实现
*/
public class BeanMyScope implements Scope {
public static final String SCOPE_MY = "my"; //@1
@Override
public Object get(String name, ObjectFactory<?> objectFactory) {
System.out.println("BeanMyScope >>>>>>>>> get:" + name); //@2
return objectFactory.getObject(); //@3
}
@Nullable
@Override
public Object remove(String name) {
return null;
}
@Override
public void registerDestructionCallback(String name, Runnable callback) {
}
@Nullable
@Override
public Object resolveContextualObject(String key) {
return null;
}
@Nullable
@Override
public String getConversationId() {
return null;
}
}
@1:定义了一个常量,作为作用域的值
@2:这个get
方法是关键,自定义作用域会自动调用这个get
方法来创建bean
对象,这个地方输出了一行日志,为了一会方便看效果
@3:通过objectFactory.getObject()
获取bean
实例返回。
下面来创建个类,作用域为上面自定义的作用域
import org.springframework.stereotype.Component;
import java.util.UUID;
@Component
@MyScope //@1
@Data
public class User {
private String username;
public User() {
System.out.println("---------创建User对象" + this); //@2
this.username = UUID.randomUUID().toString(); //@3
}
}
@1:使用了自定义的作用域
@MyScope
@2:构造函数中输出一行日志
@3:给username
赋值,通过uuid
随机生成了一个
spring
配置类,加载上面@Compontent
标注的组件
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@ComponentScan
@Configuration
public class MainConfig3 {
}
下面重点来了,测试用例
@Test
public void test3() throws InterruptedException {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
//将自定义作用域注册到spring容器中
context.getBeanFactory().registerScope(BeanMyScope.SCOPE_MY, new BeanMyScope());//@1
context.register(MainConfig3.class);
context.refresh();
System.out.println("从容器中获取User对象");
User user = context.getBean(User.class); //@2
System.out.println("user对象的class为:" + user.getClass()); //@3
System.out.println("多次调用user的getUsername感受一下效果\n");
for (int i = 1; i <= 3; i++) {
System.out.println(String.format("********\n第%d次开始调用getUsername", i));
System.out.println(user.getUsername());
System.out.println(String.format("第%d次调用getUsername结束\n********\n", i));
}
}
@1:将自定义作用域注册到
spring
容器中
@2:从容器中获取User
对应的bean
@3:输出这个bean
对应的class
,一会认真看一下,这个类型是不是User
类型的
代码后面又搞了3次循环,调用user
的getUsername
方法,并且方法前后分别输出了一行日志。
见证奇迹的时候到了,运行输出
从容器中获取User对象
user对象的class为:class com.test.User$$EnhancerBySpringCGLIB$$80233127
多次调用user的getUsername感受一下效果
********
第1次开始调用getUsername
BeanMyScope >>>>>>>>> get:scopedTarget.user
---------创建User对象com.test.User@6a370f4
7b41aa80-7569-4072-9d40-ec9bfb92f438
第1次调用getUsername结束
********
********
第2次开始调用getUsername
BeanMyScope >>>>>>>>> get:scopedTarget.user
---------创建User对象com.test.User@1613674b
01d67154-95f6-44bb-93ab-05a34abdf51f
第2次调用getUsername结束
********
********
第3次开始调用getUsername
BeanMyScope >>>>>>>>> get:scopedTarget.user
---------创建User对象com.test.User@27ff5d15
76d0e86f-8331-4303-aac7-4acce0b258b8
第3次调用getUsername结束
********
从输出的前2行可以看出:
- 调用
context.getBean(User.class)
从容器中获取bean
的时候,此时并没有调用User
的构造函数去创建User
对象 - 第二行输出的类型可以看出,
getBean
返回的user
对象是一个cglib
代理对象。
后面的日志输出可以看出,每次调用user.getUsername
方法的时候,内部自动调用了BeanMyScope#get()
方法和User
的构造函数。
通过上面的案例可以看出,当自定义的Scope
中proxyMode=ScopedProxyMode.TARGET_CLASS
的时候,会给这个bean
创建一个代理对象,调用代理对象的任何方法,都会调用这个自定义的作用域实现类(上面的BeanMyScope
)中get
方法来重新来获取这个bean
对象。
5.2 动态刷新@Value具体实现
那么我们可以利用上面讲解的这种特性来实现@Value
的动态刷新,可以实现一个自定义的Scope
,这个自定义的Scope
支持@Value
注解自动刷新,需要使用@Value
注解自动刷新的类上面可以标注这个自定义的注解,当配置修改的时候,调用这些bean
的任意方法的时候,就让spring
重启初始化一下这个bean
,这个思路就可以实现了,下面我们来写代码。
5.2.1 先来自定义一个Scope:RefreshScope
import org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.ScopedProxyMode;
import java.lang.annotation.*;
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Scope(BeanRefreshScope.SCOPE_REFRESH)
@Documented
public @interface RefreshScope {
ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS; //@1
}
要求标注
@RefreshScope
注解的类支持动态刷新@Value
的配置
@1:这个地方是个关键,使用的是ScopedProxyMode.TARGET_CLASS
5.2.2 这个自定义Scope对应的解析类
下面类中有几个无关的方法去掉了,可以忽略
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.config.Scope;
import org.springframework.lang.Nullable;
import java.util.concurrent.ConcurrentHashMap;
public class BeanRefreshScope implements Scope {
public static final String SCOPE_REFRESH = "refresh";
private static final BeanRefreshScope INSTANCE = new BeanRefreshScope();
//来个map用来缓存bean
private ConcurrentHashMap<String, Object> beanMap = new ConcurrentHashMap<>(); //@1
private BeanRefreshScope() {
}
public static BeanRefreshScope getInstance() {
return INSTANCE;
}
/**
* 清理当前
*/
public static void clean() {
INSTANCE.beanMap.clear();
}
@Override
public Object get(String name, ObjectFactory<?> objectFactory) {
Object bean = beanMap.get(name);
if (bean == null) {
bean = objectFactory.getObject();
beanMap.put(name, bean);
}
return bean;
}
}
上面的
get
方法会先从beanMap
中获取,获取不到会调用objectFactory
的getObject
让spring
创建bean
的实例,然后丢到beanMap
中
上面的clean
方法用来清理beanMap
中当前已缓存的所有bean
5.2.3 邮件配置类,使用@Value注解注入配置,这个bean作用域为自定义的@RefreshScope
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
/**
* 邮件配置信息
*/
@Component
@RefreshScope //@1
@Data
public class MailConfig {
@Value("${mail.username}") //@2
private String username;
@Override
public String toString() {
return "MailConfig{" +
"username='" + username + '\'' +
'}';
}
}
@1:使用了自定义的作用域@RefreshScope
@2:通过@Value注入mail.username对一个的值
重写了toString方法,一会测试时候可以看效果。
5.2.4 普通的bean,内部会注入MailConfig
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class MailService {
@Autowired
private MailConfig mailConfig;
}
代码比较简单,重写了toString
方法,一会测试时候可以看效果。
5.2.5 用来从db中获取邮件配置信息
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
public class DbUtil {
/**
* 模拟从db中获取邮件配置信息
*
* @return
*/
public static Map<String, Object> getMailInfoFromDb() {
Map<String, Object> result = new HashMap<>();
result.put("mail.username", UUID.randomUUID().toString());
return result;
}
}
5.2.6 spring配置类,扫描加载上面的组件
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan
public class MainConfig4 {
}
5.2.7 工具类
内部有2个方法,如下:
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.core.env.MapPropertySource;
import java.util.Map;
public class RefreshConfigUtil {
/**
* 模拟改变数据库中都配置信息
*/
public static void updateDbConfig(AbstractApplicationContext context) {
//更新context中的mailPropertySource配置信息
refreshMailPropertySource(context);
//清空BeanRefreshScope中所有bean的缓存
BeanRefreshScope.getInstance().clean();
}
public static void refreshMailPropertySource(AbstractApplicationContext context) {
Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();
//将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)
MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);
context.getEnvironment().getPropertySources().addFirst(mailPropertySource);
}
}
updateDbConfig
方法模拟修改db
中配置的时候需要调用的方法,方法中2行代码,第一行代码调用refreshMailPropertySource
方法修改容器中邮件的配置信息
BeanRefreshScope.getInstance().clean()用来清除BeanRefreshScope
中所有已经缓存的bean
,那么调用bean
的任意方法的时候,会重新出发spring
容器来创建bean
,spring
容器重新创建bean
的时候,会重新解析@Value
的信息,此时容器中的邮件配置信息是新的,所以@Value
注入的信息也是新的。
测试用例
@Test
public void test4() throws InterruptedException {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.getBeanFactory().registerScope(BeanRefreshScope.SCOPE_REFRESH,
BeanRefreshScope.getInstance());
context.register(MainConfig4.class);
//刷新mail的配置到Environment
RefreshConfigUtil.refreshMailPropertySource(context);
context.refresh();
MailService mailService = context.getBean(MailService.class);
System.out.println("配置未更新的情况下,输出3次");
for (int i = 0; i < 3; i++) { //@1
System.out.println(mailService);
TimeUnit.MILLISECONDS.sleep(200);
}
System.out.println("模拟3次更新配置效果");
for (int i = 0; i < 3; i++) { //@2
RefreshConfigUtil.updateDbConfig(context); //@3
System.out.println(mailService);
TimeUnit.MILLISECONDS.sleep(200);
}
}
@1:循环3次,输出
mailService
的信息
@2:循环3次,内部先通过@3来模拟更新db
中配置信息,然后在输出mailService
信息
5.2.8 见证奇迹的时刻,来看效果
配置未更新的情况下,输出3次
MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}
MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}
MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}
模拟3次更新配置效果
MailService{mailConfig=MailConfig{username='6bab8cea-9f4f-497d-a23a-92f15d0d6e34'}}
MailService{mailConfig=MailConfig{username='581bf395-f6b8-4b87-84e6-83d3c7342ca2'}}
MailService{mailConfig=MailConfig{username='db337f54-20b0-4726-9e55-328530af6999'}}
上面
MailService
输出了6次,前3次username
的值都是一样的,后面3次username
的值不一样了,说明修改配置起效了。
六、总结
动态@Value
实现的关键是@Scope
中proxyMode
参数,值为ScopedProxyMode.DEFAULT
,会生成一个代理,通过这个代理来实现@Value
动态刷新的效果,这个地方是关键。
七、拓展
7.1 静态方法获取@value值
@Value("${value}")
public static String value;
value
获取到的值为null
,因为静态变量加载要早于Spring
初始化。
类加载顺序:静态字段或静态语句块 -> 变量或者语句块被加载 -> 构造方法被加载
解决:
方法一: 可以使用@PostConstruct
注解在Spring
初始化之后再给静态遍历赋值
Constructor(构造方法) -> @Autowired(依赖注入) -> @PostConstruct(注释的方法)
@Component // component注解,让当前类变成Spring的bean
public class Config {
@Value("${value}")
private String value;
private static String valueStatic;
/**
* 给静态变量赋值
*/
@PostConstruct
private void init() {
valueStatic = value;
}
// 即可使用valueStatic
public static String method(){
return valueStatic;
}
}
方法二(推荐): set()方法设置
@Component
public class Config {
public static String valueStatic;
@Value("${value}")
public void setValueStatic(String value) {
valueStatic = value;
}
}
需要在类上加入@Component注解,方法名setValueStatic和参数名value可以任意命名。