Spring为我们提供了@Value和Environment来获取配置信息,但是很遗憾,不支持动态刷新配置,也就是说在配置更新之后,需要重启项目才能获取到最新的配置信息,如果要实现动态刷新,通常我们需要借助第三方配置中心,比如百度的disconf,携程的apollo、阿里的nacos等等,但这些都需要额外维护一套配置中心,下面介绍一种简单的思路来实现Environment获取最新配置的方式,基于这个思路再去实现@Value动态刷新就简单多了,本文暂不介绍@Value的方式,废话不多说,直接上代码:
1、准备数据:
CREATE TABLE `t_config` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`key` varchar(255) DEFAULT NULL,
`value` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
insert INTO t_config(`key`,`VALUE`) VALUES('test.tj','www.baidu.com');
2、Java代码实现:
- 生成一个springboot项目,这个在spring官网可以一键生成,不做赘述
- 在springboot项目的启动类上实现两个接口:
EnvironmentAware, CommandLineRunner,至于这两个接口到底有什么用,自行百度,这里就不做介绍了
- 重写setEnvironment和run方法(这个方法不是多线程的那个run方法,是CommandLineRunner这个接口的方法):
-
setEnvironment实现:
@Override public void setEnvironment(Environment environment) { this.configurableEnvironment = (ConfigurableEnvironment) environment; }
-
run实现:
@Override public void run(String... args) throws Exception { Config config = new Config(); String url = "jdbc:mysql://127.0.0.1:3306/config?serverTimezone=Asia/Shanghai&useLegacyDatetimeCode=false"; String username = "root"; String password = "123456"; Connection connection = DriverManager.getConnection(url, username, password); String sql = "select `key`,`value` from t_config"; new Thread(() -> { while (true) { try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } try { PreparedStatement preparedStatement = connection.prepareStatement(sql); ResultSet resultSet = preparedStatement.executeQuery(); while (resultSet.next()) { String key = resultSet.getString("key"); String value = resultSet.getString("value"); config.addKey(key, value); } } catch (SQLException e) { e.printStackTrace(); } configurableEnvironment.getPropertySources().addFirst(new MyPropertySource("myPropertySource", config)); } }).start(); }
- 其中用到的几个自定义的类:
public class MyPropertySource extends EnumerablePropertySource<Config> { private static final String[] EMPTY_ARRAY = new String[0]; public MyPropertySource(String name, Config config) { super(name, config); } @Override public String[] getPropertyNames() { Set<String> propertyNames = this.source.getPropertyNames(); if (propertyNames.isEmpty()) { return EMPTY_ARRAY; } return propertyNames.toArray(new String[propertyNames.size()]); } @Override public Object getProperty(String name) { return this.source.getProperty(name, null); } } public class Config { private Map<String, Object> cache = new HashMap<>(); public Object getProperty(String key, String defaultValue) { Object o = cache.get(key); return o; } public Set<String> getPropertyNames() { return cache.keySet(); } public void addKey(String key, Object value) { cache.put(key, value); } }
- 测试:127.0.0.1:8081/test/getCfg
@Controller public class TestController { @Value("${test.tj}") private String myConfig; @Autowired private ConfigurableEnvironment configurableEnvironment; @RequestMapping("/test/index") @ResponseBody public String index() { return "index"; } @RequestMapping("/test/getCfg") @ResponseBody public String getCfg() { return configurableEnvironment.getProperty("test.tj"); } }
- 更改mysql数据库里面test.tj对应的value字段值,刷新浏览器就可以看到效果了
- @Value实现动态刷新的思路:通过spring容器获取所有的bean,然后对bean里面的属性进行遍历,看看有没有@Value注解,有的话就可以解析@Value里面的key值与数据库查出来的key对比,相等的话,通过反射将查出来的值set到对应的bean即可