Spring boot 自定义注解 + 拦截器 + AOP 切面

目录

自定义拦截器 获取 自定义注解 信息

自定义注解

自定义拦截器

接口访问测试

AOP 切面 获取 自定义注解 信息

自定义注解

AOP 切面

访问测试


1、项目中某些需求可以使用自定义注解,然后在 Spring boot 拦截器 HandlerInterceptor 中获取自定义注解的信息。

2、本文环境:Spring boot 2.3.5.RELEASE + Java jdk 1.8.

自定义拦截器 获取 自定义注解 信息

自定义注解

1、其实只要参考一下其它注解就能写出自己需要的注解,其中几个重要注解如下:

2、@Target: 表示注解使用的目标位置, 常用的有:

TYPE:类,接口,注解,枚举
FIELD:成员变量
METHOD:方法
PARAMETER:形式参数
CONSTRUCTOR:构造器

3、@Retention: 表示注解生命范围, 类似 maven pom.xml 文件的 scope 属性, 可选值有:

SOURCE:编译器将丢弃注解
CLASS:注解将由编译器记录在类文件中,但不需要在运行时由VM保留,这是默认行为
RUNTIME:注解将由编译器记录在类文件中,并在运行时由VM保留,因此可以反射地读取它们

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
 * 1: @Target: 表示注解使用的目标位置, 常用的有:
 * * TYPE(类,接口,注解,枚举), FIELD(成员变量), METHOD(方法), PARAMETER(形式参数), CONSTRUCTOR(构造器)
 * 2: @Retention: 表示注解生命范围, 类似 maven pom.xml 文件的 scope 属性, 可选值有:
 * * SOURCE(编译器将丢弃注解)
 * * CLASS(注解将由编译器记录在类文件中,但不需要在运行时由VM保留,这是默认行为),
 * * RUNTIME(注解将由编译器记录在类文件中,并在运行时由VM保留,因此可以反射地读取它们)
 *
 * @author wangMaoXiong
 * @version 1.0
 * @date 2021/8/25 23:13
 */
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MultipleOperate {
    /**
     * 自定义属性值
     *
     * @return
     */
    String[] value() default {};
    /**
     * 描述
     *
     * @return
     */
    String desc() default "";
}

在线演示源码:

https://gitee.com/wangmx1993/java-se/java/org/example/annotation/ClassOperate.java
https://gitee.com/wangmx1993/java-sejava/org/example/annotation/MethodOperate.java
https://gitee.com/wangmx1993/java-se/java/org/example/annotation/MultipleOperate.java

自定义拦截器

import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Arrays;
/**
 * 自定义拦截器
 *
 * @author wangMaoXiong
 * @version 1.0
 * @date 2021/8/24 20:23
 */
public class AppInterceptor implements HandlerInterceptor {
    /**
     * 请求进入时进行拦截,返回 true 时,表示继续往下走;
     * 返回 false 时, 表示停止后续的执行,即请求不会到达控制层.
     *
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        StringBuffer requestURL = request.getRequestURL();
        System.out.println("进入拦截器,用户请求:" + requestURL);
        if (handler instanceof HandlerMethod) {
            //方法处理器, 请求的目标方法
            HandlerMethod handlerMethod = (HandlerMethod) handler;

            //1: 获取目标方法上的指定注解,不存在时,返回 Null.
            MethodOperate methodOperate = handlerMethod.getMethodAnnotation(MethodOperate.class);
            if (methodOperate != null) {
                String desc = methodOperate.desc();
                System.out.println("\t目标方法存在 @MethodOperate 注解, desc=" + desc);
            }

            //2: 获取目标方法所在类上的指定主键, 不存在时, 返回 null.
            ClassOperate classOperate = handlerMethod.getMethod().getDeclaringClass().getAnnotation(ClassOperate.class);
            if (classOperate != null) {
                String[] value = classOperate.value();
                String desc = classOperate.desc();
                String vs = value != null ? Arrays.asList(value).toString() : "";
                System.out.println("\t目标方法所在的类存在 @ClassOperate 注解, desc=" + desc + ", value=" + vs);
            }

            // 3、获取目标方法及其类上的注解
            MultipleOperate multipleOperate1 = handlerMethod.getMethodAnnotation(MultipleOperate.class);
            MultipleOperate multipleOperate2 = handlerMethod.getMethod().getDeclaringClass().getAnnotation(MultipleOperate.class);
            if (multipleOperate1 != null) {
                String desc = multipleOperate1.desc();
                String[] value = multipleOperate1.value();
                String vs = value != null ? Arrays.asList(value).toString() : "";
                System.out.println("\t目标方法存在 @MultipleOperate 注解, desc=" + desc + ", value=" + vs);
            }
            if (multipleOperate2 != null) {
                String desc = multipleOperate2.desc();
                String[] value = multipleOperate2.value();
                String vs = value != null ? Arrays.asList(value).toString() : "";
                System.out.println("\t目标方法所在的类存在 @MultipleOperate 注解, desc=" + desc + ", value=" + vs);
            }
        }
        return true;
    }
}

注册拦截器

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
 * 应用配置
 *
 * @author wangMaoXiong
 * @version 1.0
 * @date 2021/8/25 9:13
 */
@Configuration
public class AppConfig implements WebMvcConfigurer {
    /**
     * 注册拦截器
     * .addPathPatterns("/**"):表示拦截整个应用中的所有请求
     * .excludePathPatterns(String... patterns):表示排除这些规则的请求,不对它们进行拦截
     * <p>
     * spring Boot 2 以后,静态资源也会被拦截.
     * classpath:/META‐INF/resources/","classpath:/resources/","classpath:/static/","classpath:/public/"下的资源也会被拦截
     * 通常静态资源可以不需要进行拦截,可以对它们直接进行放行
     *
     * @param registry
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new AppInterceptor())
                .addPathPatterns("/**")
                .excludePathPatterns("/user/index")
                .excludePathPatterns("/webjars/**", "/css/**/*.css", "/js/**/*.js", "/fonts/**", "/images/**");
    }
}

在线演示源码:

https://gitee.com/wangmx1993/java-se/java/org/example/annotation/AppInterceptor.java
https://gitee.com/wangmx1993/java-se/java/org/example/annotation/AppConfig.java

接口访问测试

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
/**
 * 控制层接口
 *
 * @author wangMaoXiong
 * @version 1.0
 * @date 2021/8/25 9:24
 */
@RestController
@ClassOperate(desc = "AppController控制层", value = {"A1", "B2"})
@MultipleOperate(value = {"x11", "x22", "x33"}, desc = "AppController")
@SuppressWarnings("all")
public class AppController {
    /**
     * http://localhost:8080/annotation/method1
     *
     * @return
     */
    @GetMapping("/annotation/method1")
    @MethodOperate(desc = "查询方法.")
    public Map<String, Object> method1() {
        Map<String, Object> dataMap = new HashMap<>();
        dataMap.put("code", 200);
        dataMap.put("msg", "success");
        dataMap.put("data", "method1");
        return dataMap;
    }
    /**
     * http://localhost:8080/annotation/method2
     *
     * @return
     */
    @GetMapping("/annotation/method2")
    public Map<String, Object> method2() {
        Map<String, Object> dataMap = new HashMap<>();
        dataMap.put("code", 200);
        dataMap.put("msg", "success");
        dataMap.put("data", "method1");
        return dataMap;
    }
    /**
     * http://localhost:8080/annotation/method3
     *
     * @return
     */
    @GetMapping("/annotation/method3")
    @MultipleOperate(value = {"x1", "x2", "x3"}, desc = "method3")
    public Map<String, Object> method3() {
        Map<String, Object> dataMap = new HashMap<>();
        dataMap.put("code", 200);
        dataMap.put("msg", "success");
        dataMap.put("data", "method1");
        return dataMap;
    }
}

AOP 切面 获取 自定义注解 信息

自定义注解

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
 * 自定义 Redis 分布式锁注解
 * * 1: @Target: 表示注解使用的目标位置, 常用的有:
 * * * TYPE(类,接口,注解,枚举), FIELD(成员变量), METHOD(方法), PARAMETER(形式参数), CONSTRUCTOR(构造器)
 * * 2: @Retention: 表示注解生命范围, 类似 maven pom.xml 文件的 scope 属性, 可选值有:
 * * * SOURCE(编译器将丢弃注解)
 * * * CLASS(注解将由编译器记录在类文件中,但不需要在运行时由VM保留,这是默认行为),
 * * * RUNTIME(注解将由编译器记录在类文件中,并在运行时由VM保留,因此可以反射地读取它们)
 *
 * @author wangMaoXiong
 * @version 1.0
 * @date 2022/11/20 16:39
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface RedisLock {
    // 特定参数标识,默认取第 0 个下标,-1 表示取整个参数.
    int lockFiled() default 0;

    // 释放时间,单位秒(s),默认 30 s
    long lockTime() default 30;

    // 超时重试次数,默认 3次.
    int retryCount() default 3;

    // 描述信息
    String desc() default "";
}

src/main/java/com/wmx/annotation/RedisLock.java · 汪少棠/jpaTransactional - Gitee.com

AOP 切面

/**
 * AOP 切面拦截 自定义 Redis 分布式锁注解
 * <p>
 * * 1、@Aspect:声明本类为切面类
 * * 2、@Component:将本类交由 Spring 容器管理
 * * 3、@Order:指定切入执行顺序,数值越小,切面执行顺序越靠前,默认为 Integer.MAX_VALUE
 *
 * @author wangMaoXiong
 * @version 1.0
 * @date 2022/11/20 16:55
 */
@Aspect
@Order(value = 999)
@Component
public class RedisLockAspect {
    private static final Logger LOG = LoggerFactory.getLogger(RedisLockAspect.class);
    /**
     * @Pointcut :切入点声明,即切入到哪些目标方法。
     * execution:可以用于指定具体类中的具体方法
     * annotation:匹配拥有指定注解的方法; 只匹配实现类中有注解的方法,不会匹配接口中的注解方法; 如果注解是在类上,而不是方法上,并不会匹配类中的全部方法.
     * 用于被下面的通知注解引用,这样通知注解只需要关联此切入点声明即可,无需再重复写切入点表达式
     * @annotation 中的路径表示拦截特定注解
     */
    @Pointcut("@annotation(com.wmx.annotation.RedisLock)")
    public void redisLockPC() {
    }
    /**
     * 环绕通知
     * 1、@Around 的 value 属性:绑定通知的切入点表达式。可以关联切入点声明,也可以直接设置切入点表达式
     * 2、Object ProceedingJoinPoint.proceed(Object[] args) 方法:继续下一个通知或目标方法调用,返回处理结果,如果目标方法发生异常,则 proceed 会抛异常.
     * 3、假如目标方法是控制层接口,则本方法的异常捕获与否都不会影响目标方法的事务回滚
     * 4、假如目标方法是控制层接口,本方法 try-catch 了异常后没有继续往外抛,则全局异常处理 @RestControllerAdvice 中不会再触发
     *
     * @param joinPoint
     * @return
     * @throws Throwable
     */
    @Around(value = "redisLockPC()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        // 获取注解所在的目标方法
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();

        // 获取方法上注解
        RedisLock annotation = method.getAnnotation(RedisLock.class);
        Object[] args = joinPoint.getArgs();
        LOG.info("自定义 Redis 分布式锁注解:方法={} 参数={} 特定参数标识={} 超时重试次数={} 释放时间={} 描述信息={}",
                joinPoint.getSignature(), Arrays.asList(args), annotation.lockFiled(), annotation.retryCount(),
                annotation.lockTime(), annotation.desc());

        // 继续下一个通知或目标方法调用,返回处理结果,如果目标方法发生异常,则 proceed 会抛异常.
        // 如果在调用目标方法或者下一个切面通知前抛出异常,则不会再继续往后走.
        Object proceed = joinPoint.proceed(joinPoint.getArgs());
        return proceed;
    }
}

src/main/java/com/wmx/annotation/RedisLockAspect.java · 汪少棠/jpaTransactional - Gitee.com

访问测试

1、目标方法上加上自定义注解即可使用. 

    @Override
    @Transactional(rollbackFor = Exception.class)
    @RedisLock(retryCount = 3, lockTime = 30, lockFiled = -1, desc = "redis分布式锁")
    public void save(TV tv) {
        tv.setTvName(tv.getTvName() + "_xxx");
        tvRepository.save(tv);
    }

  • 6
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

蚩尤后裔-汪茂雄

芝兰生于深林,不以无人而不芳。

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值