

SpringBoot + 自定义注解 + AOP 高级玩法打造通用开关 - 程序员济癫
source link: https://www.cnblogs.com/fulongyuanjushi/p/17768637.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

SpringBoot + 自定义注解 + AOP 高级玩法打造通用开关
最近在工作中迁移代码的时候发现了以前自己写的一个通用开关实现,发现挺不错,特地拿出来分享给大家。
为了有良好的演示效果,我特地重新建了一个项目,把核心代码提炼出来加上了更多注释说明,希望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学会了吗。
如果喜欢,请点赞+关注↓↓↓,持续分享干货哦!
__EOF__
Recommend
-
57
springboot + shiro 权限注解、统一异常处理、请求乱码解决 后台权限管理系统 20200808新版本更新 版本升级及内容优化版本,改动内容: 版本更新,springboot从1.5+升级到2.1+; 权限缓...
-
27
-
4
环境:SpringBoot2.3.8.RELEASE + JDK1.8 本文教你如何在SpringBoot环境下使得自定义的注解能够使用${xxx}表达式。 相关依赖
-
7
【DB系列】SpringBoot缓存注解@Cacheable之自定义key策略及缓存失效时间指定 ...
-
7
在实际生产项目中,经常需要对如身份证信息、手机号、真实姓名等的敏感数据进行加密数据库存储,但在业务代码中对敏感信息进行手动加解密则十分不优雅,甚至会存在错加密、漏加密、业务人员需要知道实际的加密规则等的情况。 本文将介绍使用springboot+m...
-
8
巧用自定义注解,一行代码搞定审计日志 任何一个软件系统,都不可避免的会碰到【
-
4
SpringBoot使用自定义注解+AOP+Redis实现接口限流
-
5
SpringBoot下Validation自定义验证注解(简单实现) 精选 原创 代码不是马 2022-10-...
-
9
我们在企业级的开发中,必不可少的是对日志的记录,实现有很多种方式,常见的就是基于AOP+注解进行保存,但是考虑到程序的流畅和效率,我们可以使用异步进行保存,小编最近在spring和springboot源码中看到有很多的监听处理贯...
-
6
1 springBoot的依赖 确定项目中包含可以注解的依赖 <dependency> <groupId>org.springframework.boot</groupId> <...
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK