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

1.项目结构
SpringBoot+自定义注解+AOP高级玩法打造通用开关_第1张图片
2.引入依赖

    <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();
    }
}

10.Redis中把开关加上
在这里插入图片描述
11.启动服务
SpringBoot+自定义注解+AOP高级玩法打造通用开关_第2张图片
将redis中开关置为1
SpringBoot+自定义注解+AOP高级玩法打造通用开关_第3张图片
欢迎大家积极留言交流学习心得,点赞的人最美丽!

你可能感兴趣的:(Spring,Boot,spring,boot,java,AOP,注解)