首先需要知道五个元注解,(主要用到的是基本的四个,后面新增了一个但不常用),自己网上查都有教程
先来说一下自定义注解的实现思路 (动态代理情况)
首先自定义一个注解,一般默认value为参数,想要多参数的话,自己编写参数,默认都是缺省default范围,下面是我自己写的一个权限校验的自定义注解demo
/**
* @author 申恒基
* 创建时间 2023-07-20 10:44
* 描述:自定义权限校验注解
*/
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface AllowUserRole {
RoleEnum[] value() default {};
}
这样我们在一个方法/类/...(看@Target的限制)上就可以使用我们的注解了
自定义注解肯定是要实现一些我们想要的功能的,比如权限校验,参数注入等等。那我们怎样才能达到我们想要的这种效果呢,思路也很简单,就是在执行我们注解标记的方法之前,先执行我们自己写的方法,在自己写的方法中,我们编写对当前标记注解的类/方法执行前需要操作的逻辑即可,那这要怎么写呢,目前实现的方法有两种:
1.AspectJ ---- SpringAOP
2.Interceptor ---- 拦截器
1. AspectJ ---- SpringAOP
(以下内容需掌握SpringAOP能帮助更好的理解,如果阅读吃力可以先去复习一下SpringAOP的相关知识点再来继续阅读)
引入依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>6.0.9</version>
</dependency>
先介绍一下aop的核心功能,也是决定注解是否生效的决定因素,切点!
aop的 环绕通知 -- around 可以让我们手动决定注解标记的方法是否需要执行,其中常用的切点的定义如下:
@Around("@annotation(com.example.learn.common.utils.annotion.AllowUserRole)")
@Around("@within(com.example.learn.common.utils.annotion.AllowUserRole)")
关键区别在于@annotation和@within,@annotation注解的意思是该注解标记的方法前都会进入aop,而@within注解的意思是标记在类上的所有方法执行时都会进入aop,如果出现作用在某一块注解不生效的情况,请检查该位置的注解类型是否正确
aop目前支持的切入点指示符如下:
execution: 用于匹配方法执行的连接点;
within: 用于匹配指定类型内的方法执行;
this:用于匹配当前AOP代理对象类型的执行方法;注意是AOP代理对象的类型匹配,这样就可能包括引入接口也类型匹配;
target:用于匹配当前目标对象类型的执行方法;注意是目标对象的类型匹配,这样就不包括引入接口也类型匹配;
args: 用于匹配当前执行的方法传入的参数为指定类型的执行方法;
@within: 用于匹配所以持有指定注解类型内的方法;
@target: 用于匹配当前目标对象类型的执行方法,其中目标对象持有指定的注解:
@args: 用于匹配当前执行的方法传入的参数持有指定注解的执行;
@annotation: 用于匹配当前执行方法持有指定注解的方法;
bean: Spring AOP扩展的,AspectJ没有对于指示符,用于匹配特定名称的Bean对象的执行方法reference pointcut: 表示引用其他命名切入点,只有@ApectJ风格支持,Schema风格不支持。
介绍完AOP后接下来我们说说注解对应的方法要怎么写,非常简单
先看代码:
/**
* @author 申恒基
* 创建时间 2023-07-20 11:35
* 描述:自定义权限校验注解
*/
@Aspect
@Component
public class AllowUserRoleAspect {
/**
* 对类上标记的权限注解进行校验
*/
@Around("@within(com.example.learn.common.utils.annotion.AllowUserRole)")
public Object allowUserRoleInClass(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
AllowUserRole annotationMethod = signature.getMethod().getAnnotation(AllowUserRole.class);
//如果方法上该注解存在,就放行,给校验方法的aop自己校验
if(annotationMethod == null){
AllowUserRole annotation = joinPoint.getTarget().getClass().getAnnotation(AllowUserRole.class);
if (annotation != null && roleIsNull(annotation.value())) {
throw new CustomException(ExceptionEnum.INSUFFICIENT_PRIVILEGE_ERROR);
}
}
return joinPoint.proceed();
}
/**
* 对方法上标记的权限注解进行校验
*/
@Around("@annotation(com.example.learn.common.utils.annotion.AllowUserRole)")
public Object allowUserRoleInMethod(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
//获取类注解标注的方法
Method method = methodSignature.getMethod();
AllowUserRole annotation = method.getAnnotation(AllowUserRole.class);
if (annotation == null) {
return joinPoint.proceed();
}
if (roleIsNull(annotation.value())) {
throw new CustomException(ExceptionEnum.INSUFFICIENT_PRIVILEGE_ERROR);
}
return joinPoint.proceed();
}
/**
* 检查权限存在,不存在返回true,存在返回false
*
* @param roleEnums 权限枚举类
* @return ture/false
*/
private boolean roleIsNull(RoleEnum[] roleEnums) {
for (RoleEnum role : roleEnums) {
if (Objects.equals(RoleEnum.matchOf(UserUtils.getRole()), role)) {
return false;
}
}
return true;
}
}
核心在这两句:
//获取方法标签
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
//获取类注解标注的方法
Method method = methodSignature.getMethod();
//获取指定的注解
AllowUserRole annotation = method.getAnnotation(AllowUserRole.class);
既然能获得注解,那也就可以获得注解的参数了
//获取注解当中的参数 annotation.value();
如果想要获得类上的注解,得这么写
//getTarget()是获取目标类,其他就不用我解释了吧
AllowUserRole annotation = joinPoint.getTarget().getClass().getAnnotation(AllowUserRole.class);
简单吧,既然获得了数据就可以写我们自己的业务了,写完打上注解就能自动生效啦,如果没有生效可以看一下我上面写的注解未生效的原因,@annotation和@within,如果想获得方法传入的参数啥的等等,就百度一下,看aop的ProceedingJoinPoint的API,基本上都可以搜到。
因为我用到了枚举类这里给大家放一下我枚举类的代码:
/**
* @author 申恒基
* 创建时间 2023-07-19 13:18
* 描述:角色枚举类
*/
@Getter
@AllArgsConstructor
public enum RoleEnum {
/*
系统角色
*/
ADMIN("ROLE_admin"),
TEACHER("ROLE_teacher"),
STUDENT("ROLE_student");
final String role;
public static RoleEnum matchOf(String role){
for (RoleEnum value : RoleEnum.values()) {
if (StringUtils.equals(value.getRole(),role)){
return value;
}
}
return null;
}
}