SpringBoot + 自定义注解 + AOP 高级玩法打造通用开关

news2025/1/8 12:46:14

前言

最近在工作中迁移代码的时候发现了以前自己写的一个通用开关实现,发现挺不错,特地拿出来分享给大家。

为了有良好的演示效果,我特地重新建了一个项目,把核心代码提炼出来加上了更多注释说明,希望xdm喜欢。

案例

1、项目结构

image

2、引入依赖
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

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

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

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>
3、yml配置

连接Redis的配置修改成自己的

server:
  port: 8888

spring:
  redis:
    database: 6
    host: xx.xx.xx.xx
    port: 6379
    password: 123456
    jedis:
      pool:
        max-active: 100
        max-wait: -1ms
        max-idle: 50
        min-idle: 1
4、自定义注解

这里稍微说明下,定义了一个key对应不同功效的开关,定义了一个val作为开关是否打开的标识,以及一个message作为消息提示。

package com.example.commonswitch.annotation;

import com.example.commonswitch.constant.Constant;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * <p>
 * 通用开关注解
 * </p>
 *
 * @author 程序员济癫
 * @since 2023-10-16 17:38
 */
@Target({ElementType.METHOD})  // 作用在方法上
@Retention(RetentionPolicy.RUNTIME)  // 运行时起作用
public @interface ServiceSwitch {

   /**
    * 业务开关的key(不同key代表不同功效的开关)
    * {@link Constant.ConfigCode}
    */
   String switchKey();

   // 开关,0:关(拒绝服务并给出提示),1:开(放行)
   String switchVal() default "0";

   // 提示信息,默认值可在使用注解时自行定义。
   String message() default "当前请求人数过多,请稍后重试。";
}
5、定义常量

主要用来存放各种开关的key

package com.example.commonswitch.constant;

/**
 * <p>
 * 常量类
 * </p>
 *
 * @author 程序员济癫
 * @since 2023-10-16 17:45
 */
public class Constant {

   // .... 其他业务相关的常量 ....

   // 配置相关的常量
   public static class ConfigCode {

      // 挂号支付开关(0:关,1:开)
      public static final String REG_PAY_SWITCH = "reg_pay_switch";
      // 门诊支付开关(0:关,1:开)
      public static final String CLINIC_PAY_SWITCH = "clinic_pay_switch";

      // 其他业务相关的配置常量
      // ....
   }
}
6、AOP核心实现

核心实现中我专门加了详细的注释说明,保证大家一看就懂,而且把查询开关的方式列举出来供大家自己选择。

package com.example.commonswitch.aop;

import com.example.commonswitch.annotation.ServiceSwitch;
import com.example.commonswitch.constant.Constant;
import com.example.commonswitch.exception.BusinessException;
import com.example.commonswitch.util.Result;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

/**
 * <p>
 * 开关实现的切面类
 * </p>
 *
 * @author 程序员济癫
 * @since 2023-10-16 17:56
 */
@Aspect
@Component
@Slf4j
@AllArgsConstructor
public class ServiceSwitchAOP {

   private final StringRedisTemplate redisTemplate;

   /**
    * 定义切点,使用了@ServiceSwitch注解的类或方法都拦截
    */
   @Pointcut("@annotation(com.example.commonswitch.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 switchVal = annotation.switchVal();
            String message = annotation.message();

            /*
              获取配置项说明
              这里有两种方式:1、配置加在Redis,查询时从Redis获取;
                          2、配置加在数据库,查询时从表获取。(MySQL单表查询其实很快,配置表其实也没多少数据)
              我在工作中的做法:直接放到数据库,但是获取配置项的方法用SpringCache缓存,
                           然后在后台管理中操作配置项,变更时清理缓存即可。
                           我这么做就是结合了上面两种各自的优点,因为项目中配置一般都是用后台管理来操作的,
                           查表当然更舒适,同时加上缓存提高查询性能。
             */

            // 下面这块查询配置项,大家可以自行接入并修改。
            // 数据库这么查询:String configVal = systemConfigService.getConfigByKey(switchKey);
            // 这里我直接从redis中取,使用中大家可以按照意愿自行修改。
            String configVal = redisTemplate.opsForValue().get(Constant.ConfigCode.REG_PAY_SWITCH);
            if (switchVal.equals(configVal)) {
               // 开关打开,则返回提示。
               return new Result<>(HttpStatus.FORBIDDEN.value(), message);
            }
         }

         // 放行
         return point.proceed(args);

      } catch (Throwable e) {
         throw new BusinessException(e.getMessage(), e);
      }
   }
}
7、使用注解

我们定义一个服务来使用这个开关,我设定了一个场景是挂号下单,也就是把开关用在支付业务这里。

因为支付场景在线上有可能出现未知问题,比如第三方rpc调用超时或不响应,或者对方业务出现缺陷,导致我方不断出现长款,那么我们此时立马操作后台将支付开关关掉,能最大程度止损。

package com.example.commonswitch.service;

import com.example.commonswitch.annotation.ServiceSwitch;
import com.example.commonswitch.constant.Constant;
import com.example.commonswitch.util.Result;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;

/**
 * <p>
 * 挂号服务
 * </p>
 *
 * @author 程序员济癫
 * @since 2023-10-16 18:48
 */
@Service
public class RegService {

   /**
    * 挂号下单
    */
   @ServiceSwitch(switchKey = Constant.ConfigCode.REG_PAY_SWITCH)
   public Result createOrder() {

      // 具体下单业务逻辑省略....

      return new Result(HttpStatus.OK.value(), "挂号下单成功");
   }
}
8、测试效果

好了,接下来我们定义一个接口来测试效果如何。

package com.example.commonswitch.controller;

import com.example.commonswitch.service.RegService;
import com.example.commonswitch.util.Result;
import lombok.AllArgsConstructor;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * <p>
 * 挂号接口
 * </p>
 *
 * @author 程序员济癫
 * @since 2023-10-16 18:51
 */
@RestController
@RequestMapping("/api/reg")
@AllArgsConstructor
public class RegController {

   private final RegService regService;

   @PostMapping("/createOrder")
   public Result createOrder() {

      return regService.createOrder();
   }
}

Redis中把开关加上去(实际工作中是后台添加的哈),此时开关是1,表示开关打开。

image

调接口,可以发现,目前是正常的业务流程。

image

接下来,我们假定线上出了问题,要立马将开关关闭。(还是操作Redis,实际工作中是后台直接关掉哈)

我们将其改为0,也就是表示开关给关闭。

image

看效果,OK,没问题,是我们预想的结果。

image

这里要记住一点,提示可以自定义,但是不要直接返回给用户系统异常,给一个友好提示即可。

下载

Gitee:https://gitee.com/fangfuji/java-share

找到相关的博文名称,然后下载到本地IDEA运行即可。

总结

文中使用到的技术主要是这些:SpringBoot、自定义注解、AOP、Redis、Lombok。

其中,自定义注解和AOP是核心实现,Redis是可选项,你也可以接入到数据库。

lombok的话大家可以仔细看代码,我用它帮助省略了所有@Autowaird,这样就使用了官方及IDEA推荐的构造器注入方式。

好了,今天的小案例,xdm学会了吗。


如果喜欢,请点赞+关注↓↓↓,持续分享干货哦!

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

图解Dubbo,Dubbo 服务治理详解

目录 一、介绍1、介绍 Dubbo 服务治理的基本概念和重要性2、阐述 Dubbo 服务治理的实现方式和应用场景 二、Dubbo 服务治理的原理1、Dubbo 服务治理的架构设计2、Dubbo 服务治理的注册与发现机制3、Dubbo 服务治理的负载均衡算法 三、Dubbo 服务治理的实现方式1、基于 Docker 容…

Flowable介绍及使用示例

文章目录 Flowable简介底层实现JavaSpring FrameworkMyBatisActiviti Flowable的使用示例引入依赖创建流程定义部署流程定义启动流程实例启动流程实例处理任务监控流程实例 高级用法流程监听器事件驱动定时任务其他高级功能 使用时可能遇到的问题和注意事项结论参考文献 Flowab…

微信群发消息怎么发?群发消息,只要这4个步骤!

微信是我们日常生活中使用最广泛的社交软件之一。用户通过微信可以向好友、家人、同事等联系人发送文字、图片、视频、语音、文件等信息&#xff0c;是一款非常实用的即时通信应用程序。 除了与好友进行单独聊天&#xff0c;我们有时候可能也需要将信息进行群发。但是还有很多…

又要报销了,还在手动下载整理发票吗?

大多数公司都是每个月定期提交报销&#xff0c;一般报销用的发票都是电子发票发到邮箱&#xff0c;每次要报销时都需要登录邮箱&#xff0c;点开邮件&#xff0c;一个个下载整理&#xff0c;工作量不大&#xff0c;但是发票多了也着实很烦。这个月终于下决心把这个过程自动化一…

事务管理 vs. 锁控制:你真的分得清吗?何时使用何种并发控制策略?

分布式锁和事务是分布式系统中两个重要的概念&#xff0c;它们都用于解决分布式环境下的数据一致性问题。 一、概念 分布式锁 分布式锁是一种用于在分布式环境中控制对共享资源访问的锁。分布式锁可以防止多个进程或线程同时访问共享资源&#xff0c;从而避免数据冲突和资源…

Mini小主机All-in-one搭建教程4-安装Windows11系统

Mini小主机All-in-one搭建教程4-安装Windows11系统 硬件介绍 在狗东买的 极摩客M2 到手价是2799元 具体配置如下&#xff1a; 酷睿英特尔11代标压i7 11390H 64G1TB固态。 以下是 安装Windows11系统的教程。 安装Windows11系统 下载镜像包 首先下Windows系统的懒人镜像包&…

【特纳斯电子】基于物联网的智能油烟机-仿真设计

视频及资料链接&#xff1a;基于物联网的智能油烟机-仿真设计 - 电子校园网 (mcude.com) 编号&#xff1a; T0332203M-FZ 设计简介&#xff1a; 本设计是基于物联网的智能油烟机系统&#xff0c;主要实现以下功能&#xff1a; 1.通过OLED显示燃气浓度&#xff1b; 2.可通过…

多媒体应用设计师 第4章 移动多媒体技术基础

1.移动多媒体技术基础 1.1.移动互联网的定义 移动互联网是指利用互联网提供的技术、平台、应用以及商业模式&#xff0c;与移动通信技术相结合并用于实践活动的总称。 1.2.移动互联网的特征 移动互联网三个层面&#xff1a;终端、软件、应用 移动互联网特征&#xff1a;2版…

在搜狗浏览器中设置代理

要在搜狗浏览器中设置代理&#xff0c;请按照以下步骤操作&#xff1a; 打开搜狗浏览器。在浏览器顶部菜单栏中点击“设置”&#xff08;一般位于右上角&#xff09;。在设置菜单中点击“代理设置”。在代理设置页面中&#xff0c;将“使用代理”选项设置为“自动检测”或“al…

通讯网关软件026——利用CommGate X2ORACLE-U实现OPC UA数据转入ORACLE

本文介绍利用CommGate X2ORACLE-U实将OPC UA数据源中的数据转入到ORACLE数据库。CommGate X2ORACLE-U是宁波科安网信开发的网关软件&#xff0c;软件可以登录到网信智汇(http://wangxinzhihui.com)下载。 【案例】如下图所示&#xff0c;将OPC UA数据源的数据写入到ORACLE数据…

ssh 报错:Permission denied, please try again.

报错问题&#xff1a;执行一条远程scp远程拷贝&#xff0c;在此之前已配置好ssh无密登录&#xff0c; sudo scp -r hadoop-3.2.0 slave2:/usr/local/src/ 确保 /etc/ssh/sshd_config文件下 PasswordAuthentication no 改为 PasswordAuthentication yes 和 PermitRootLogin no …

云爬虫系统设计:云平台资源管理优化爬虫性能

目录 1、云爬虫系统概述 2、云平台资源管理优化爬虫性能的关键措施 2.1 资源池化 2.2 负载均衡 2.3 任务调度 2.4 异常处理和恢复 2.5 数据存储与处理 2.6 数据清洗和去重 2.7 分布式爬虫 2.8 任务优先级与质量 2.9节能与环保 2.10监控与日志 总结 随着互联网的快…

成都瀚网科技:如何有效运营抖店来客呢?

随着电子商务的快速发展和移动互联网的普及&#xff0c;越来越多的企业开始将目光转向线上销售渠道。其中&#xff0c;抖音成为备受关注的平台。作为中国最大的短视频社交平台之一&#xff0c;抖音每天吸引数亿用户&#xff0c;这也为企业提供了巨大的商机。那么&#xff0c;如…

解决github打开慢的问题

1&#xff0c;修改hosts&#xff08;可以从这个链接 https://raw.hellogithub.com/hosts 获取对应的host配置&#xff09;。 140.82.112.3 github.com 151.101.1.194 github.global.ssl.fastly.net 2&#xff0c;刷新dns缓存。 # 打开CMD运行如下命令 ipconfig /flushdns 之…

MATLAB-自动批量读取文件,并按文件名称或时间顺序进行数据处理

我在处理文件数据时&#xff0c;发现一个一个文件处理效率太低&#xff0c;因此学习了下MATLAB中自动读取特定路径下文件信息的程序&#xff0c;并根据读取信息使用循环进行数据处理&#xff0c;提高效率&#xff0c;在此分享给大家这段代码并给予一些说明&#xff0c;希望能为…

小程序新增功能页面

需求背景: 小程序主页面有个报名板块,我打算替换主页面报名板块菜单,迁移到我的页面里面, 替换成资讯栏目,我喜欢分享最新技术,开源课题,IT资讯,本想做成论坛的效果,由于时间问题,先替换添加板块 替换后效果: 模块功能: 添加、修改、删除、查看 文件目录:// 添…

git本地仓库及远端仓库推送【linux】

git本地仓库及远端仓库推送【linux】 一.git上创建仓库二.linux中git三板斧i.检查是否安装gitii.克隆仓库到本地iii.提交到本地仓库iiii.上传到远端仓库 三.其他内容补充git loggit status.gitignore 一.git上创建仓库 已经创建好的可以直接跳到第二步进入到创建仓库界面&…

Qt应用开发(基础篇)——头部视图 QHeaderView

一、前言 QHeaderView类继承于QAbstractItemView&#xff0c;为项目视图(QTableView、QTreeView等)提供标题行或标题列。 树结构视图 QTreeView 表格视图 QTableView 视图基类 QAbstractItemView QHeaderView有section的概念&#xff0c;表示整条标题栏的一个个小部分&#xff…

为小公司申请企业邮箱的步骤和方法

对于小公司来说&#xff0c;拥有自己的企业邮箱不仅可以提高公司的专业形象&#xff0c;还可以更好地管理内部和外部的通信。小公司应该如何申请企业邮箱呢&#xff1f;以下是一份详尽的指南。 小公司应该如何申请企业邮箱呢&#xff1f;基本上由三步组成&#xff1a;确定自己的…

class的get和set

class的get和set 一、使用场景二、代码实现 一、使用场景 当我们需要在用户获取或设置实例某个属性的时候做一些附加的操作的时候&#xff0c;就能利用这个特性。 二、代码实现 class Person {#name #age 0 // 设置私有属性存储值&#xff0c;避免被外部修改constructor(na…