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

前言

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

为了有良好的演示效果,我特地重新建了一个项目,把核心代码提炼出来加上了更多注释说明,希望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: 8888spring:redis:database: 6host: xx.xx.xx.xxport: 6379password: 123456jedis:pool:max-active: 100max-wait: -1msmax-idle: 50min-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.rhkb.cn/news/160326.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SQL注入漏洞

0x01 漏洞介绍 泛微e-office系统是标准、易用、快速部署上线的专业协同OA软件&#xff0c;国内协同OA办公领域领导品牌&#xff0c;致力于为企业用户提供专业OA办公系统、移动OA应用等协同OA整体解决方案。泛微e-office深谙改革之道以迎变革之机&#xff0c;沉心产品研发数十载…

电子笔记真的好用吗?手机上适合记录学习笔记的工具

提及笔记&#xff0c;不少人都会和学习挂钩&#xff0c;的确学习过程中我们经常会遇到很多难题&#xff0c;而经常记录笔记可以有效地帮助大家记住很多知识&#xff0c;而且时常拿出笔记查看一下&#xff0c;可方便巩固过去学习的知识。 手机作为大家日常随身携带的工具&#…

Matlab进阶绘图第31期—桑基图(Sankey Chart)

桑基图&#xff08;Sankey Chart&#xff09;本质为一种流程图&#xff0c;可以很好地展示数据的层次结构以及流量变化。 桑基图主要由节点块与流动路径线组成。 其中&#xff0c;节点块用于表示类别&#xff1b;流动路径线除了可以直观地表示流动的方向&#xff0c;其宽度还…

【EI会议征稿】第九届能源资源与环境工程研究进展国际学术会议(ICAESEE 2023)

第九届能源资源与环境工程研究进展国际学术会议&#xff08;ICAESEE 2023&#xff09; 2023 9th International Conference on Advances in Energy Resources and Environment Engineering 第九届能源资源与环境工程研究进展国际学术会议&#xff08;ICAESEE 2023&#xff09;…

补体C3/C4(C3/C4)介绍

补体是一种血清蛋白质&#xff0c;存在于人和脊椎动物血清及组织液中&#xff0c;不耐热&#xff0c;活化后具有酶活性、可介导免疫应答和炎症反应。可被抗原-抗体复合物或微生物所激活&#xff0c;导致病原微生物裂解或被吞噬。可通过三条既独立又交叉的途径被激活&#xff0c…

【MATLAB源码-第48期】基于matlab的16QAM信号盲解调仿真。

操作环境&#xff1a; MATLAB 2022a 1、算法描述 16QAM (16个象限幅度调制) 是一种广泛使用的数字调制技术。在无线和有线通信系统中&#xff0c;为了在固定的带宽内发送更多的信息&#xff0c;高阶调制如16QAM被使用。下面是16QAM盲解调的基本步骤、优缺点及应用场景。 16Q…

从零开始学习调用百度地图网页API:一、注册百度地图账号

目录 注册账号申请AK 注册账号 https://lbsyun.baidu.com/index.php?titlejspopular3.0/guide/getkey JavaScript API只支持浏览器类型的ak 申请AK 注&#xff1a;使用示例时&#xff0c;需要在百度地图示例加上https:&#xff0c;替换ak。

蓝桥杯——递增序列和货物摆放

文章目录 前言一、递增序列二、货物摆放总结 前言 多多练习 一、递增序列 解题思路&#xff1a; 代码实现&#xff1a; 当然我的这个代码也不是很正确 发这道题就是向大家集思广益&#xff0c;我的就只作为一个参考啦~ #include <stdio.h> #include <stdlib.h>…

提升医疗服务质量:将互联网医院源码应用于实践

随着科技的快速发展&#xff0c;医疗行业也亟需寻求创新的解决方案来提升服务质量。在这个数字化时代&#xff0c;互联网医院源码成为了引人注目的选择&#xff0c;为医疗机构和患者之间的沟通和协作提供了前所未有的便利。作为该领域的专家&#xff0c;我将介绍互联网医院源码…

Linux:Termius连接本地虚拟机与虚拟机快照

Termius连接本地虚拟机与虚拟机快照 1. Termius连接本地虚拟机2. 虚拟机快照与还原2.1 设置快照以及恢复 附录 1. Termius连接本地虚拟机 ifconfig -a 查看配置 连接成功 2. 虚拟机快照与还原 在学习阶段我们无法避免的可能损坏Linux操作系统。 如果损坏的话&#xff0c;重新…

【(数据结构)- 顺序表的实现】

顺序表的实现 一.数据结构的相关概念1、什么是数据结构2、为什么需要数据结构&#xff1f; 二.顺序表1.顺序表的概念及结构1.1 线性表 2、顺序表分类3、动态顺序表的实现&#xff08;1&#xff09;头文件 —— &#xff08;顺序结构的创建和相关操作函数的定义&#xff09;(2) …

DCDC Buck电路地弹造成的影响

很多读者都应该听过地弹&#xff0c;但是实际遇到的地弹的问题应该很少。本案例就是一个地弹现象导致电源芯片工作不正常的案例。 問題描述 如下图1 &#xff0c;产品其中一个供电是12V转3.3V的电路&#xff0c;产品发货50K左右以后&#xff0c;大约有1%的产品无法启动&#…

图解Dubbo,Dubbo 服务治理详解

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

基于Vue+webpack之H5打包资源优化

前言 基于公司的业务以及今年接触到的项目大部分都是APP混合开发&#xff0c;即原生Android/ios H5页面开发APP。项目从产品需求的评审到方案的评审再到开发提测...这一套流程下来让我收货颇多。总想找个时间好好记录一番&#xff0c;大概还是自己懒惰了&#xff0c;一直拖到现…

【OpenCV-PyQt5-PyGame-imutils】探索Python中的图像和视频捕获:性能分析与选择指南

前言 随着计算机视觉和多媒体应用的不断发展&#xff0c;图像和视频捕获变得越来越重要。在Python中&#xff0c;有多种库和工具可供选择&#xff0c;用于打开摄像头、捕获图像、以及处理视频流。本文旨在为读者提供对这些捕获方法的全面了解&#xff0c;并介绍如何计算平均帧…

JVM类装载器详解

目录 一、类装载的过程 1.1 装载(Load) 1.2 链接(Link) 1.2.1 验证(Varify) 二、类装载器组成 1. JVM 中内置了三个重要的 ClassLoader&#xff0c;同时按如下顺序进行加载&#xff1a; 2、图解 3、加载原则 所谓的双亲委派 类加载器负责在运行时将Java类动态加载到Java虚拟机&…

零售数据分析模板鉴赏-品类销售结构报表

不管是服装零售&#xff0c;还是连锁超市或者其他&#xff0c;只要是零售行业就绕不过商品数据分析&#xff0c;那么商品数据分析该怎么做&#xff1f;奥威BI的零售数据分析方案早早就预设好相关报表模板&#xff0c;点击应用后&#xff0c;一键替换数据源&#xff0c;立得新报…

论文阅读:Segment Any Point Cloud Sequences by Distilling Vision Foundation Models

目录 概要 Motivation 整体架构流程 技术细节 小结 论文地址&#xff1a;[2306.09347] Segment Any Point Cloud Sequences by Distilling Vision Foundation Models (arxiv.org) 代码地址&#xff1a;GitHub - youquanl/Segment-Any-Point-Cloud: [NeurIPS23 Spotlight]…

学生用什么样的台灯比较好?分享最合适学生使用的台灯

随着现在生活水平的提高&#xff0c;越来越多人重视健康的问题。尤其是对于孩子&#xff0c;很多家长对其可谓是百般担心、千般呵护&#xff0c;害怕出现什么问题&#xff0c;其中最主要的就是近视。而如今&#xff0c;我国青少年儿童的近视率可不低的&#xff0c;达到了52.7%&…

字符与数字的相互转换

一、字符转数字 char类型字符转换为数字&#xff0c;其实是转换为ASCII码值 有两种方式&#xff1a; 1.强制类型转换&#xff0c;结果为对应的ASCII码值 char v1 a;char v2 z;char v3 1;char v4 9;int num1 (int)v1;int num2 (int)v2;int num3 (int)v3;int num4 (int)v…