SpringBoot实战:SpringBoot项目运行时修改属性的方法

科技   2024-09-14 08:30   河北  

引言

在许多实际的应用场景中,动态管理应用程序的配置显得尤为重要。特别是在微服务架构的复杂环境中,由于业务需求、操作变更或外部条件的变化,不同的服务常常需要实现配置的实时更新。应用程序可能需要根据用户所处的具体环境、外部API的实时数据或遵循不断变化的业务需求来灵活调整其行为。传统的application.properties文件因其静态特性,在应用程序不重启的情况下难以进行配置修改,这限制了系统的灵活性和响应速度。
幸运的是,Spring Boot框架为我们提供了多种高效且强大的解决方案,使得在运行时动态调整配置成为可能,而无需中断应用的运行。这些能力使得在实时应用中实现功能开关的快速切换、动态更新与第三方服务相关的配置等变得简单直接,从而极大地提升了应用的灵活性和用户体验。

一、代码示例

  1. 定义一个 作用域为 Prototype 类型的 Bean

当我们需要在不干扰已创建的Bean实例状态,同时也不改变全局应用程序配置的情况下,动态地调整特定Bean的某些属性时,直接将带有@Value注解的属性注入到@Service类或其他组件中是不够灵活的。这是因为这些属性在Spring应用程序上下文的生命周期中被视为静态的,即它们的注入过程是在容器初始化阶段完成的,并且是一次性的。一旦Bean被创建并注入其依赖项,这些属性的值就固定了,除非通过重启应用或特定的更新机制,否则它们不会随外部变化而更新。
因此,为了实现属性的动态调整,我们需要探索其他机制,如使用Spring Cloud Config、Spring的@RefreshScope注解(针对支持动态刷新的配置源),或者编写自定义的Bean监听器或配置更新服务,以响应外部事件并更新Bean的属性值。这些方法允许我们在不中断服务的情况下,根据需要动态地调整Bean的属性。

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

2.使用@RefreshScope

我们可以利用Spring Cloud的@RefreshScope注解以及/actuator/refresh端点来实现配置的动态更新。这种方式的核心机制在于,它会将标记了@RefreshScope的Bean封装成代理对象。当通过/actuator/refresh端点触发配置刷新时,Spring Cloud会重新解析这些Bean的依赖配置(特别是那些通过外部配置中心如Spring Cloud Config管理的配置),并重新获取Bean的实例(实际上是重新获取这些代理对象所代理的真实Bean实例)。这样,每次通过代理对象访问Bean时,如果配置已更新,则会反映最新的配置值,从而实现了配置的实时更新。
此方法与前面提到的在Spring Boot中动态调整Bean属性的需求相契合,但更加侧重于通过Spring Cloud的生态系统来实现配置的集中管理和动态刷新。通过使用@RefreshScope,开发者可以更容易地实现微服务架构中配置的动态调整,而无需重启整个应用程序。


pom.xml文件引入依赖

<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

配置完成后 定义需要实时刷新的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() ;
  }
}


Java技术前沿
专注分享Java技术,包括但不限于 SpringBoot,SpringCloud,Docker,消息中间件等。
 最新文章