@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的过程:

  1. 将@Value注解的value参数值作为Environment.resolvePlaceholders方法参数进行解析
  2. Environment内部会访问MutablePropertySources来解析
  3. 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注解及动态刷新实现相关推荐

  1. Spring 3.0 注解注入详解

    Spring 3.0 注解注入详解 2011-04-15 09:44 17ZOUGUO ITEYE博客 我要评论(1) 字号:T | T AD: 一.各种注解方式 1.@Autowired注解(不推荐 ...

  2. Spring-学习笔记06【Spring的新注解】

    Java后端 学习路线 笔记汇总表[黑马程序员] Spring-学习笔记01[Spring框架简介][day01] Spring-学习笔记02[程序间耦合] Spring-学习笔记03[Spring的 ...

  3. @Value@PropertySource@ConfigurationProperties注解使用

    使用配置文件注入属性 Spring Boot 默认的配置文件src/main/java/resources/application.properties或者src/main/java/resource ...

  4. Spring系列(九):Spring属性赋值注解@Value 用法介绍

    今天给大家分享Spring属性赋值注解@Value 用法,希望对大家能有所帮助! 1.@Value注解的作用 @Value注解的作用主要可以给属性直接赋值.也可以读取配置文件中的值给属性赋值 2.@V ...

  5. 近100个Spring/SpringBoot常用注解汇总!

    作者 | Guide 来源 | JavaGuide(微信公众号) 毫不夸张地说,这篇文章介绍的 Spring/SpringBoot 常用注解基本已经涵盖你工作中遇到的大部分常用的场景.对于每一个注解我 ...

  6. Spring 4 xml 注解配置谅解 spring

    2019独角兽企业重金招聘Python工程师标准>>> Spring 4 xml 注解配置谅解 博客分类: spring <Spring in Action>4th Ed ...

  7. 朱晔和你聊Spring系列S1E9:聊聊Spring的那些注解

    本文我们来梳理一下Spring的那些注解,如下图所示,大概从几方面列出了Spring的一些注解: 如果此图看不清楚也没事,请运行下面的代码输出所有的结果. Spring目前的趋势是使用注解结合Java ...

  8. 接近8000字的Spring/SpringBoot常用注解总结!安排!

    文章目录 0.前言 1. `@SpringBootApplication` 2. Spring Bean 相关 2.1. `@Autowired` 2.2. `Component`,`@Reposit ...

  9. Spring中常用注解及作用

    1.@Component 类注解,设置该类为spring管理的bean,属性可以定义bean的id,添加在类定义的上方,@Component注解不可以添加在接口上,因为接口无法创建对象. eg: 步骤 ...

  10. shiro和Spring整合使用注解时没有执行realm的doGetAuthorizationInfo回调方法的解决

    shiro和Spring整合使用注解时没有执行realm的doGetAuthorizationInfo回调方法的解决 from :http://blog.csdn.net/babys/article/ ...

最新文章

  1. Go 学习笔记(23)— 并发(02)[竞争,锁资源,原子函数sync/atomic、互斥锁sync.Mutex]
  2. java的HttpClient如何去支持无证书访问https
  3. CMPP3.0 长短信实现方案
  4. springboot---【jdbc】数据库连接池
  5. Another way to define Angular controller
  6. JAVA_Thread_deadlock
  7. matlab进行ai研究,matlab【人工智能项目】—ELM地质分类研究
  8. PHP下载文件(隐藏真实的下载地址)
  9. Win10安装MySql步骤
  10. 力特usb转232驱动程序下载_电脑USB接口、U盘接口不能使用的原因及解决方法
  11. mysql取分组最新的一条_基于mysql实现group by取各分组最新一条数据
  12. 高等数学张宇18讲 第五讲 中值定理
  13. 常见的几种IDEA使用技巧
  14. 求2n个数中最大值和最小值的最少比较次数
  15. Internal Error (Network has dynamic or shape inputs, but no optimization profile has been defined.)
  16. Flickr 的最受欢迎图片
  17. ElasticSearch英文基本查询
  18. 商丘工学院c语言试卷,商丘工学院New Radio82期:青春
  19. 『湛蓝美声』 Mario Frangoulis
  20. 个人向的前端的坑坑洼洼的记录(1)

热门文章

  1. python+selenium+new——窗口——网页——切换窗口——切换frame——alert弹窗
  2. 麦克风声源定位原理_基于麦克风阵列声源定位系统最新版
  3. some以及every的区别
  4. 笨小孩理财-守正出奇
  5. 大数据开发——Hive实战案例
  6. 后缀.jar的是什么文件?
  7. 教你使用华为ENSP模拟器配置静态NAT(一)
  8. 3d游戏建模行业发展前景和待遇怎么样?学习的渠道有哪些
  9. Python编程 | 系统编程 | 脚本运行上下文 | 标准流
  10. 【php】获取路径(目录)