前言

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

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

案例

1、项目结构

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,表示开关打开。

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

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

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

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

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

总结

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

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

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

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


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

SpringBoot + 自定义注解 + AOP 高级玩法打造通用开关的更多相关文章

  1. java/springboot自定义注解实现AOP

    java注解 即是注释了,百度解释:也叫元数据.一种代码级别的说明. 个人理解:就是内容可以被代码理解的注释,一般是一个类. 元数据 也叫元注解,是放在被定义的一个注解类的前面 ,是对注解一种限制. ...

  2. SpringBoot 自定义注解 实现多数据源

    SpringBoot自定义注解实现多数据源 前置学习 需要了解 注解.Aop.SpringBoot整合Mybatis的使用. 数据准备 基础项目代码:https://gitee.com/J_look/ ...

  3. 轻量级高性能ORM框架:Dapper高级玩法

    Dapper高级玩法1: 数据库中带下划线的表字段自动匹配无下划线的Model字段. Dapper.DefaultTypeMap.MatchNamesWithUnderscores = true; 备 ...

  4. 【ASP.NET Core】依赖注入高级玩法——如何注入多个服务实现类

    依赖注入在 ASP.NET Core 中起中很重要的作用,也是一种高大上的编程思想,它的总体原则就是:俺要啥,你就给俺送啥过来.服务类型的实例转由容器自动管理,无需我们在代码中显式处理. 因此,有了依 ...

  5. 【Python基础】random 的高级玩法

    random 模块的高级玩法 1.python 随机产生姓名 方式一: import random xing = [ '赵', '钱', '孙', '李', '周', '吴', '郑', '王', ' ...

  6. [技术博客] SPRINGBOOT自定义注解

    SPRINGBOOT自定义注解 在springboot中,有各种各样的注解,这些注解能够简化我们的配置,提高开发效率.一般来说,springboot提供的注解已经佷丰富了,但如果我们想针对某个特定情景 ...

  7. Word 查找替换高级玩法系列之 -- 段首批量添加字符

    打开「查找和替换」输入框,按照下图操作: 更多查找替换高级玩法,参看:Word查找替换高级玩法系列 -- 目录篇 未完 ...... 点击访问原文(进入后根据右侧标签,快速定位到本文)

  8. Word 查找替换高级玩法系列之 -- 把论文中的缩写词快速变成目录下边的注释表

    1. 前言 问题:Word写论文如何把文中的缩写快速转换成注释表? 原来样子: 想要的样子: 2. 步骤 使用查找替换高级用法,替换缩写顺序 选中所有文字 打开查找替换对话框,输入以下表达式: 替换后 ...

  9. maven 高级玩法

    maven 高级玩法 标签(空格分隔): maven 实用技巧 Maven 提速 多线程 # 用 4 个线程构建,以及根据 CPU 核数每个核分配 1 个线程进行构建 $ mvn -T 4 clean ...

  10. SpringCloud微服务实战——搭建企业级开发框架(三十九):使用Redis分布式锁(Redisson)+自定义注解+AOP实现微服务重复请求控制

      通常我们可以在前端通过防抖和节流来解决短时间内请求重复提交的问题,如果因网络问题.Nginx重试机制.微服务Feign重试机制或者用户故意绕过前端防抖和节流设置,直接频繁发起请求,都会导致系统防重 ...

随机推荐

  1. 西门子S7系列PLC以太网通讯处理器编程调试方法

    捷米特(北京)科技有限公司研发的捷米特以太网通讯模块,转以太网通讯模块型号有ETH-S7200-JM01和ETH-S7300-JM01,适用于西门子S7-200/S7-300/S7-400.SMART ...

  2. 即构发布 LCEP 产品「RoomKit」 ,实现房间内0代码接入

    2021年2月5日,即构科技正式发布全新形态「低代码互动平台」(Low-code Engagement Platform,简称LCEP)产品「RoomKit」. RoomKit定位为低代码互动平台(L ...

  3. keycloak~AbstractJsonUserAttributeMapper的作用

    AbstractJsonUserAttributeMapper 它是一个抽象类,用来更新条件更新用户属性(user_attribute)的信息,我们在实现自己的mapper时,需要关注3个方法,下面分 ...

  4. Spring的依赖注入方式(set及constructor)

    Bean的依赖注入方式: set方法注入 P命名空间注入本质也是set方法注入,但比起上面的set方法进行注入更加方便,主要体现在配置文件中,如下: 首先,引入P命名空间: xmlns:p=" ...

  5. 达梦数据库: SQL查询报错《不是 GROUP BY 表达式解决方法》

    报错信息: ****: 第4 行附近出现错误: 不是 GROUP BY 表达式 修改办法: 达梦可以配置兼容参数,COMPATIBLE_MODE=4,静态参数,需要重启数据库后生效! sp_set_p ...

  6. 记一次 .NET 某物流API系统 CPU爆高分析

    一:背景 1. 讲故事 前段时间有位朋友找到我,说他程序CPU直接被打满了,让我帮忙看下怎么回事,截图如下: 看了下是两个相同的程序,既然被打满了那就抓一个 dump 看看到底咋回事. 二:为什么会打 ...

  7. python3使用ESL和sipp自动多轮压测FreeSWITCH

    环境:CentOS 7.6_x64   FreeSWITCH版本 :1.10.9   sipp版本:3.6.1   python版本:3.9.12 日常工作中,有时会遇到批量自动压测FreeSWITC ...

  8. 修复grub分区

    修复grub分区 实验环境:grup.cfg文件丢失,引导出错 1,删除grup.cfg配置文件 2,重启虚拟机 3,重启进入救援模式 再读进度条的时候快速点击Esc键 选着光驱或者u盘启动  

  9. 无linux基础也能熟练掌握git的基本操作

    git是一个用来管理项目的工具,它的远程仓库有github.gitee.gitlab代码托管中心,既可以用于个人共享代码,又可以用于团队进行项目的协作与发布,那么我们一起来了解一下git该如何使用~ ...

  10. 【go笔记】标准库-strings

    标准库-strings 前言 标准库strings用于处理utf-8编码的字符串. 字符串比较-Compare func Compare(a,b string) int 若 a==b ,则返回0:若 ...