首页 > 其他分享 >聊聊springboot项目脱离配置中心,如何实现属性动态刷新

聊聊springboot项目脱离配置中心,如何实现属性动态刷新

时间:2024-07-16 09:18:48浏览次数:7  
标签:return String url private springboot 聊聊 刷新 public 属性

前言

如果大家有开发过微服务项目,那对配置中心应该是耳熟能详了,配置中心有个很有用的能力,就是热更新属性,即不重启服务,就能做到属性的动态变更。而我们今天讲的话题是,怎么样不使用配置中心,也能达到如上的效果

如何实现属性的热更新

如果我们属性是配置在配置文件中,我们可以通过监听文件的变化,然后进行属性重新绑定。那我们如何实现这种效果呢,我们可以利用hutool提供的cn.hutool.core.io.watch.WatchMonitor或者是apache提供的commons-io下的org.apache.commons.io.monitor.FileAlterationObserver实现文件监听变化,然后在监听变化的监听器里面进行属性绑定。然而今天我们介绍不是这种,我们介绍是通过spring-cloud-context里面提供的

org.springframework.cloud.context.environment.EnvironmentManager

来实现如上效果

如何实现

1、在项目的pom引入spring-cloud-context gav

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

因为要暴露env端点,所以还要引入

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

2、在项目的yml文件开启访问env端点以及将management.endpoint.env.post.enabled设置为true

示例

management:
  endpoints:
    web:
      exposure:
        include: "*"

  endpoint:
    health:
      show-details: always
    env:
      post:
        enabled: true

注: management.endpoint.env.post.enabled不配制,默认也生效

3、通过客户端工具post请求访问http://ip:端口/actuator/env。以json格式发送

json格式的数据如下

{
"name":"需要变更的key",
"value":"变更后的value"
}

通过以上3步配置,就可以实现属性的变更了,是不是感觉到很简单。不过正常我们会浅浅封装下,在讲如何浅浅封装的时候,我先讲下,他大体实现变更的流程思路.如下

如何浅浅封装

1、封装属性绑定接口

@FunctionalInterface
public interface PropertyRebinder {

    void binder(RefreshProperty refreshProperty);
}

2、封装属性变更同步接口

public interface PropertyRefreshedSync {

    void execute(String name,Object value);
}

3、监听EnvironmentChangeEvent事件

核心代码如下

  @EventListener(EnvironmentChangeEvent.class)
    public void listener(EnvironmentChangeEvent event){
        if(CollectionUtils.isEmpty(propertyRebinders)){
            return;
        }
        RefreshProperty refreshProperty = get(event.getKeys());
        propertyRebinders.forEach(propertyRebinder -> run(() -> propertyRebinder.binder(refreshProperty)));

    }

示例应用

示例模拟演示一个授权访问的例子

1、编写授权属性配置类

@Data
@AllArgsConstructor
@NoArgsConstructor
@ConfigurationProperties(prefix = AuthProperty.PREFIX)
public class AuthProperty {

    public static final String PREFIX = "lybgeek.auth";

    private boolean enabled;

    private String tokenKey = "token";

    private List<String> whitelistUrls;
}

2、编写授权拦截器

@Slf4j
public class AuthHandlerInterceptor implements HandlerInterceptor {

    @Autowired
    private AuthProperty authProperty;

    @Autowired
    private WebEndpointProperties webEndpointProperties;
    
    private final AntPathMatcher antPathMatcher = new AntPathMatcher();

    public static final String MOCK_TOKEN_VALUE = "123456";

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if(log.isDebugEnabled()){
            log.debug("url:{},queryString:{}",request.getRequestURI(),request.getQueryString());
        }
        if(!authProperty.isEnabled()){
            return true;
        }
        if(isWhiteList(request)){
            return true;
        }

        String token = request.getHeader(authProperty.getTokenKey());
        if(MOCK_TOKEN_VALUE.equals(token)){
            return true;
        }

        throw new AuthException("token is not valid:" + token, HttpStatus.UNAUTHORIZED.name());
    }

    private boolean isWhiteList(HttpServletRequest request) {
        String url = request.getRequestURI();
        if(CollectionUtil.isNotEmpty(authProperty.getWhitelistUrls())){
            for (String whitelistUrl : authProperty.getWhitelistUrls()) {
               boolean isMatch = isMatch(whitelistUrl,url);
               if(isMatch){
                   return true;
                }
            }
        }
        boolean isMatchLogger = isMatch("/"+BASE_LOG_URL + "/**",url);
        if(isMatchLogger){
            return true;
        }
        return isMatch(webEndpointProperties.getBasePath() + "/**",url);
    }

    private boolean isMatch(String pattern, String url){
        if(antPathMatcher.match(pattern,url)){
            if(log.isDebugEnabled()){
                log.debug("url: {} is in whitelist",url);
            }
            return true;
        }
        return false;
    }
}

3、授权拦截器装配

Configuration
@EnableConfigurationProperties(AuthProperty.class)
public class AuthAutoConfiguration implements WebMvcConfigurer {



    @Bean
    @ConditionalOnMissingBean
    public AuthHandlerInterceptor authHandlerInterceptor(){
        return new AuthHandlerInterceptor();
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(authHandlerInterceptor()).addPathPatterns("/**");
    }
}

4、编写需授权访问的控制器

@RestController
@RequestMapping("config")
@RequiredArgsConstructor
public class ConfigController {

    private final AuthProperty authProperty;


    @GetMapping("get")
    public AuthProperty get(){
        return authProperty;
    }


}

5、测试

a、 场景一:授权拦截器关闭

  @Test
    public void testGetProperty(){
                   ForestResponse response = Forest.get(serverUrl + "/config/get").executeAsResponse();
            PrintUtils.print(response.getContent());
    }

一开始我们授权拦截器是关闭的,因此我们访问"/config/get",正常是可以访问

b、 场景二:打开授权拦截器

 @Test
    public void testRefreshPropertyEnabled(){
        String name = AuthProperty.PREFIX + ".enabled";
        String value = "true";
        refreshProperty(name, value);
    }

控制台输出

此时再访问"/config/get",观察控制台结果


因为没授权,因此无法访问

c、 场景三:打开授权拦截器,新增白名单

   @Test
    public void testRefreshPropertyWhitelistUrls(){
        String name = AuthProperty.PREFIX + ".whitelistUrls";
        List<String> whitelistUrls = new ArrayList<>();
        whitelistUrls.add("/config/refresh");
        whitelistUrls.add("/config/get");
        String value = String.join(",", whitelistUrls);
        refreshProperty(name, value);
    }

控制台输出

此时在访问"/config/get",观察控制台结果

可以正常拿到结果,而且结果还是属性热更新后的结果,说明整个动态刷新的效果是有效的

总结

利用spring-cloud-context提供的API来实现一个属性热更新,还是挺容易的。但这种方式是有局限性的,比如集群环境,就涉及到属性的更新同步,其次因为变更,本质是刷新bean的内存值,这就意味着服务一旦重启,刷新的值就会恢复成初始值。

可能大家会感觉spring-cloud-context提供的这个功能有点鸡肋,还不如直接用配置中心,但如果大家springcloud用得多,就会发现springcloud它可能更多提供是API抽象能力,而非具体实现。因此我们其实可以根据springcloud 提供的API扩展出一个简易版的配置中心出来

其次上述的方式有一种感觉挺实用的功能是结合业务场景,做业务属性的热替换,比如示例中的授权属性,动态添加白名单,当然使用的前提是项目中没有使用配置中心

最后再补充说明一下,上述的方式是针对加了@ConfigurationProperties注解属性的动态刷新。还有一种是加了@Value注解的属性,该属性刷新本文没介绍,不过这边提供一下@Value的实现刷新的思路。

思路如下

在引用@Value属性的bean,通常是一个controller,在这个controller加上@RefreshScope注解。当监听器监听到EnvironmentChangeEvent事件后,触发调用下

org.springframework.cloud.context.refresh.ContextRefresher#refresh

方法。就可实现@Value值变化的动态刷新。感兴趣的朋友,可以查看下方demo链接

demo链接

https://github.com/lyb-geek/springboot-learning/tree/master/springboot-config-refresh

标签:return,String,url,private,springboot,聊聊,刷新,public,属性
From: https://www.cnblogs.com/linyb-geek/p/18085121

相关文章

  • SpringBoot+Vue母婴用品商城(前后端分离)
    技术栈JavaSpringBootMavenMySQLVueElement-UIShiroMybatis-Plus系统角色功能用户管理员系统功能截图......
  • 基于SpringBoot+Vue+uniapp的邮件过滤系统的详细设计和实现(源码+lw+部署文档+讲解等)
    文章目录前言详细视频演示具体实现截图技术栈后端框架SpringBoot前端框架Vue持久层框架MyBaitsPlus系统测试系统测试目的系统功能测试系统测试结论为什么选择我代码参考数据库参考源码获取前言......
  • 基于SpringBoot+Vue+uniapp的美食推荐小程序的详细设计和实现(源码+lw+部署文档+讲解
    文章目录前言详细视频演示具体实现截图技术栈后端框架SpringBoot前端框架Vue持久层框架MyBaitsPlus系统测试系统测试目的系统功能测试系统测试结论为什么选择我代码参考数据库参考源码获取前言......
  • 基于SpringBoot+Vue+uniapp的生鲜食品订购的详细设计和实现(源码+lw+部署文档+讲解等)
    文章目录前言详细视频演示具体实现截图技术栈后端框架SpringBoot前端框架Vue持久层框架MyBaitsPlus系统测试系统测试目的系统功能测试系统测试结论为什么选择我代码参考数据库参考源码获取前言......
  • SpringBoot 配置⽂件
    主要介绍:1.SpringBoot配置⽂件的格式以及对应的语法2.了解两个配置⽂件格式的差异1.配置⽂件作⽤        计算机上有数以千计的配置⽂件,我们使⽤的绝⼤多数软件,⽐如浏览器,微信,Idea,甚⾄电脑,⼿机,都离不开配置⽂件.        ......
  • springboot常用注解大全(超详细, 30个)
    SpringBoot注解主要用于简化配置、自动装配组件和实现声明式服务。以下是详细的介绍:1、Springboot注解核心注解1.@SpringBootApplication作用:标注一个主程序类,表明这是一个SpringBoot应用程序的入口。功能:这是一个复合注解,组合了@Configuration、@EnableAutoConfigur......
  • 基于springboot+vue“智慧食堂”(毕设+实现+源码+数据库)
    摘要随着Internet的发展,人们的日常生活已经离不开网络。未来人们的生活与工作将变得越来越数字化,网络化和电子化。网上管理,它将是直接管理“智慧食堂”系统的最新形式。本LW是以构建“智慧食堂”系统为目标,使用java技术制作,由管理员和用户两大部分组成。着重论述了系统设计......
  • SpringBoot实战:轻松实现接口数据脱敏
    引言在现代的互联网应用中,数据安全和隐私保护变得越来越重要。尤其是在接口返回数据时,如何有效地对敏感数据进行脱敏处理,是每个开发者都需要关注的问题。本文将通过一个简单的SpringBoot项目,介绍如何实现接口数据脱敏。一、接口数据脱敏概述1.1接口数据脱敏的定义接口数据脱......
  • 基于SpringBoot网络安全科普小程序
    摘要随着科技的快速的发展和网络信息的普及,信息化管理已经融入到了人们的日常生活中,各行各业都开始采用信息化管理系统,通过计算机信息化管理,首先可以减轻人们工作量,而且采用信息化管理数据信息更加的严谨,可以直接实现智能化管理和数据存储。同样这样的技术也可以直接应用到网......
  • 基于SpringBoot+MySQL+SSM+Vue.js的购物商城系统(附论文)
    获取见最下方名片获取见最下方名片获取见最下方名片演示视频基于SpringBoot+MySQL+SSM+Vue.js的购物商城系统(附论文)技术描述开发工具:Idea/Eclipse数据库:MySQLJar包仓库:Maven前端框架:Vue/ElementUI后端框架:Spring+SpringMVC+Mybatis+SpringBoot文字描......