自定义注解实现服务的动态开关

shigen日更文章的博客写手,擅长Java、python、vue、shell等编程语言和各种应用程序、脚本的开发。记录成长,分享认知,留住感动。

🧑‍💻🧑‍💻🧑‍💻Make things different and more efficient

接近凌晨了,今天的稿子还没来得及写,甚是焦虑,于是熬了一个夜也的给它写完。正如我的题目所说:《自定义注解实现服务动态开关》,接下来和shigen一起来揭秘吧。


前言

shigen实习的时候,遇到了业务场景:**实现服务的动态开关,避免redis的内存被打爆了。**当时的第一感受就是这个用nacos配置一下不就可以了,nacos不就是有一个注解refreshScope,配置中心的配置文件更新了,服务动态的更新。当时实现是这样的:

在我的nacos上这样配置的:

service:
	enable: true

那对应的java部分的代码就是这样的:

class Service {
  @Value("service.enable")
  private boolean serviceEnable;
  
  public void method() {
    if (!serviceEnable) {
      return;
    }
    // 业务逻辑
  }
}

貌似这样是可以的,因为我们只需要动态的观察数据的各项指标,遇到了快要打挂的情况,直接把布尔值换成false即可。


但是不优雅,我们来看看有什么不优雅的:

  1. 配置的动态刷新是有延迟的。nacos的延迟是依赖于网络的;
  2. 不亲民。万一哪个开发改坏了配置,服务就是彻底的玩坏了;而且,如果业务想做一个动态的配置,任何人都可以在系统上点击开关,类似于下边的操作:

服务开关操作

element-UI的动态开关

nacos配置的方式直接不可行了!

那给予以上的问题,相信部分的伙伴已经思考到了:那我把配置放在redis中呗,内存数据库,直接用外部接口控制数据。

很好,这种想法打开了今天的设计思路。我们先协一点伪代码:

@getMapping(value="switch") 
public Integer switch() {
    Integer status = redisTemplate.get("key");
    if (status == 1) {
      status = 0;
    } else {
      status = 1;
    }
    redisTemplate.set("key", status);
    return status;
}


@getMapping(value= "pay")
public Result pay() {
  Integer status = redisTemplate.get("key");
  if (status ==0) {
    throw new Bizexception("服务不可用");
  } else {
    doSometing();
  }
}

貌似超级完美了,但是想过没有,业务的侵入很大呢。而且,万一我的业务拓展了,别的地方也需要这样的配置,岂不是直接复制粘贴?那就到此为止吧。


我觉得任何业务的设计都是需要去思考的,一味的写代码,做着CRUD的各种操作,简直是等着被AI取代吧。

那接下来分享shigen的设计,带着大家从我的视角分析我的思考和设计点、关注点。

代码设计

注解设计
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ServiceSwitch {

    String switchKey();

    String message() default "当前业务已关闭,请稍后再试!";

}

我在设计的时候,考虑到了不同的业务模块和失败的信息,这些都可以抽取出来,在使用的时候,直接加上注解即可。具体的方法和拦截,我们采用spring的AOP来做。

常量类
public class Constants {

    public static final String ON = "1";
    public static final String OFF = "0";

    public static class Service {

        public static final String ORDER = "service-order";
        public static final String PAY = "service-pay";
    }

}

既然涉及到了业务模块和状态值,那配置一个常量类是再合适不过了。

业务代码
  @ServiceSwitch(switchKey = Constants.Service.PAY)
  public Result pay() {
      log.info("paying now");
      return Result.success();
  }

业务代码上,我们肯定喜欢这样的设计,直接加上一个注解标注我们想要控制的模块。

请注意,核心点来了,我们注解的AOP怎么设计?

AOP设计

老方式,我们先看一下代码:

@Aspect
@Component
@Slf4j
public class ServiceSwitchAOP {

    @Resource
    private RedisTemplate<String, String> redisTemplate;

    /**
     * 定义切点,使用了@ServiceSwitch注解的类或方法都拦截 需要用注解的全路径
     */
    @Pointcut("@annotation(main.java.com.shigen.redis.annotation.ServiceSwitch)")
    public void pointcut() {
    }

    @Around("pointcut()")
    public Object around(ProceedingJoinPoint point) {

        // 获取被代理的方法的参数
        Object[] args = point.getArgs();
        // 获取被代理的对象
        Object target = point.getTarget();
        // 获取通知签名
        MethodSignature signature = (MethodSignature) point.getSignature();

        try {

            // 获取被代理的方法
            Method method = target.getClass().getMethod(signature.getName(), signature.getParameterTypes());
            // 获取方法上的注解
            ServiceSwitch annotation = method.getAnnotation(ServiceSwitch.class);

            // 核心业务逻辑
            if (annotation != null) {

                String switchKey = annotation.switchKey();
                String message = annotation.message();
                /**
                 * 配置项: 可以存储在mysql、redis 数据字典
                 */
                String configVal = redisTemplate.opsForValue().get(switchKey);
                if (Constants.OFF.equals(configVal)) {
                    // 开关关闭,则返回提示。
                    return new Result(HttpStatus.FORBIDDEN.value(), message);
                }
            }

            // 放行
            return point.proceed(args);
        } catch (Throwable e) {
            throw new RuntimeException(e.getMessage(), e);
        }
    }
}

拦截我的注解,实现一个切点,之后通知切面进行操作。在切面的操作上,我们读取注解的配置,然后从redis中拿取对应的服务状态。如果服务的状态是关闭的,直接返回我们自定义的异常类型;服务正常的话,继续进行操作。

接口测试

最后,我写了两个接口实现了服务的调用和服务模块状态值的切换。

@RestController
@RequestMapping(value = "serviceSwitch")
public class ServiceSwitchTestController {

    @Resource
    private ServiceSwitchService serviceSwitchService;

    @GetMapping(value = "pay")
    public Result pay() {
        return serviceSwitchService.pay();
    }

    @GetMapping(value = "switch")
    public Result serviceSwitch(@RequestParam(value = "status", required = false) String status) {
        serviceSwitchService.switchService(status);
        return Result.success();
    }
}

代码测试

测试服务正常

服务状态正常情况下的测试

此时,redis中服务的状态值是1,服务也可以正常的调用。

测试服务不正常

我们先调用接口,改变服务的状态:

调用接口,切换服务的状态

再次调用服务:

服务模块关闭

发现服务403错误,已经不能调用了。我们改变一下状态,服务又可以用了,这里就不做展示了。


以上就是今天分享的全部内容了,觉得不错的话,记得点赞 在看 关注支持一下哈,您的鼓励和支持将是shigen坚持日更的动力。同时,shigen在多个平台都有文章的同步,也可以同步的浏览和订阅:

平台账号链接
CSDNshigen01shigen的CSDN主页
知乎gen-2019shigen的知乎主页
掘金shigen01shigen的掘金主页
腾讯云开发者社区shigenshigen的腾讯云开发者社区主页
微信公众平台shigen公众号名:shigen

shigen一起,每天不一样!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/147263.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

matlab语言的由来与发展历程

MATLAB语言的由来可以追溯到1970年代后期。当时&#xff0c;Cleve Moler教授在New Mexico大学计算机系担任系主任&#xff0c;他为了LINPACK和EISPACK两个FORTRAN程序集开发项目提供易学、易用、易改且易交互的矩阵软件而形成了最初的MATLAB。 1984年&#xff0c;MATLAB推出了…

JVM 内存区域

JVM内存结构模型 程序计数器&#xff1a; 1.线程私有的&#xff0c;是一块较小的内存空间&#xff0c;当前线程所执行的字节码的行号指示器 2.每个线程都有一个独立的程序计数器&#xff0c;各线程之间程序计数器互不影响&#xff0c;独立存储 3.此内存区域是唯一一个在java虚拟…

C++ vector中capacity()和size() 的区别

文章目录 1 capacity()和size() 介绍2 vector满了之后&#xff0c;capacity()会自动了扩充为原来的2倍 &#xff1f; 1 capacity()和size() 介绍 size是指容器当前拥有元素的个数&#xff0c; capacity是指容器在必须分配新的存储空间之前可以存放的元素总数。 如vector<i…

Linux常用命令——bzgrep命令

在线Linux命令查询工具 bzgrep 使用正则表达式搜索.bz2压缩包中文件 补充说明 bzgrep命令使用正则表达式搜索“.bz2”压缩包中文件&#xff0c;将匹配的行显示到标注输出。 语法 bzgrep(参数)参数 搜索模式&#xff1a;指定要搜索的模式&#xff1b;.bz2文件&#xff1a…

【教3妹学编程-算法题】K 个元素的最大和

3妹&#xff1a;“太阳当空照&#xff0c;花儿对我笑&#xff0c;小鸟说早早早&#xff0c;你为什么背上炸药包” 2哥 :3妹&#xff0c;什么事呀这么开发。 3妹&#xff1a;2哥你看今天的天气多好啊&#xff0c;阳光明媚、万里无云、秋高气爽&#xff0c;适合秋游。 2哥&#x…

【前端开发】JS Vue React中的通用递归函数

目录 前言 一、递归函数的由来 二、功能实现 1.后台数据 2.处理数据 3.整体代码 总结 &#x1f642;博主&#xff1a;冰海恋雨. &#x1f642;文章核心&#xff1a;【前端开发】JS Vue React中的通用递归函数 前言 大家好&#xff0c;今天和大家分享一下在前端开发中j…

基于springboot实现校园医疗保险管理系统【项目源码】

基于springboot实现校园医疗保险管理系统演示 系统开发平台 在线校园医疗保险系统中&#xff0c;Eclipse能给用户提供更多的方便&#xff0c;其特点一是方便学习&#xff0c;方便快捷&#xff1b;二是有非常大的信息储存量&#xff0c;主要功能是用在对数据库中查询和编程。其…

旺店通·企业版对接打通金蝶云星空查询调拨单接口与分布式调入单新增接口

旺店通企业版对接打通金蝶云星空查询调拨单接口与分布式调入单新增接口 源系统:旺店通企业版 旺店通是北京掌上先机网络科技有限公司旗下品牌&#xff0c;国内的零售云服务提供商&#xff0c;基于云计算SaaS服务模式&#xff0c;以体系化解决方案&#xff0c;助力零售企业数字化…

msys2 + MSVC(VS2019)编译ffmpeg6.0源码

以前使用的v1.2版&#xff0c;很多功能和使用方法发生了变化&#xff0c;需要重新编译新的ffmpeg版。 编译环境: windows 10 , VS2019, MSYS2 1. msys2 下载安装 MSYS2 , https://www.msys2.org/ 2. msys2 环境配置打开 msys2 2.1 安装相关软件 然后输入以下命令安装&…

gmpy2 GMP is_prime函数底层c代码分析

偶然看到一篇paper&#xff08;2018年发表&#xff09;&#xff0c;说GMP中的素性检测使用的是单独的Miller_Rabin方法&#xff0c;单独的Miller_Rabin素性检测会存在部分安全问题&#xff08;低概率&#xff09;&#xff0c;然后突然想求证一下最新版本的GMP中是否进行了修改。…

Python如何使用Matplotlib模块的pie()函数绘制饼形图?

Python如何使用Matplotlib模块的pie函数绘制饼形图&#xff1f; 1 模块安装2 实现思路3 pie()函数说明4 实现过程4.1 导入包4.2 定义一个类4.3 读取数据并处理4.4 定义饼图绘制方法 5 完整源码 1 模块安装 先安装matplotlib&#xff1a; pip install matplotlib安装numpy模块…

Linux Docker 图形化工具 Portainer远程访问

文章目录 前言1. 部署Portainer2. 本地访问Portainer3. Linux 安装cpolar4. 配置Portainer 公网访问地址5. 公网远程访问Portainer6. 固定Portainer公网地址 前言 Portainer 是一个轻量级的容器管理工具&#xff0c;可以通过 Web 界面对 Docker 容器进行管理和监控。它提供了可…

exsi的安装和配置

直接虚拟真实机 vcent server 管理大量的exsi SXI原生架构模式的虚拟化技术&#xff0c;是不需要宿主操作系统的&#xff0c;它自己本身就是操作系统。因此&#xff0c;装ESXI的时候就等同于装操作系统&#xff0c;直接拿iso映像(光盘)装ESXI就可以了。 VMware vCente…

在Vue.js中,什么是slot(插槽)?它的作用是什么?

聚沙成塔每天进步一点点 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 欢迎来到前端入门之旅&#xff01;感兴趣的可以订阅本专栏哦&#xff01;这个专栏是为那些对Web开发感兴趣、刚刚踏入前端领域的朋友们量身打造的。无论你是完全的新手还是有一些基础的开发…

2.6 Windows驱动开发:使用IO与DPC定时器

本章将继续探索驱动开发中的基础部分&#xff0c;定时器在内核中同样很常用&#xff0c;在内核中定时器可以使用两种&#xff0c;即IO定时器&#xff0c;以及DPC定时器&#xff0c;一般来说IO定时器是DDK中提供的一种&#xff0c;该定时器可以为间隔为N秒做定时&#xff0c;但如…

万宾科技内涝积水监测仪效果,预警城市积水

当城市之中出现强降雨或者大暴雨&#xff0c;可能会导致雨水不断堆积到城市排水管网之中&#xff0c;可能还会淹没城市的排水系统时&#xff0c;这种现象被称为城市之中的内涝&#xff0c;并且在许多城市之中内涝问题日益引起人们的关注。 内涝积水监测仪的出现成为了希望的灯塔…

4种互斥机制比较

4种互斥机制 关中断禁止任务切换信号量互斥信号量 关中断 关中断&#xff08;Disable Interrupts&#xff09;&#xff1a;通过禁用中断来实现互斥。在关中断期间&#xff0c;任何中断请求都会被忽略&#xff0c;从而确保了临界区的独占性。然而&#xff0c;这种方法会导致系统…

【python 生成器 面试必备】yield关键字,协程必知必会系列文章--自己控制程序调度,体验做上帝的感觉 1

python生成器系列文章目录 第一章 yield — Python (Part I) 文章目录 python生成器系列文章目录前言1. Generator Function 生成器函数2.并发和并行&#xff0c;抢占式和协作式2.Let’s implement Producer/Consumer pattern using subroutine: 生成器的状态 generator’s st…

Opencv!!在树莓派上安装Opencv!

一、更新树莓派系统 sudo apt-get update sudo apt-get upgrade二、安装python-opencv sudo apt-get install libopencv-dev sudo apt-get install python3-opencv三、查看是否安装成功 按以下命令顺序执行&#xff1a; python import cv2 cv2.__version__如果出现版本号&a…

Linux文件系统之inode

文章目录 1. 磁盘1.1 认识磁盘1.2 磁盘物理构造1.3 磁盘逻辑结构 2. 文件系统3. 如何理解目录 1. 磁盘 1.1 认识磁盘 文件 内容 属性&#xff0c;而文件是存储在磁盘上&#xff0c;那么可以理解为磁盘上存储的文件 存储的文件内容 存储的文件属性。 文件的内容采用的是块式…