【问题标题】:Dynamically update the @value annotated fields in spring在spring中动态更新@value注解的字段
【发布时间】:2020-09-28 10:36:12
【问题描述】:

我正在尝试动态更新我的应用程序中的 @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 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);
    }

}

Map&lt;String, Object&gt; 的初始值由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 CustomPropertySorce("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("*******************************************************");
            }
        });
        
        
        
//      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();
    }

}

如果您认为这不是向RestController 注入值的正确方法,请告诉我。接受所有可能的替代建议/最佳做法。

【问题讨论】:

  • 看看java.util.concurrent.atomic.AtomicReference。我没有时间输入示例作为答案,但我用它来分享对具有变化值的Map 的引用。
  • 您也可以reload all properties使用执行器和云。为此,有注释@RefreshScope
  • @flaxel 我正在创建一个库。因此,我无法控制注入属性的 bean 的范围。 RefreshScope 表示“Spring Cloud 也引入了 RefreshScope,我们可以将其用于配置类或 bean。因此,默认范围将是刷新而不是单例。”
  • @Paul 您的意思是将“Map source”包装在原子引用中吗?我想知道当负责保存值本身的“环境”显示更新的值时,这将如何提供帮助。
  • 我试过 AtomicReference.... Map initialSource = new HashMap(); initialSource.put("prop1","yoyo"); initialSource.put("prop2",new Date().getTime()); AtomicReference> source = new AtomicReference(); source.set(initialSource);但没有帮助

标签: java spring spring-boot


【解决方案1】:

谢谢@flaxel。我使用@RefreshScope 解决了这个问题。 如果对有相同查询的人有帮助,请在此处发布解决方案。

在这种特殊情况下,我在我的控制器上应用了@RefreshScope 以使用新值刷新 bean。

You can refer to this 链接,然后再将 @RefreshScope 应用于您的 bean。

正是 spring boot 执行器促成了这种刷新机制。所以为了让它工作,你的类路径中必须有执行器。

implementation group: 'org.springframework.boot', name: 'spring-boot-starter-actuator', version: "${springboot_version}"

然后如前所述,将RefreshScope添加到需要刷新的bean中。

最后,调用actuator/refresh 端点来触发刷新。

如果您想以编程方式执行此操作,请将RefreshEndpoint 类的实例自动装配 到您的bean 并调用其中的refresh() 方法。 [注意:您不必严格遵循这种方法,但我给出了一个可以自动装配的线索]

@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();
}

}

****************更多(如果您正在开发库)********************

如果您正在开发一个库并且必须从当前的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

【讨论】:

    猜你喜欢
    • 2017-01-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-05-04
    • 2019-12-15
    • 2011-08-04
    • 1970-01-01
    相关资源
    最近更新 更多