目录
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);
}