《深入浅出Spring》@PropertySource、@Value注解及动态刷新实现
@Value的用法
系统中需要连接db,连接db有很多配置信息。
系统中需要发送邮件,发送邮件需要配置邮件服务器的信息。
还有其他的一些配置信息。
我们可以将这些配置信息统一放在一个配置文件中,上线的时候由运维统一修改。
那么系统中如何使用这些配置信息呢,spring中提供了@Value注解来解决这个问题。
通常我们会将配置信息以key=value的形式存储在properties配置文件中。
通过@Value(“${配置文件中的key}”)来引用指定的key对应的value。
@Value使用步骤
步骤一:使用@PropertySource注解引入配置文件
将@PropertySource放在类上面,如下
@PropertySource({"配置文件路径1","配置文件路径2"...})
@PropertySource注解有个value属性,字符串数组类型,可以用来指定多个配置文件的路径。如:@Component
@PropertySource({"classpath:com/yuan11/db.properties"})
public class DbConfig {}
步骤二:使用@Value注解引用配置文件的值
通过@Value引用上面配置文件中的值:
语法
@Value("${配置文件中的key:默认值}")
@Value("${配置文件中的key}")
如:@Value("${password:123}")
上面如果password不存在,将123作为值@Value("${password}")
上面如果password不存在,值为${password}
假如配置文件如下:
jdbc.url=jdbc:mysql://localhost:3306/mysql?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;
@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的过程:
- 将@Value注解的value参数值作为Environment.resolvePlaceholders方法参数进行解析
- Environment内部会访问MutablePropertySources来解析
- MutablePropertySources内部有多个PropertySource,此时会遍历PropertySource列表,调用PropertySource.getProperty方法来解析key对应的值
通过上面过程,如果我们想改变@Value数据的来源,只需要将配置信息包装为PropertySource对象,丢到Environment中的MutablePropertySources内部就可以了。
下面我们就按照这个思路来一个。
/*** 邮件配置信息*/
@Component
public class MailConfig {@Value("${mail.host}")private String host;@Value("${mail.username}")private String username;@Value("${mail.password}")private String password;public String getHost() {return host;}public void setHost(String host) {this.host = host;}public String getUsername() {return username;}public void setUsername(String username) {this.username = username;}public String getPassword() {return password;}public void setPassword(String password) {this.password = password;}@Overridepublic String toString() {return "MailConfig{" +"host='" + host + '\'' +", username='" + username + '\'' +", password='" + password + '\'' +'}';}
}
再来个类DbUtil,getMailInfoFromDb方法模拟从db中获取邮件配置信息,存放在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", "test");result.put("mail.password", "123");return result;}
}
spring配置类
@Configuration
@ComponentScan
public class MainConfig2 {}
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=‘test’, password=‘123’}
如果我们将配置信息放在db中,可能我们会通过一个界面来修改这些配置信息,然后保存之后,希望系统在不重启的情况下,让这些值在spring容器中立即生效。
@Value动态刷新的问题的问题,springboot中使用@RefreshScope实现了。
实现@Value动态刷新
这块需要先讲一个知识点,用到的不是太多,所以很多人估计不太了解,但是非常重要的一个点,我们来看一下。这个知识点是自定义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对象。
动态刷新@Value
那么我们可以利用上面讲解的这种特性来实现@Value的动态刷新,可以实现一个自定义的Scope,这个自定义的Scope支持@Value注解自动刷新,需要使用@Value注解自动刷新的类上面可以标注这个自定义的注解,当配置修改的时候,调用这些bean的任意方法的时候,就让spring重启初始化一下这个bean
自定义一个Scope:RefreshScope
@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
自定义Scope对应的解析类
public class BeanRefreshScope implements Scope {public static final String SCOPE_REFRESH = "refresh";private static final BeanRefreshScope INSTANCE = new BeanRefreshScope();//来个map用来缓存beanprivate ConcurrentHashMap<String, Object> beanMap = new ConcurrentHashMap<>(); //@1private BeanRefreshScope() {}public static BeanRefreshScope getInstance() {return INSTANCE;}/*** 清理当前*/public static void clean() {INSTANCE.beanMap.clear();}@Overridepublic 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
使用@Value注解注入配置,这个bean作用域为自定义的@RefreshScope
/*** 邮件配置信息*/
@Component
@RefreshScope //@1
public class MailConfig {@Value("${mail.username}") //@2private String username;public String getUsername() {return username;}public void setUsername(String username) {this.username = username;}@Overridepublic String toString() {return "MailConfig{" +"username='" + username + '\'' +'}';}
@1:使用了自定义的作用域@RefreshScope
@2:通过@Value注入mail.username对一个的值
重写了toString方法,一会测试时候可以看效果。
内部会注入MailConfig
@Component
public class MailService {@Autowiredprivate MailConfig mailConfig;@Overridepublic String toString() {return "MailService{" +"mailConfig=" + mailConfig +'}';}
}
模拟从db中获取邮件配置信息
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;}
}
spring配置类,扫描加载上面的组件
@Configuration
@ComponentScan
public class MainConfig4 {}
工具类
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的配置到EnvironmentRefreshConfigUtil.refreshMailPropertySource(context);context.refresh();MailService mailService = context.getBean(MailService.class);System.out.println("配置未更新的情况下,输出3次");for (int i = 0; i < 3; i++) { //@1System.out.println(mailService);TimeUnit.MILLISECONDS.sleep(200);}System.out.println("模拟3次更新配置效果");for (int i = 0; i < 3; i++) { //@2RefreshConfigUtil.updateDbConfig(context); //@3System.out.println(mailService);TimeUnit.MILLISECONDS.sleep(200);}
}
@1:循环3次,输出mailService的信息
@2:循环3次,内部先通过@3来模拟更新db中配置信息,然后在输出mailService信息
RESULT:
配置未更新的情况下,输出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’}}
springboot中的@RefreshScope注解 可以直接使用
《深入浅出Spring》@PropertySource、@Value注解及动态刷新实现相关推荐
- Spring 3.0 注解注入详解
Spring 3.0 注解注入详解 2011-04-15 09:44 17ZOUGUO ITEYE博客 我要评论(1) 字号:T | T AD: 一.各种注解方式 1.@Autowired注解(不推荐 ...
- Spring-学习笔记06【Spring的新注解】
Java后端 学习路线 笔记汇总表[黑马程序员] Spring-学习笔记01[Spring框架简介][day01] Spring-学习笔记02[程序间耦合] Spring-学习笔记03[Spring的 ...
- @Value@PropertySource@ConfigurationProperties注解使用
使用配置文件注入属性 Spring Boot 默认的配置文件src/main/java/resources/application.properties或者src/main/java/resource ...
- Spring系列(九):Spring属性赋值注解@Value 用法介绍
今天给大家分享Spring属性赋值注解@Value 用法,希望对大家能有所帮助! 1.@Value注解的作用 @Value注解的作用主要可以给属性直接赋值.也可以读取配置文件中的值给属性赋值 2.@V ...
- 近100个Spring/SpringBoot常用注解汇总!
作者 | Guide 来源 | JavaGuide(微信公众号) 毫不夸张地说,这篇文章介绍的 Spring/SpringBoot 常用注解基本已经涵盖你工作中遇到的大部分常用的场景.对于每一个注解我 ...
- Spring 4 xml 注解配置谅解 spring
2019独角兽企业重金招聘Python工程师标准>>> Spring 4 xml 注解配置谅解 博客分类: spring <Spring in Action>4th Ed ...
- 朱晔和你聊Spring系列S1E9:聊聊Spring的那些注解
本文我们来梳理一下Spring的那些注解,如下图所示,大概从几方面列出了Spring的一些注解: 如果此图看不清楚也没事,请运行下面的代码输出所有的结果. Spring目前的趋势是使用注解结合Java ...
- 接近8000字的Spring/SpringBoot常用注解总结!安排!
文章目录 0.前言 1. `@SpringBootApplication` 2. Spring Bean 相关 2.1. `@Autowired` 2.2. `Component`,`@Reposit ...
- Spring中常用注解及作用
1.@Component 类注解,设置该类为spring管理的bean,属性可以定义bean的id,添加在类定义的上方,@Component注解不可以添加在接口上,因为接口无法创建对象. eg: 步骤 ...
- shiro和Spring整合使用注解时没有执行realm的doGetAuthorizationInfo回调方法的解决
shiro和Spring整合使用注解时没有执行realm的doGetAuthorizationInfo回调方法的解决 from :http://blog.csdn.net/babys/article/ ...
最新文章
- Go 学习笔记(23)— 并发(02)[竞争,锁资源,原子函数sync/atomic、互斥锁sync.Mutex]
- java的HttpClient如何去支持无证书访问https
- CMPP3.0 长短信实现方案
- springboot---【jdbc】数据库连接池
- Another way to define Angular controller
- JAVA_Thread_deadlock
- matlab进行ai研究,matlab【人工智能项目】—ELM地质分类研究
- PHP下载文件(隐藏真实的下载地址)
- Win10安装MySql步骤
- 力特usb转232驱动程序下载_电脑USB接口、U盘接口不能使用的原因及解决方法
- mysql取分组最新的一条_基于mysql实现group by取各分组最新一条数据
- 高等数学张宇18讲 第五讲 中值定理
- 常见的几种IDEA使用技巧
- 求2n个数中最大值和最小值的最少比较次数
- Internal Error (Network has dynamic or shape inputs, but no optimization profile has been defined.)
- Flickr 的最受欢迎图片
- ElasticSearch英文基本查询
- 商丘工学院c语言试卷,商丘工学院New Radio82期:青春
- 『湛蓝美声』 Mario Frangoulis
- 个人向的前端的坑坑洼洼的记录(1)