<dependencies>
<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>
</dependencies>
3.yml配置
server:
port: 8080
spring:
redis:
database: 0
host: x.x.x.x
port: 6379
password: 123456
jedis:
pool:
max-active: 10
max-wait: -1ms
max-idle: 5
min-idle: 1
4.自定义注解
package com.example.springbootaop.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 通用开关注解
* @author shixc
* 2023/10/17
*/
@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.定义常量
package com.example.springbootaop.constant;
/**
* 常量
* @author shixc
* 2023/10/17
*/
public class Constant {
// .... 其他业务相关的常量 ....
// 配置相关的常量
public static class ConfigCode {
// 挂号支付开关(0:关,1:开)
public static final String REG_PAY_SWITCH = "reg_pay_switch";
// 其他业务相关的配置常量
// ....
}
}
6.AOP核心实现
package com.example.springbootaop.aop;
import com.example.springbootaop.annotation.ServiceSwitch;
import com.example.springbootaop.constant.Constant;
import com.example.springbootaop.util.Result;
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.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
/**
* AOP核心实现
* @author shixc
* 2023/10/17
*/
@Aspect
@Component
public class ServiceSwitchAOP {
private final StringRedisTemplate redisTemplate;
public ServiceSwitchAOP(StringRedisTemplate redisTemplate) {
this.redisTemplate = redisTemplate;
}
/**
* 定义切点,使用了@ServiceSwitch注解的类或方法都拦截
*/
@Pointcut("@annotation(com.example.springbootaop.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 Result.fail(HttpStatus.FORBIDDEN.value()+"", message);
}
}
// 放行
return point.proceed(args);
} catch (Throwable e) {
throw new RuntimeException(e.getMessage(), e);
}
}
}
7.使用注解
package com.example.springbootaop.service;
import com.example.springbootaop.annotation.ServiceSwitch;
import com.example.springbootaop.constant.Constant;
import com.example.springbootaop.util.Result;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
/**
* @author shixc
* 2023/10/17
*/
@Service
public class RegService {
/**
* 挂号下单
*/
@ServiceSwitch(switchKey = Constant.ConfigCode.REG_PAY_SWITCH)
public Result createOrder() {
// 具体下单业务逻辑省略....
return Result.succeed("挂号下单成功");
}
}
8.工具类
import java.io.Serializable;
/**
* @author shixc
* 2023/10/17
*/
public final class Result<T> implements Serializable {
private static final long serialVersionUID = -8780880627029425781L;
private String code;
private long timestamp = System.currentTimeMillis();
private String message;
private T data;
public Result() {
}
private Result(String code, String message, T data) {
this.code = code;
this.message = message;
this.data = data;
}
public String getCode() {
return this.code;
}
public long getTimestamp() {
return this.timestamp;
}
public String getMessage() {
return this.message;
}
public T getData() {
return this.data;
}
public static <T> Result<T> succeed() {
return new Result("0", "", (Object) null);
}
public static <T> Result<T> succeed(T data) {
return new Result("0", "", data);
}
public static <T> Result<T> panic() {
return new Result("000000", "系统运行发生异常,请联系IT处理", (Object) null);
}
public static <T> Result<T> illegalArgument(String message) {
return new Result("000001", message, (Object) null);
}
public static <T> Result<T> fail(String code, String message) {
return new Result(code, message, (Object) null);
}
public static <T> Result<T> fail(String code, String message, T data) {
return new Result(code, message, data);
}
public boolean success() {
return this.code.equals("0");
}
public boolean failure() {
return !this.success();
}
}
9.测试接口
package com.example.springbootaop.controller;
import com.example.springbootaop.service.RegService;
import com.example.springbootaop.util.Result;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 挂号Controller
* @author shixc
* 2023/10/17
*/
@RestController
@RequestMapping("reg")
public class RegController {
private final RegService regService;
public RegController(RegService regService) {
this.regService = regService;
}
@GetMapping("/create")
public Result createOrder() {
return regService.createOrder();
}
}