文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

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

2024-11-29 19:25

关注

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,以此来达到实时更新属性。

引入依赖


  org.springframework.cloud
  spring-cloud-context


  org.springframework.boot
  spring-boot-starter-actuator

开启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 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全家桶实战案例源码内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯