@Component
public class AppProperties extends ReloadableProperties {
public String dynamicProperty() {
return environment.getProperty("dynamic.prop");
}
public String anotherDynamicProperty() {
return environment.getProperty("another.dynamic.prop");
}
@Override
protected void propertiesReloaded() {
// do something after a change in property values was done
}
}
确保将@EnableScheduling添加到@SpringBootApplication
@SpringBootApplication
@EnableScheduling
public class MainApp {
public static void main(String[] args) {
SpringApplication.run(MainApp.class, args);
}
}
// imports from java.* and javax.crypto.*
public abstract class ReloadableProperties {
private volatile Properties properties = null;
private volatile String propertiesPassword = null;
private volatile long lastModTimeOfFile = 0L;
private volatile long lastTimeChecked = 0L;
private volatile Path propertyFileAddress;
abstract protected void propertiesUpdated();
public class DynProp {
private final String propertyName;
public DynProp(String propertyName) {
this.propertyName = propertyName;
}
public String val() {
try {
return ReloadableProperties.this.getString(propertyName);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
}
protected void init(Path path) {
this.propertyFileAddress = path;
initOrReloadIfNeeded();
}
private synchronized void initOrReloadIfNeeded() {
boolean firstTime = lastModTimeOfFile == 0L;
long currentTs = System.currentTimeMillis();
if ((lastTimeChecked + 3000) > currentTs)
return;
try {
File fa = propertyFileAddress.toFile();
long currModTime = fa.lastModified();
if (currModTime > lastModTimeOfFile) {
lastModTimeOfFile = currModTime;
InputStreamReader isr = new InputStreamReader(new FileInputStream(fa), StandardCharsets.UTF_8);
Properties prop = new Properties();
prop.load(isr);
properties = prop;
isr.close();
File passwordFiles = new File(fa.getAbsolutePath() + ".key");
if (passwordFiles.exists()) {
byte[] bytes = Files.readAllBytes(passwordFiles.toPath());
propertiesPassword = new String(bytes,StandardCharsets.US_ASCII);
propertiesPassword = propertiesPassword.trim();
propertiesPassword = propertiesPassword.replaceAll("(\\r|\\n)", "");
}
}
updateProperties();
if (!firstTime)
propertiesUpdated();
} catch (Exception e) {
e.printStackTrace();
}
}
private void updateProperties() {
List<DynProp> dynProps = Arrays.asList(this.getClass().getDeclaredFields())
.stream()
.filter(f -> f.getType().isAssignableFrom(DynProp.class))
.map(f-> fromField(f))
.collect(Collectors.toList());
for (DynProp dp :dynProps) {
if (!properties.containsKey(dp.propertyName)) {
System.out.println("propertyName: "+ dp.propertyName + " does not exist in property file");
}
}
for (Object key : properties.keySet()) {
if (!dynProps.stream().anyMatch(dp->dp.propertyName.equals(key.toString()))) {
System.out.println("property in file is not used in application: "+ key);
}
}
}
private DynProp fromField(Field f) {
try {
return (DynProp) f.get(this);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return null;
}
protected String getString(String param) throws Exception {
initOrReloadIfNeeded();
String value = properties.getProperty(param);
if (value.startsWith("ENC(")) {
String cipheredText = value
.replace("ENC(", "")
.replaceAll("\\)$", "");
value = decrypt(cipheredText, propertiesPassword);
}
return value;
}
public static String encrypt(String plainText, String key)
throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException, InvalidKeySpecException {
SecureRandom secureRandom = new SecureRandom();
byte[] keyBytes = key.getBytes(StandardCharsets.US_ASCII);
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
KeySpec spec = new PBEKeySpec(key.toCharArray(), new byte[]{0,1,2,3,4,5,6,7}, 65536, 128);
SecretKey tmp = factory.generateSecret(spec);
SecretKey secretKey = new SecretKeySpec(tmp.getEncoded(), "AES");
byte[] iv = new byte[12];
secureRandom.nextBytes(iv);
final Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
GCMParameterSpec parameterSpec = new GCMParameterSpec(128, iv); //128 bit auth tag length
cipher.init(Cipher.ENCRYPT_MODE, secretKey, parameterSpec);
byte[] cipherText = cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8));
ByteBuffer byteBuffer = ByteBuffer.allocate(4 + iv.length + cipherText.length);
byteBuffer.putInt(iv.length);
byteBuffer.put(iv);
byteBuffer.put(cipherText);
byte[] cipherMessage = byteBuffer.array();
String cyphertext = Base64.getEncoder().encodeToString(cipherMessage);
return cyphertext;
}
public static String decrypt(String cypherText, String key)
throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException, InvalidKeySpecException {
byte[] cipherMessage = Base64.getDecoder().decode(cypherText);
ByteBuffer byteBuffer = ByteBuffer.wrap(cipherMessage);
int ivLength = byteBuffer.getInt();
if(ivLength < 12 || ivLength >= 16) { // check input parameter
throw new IllegalArgumentException("invalid iv length");
}
byte[] iv = new byte[ivLength];
byteBuffer.get(iv);
byte[] cipherText = new byte[byteBuffer.remaining()];
byteBuffer.get(cipherText);
byte[] keyBytes = key.getBytes(StandardCharsets.US_ASCII);
final Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
KeySpec spec = new PBEKeySpec(key.toCharArray(), new byte[]{0,1,2,3,4,5,6,7}, 65536, 128);
SecretKey tmp = factory.generateSecret(spec);
SecretKey secretKey = new SecretKeySpec(tmp.getEncoded(), "AES");
cipher.init(Cipher.DECRYPT_MODE, secretKey, new GCMParameterSpec(128, iv));
byte[] plainText= cipher.doFinal(cipherText);
String plain = new String(plainText, StandardCharsets.UTF_8);
return plain;
}
}
你可以这样使用它:
public class AppProperties extends ReloadableProperties {
public static final AppProperties INSTANCE; static {
INSTANCE = new AppProperties();
INSTANCE.init(Paths.get("application.properties"));
}
@Override
protected void propertiesUpdated() {
// run code every time a property is updated
}
public final DynProp wsUrl = new DynProp("ws.url");
public final DynProp hiddenText = new DynProp("hidden.text");
}
@Component
public class ConfigProperties {
private final Environment environment;
public ConfigProperties(Environment environment) {
this.environment = environment;
}
public String getProperty(String name){
return environment.getProperty(name);
}
}
6条答案
按热度按时间yc0p9oo01#
经过进一步研究,reloading properties must be carefully considered.例如,在Spring中,我们可以重新加载属性的“当前”值,而不会遇到太多问题。但是,当在上下文初始化时基于www.example.com文件中存在的值application.properties(例如,数据源、连接池、队列等)初始化资源时,必须特别注意。
备注:
用于Spring和Java EE的抽象类并不是干净代码的最佳示例。但是它很容易使用,并且它确实解决了这些基本的初始要求:
**对于Sping Boot **
此代码有助于热重新加载application.properties文件,而无需使用Spring Cloud Config服务器(对于某些用例可能会过度)
这个抽象类你可以直接复制粘贴(好东西:D)它是一个code derived from this SO answer
然后创建一个bean类,允许从applicatoin.properties使用抽象类的www.example.com检索属性值
确保将@EnableScheduling添加到@SpringBootApplication
现在,您可以在任何需要的地方自动连接AppProperties Bean。只要确保总是调用其中的方法,而不是将其值保存在变量中。并确保重新配置使用可能不同的属性值初始化的任何资源或bean。
目前,我只使用一个外部和默认找到的
./config/application.properties
文件对此进行了测试。面向Java EE
我创建了一个通用的Java SE抽象类来完成这项工作。
您可以复制并粘贴此:
你可以这样使用它:
如果你想使用编码的属性,你可以将它的值包含在ENC()中,解密的密码将在属性文件的相同路径和名称中搜索,并添加一个.key扩展名。在本例中,它将在application.properties.key文件中查找密码。
application.properties ->
application.properties.key ->
关于Java EE解决方案的属性值加密,我查阅了帕特里克Favre-Bulle关于Symmetric Encryption with AES in Java and Android的优秀文章。然后检查了关于AES/GCM/NoPadding的SO问题中的密码,块模式和填充。最后,我从@erickson的密码中导出了AES位,这是关于AES Password Based Encryption的SO中的优秀答案。关于Spring中值属性的加密,我认为它们与Java Simplified Encryption集成在一起
这是否符合最佳实践的条件可能超出了范围。这个答案展示了如何在Sping Boot 和Java EE中拥有可重新加载的属性。
ozxc1zmp2#
这个功能可以通过使用Spring Cloud Config Server和refresh scope client来实现。
服务器
服务器(Sping Boot 应用程序)提供存储在Git存储库中的配置:
application.yml:
配置文件
configuration-client.properties
(在Git存储库中):客户端
客户端(Sping Boot 应用程序)通过使用@RefreshScope annotation从配置服务器读取配置:
bootstrap.yml:
当Git仓库中的配置发生更改时:
通过向
/refresh
端点发送POST
请求来重新加载配置变量:现在你有了新的值
New
。此外,如果
Foo
类更改为RestController
并具有相应的endpont,则Foo
类可以通过RESTful API
将值提供给应用程序的其余部分。sycxhyv73#
我使用了@大卫Hofmann的概念,并做了一些修改,因为并不是所有的都是好的。首先,在我的例子中,我不需要自动重载,我只需要调用REST控制器来更新属性。第二个案例@大卫Hofmann的方法对我来说不适用于外部文件。
现在,这段代码可以处理来自资源(应用程序内部)和外部的application.properties文件。我把外部文件放在jar附近,当应用程序启动时,我使用这个**--spring.config.location= app.properties**参数。
}
我希望我的方法能帮助到一些人
b5lpy0ml4#
正如@Boris所提到的,Spring Cloud Config是避免补丁解决方案的方法。为了保持最小的设置,我建议使用本机类型(文件类型)嵌入配置服务器方法。
为了支持自动配置刷新而不需要手动调用执行器端点,我创建了一个目录侦听器来检测文件更改并分派刷新范围事件。
概念验证存储库(git)
v09wglhw5#
对于spring Boot ,有一篇关于here主题的非常好的文章,但是对于多个属性文件,它并不完美。在我的情况下,我有2个属性文件,一个不敏感,一个包含密码。我接着说:
扩展spring的PropertySource,以便可以将可重载版本添加到环境中。
现在将所有的属性文件(现在可重载)添加到Spring的env中
从article
我们已经添加了新的属性源作为第一项,因为我们希望它用相同的键覆盖任何现有的属性
在我们的例子中,我们有2个“可重载”的属性源,这两个都将首先被查找。
最后再创建一个类,我们可以从这个类访问env的属性
现在,您可以自动连接
ConfigProperties
并始终获取文件中的最新属性,而无需重新启动应用程序。其中
test.property
来自第一个文件,db.password
来自另一个文件。wmomyfyw6#
如果您想实时更改属性,并且不想重新启动服务器,请按照以下步骤操作:
1). Application.properties
2).在pom.xml中添加以下依赖项
3).将www.example.com放在application.properties
/target/config
文件夹中。在/target
文件夹中创建jar4).在www.example.com下面添加一个类ApplcationProperties.java
5).写入Controller.java并注入ApplcationProperties
6).运行spring Boot 应用程序
从浏览器调用
localhost:XXXX/test
Output : xyz
7).将www.example.com中的值application.properties从xyz更改为abc
8).使用postman发送POST请求到localhost:XXXX/actuator/refresh
response: ["app.name"]
9).从浏览器调用localhost:XXXX/find
Output : abc