• 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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    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 "当前请求人数过多,请稍后重试。";
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    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";
            // 其他业务相关的配置常量
            // ....
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    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);
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78

    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("挂号下单成功");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    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();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55

    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();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    10.Redis中把开关加上
    在这里插入图片描述
    11.启动服务
    在这里插入图片描述
    redis中开关置为1
    在这里插入图片描述
    欢迎大家积极留言交流学习心得,点赞的人最美丽!

  • 相关阅读:
    使用Qt创建一个空的项目
    Java dom4j操作xml文件的方法大全
    一文读透react精髓
    完全分布式运行模式
    25、业务层标准开发(也就是service)
    日常生活中的常用命令及操作
    php rsa加解密
    月报总结|Moonbeam6月份大事一览
    K8S之Deployment的介绍和使用
    千万别再学python编程了?编程没用了?马上就要被淘汰啦?
  • 原文地址:https://blog.csdn.net/xiaoxiaodaxiake/article/details/133886761