1. 案例
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.8.0</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.0</version>
</dependency>
1.1 验权服务
1.1.1 验权服务切面注解
package me.common.framework.log;
import java.lang.annotation.*;
/**
* 验权服务切面注解
*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface BackendPermission {
/**
* API服务协议名称
*/
String protocol();
/**
* 是否打印响应
*/
boolean printResponseBody() default true;
}
1.1.2 验权服务切面
package me.common.framework.log;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import me.common.api.exceptions.PermissionException;
import me.common.enums.PermissionExceptionEnum;
import org.apache.commons.lang3.ArrayUtils;
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.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.util.Map;
/**
* 验权服务切面
*/
@Aspect
@Component
@Slf4j
@Order(1)// 在同一个地方有多个AOP注解引用时的排序, 数字越小越优先执行
public class BackendPermissionAop {
@Autowired
private 业务处理的Service service;
@Pointcut("@annotation(me.common.framework.log.BackendPermission)")
public void checkPoint() {
}
/*@Before("checkPoint() && @annotation(backendPermission)")
public void before(BackendPermission backendPermission) {
try {
log.info(">>> [权限校验] BackendPermissionAop");
if (!service.checkPermission(backendPermission.protocol())) {
throw new InvoiceBizException("您没有当前操作权限");
}
} catch (InvoiceBizException e) {
throw e;
} catch (Exception e) {
throw new InvoiceBizException("验权异常");
}
}*/
@Around("checkPoint() && @annotation(backendPermission)")
public Object around(ProceedingJoinPoint joinPoint, BackendPermission backendPermission) throws Throwable {
String protocol = backendPermission.protocol();
String funcName = joinPoint.getSignature().getName();
// 获取方法入参
String requestBody = getRequestBody(joinPoint.getArgs());
log.info(">>> [权限校验] - {}; 方法: {}; 入参: {}", protocol, funcName, requestBody);
Boolean checkResult = service.checkBackendPermission(protocol, requestBody);
if (checkResult) {
throw new PermissionException(PermissionExceptionEnum.UNAUTHORIZED_CODE, "" + checkResult.get("权限申请链接"));
}
// 获取方法出参
Object originalResponse = joinPoint.proceed();
boolean printResponseBody = apiService.printResponseBody();
String responseBody = getResponseBody(originalResponse, printResponseBody);
log.info(">>> [权限校验] - {}; 方法: {}; 出参: {}", protocol, funcName, responseBody);
return originalResponse;
}
/**
* 根据请求参数生成请求体JSON串
* 建议API入参为一个实体,不要分成多个参数,因此这里仅将第一个参数JSON化
*
* @param args 入参
* @return 请求体JSON串
*/
private String getRequestBody(Object[] args) {
if (ArrayUtils.isEmpty(args)) {
return "null";
}
return JSON.toJSONString(args[0]);
}
private String getResponseBody(Object originalResponse, boolean printResponseBody) {
if (!printResponseBody) {
return "";
}
return JSON.toJSONString(originalResponse);
}
}
1.1.3 使用
@BackendPermission(protocol = "权限校验")
public void queryInfo() throws InvoiceServiceException {
return null;
}
1.2 异常统一处理服务
1.2.1 异常抓取切面注解
package me.common.framework.log;
import java.lang.annotation.*;
/**
* 验权服务异常抓取切面注解
*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface BackendPermissionException {
}
1.2.2 异常抓取切面
package me.common.framework.log;
import lombok.extern.slf4j.Slf4j;
import me.backend.api.model.response.GatewayResult;
import me.common.api.exceptions.PermissionException;
import me.common.api.exceptions.InvoiceBizException;
import me.exception.InvoiceServiceException;
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.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
/**
* 验权服务异常抓取切面
*/
@Aspect
@Component
@Slf4j
@Order(2)// 在同一个地方有多个AOP注解引用时的排序, 数字越小越优先执行
public class BackendPermissionExceptionAop {
@Pointcut("@annotation(me.common.framework.log.BackendPermissionException)")
public void checkPoint() {
}
@Around("checkPoint() && @annotation(backendPermissionException)")
public Object around(ProceedingJoinPoint joinPoint, BackendPermissionException backendPermissionException) throws Throwable {
try {
String funcName = joinPoint.getSignature().getName();
log.info(">>> [权限校验异常抓取] - 方法: {}", funcName);
return joinPoint.proceed();
} catch (PermissionException e) {
log.error(">>> [权限校验异常抓取] - 权限校验失败: ", e);
GatewayResult result = new GatewayResult();
Map<String, Object> extInfo = new HashMap<>();
extInfo.put("权限申请链接", e.getPermissionApplyLink());
result.setExtInfo(extInfo);
result.setResultCode(e.getPermissionExceptionEnum().getCode());
result.setSuccess(false);
result.setResultMessage(e.getPermissionExceptionEnum().getMsg());
return result;
} catch (InvoiceBizException | InvoiceServiceException e) {
log.error(">>> [权限校验异常抓取] - 业务异常: ", e);
GatewayResult result = new GatewayResult();
result.setSuccess(false);
result.setResultMessage(e.getMessage());
return result;
} catch (Exception e) {
log.error(">>> [权限校验异常抓取] - 系统异常: ", e);
GatewayResult result = new GatewayResult();
result.setSuccess(false);
result.setResultMessage("系统异常");
return result;
}
}
}
1.2.3 使用
@Override
@BackendPermissionException
public void testMethod(TestRequest request) throws InvoiceServiceException {
return null;
}
2. AOP注意点
2.1 @Before
入参不能引入ProceedingJoinPoint joinPoint, 也就无法获取方法的入参和出参
2.2 在方法上使用
如果要在方法上使用AOP注解, 必须手动指定切入点@Pointcut
2.3 在类上使用
在类上使用不需指定切入点@Pointcut, 但是若这种写法的注解用在方法上, 不会生效
2.4 多个AOP注解在同一个方法上使用
如案例中的AOP, 在同一个方法上即使用验权切面, 又使用异常切面时, 异常切面无法抓取到验权切面中抛出的异常, 切面里的异常会往更上层抛出, 例如:
@BackendPermission(protocol = "权限校验")
@BackendPermissionException
public void queryInfo() throws InvoiceServiceException {
return null;
}