英文:
Dynamically update the @value annotated fields in spring
问题
我正试图动态更新应用程序中带有 @value
注解的字段。
首先,该应用程序具有自定义的属性源,其源是一个 Map<Object, String>
。
在一个定时器被启用以在一分钟间隔后更新这些值。
package com.test.dynamic.config;
import java.util.Date;
import java.util.Map;
import org.springframework.core.env.EnumerablePropertySource;
import org.springframework.util.StringUtils;
public class CustomPropertySource extends EnumerablePropertySource<Map<String, Object>> {
public CustomPropertySource(String name, Map<String, Object> source) {
super(name, source);
new java.util.Timer().schedule(new java.util.TimerTask() {
@Override
public void run() {
source.put("prop1", "yoyo-modified");
source.put("prop2", new Date().getTime());
System.out.println("Updated Source: " + source);
}
}, 60000);
}
@Override
public String[] getPropertyNames() {
return StringUtils.toStringArray(this.source.keySet());
}
@Override
public Object getProperty(String name) {
return this.source.get(name);
}
}
属性源的初始值 Map<String, Object>
由 PropertySourceLocator
提供。(这并非真实情况,我只是尝试重新创建这里使用的逻辑)
package com.test.dynamic.config;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import org.springframework.cloud.bootstrap.config.PropertySourceLocator;
import org.springframework.core.env.Environment;
import org.springframework.core.env.PropertySource;
public class CustomPropertySourceLocator implements PropertySourceLocator {
@Override
public PropertySource<?> locate(Environment environment) {
Map<String, Object> source = new HashMap<String, Object>() {{
put("prop1", "yoyo");
put("prop2", new Date().getTime());
}};
return new CustomPropertySource("custom_source", source);
}
}
在下面的 RestController
类中,我使用 @Value
注入这些属性。
environment.getProperty("prop1");
正在提供更新的值,但 @value
注解的字段没有更新。
我还尝试使用 environment.propertySources().addFirst
方法注入一个新的属性源 updatedMap
,以便它优先于其他属性源。但这个努力也没有成功。如果有任何线索,将不胜感激。
package com.test.dynamic.config.controller;
import java.util.HashMap;
import java.util.Map;
import javax.annotation.Resource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MapPropertySource;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class DataController {
@Value("${prop1}")
private String propertyOne;
@Value("${prop2}")
private Long propertyTwo;
@Autowired
private ConfigurableEnvironment environment;
@GetMapping("/p1")
private String getProp1() {
System.out.println("~~~~>" + environment.getPropertySources());
environment.getPropertySources().forEach(ps -> {
if (ps.containsProperty("prop1") || ps.containsProperty("prop2")) {
System.out.println("*******************************************************");
System.out.println(ps.getName());
System.out.println(ps.getProperty("prop1"));
System.out.println(ps.getProperty("prop2"));
System.out.println("*******************************************************");
}
});
return propertyOne;
}
@GetMapping("/p2")
private Long getProp2() {
System.out.println("~~~~>" + environment.getPropertySources());
return propertyTwo;
}
@GetMapping("/update")
public String updateProperty() {
Map<String, Object> updatedProperties = new HashMap<>();
updatedProperties.put("prop1", "Property one modified");
MapPropertySource mapPropSource = new MapPropertySource("updatedMap", updatedProperties);
environment.getPropertySources().addFirst(mapPropSource);
return environment.getPropertySources().toString();
}
}
如果您认为这不是将值注入到 RestController
的正确方式,请告诉我。我接受所有可能的替代建议和最佳实践。
英文:
I am trying to dynamically update the @value
annotated fields in my application.
First of all, this application has a custom property source, with source being a Map<Object, String>
.
A timer is enabled to update the values after a minute interval.
package com.test.dynamic.config;
import java.util.Date;
import java.util.Map;
import org.springframework.core.env.EnumerablePropertySource;
import org.springframework.util.StringUtils;
public class CustomPropertySorce extends EnumerablePropertySource<Map<String, Object>> {
public CustomPropertySorce(String name, Map<String, Object> source) {
super(name, source);
new java.util.Timer().schedule(new java.util.TimerTask() {
@Override
public void run() {
source.put("prop1", "yoyo-modified");
source.put("prop2", new Date().getTime());
System.out.println("Updated Source :" + source);
}
}, 60000);
}
@Override
public String[] getPropertyNames() {
// TODO Auto-generated method stub
return StringUtils.toStringArray(this.source.keySet());
}
@Override
public Object getProperty(String name) {
// TODO Auto-generated method stub
return this.source.get(name);
}
}
Initial values of source Map<String, Object>
is supplied from the PropertySourceLocator
. (This is not the real scenario, but I am trying to recreate the logic used here)
package com.test.dynamic.config;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import org.springframework.cloud.bootstrap.config.PropertySourceLocator;
import org.springframework.core.env.Environment;
import org.springframework.core.env.PropertySource;
public class CustomPropertySourceLocator implements PropertySourceLocator {
@Override
public PropertySource<?> locate(Environment environment) {
Map<String, Object> source=new HashMap<String,Object>(){{put("prop1","yoyo");put("prop2",new Date().getTime());}};
return new CustomPropertySorce("custom_source",source);
}
}
RestController
class where I inject these properties using @Value
is given below.
environment.getProperty("prop1");
is supplying updated value, but not the @value
annotated fields.
I also tried to inject a new property source updatedMap
using the addFirst
method of environment.propertySources()
assuming that it will take precedence over the others. But that effort also went futile. any clue is much appreciated.
package com.test.dynamic.config.controller;
import java.util.HashMap;
import java.util.Map;
import javax.annotation.Resource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MapPropertySource;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class DataController {
@Value("${prop1}")
private String propertyOne;
@Value("${prop2}")
private Long propertyTwo;
@Autowired
private ConfigurableEnvironment environment;
@GetMapping("/p1")
private String getProp1() {
System.out.println("~~~~>"+environment.getPropertySources());
environment.getPropertySources().forEach(ps -> {
if(ps.containsProperty("prop1") || ps.containsProperty("prop2")) {
System.out.println("*******************************************************");
System.out.println(ps.getName());
System.out.println(ps.getProperty("prop1"));
System.out.println(ps.getProperty("prop2"));
System.out.println("*******************************************************");
}
});
// env.get
return propertyOne;
// return environment.getProperty("prop1");
}
@GetMapping("/p2")
private Long getProp2() {
System.out.println("~~~~>"+environment.getPropertySources());
// env.get
return propertyTwo;
// return environment.getProperty("prop1");
}
@GetMapping("/update")
public String updateProperty() {
Map<String, Object> updatedProperties = new HashMap<>();
updatedProperties.put("prop1", "Property one modified");
MapPropertySource mapPropSource = new MapPropertySource("updatedMap", updatedProperties);
environment.getPropertySources().addFirst(mapPropSource);
return environment.getPropertySources().toString();
}
}
If you think this is not the right way of injecting values to a RestController
, please let me know. All possible alternate suggestions/best practices are accepted.
答案1
得分: 2
谢谢 @flaxel
。我使用了 @RefreshScope
来解决这个问题。
如果这个解决方案对有同样问题的人有帮助,我在这里发布一下。
在这个特定的情况下,我在我的控制器上应用了 @RefreshScope
,以便刷新带有新值的 bean。
在应用 @RefreshScope
到你的 bean 之前,你可以在这个链接中查看。
Spring Boot 的执行器实现了这个刷新机制。所以为了让这个工作,你必须在你的类路径中有执行器。
implementation group: 'org.springframework.boot', name: 'spring-boot-starter-actuator', version: "${springboot_version}"
然后,就像之前讨论的那样,将 RefreshScope
添加到需要刷新的 bean 上。
最后,调用 actuator/refresh
端点来触发刷新。
如果你想以编程的方式执行这个操作,可以通过 Autowire 将 RefreshEndpoint
类的实例注入到你的 bean 中,并在其中调用 refresh()
方法。
【注意:你不必严格遵循这种方法,我只是给出一个提示,说明可以进行注入操作】
@RefreshScope
@RestController
public class DataController {
@Value("${prop1}")
private String prop1;
@Autowired
private RefreshEndpoint refreshEndpoint;
@GetMapping("/p1")
public String getProp1(){
return prop1;
}
@GetMapping("/refresh")
public void refresh(){
refreshEndpoint.refresh();
}
}
**************** 更多(如果你正在开发一个库) ********************
如果你正在开发一个库,你需要从当前的 ApplicationContext
中获取 RefreshEndpoint
实例怎么办?
简单地将 RefreshEndpoint
进行自动注入可能会得到一个空引用。相反,你可以通过下面给出的方法获取当前的 ApplicationContext
。然后使用 ApplicationContext
获取 RefreshEndpoint
实例,并在上面调用 refresh()
方法。
public class LocalApplicationContextFetcher implements
ApplicationContextInitializer<ConfigurableApplicationContext> {
private static ApplicationContext ctx;
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
ctx = applicationContext;
}
public static ApplicationContext getCtx() {
return ctx;
}
public static void refresh(){
ctx.getBean(RefreshEndpoint.class).refresh();
}
}
最后,将这个类添加到 spring.factories
中,以便被 Spring 调用。
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
com.x.y.z.LocalApplicationContextFetcher
英文:
Thank you @flaxel
. I used @RefreshScope
to resolve this issue.
Posting the solution here if it helps someone with the same query.
In this particular case, I applied @RefreshScope
on my Controller to refresh the bean with new values.
You can refer to this link before applying @RefreshScope
to your bean.
It is the spring boot actuator that facilitates this refresh mechanism. So in order for this to work, you must have actuator in your classpath.
implementation group: 'org.springframework.boot', name: 'spring-boot-starter-actuator', version: "${springboot_version}"
Then as discussed earlier, add RefreshScope
to the bean that needs to be refreshed.
Finally, invoke the actuator/refresh
endpoint to trigger the refresh.
If you want to programmatically do it, Autowire an instance of RefreshEndpoint
class to your bean and invoke the refresh()
method in it.
[Note: You don’t have to strictly follow this approach, but I am giving a clue that it can be Autowired]
@RefreshScope
@RestController
public class DataController {
@Value("${prop1}")
private String prop1;
@Autowired
private RefreshEndpoint refreshEndpoint;
@GetMapping("/p1")
public String getProp1(){
return prop1;
}
@getMappig("/refresh")
public void refresh(){
refreshEndpoint.refresh();
}
}
**************** MORE (if you are developing a library) ********************
What if you are developing a library and you have to get the RefreshEndpoint
instance from the current ApplicationContext
?
Simply Autowiring RefreshEndpoint
may give you a null reference. Instead, you can get hold of the current ApplicationContext
by the method given below. And use the ApplicationContext
to get the RefreshEndpoint
instance to invoke the refresh()
method on it.
public class LocalApplicationContextFetcher implements
ApplicationContextInitializer<ConfigurableApplicationContext> {
private static ApplicationContext ctx;
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
ctx = applicationContext;
}
public static ApplicationContext getCtx() {
return ctx;
}
public static void refresh(){
ctx.getBean(RefreshEndpoint.class).refresh();
}
}
Finally, add this class to the spring.factories
to get invoked by spring.
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
com.x.y.z.LocalApplicationContextFetcher
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论