运行时修改 Spring Boot 属性的 N 种方式

开发 前端
application.properties文件是静态的,应用不重启的情况下就无法更改。然而,Spring Boot    提供了几种强大的方法来在运行时调整配置,而无需停机。无论是在实时应用中功能切换,还是更改第三方相关配置都无需重启应用。

环境:Spring Boot3.2.5

1. 简介

在许多实际场景中,动态管理应用程序配置可能是一个关键需求。在微服务架构中,由于某些操作,不同的服务可能需要实时配置更改。应用可能需要根据用户环境、外部API的数据或遵守动态变化的要求来调整其行为。

application.properties文件是静态的,应用不重启的情况下就无法更改。然而,Spring Boot    提供了几种强大的方法来在运行时调整配置,而无需停机。无论是在实时应用中功能切换,还是更改第三方相关配置都无需重启应用。

本篇文章将介绍几种在不直接修改application.properties文件的情况下,动态更新 Spring Boot 应用程序中属性的策略。

2. 实战案例

2.1 将Bean定义为Prototype作用域

当我们需要在不影响已创建的 Bean 实例或不改变全局应用程序状态的情况下动态调整特定 Bean 的属性时,直接注入带有@Value 注解的 @Service类是不行的,因为这些属性在应用程序上下文的生命周期中是静态的,简单说注入是一次性的。

我们可以使用@Configuration 配置类中的@Bean方法创建具有可修改属性的 Bean。这种方法允许在应用程序执行过程中动态更改属性:

@Configuration
public class AppConfig {


  @Bean
  // 声明为多例bean作用域
  @Scope("prototype")
  public UserService userService(@Value("${pack.app.title:}") String title) {
    return new UserService(title) ;
  }
}
public class UserService {
  private final String title;


  public UserService(String title) {
    this.title= title;
  }
  // getters, setters
}

通过@Scope将UserService作用域声明为多例,这样能保证每次通过getBean都拿到的是一个新的对象,既然是新对象那么每次都会重新注入title值。如下示例:

@RestController
public class UserController {
  @Resource
  private ApplicationContext context; 
  @Resource
  private ApplicationContext context ;
  @GetMapping("/update")
  public String update() {
    // 设置系统属性值;
    // 注意你不能吧属性定义在application.yml配置文件中
    System.setProperty("pack.app.title", "xxxooo - " + new Random().nextInt(10000)) ;
    return "update success" ;
  }
  @GetMapping("/title")
  public String title() {
    return this.context.getBean("us", UserService.class).getTitle() ;
  }
}

每次都是getBean获取新对象,所以属性title每次注入的都是最新的值。

2.2 使用@RefreshScope

我们可以使用 Spring Cloud 的@RefreshScope注解和/actuator/refresh端点。该接口会刷新所有@RefreshScopeBean,其实此种方式与上面2.1类似,首先是将bean对象生成代理,再通过代理对象执行时每次都重新获取getBean,以此来达到实时更新属性。

引入依赖

<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-context</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

开启refresh接口

management:
  endpoint:
    refresh:
      enabled: true
  endpoints:
    web:
      exposure:
        include: refresh

如果你想在每次调用该接口时都记录日志,进行如下日志级别设置:

logging:
  level:
    org.springframework.boot.actuate: debug

以上配置完成后,接下来就可以定义需要实时刷新的bean对象了;

@RefreshScope
@Component
public class AppComponent {
  @Value("${pack.app.title:}")
  private String title ;


  public String getTitle() {
    return this.title ;
  }
}

接下来需要完成的是,动态向Environment中添加PropertySource该对象中存入的是我们需要动态刷新的值。

@Service
public class PackPropertyService {
  private static final String PACK_PROPERTIES_SOURCE_NAME = "packDynamicProperties" ;


  private final ConfigurableEnvironment environment ;
  public PackPropertyService(ConfigurableEnvironment environment) {
    this.environment = environment ;
  }
  // 更新或者添加PropertySource操作
  public void updateProperty(String key, String value) {
    MutablePropertySources propertySources = environment.getPropertySources() ;
    if (!propertySources.contains(PACK_PROPERTIES_SOURCE_NAME)) {
      Map<String, Object> properties = new HashMap<>() ;
      properties.put(key, value) ;
      propertySources.addFirst(new MapPropertySource(PACK_PROPERTIES_SOURCE_NAME, properties)) ;
    } else {
      // 替换更新值
      MapPropertySource propertySource = (MapPropertySource) propertySources.get(PACK_PROPERTIES_SOURCE_NAME) ;
      propertySource.getSource().put(key, value) ;
    }
  }
}

接下来写一个接口进行更新属性值;

@RestController
@RequestMapping("/configprops")
public class PropertyController {
    
  private final PackPropertyService pps ;
  private final AppComponent app ;
  public PropertyController(PackPropertyService pps, AppComponent app) {
    this.pps = pps ;
    this.app = app ;
  }
    
  @PostMapping("/update")
  public String updateProperty(String key, String value) {
    pps.updateProperty(key, value) ;
    return "update success" ;
  }
  @GetMapping("/title")
  public String title() {
    return app.getTitle() ;
  }
}

首先调用/configprops/update接口更新属性,接着调用 /actuator/refresh 接口进行刷新操作,最后调用/configprops/title接口时,AppComponent代理对象会会使用更新的配置属性重新初始化 Bean。

2.3 使用外部配置文件

在某些情况下,有必要在应用程序部署包之外管理配置更新,以确保对属性进行持续更改。这也允许我们将更改分发给多个应用程序。

在本例中,我将使用与之前相同的 Spring Cloud 设置来启用@RefreshScope和/actuator/refresh支持。我们使用外部文件external-config.properties。

我们还是借助上面的/configprops/title接口访问属性,通过 /actuator/refresh 接口属性配置。

我们需要做的是在启动服务是添加如下启动参数,指定外部配置文件

图片图片

初始文件内容;

pack.app.title=xxxooo

图片图片

当我们修改次文件内容后,直接调用/actuator/refresh 接口;

图片图片

返回了已经更新的配置属性key,再次访问/title接口;

图片图片

得到了最新的值。

责任编辑:武晓燕 来源: Spring全家桶实战案例源码
相关推荐

2015-07-20 15:44:46

Swift框架MJExtension反射

2020-09-28 15:54:18

Python语言技术

2024-03-21 09:15:58

JS运行的JavaScrip

2020-12-07 13:31:43

GoMutex开发者

2021-09-11 15:38:23

容器运行镜像开放

2019-07-12 09:30:12

DashboardDockerDNS

2022-05-09 07:20:10

监控项目工具

2023-08-27 21:07:02

2023-08-21 09:37:57

MySQL工具MariaDB

2024-01-29 08:07:42

FlinkYARN架构

2021-08-18 08:32:09

代码运行时间示波器

2024-03-20 10:46:00

云原生容器

2013-11-26 16:49:55

Android开发运行时KitKat

2023-07-28 10:42:43

2023-01-03 09:10:21

2022-01-19 08:50:53

设备树Linux文件系统

2021-08-27 00:21:19

JSJust源码

2022-12-30 08:08:30

2023-08-29 08:20:35

Kubernete跨云容器

2023-02-12 12:00:57

点赞
收藏

51CTO技术栈公众号