简体   繁体   English

如何在 Java EE 和 Spring Boot 中热重载属性?

[英]How to hot-reload properties in Java EE and Spring Boot?

Many in-house solutions come to mind.许多内部解决方案浮现在脑海中。 Like having the properties in a database and poll it every N secs.就像在数据库中拥有属性并每 N 秒轮询一次。 Then also check the timestamp modification for a.properties file and reload it.然后还要检查 a.properties 文件的时间戳修改并重新加载它。

But I was looking in Java EE standards and spring boot docs and I can't seem to find some best way of doing it.但我正在查看 Java EE 标准和 spring 引导文档,但我似乎无法找到一些最好的方法。

I need my application to read a properties file(or env. variables or DB parameters), then be able to re-read them.我需要我的应用程序读取属性文件(或环境变量或数据库参数),然后能够重新读取它们。 What is the best practice being used in production?生产中使用的最佳实践是什么?

A correct answer will at least solve one scenario (Spring Boot or Java EE) and provide a conceptual clue on how to make it work on the other一个正确的答案至少会解决一个场景(Spring Boot 或 Java EE)并提供关于如何使其在另一个场景中工作的概念线索

After further research, reloading properties must be carefully considered .经过进一步研究,必须仔细考虑重装属性 In Spring, for example, we can reload the 'current' values of properties without much problem.例如,在 Spring 中,我们可以毫无问题地重新加载属性的“当前”值。 But.但。 Special care must be taken when resources were initialized at the context initialization time based on the values that were present in the application.properties file (eg Datasources, connection pools, queues, etc.).在上下文初始化时根据 application.properties 文件中存在的值(例如数据源、连接池、队列等)初始化资源时,必须特别小心。

NOTE :注意

The abstract classes used for Spring and Java EE are not the best example of clean code.用于 Spring 和 Java EE 的抽象类并不是干净代码的最佳示例。 But it is easy to use and it does address this basic initial requirements:但它易于使用,并且确实满足了以下基本的初始要求:

  • No usage of external libraries other than Java 8 Classes.不使用 Java 8 类以外的外部库。
  • Only one file to solve the problem (~160 lines for the Java EE version).只需一个文件即可解决问题(Java EE 版本约 160 行)。
  • Usage of standard Java Properties UTF-8 encoded file available in the File System.使用文件系统中可用的标准 Java 属性 UTF-8 编码文件。
  • Support encrypted properties.支持加密属性。

For Spring Boot对于 Spring Boot

This code helps with hot-reloading application.properties file without the usage of a Spring Cloud Config server (which may be overkill for some use cases)此代码有助于在不使用 Spring Cloud Config 服务器的情况下热重载 application.properties 文件(这对于某些用例可能有点过分)

This abstract class you may just copy & paste (SO goodies :D ) It's a code derived from this SO answer这个抽象类你可以复制和粘贴(SO goodies :D)这是从这个 SO answer 派生代码

// imports from java/spring/lombok
public abstract class ReloadableProperties {

  @Autowired
  protected StandardEnvironment environment;
  private long lastModTime = 0L;
  private Path configPath = null;
  private PropertySource<?> appConfigPropertySource = null;

  @PostConstruct
  private void stopIfProblemsCreatingContext() {
    System.out.println("reloading");
    MutablePropertySources propertySources = environment.getPropertySources();
    Optional<PropertySource<?>> appConfigPsOp =
        StreamSupport.stream(propertySources.spliterator(), false)
            .filter(ps -> ps.getName().matches("^.*applicationConfig.*file:.*$"))
            .findFirst();
    if (!appConfigPsOp.isPresent())  {
      // this will stop context initialization 
      // (i.e. kill the spring boot program before it initializes)
      throw new RuntimeException("Unable to find property Source as file");
    }
    appConfigPropertySource = appConfigPsOp.get();

    String filename = appConfigPropertySource.getName();
    filename = filename
        .replace("applicationConfig: [file:", "")
        .replaceAll("\\]$", "");

    configPath = Paths.get(filename);

  }

  @Scheduled(fixedRate=2000)
  private void reload() throws IOException {
      System.out.println("reloading...");
      long currentModTs = Files.getLastModifiedTime(configPath).toMillis();
      if (currentModTs > lastModTime) {
        lastModTime = currentModTs;
        Properties properties = new Properties();
        @Cleanup InputStream inputStream = Files.newInputStream(configPath);
        properties.load(inputStream);
        environment.getPropertySources()
            .replace(
                appConfigPropertySource.getName(),
                new PropertiesPropertySource(
                    appConfigPropertySource.getName(),
                    properties
                )
            );
        System.out.println("Reloaded.");
        propertiesReloaded();
      }
    }

    protected abstract void propertiesReloaded();
}

Then you make a bean class that allows retrieval of property values from applicatoin.properties that uses the abstract class然后创建一个 bean 类,允许从使用抽象类的 applicatoin.properties 检索属性值

@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
    }
}

Make sure to add @EnableScheduling to your @SpringBootApplication确保将 @EnableScheduling 添加到您的 @SpringBootApplication

@SpringBootApplication
@EnableScheduling
public class MainApp  {
   public static void main(String[] args) {
      SpringApplication.run(MainApp.class, args);
   }
}

Now you can auto-wire the AppProperties Bean wherever you need it.现在您可以在任何需要的地方自动连接 AppProperties Bean。 Just make sure to always call the methods in it instead of saving it's value in a variable.只需确保始终调用其中的方法,而不是将其值保存在变量中。 And make sure to re-configure any resource or bean that was initialized with potentially different property values.并确保重新配置任何使用可能不同的属性值初始化的资源或 bean。

For now, I have only tested this with an external-and-default-found ./config/application.properties file.目前,我仅使用外部和默认找到的./config/application.properties文件对此进行了测试。

For Java EE对于 Java EE

I made a common Java SE abstract class to do the job.我制作了一个通用的 Java SE 抽象类来完成这项工作。

You may copy & paste this:您可以复制并粘贴此内容:

// 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;
  }
}

Then you can use it this way:然后你可以这样使用它:

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");

}

In case you want to use encoded properties you may enclose it's value inside ENC() and a password for decryption will be searched for in the same path and name of the property file with an added .key extension.如果您想使用编码属性,您可以将它的值包含在 ENC() 中,并且将在属性文件的相同路径和名称中搜索解密密码,并添加 .key 扩展名。 In this example it will look for the password in the application.properties.key file.在本例中,它将在 application.properties.key 文件中查找密码。

application.properties -> application.properties ->

ws.url=http://some webside
hidden.text=ENC(AAAADCzaasd9g61MI4l5sbCXrFNaQfQrgkxygNmFa3UuB9Y+YzRuBGYj+A==)

aplication.properties.key -> aplication.properties.key ->

password aca

For the encryption of property values for the Java EE solution I consulted Patrick Favre-Bulle excellent article on Symmetric Encryption with AES in Java and Android .对于 Java EE 解决方案的属性值加密,我咨询了 Patrick Favre-Bulle 关于Java 和 Android 中使用 AES 对称加密的优秀文章。 Then checked the Cipher, block mode and padding in this SO question about AES/GCM/NoPadding .然后在这个关于AES/GCM/NoPadding 的问题中检查了密码、块模式和填充。 And finally I made the AES bits be derived from a password from @erickson excellent answer in SO about AES Password Based Encryption .最后,我使 AES 位从@erickson 在 SO about AES Password Based Encryption 中的优秀答案中导出。 Regarding encryption of value properties in Spring I think they are integrated with Java Simplified Encryption关于 Spring 中值属性的加密,我认为它们与Java Simplified Encryption集成

Wether this qualify as a best practice or not may be out of scope.这是否符合最佳实践可能超出了范围。 This answer shows how to have reloadable properties in Spring Boot and Java EE.此答案显示了如何在 Spring Boot 和 Java EE 中拥有可重新加载的属性。

This functionality can be achieved by using a Spring Cloud Config Server and a refresh scope client .此功能可以通过使用Spring Cloud Config Server刷新范围客户端来实现

Server服务器

Server (Spring Boot app) serves the configuration stored, for example, in a Git repository:服务器(Spring Boot 应用程序)提供存储在例如 Git 存储库中的配置:

@SpringBootApplication
@EnableConfigServer
public class ConfigServer {
  public static void main(String[] args) {
    SpringApplication.run(ConfigServer.class, args);
  }
}

application.yml:应用程序.yml:

spring:
  cloud:
    config:
      server:
        git:
          uri: git-repository-url-which-stores-configuration.git

configuration file configuration-client.properties (in a Git repository):配置文件configuration-client.properties (在 Git 存储库中):

configuration.value=Old

Client客户

Client (Spring Boot app) reads configuration from the configuration server by using @RefreshScope annotation:客户端(Spring Boot 应用程序)使用@RefreshScope注解从配置服务器读取配置:

@Component
@RefreshScope
public class Foo {

    @Value("${configuration.value}")
    private String value;

    ....
}

bootstrap.yml:引导程序.yml:

spring:
  application:
    name: configuration-client
  cloud:
    config:
      uri: configuration-server-url

When there is a configuration change in the Git repository:当 Git 存储库中的配置发生更改时:

configuration.value=New

reload the configuration variable by sending a POST request to the /refresh endpoint:通过向/refresh端点发送POST请求来重新加载配置变量:

$ curl -X POST http://client-url/actuator/refresh

Now you have the new value New .现在您有了新值New

Additionally Foo class can serve the value to the rest of application via RESTful API if its changed to RestController and has a corresponding endpont.此外,如果Foo类更改为RestController并具有相应的端点,则可以通过RESTful API将该值提供给应用程序的其余部分。

I used @David Hofmann concept and made some changes because of not all was good.我使用了@David Hofmann 的概念并进行了一些更改,因为并非一切都很好。 First of all, in my case I no need auto-reload, I just call the REST controller for updating properties.首先,在我的情况下,我不需要自动重新加载,我只需调用 REST 控制器来更新属性。 The second case @David Hofmann's approach not workable for me with outside files.第二种情况@David Hofmann 的方法对我来说不适用于外部文件。

Now, this code can work with application.properties file from resources(inside the app) and from an outside place.现在,此代码可以与来自资源(应用程序内部)和外部位置的application.properties文件一起使用 The outside file I put near jar, and I use this --spring.config.location=app.properties argument when the application starts.我放在 jar 附近的外部文件,我在应用程序启动时使用这个--spring.config.location=app.properties参数。

@Component
public class PropertyReloader { 
private final Logger logger = LoggerFactory.getLogger(getClass());

@Autowired
private StandardEnvironment environment;
private long lastModTime = 0L;
private PropertySource<?> appConfigPropertySource = null;
private Path configPath;
private static final String PROPERTY_NAME = "app.properties";

@PostConstruct
private void createContext() {
    MutablePropertySources propertySources = environment.getPropertySources();
    // first of all we check if application started with external file
    String property = "applicationConfig: [file:" + PROPERTY_NAME + "]";
    PropertySource<?> appConfigPsOp = propertySources.get(property);
    configPath = Paths.get(PROPERTY_NAME).toAbsolutePath();
    if (appConfigPsOp == null) {
       // if not we check properties file from resources folder
        property = "class path resource [" + PROPERTY_NAME + "]";
        configPath = Paths.get("src/main/resources/" + PROPERTY_NAME).toAbsolutePath();
    }
    appConfigPsOp = propertySources.get(property);
    appConfigPropertySource = appConfigPsOp;
 }
// this method I call into REST cintroller for reloading all properties after change 
//  app.properties file
public void reload() {
    try {
        long currentModTs = Files.getLastModifiedTime(configPath).toMillis();
        if (currentModTs > lastModTime) {
            lastModTime = currentModTs;
            Properties properties = new Properties();
            @Cleanup InputStream inputStream = Files.newInputStream(configPath);
            properties.load(inputStream);
            String property = appConfigPropertySource.getName();
            PropertiesPropertySource updatedProperty = new PropertiesPropertySource(property, properties);
            environment.getPropertySources().replace(property, updatedProperty);
            logger.info("Configs {} were reloaded", property);
        }
    } catch (Exception e) {
        logger.error("Can't reload config file " + e);
    }
}

} }

I hope that my approach will help somebody我希望我的方法能帮助某人

For spring boot, there's a really good article on this topic here , but for multiple property files it doesn't work perfectly.对于 spring 引导,这里有一篇关于此主题的非常好的文章,但对于多个属性文件,它不能完美地工作。 In my case I had 2 property files, one non sensitive and one containing the passwords.在我的例子中,我有 2 个属性文件,一个是非敏感文件,另一个包含密码。 I proceeded with the following:我进行了以下操作:

<dependency>
    <groupId>commons-configuration</groupId>
    <artifactId>commons-configuration</artifactId>
    <version>1.10</version>
</dependency>

Extend the spring's PropertySource so that you can add the reloadable version to the environment.扩展 spring 的 PropertySource,以便您可以将可重新加载的版本添加到环境中。

public class ReloadablePropertySource extends PropertySource {

    private final PropertiesConfiguration propertiesConfiguration;

    public ReloadablePropertySource(String name, String path, ConfigurationListener listener) {
        super(StringUtils.hasText(name) ? name : path);
        try {
            this.propertiesConfiguration = getConfiguration(path, listener);
        } catch (Exception e) {
            throw new MissingRequiredPropertiesException();
        }
    }

    @Override
    public Object getProperty(String s) {
        return propertiesConfiguration.getProperty(s);
    }

    private PropertiesConfiguration getConfiguration(String path, ConfigurationListener listener) throws ConfigurationException {
        PropertiesConfiguration configuration = new PropertiesConfiguration(path);
        FileChangedReloadingStrategy reloadingStrategy = new FileChangedReloadingStrategy();
        reloadingStrategy.setRefreshDelay(5000);
        configuration.setReloadingStrategy(reloadingStrategy);
        configuration.addConfigurationListener(listener);
        return configuration;
    }
}

Now add all of your properties files (now reloadable) inside the spring's env现在将所有属性文件(现在可重新加载)添加到 spring 的环境中

@Configuration
public class ReloadablePropertySourceConfig {

    private final ConfigurableEnvironment env;

    @Value("${spring.config.location}")
    private String appConfigPath;

    @Value("${spring.config.additional-location}")
    private String vaultConfigPath;

    public ReloadablePropertySourceConfig(ConfigurableEnvironment env) {
        this.env = env;
    }

    @Bean
    @ConditionalOnProperty(name = "spring.config.location")
    public ReloadablePropertySource getAppConfigReloadablePropertySource(){
        ReloadablePropertySource rps = new ReloadablePropertySource("dynamicNonSensitive", appConfigPath, new PropertiesChangeListener());
        MutablePropertySources sources = env.getPropertySources();
        sources.addFirst(rps);
        return rps;
    }

    @Bean
    @ConditionalOnProperty(name = "spring.config.additional-location")
    public ReloadablePropertySource getVaultReloadablePropertySource(){
        ReloadablePropertySource rps = new ReloadablePropertySource("dynamicVault", vaultConfigPath, new PropertiesChangeListener());
        MutablePropertySources sources = env.getPropertySources();
        sources.addFirst(rps);
        return rps;
    }

    private static class PropertiesChangeListener implements ConfigurationListener{

        @Override
        public void configurationChanged(ConfigurationEvent event) {
            if (!event.isBeforeUpdate()){
                System.out.println("config refreshed!");
            }
        }
    }
}

From the article文章

We've added the new property source as the first item because we want it to override any existing property with the same key我们将新属性 source 添加为第一项,因为我们希望它用相同的键覆盖任何现有属性

In our case, we have 2 "reloadable" property sources and both will be looked up first.在我们的例子中,我们有 2 个“可重新加载”的属性源,它们都将首先被查找。

Finally create one more class from which we can access the env's properties最后再创建一个 class,我们可以从中访问环境的属性

@Component
public class ConfigProperties {

    private final Environment environment;

    public ConfigProperties(Environment environment) {
        this.environment = environment;
    }

    public String getProperty(String name){
        return environment.getProperty(name);
    }
}

Now you can autowire ConfigProperties and always get the latest property in the files without requiring to restart the application.现在您可以自动装配ConfigProperties并始终获取文件中的最新属性,而无需重新启动应用程序。

@RestController
@Slf4j
public class TestController {

    @Autowired
    private ConfigProperties env;

    @GetMapping("/refresh")
    public String test2() {
        log.info("hit");
        String updatedProperty = env.getProperty("test.property");
        String password = env.getProperty("db.password");
        return updatedProperty + "\n" + password;
    }

}

where test.property is coming from 1st file and db.password is coming from another.其中test.property来自第一个文件,而db.password来自另一个文件。

As mentioned by @Boris, Spring Cloud Config is the way to go to avoid patchy solution.正如@Boris 所提到的,Spring Cloud Config 是避免不完整解决方案的方法。 To keep the setup minimum, I will suggest the Embedding the Config Server Approach with native type (file type).为了保持设置最少,我将建议使用本机类型(文件类型) 嵌入配置服务器方法

To support automatic config refresh without calling the actuator endpoint manually, I have created a directory listener to detect file changes and to dispatch refresh scope event.为了在不手动调用执行器端点的情况下支持自动配置刷新,我创建了一个目录侦听器来检测文件更改并调度刷新范围事件。

Proof Of Concept repo ( git )概念证明 repo ( git )

If you want to change the properties at realtime and don't want to restart the server then follow the below steps:如果您想实时更改属性并且不想重新启动服务器,请按照以下步骤操作:

1). 1)。 Application.properties应用程序属性

app.name= xyz
management.endpoints.web.exposure.include=*

2). 2)。 Add below dependencies in pom.xml在 pom.xml 中添加以下依赖项

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-rest</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-context</artifactId>
        <version>2.1.1.RELEASE</version>
    </dependency>

3).Place application.properties in /target/config folder. 3).将 application.properties 放在/target/config文件夹中。 Create the jar in /target folder/target文件夹中创建 jar

4).add a classas below ApplcationProperties.java 4).在ApplcationProperties.java下面添加一个类

@Component
@RefreshScope
@ConfigurationProperties(prefix = "app")
public class ApplicationProperties {
private String name;

public String getName() {
    return name;
}

public void setName(String name) {
    this.name = name;
}
}

5). 5)。 Write Controller.java and inject ApplcationProperties编写 Controller.java 并注入 ApplcationProperties

@RestController
public class TestController {

@Autowired
private ApplicationProperties applcationProperties;

@GetMapping("/test")
public String getString() {
    return applcationProperties.getName();
}
}

6).Run the spring boot application 6).运行spring boot应用

Call localhost:XXXX/test from your browser从浏览器调用localhost:XXXX/test

Output : xyz

7). 7)。 Change the value in application.properties from xyz to abc将 application.properties 中的值从 xyz 更改为 abc

8). 8). Using postman send a POST request to localhost:XXXX/actuator/refresh使用 postman 向 localhost:XXXX/actuator/refresh 发送 POST 请求

response: ["app.name"]

9). 9). Call localhost:XXXX/find from your browser从浏览器调用 localhost:XXXX/find

Output : abc

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM