通过SpringBoot自定义注解实现AOP角色权限校验之前,首先先要了解一下注解的基本知识:
Annotation是Java重要的组成部分,从J2SE 5.0时代就已经存在了。在我们的代码中,我们随处可以看到许多注解,例如@Autowired、@Override、@Service。这些注解我们可能非常熟悉,但是注解的作用、工作原理、工作方式以及我们如何自定义注解,我们可能并不熟悉。下面将逐步介绍:
1、什么是注解
注解是一种特殊的元数据,元数据是关于数据的数据,所以,注解就是代码的元数据。我们看下面这个例子:
@Override
public void process(SjscData sjscData) {
String token = sjscData.getTokenID();
String rwmxid = sjscData.getNo();
}
我们用@Override注解process()方法,说明我们要重写process()方法。这里,即使我们不用@Override注解,这个方法任然可以正常执行。那注解的好处是什么呢?@Override告诉编译器,子类要重写这个方法,需要按照自己定义的格式输出内容,也就是覆盖了父类的方法,如果父类中没有这个方法,编译器就会报错。
所以,注解是一种特殊的Java构造器,它可以用于装饰类,方法,字段,参数,变量,构造函数或包。
2、为什么要使用注解
在注释之前(甚至之后),XML被广泛用于元数据。但是随着项目中代码量的增大,XML维护变得越来越麻烦。开发人员希望元数据与代码是紧密耦合,而XML与代码之间的耦合非常松散(在某些情况下,几乎是分开的)。
关于使用注解还是基于XML进行标记一直是一个具有争议的话题,对于开发人员来说,记住以下两个原则即可:
1、假设您要设置一些应用程序范围内的常量。在这种情况下,XML是一个更好的选择,因为它与任何特定的代码段都不相关。2、如果要将某个方法作为服务公开,则注解将是一个更好的选择,因为它需要与该方法紧密结合。另一个重要因素是,注解定义了在代码中定义元数据的标准方式。在注解之前,开发人员使用自己的方式定义元数据,这造成元数据定义混乱。但要注解是标准化的东西,更容易维护。
现在,大多数框架都将XML和注解结合使用,以充分利用两者的积极方面。
3、注解的工作方式
注解仅仅是元数据,不包括任何业务逻辑。那它的使用者是谁呢?像@Override这种注解,JVM是它的使用者,它在字节码级别工作。如果我们自己编写了一个注解,那我们就要去实现它的消费者,否则,我们自定义的注解是没有任何作用的。
4、编写自定义注解
在编写自定义注解的时候,我们首先需要搞清楚几个默认的注解,这几个注解仅仅作用与另一个注解之上。
@Documented
@Documented注解表明这个注解要被javadoc记录。注解默认状态下是不被javadoc记录的。
@Retention
注解表明该注解保留到那个阶段,主要有三个值:
SOURCE —— 这种注解保留在源代码级别,编译时就会被忽略
CLASS —— 这种注解编译时被保留,在class文件中存在,但JVM将会忽略
RUNTIME —— 这种注解将被JVM保留,利用反射机制可以获取并使用。
@Target
@Target注解表明该注解作用的范围。包括package、method、field、构造方法、成员变量、枚举值等属性。
@Inherited
@Inherited注解表明该注解是否影响子类。如果定义的注解上使用了@Inherited标记,则使用该注解的某个父类,它的子类默认继承所有的属性
了解完自定义注解需要使用到的注解,我们开始自定义注解。
SpringBoot工程
POM.xml 中引入以下依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
自定义注解
package com.zhanglf.aop.annotation;
import java.lang.annotation.*;
/**
* 使用注解统一校验角色权限
* @author zhanglf
* @date 2019-04-29
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface PermissionCheck {
//自定义角色值,如果是多个角色,用逗号分割。
public String role() default "";
}
aop绑定自定义注解。并在aop中实现处理逻辑
package com.jd.mee.catalog.aop;
import com.zhanglf.aop.annotation.PermissionCheck;
import com.zhanglf.common.result.CodeMsg;
import com.zhanglf.common.util.JwtUtil;
import com.zhanglf.entity.UserInfo;
import com.zhanglf.exception.BizException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
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.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
/**
* 角色权限校验-AOP
* @author zhanglf
* @date 2019-04-29
*/
@Aspect
@Component
@Slf4j
public class PermissionCheckAspect {
//切入点表达式决定了用注解方式的方法切还是针对某个路径下的所有类和方法进行切,方法必须是返回void类型
@Pointcut("@annotation(com.zhanglf.aop.annotation.PermissionCheck)")
private void permissionCheckCut(){};
//定义了切面的处理逻辑。即方法上加了@PermissionCheck
@Around("permissionCheckCut()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
log.info("==========哈哈哈,进入AOP============================");
//1.记录日志信息
Signature signature = pjp.getSignature();
String className = pjp.getTarget().getClass().getSimpleName();
String methodName = signature.getName();
log.info("className:{},methodName:{}",className,methodName);
//2.角色权限校验
MethodSignature methodSignature = (MethodSignature)signature;
Method targetMethod = methodSignature.getMethod();
if(targetMethod.isAnnotationPresent(PermissionCheck.class)){
//获取方法上注解中表明的权限
PermissionCheck permission = (PermissionCheck)targetMethod.getAnnotation(PermissionCheck.class);
String role = permission.role();
log.info("当前接口请求的用户角色role:{}",role);
if(StringUtils.isNotEmpty(role)){
String[] roles = role.split(",");//接口允许的角色
List<String> list = Arrays.asList(roles);
//如果该接口只允许老师角色访问。则要获取当前用户是不是老师角色。
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
String authorization = request.getHeader("Authorization");
UserInfo userInfo = JwtUtil.parseAccessToken(authorization);
String userRole = userInfo.getUserRole();//用户的角色
if(list.contains(userRole)){
log.info("AOP权限角色校验通过,进入业务层处理!");
//3.执行业务逻辑,放行
return pjp.proceed();
}else{
//如果没有权限,抛出异常,由Spring框架捕获,跳转到错误页面
throw new BizException(CodeMsg.ROLE_HAVE_NO_PERMISSION);
}
}
}
throw new BizException(CodeMsg.NO_ROLE_CONFIGER);
}
}
aop的使用:用在接口层校验登陆的角色是否有权限使用该接口
参考资料:
(45条消息)java注解学习---元注解,注解类型,注解支持元素类型,注解基础