本文主要介绍切面,实例介绍了日志处理,与自定义注解捕获等,文章末附源码地址
简介
AOP为Aspect Oriented Programming的缩写, 意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。 在日常开发当中经常用来记录日志,方法跟踪、拦截、事务,权限等
切面方法说明:
注解作用
@Aspect把当前类标识为一个切面供容器读取@Pointcut(切入点):就是带有通知的连接点,在程序中主要体现为书写切入点表达式@Before标识一个前置增强方法,其不能阻止业务模块的执行,除非抛出异常;@AfterReturning后置增强,相当于AfterReturningAdvice,方法退出时执行@AfterThrowing异常抛出增强,相当于ThrowsAdvice@After方法之后执行,不管是抛出异常或者正常退出都会执行,类似于finally的作用;@Around环绕增强,方法执行之前,与执行之后均会执行
引入AOP依赖
<dependency>
<groupId>org.springframework.boot
</groupId>
<artifactId>spring-boot-starter-aop
</artifactId>
</dependency>
创建测试controller
注:参数就不贴在这里了,底部附源码地址
@RestController
@RequestMapping("/user")
public class UserInfoController {
@PostMapping(value
= "/add-user")
public Response
addUser1(@RequestBody UserAddParam addParam
) {
return Response
.success();
}
}
定义测试切面
package com
.example
.config
.aspect
;
import com
.alibaba
.fastjson
.JSON
;
import lombok
.extern
.slf4j
.Slf4j
;
import org
.aspectj
.lang
.JoinPoint
;
import org
.aspectj
.lang
.ProceedingJoinPoint
;
import org
.aspectj
.lang
.annotation
.*
;
import org
.springframework
.stereotype
.Component
;
@Aspect
@Component
@Slf4j
public class AspectExample {
@Pointcut("execution(public * com.example.controller..*.*(..))")
public void pointcut() {
log
.info("[切面处理] >> 使用注解 @Pointcut 定义切点位置");
}
@Before("pointcut()")
public void beforeMethod(JoinPoint joinPoint
) {
log
.info("[切面处理] >> 使用注解 @Before 调用了方法前置通知 ");
}
@After("pointcut()")
public void afterMethod(JoinPoint joinPoint
) {
log
.info("[切面处理] >> 使用注解 @After 调用了方法后置通知 ");
}
@AfterReturning(value
= "pointcut()", returning
= "result")
public void afterReturningMethod(JoinPoint joinPoint
, Object result
) {
log
.info("[切面处理] >> 使用注解 @AfterReturning 调用了方法返回后通知 ");
}
@AfterThrowing(value
= "pointcut()", throwing
= "e")
public void afterThrowingMethod(JoinPoint joinPoint
, Exception e
) {
log
.info("[切面处理] >> 使用注解 @AfterThrowing 调用了方法异常通知 ");
}
@Around("pointcut()")
public Object
aroundMethod(ProceedingJoinPoint pjp
) throws Throwable
{
String methodName
= pjp
.getSignature().getName();
log
.info("[切面处理] >> 使用注解 @Around 方法 :{} 执行之前 ", methodName
);
Object result
= pjp
.proceed();
log
.info("[切面处理] >> 使用注解 @Around 方法 :{} 执行之后 ,返回值:{}", methodName
, JSON
.toJSONString(result
));
return result
;
}
}
测试结果 控制台输出结果
执行顺序分析
使用实例
1.打印controller方法参数日志
package com
.example
.config
.aspect
;
import com
.example
.util
.SystemClock
;
import lombok
.extern
.slf4j
.Slf4j
;
import org
.apache
.commons
.lang3
.builder
.ToStringBuilder
;
import org
.apache
.commons
.lang3
.builder
.ToStringStyle
;
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
.aspectj
.lang
.reflect
.MethodSignature
;
import org
.springframework
.stereotype
.Component
;
import javax
.servlet
.http
.HttpServletRequest
;
import java
.lang
.reflect
.Method
;
import java
.util
.Random
;
@Aspect
@Component
@Slf4j
public class ControllerAspect {
private static Random random
= new Random();
@Pointcut("execution(public * com.example.controller..*.*(..))")
private void allMethod() {
}
@Around("allMethod()")
public Object
doAround(ProceedingJoinPoint call
) throws Throwable
{
MethodSignature signature
= (MethodSignature
) call
.getSignature();
Method method
= signature
.getMethod();
String
[] classNameArray
= method
.getDeclaringClass().getName().split("\\.");
String methodName
= classNameArray
[classNameArray
.length
- 1] + " >>> " + method
.getName();
String params
= buildParamsDefault(call
);
long start
= SystemClock
.millisClock().now();
String reqId
= getRandom(10);
Object result
= null
;
try {
log
.info("日志标识:{} >> [接口请求开始] 方法名:{} , 请求参数:{}", reqId
, methodName
, params
);
result
= call
.proceed();
return result
;
} finally {
long runTimes
= SystemClock
.millisClock().now() - start
;
log
.info("日志标识:{} >> [接口请求结束] 方法名:{} , 请求耗时:{} ms", reqId
, methodName
, runTimes
);
}
}
private String
buildParamsDefault(ProceedingJoinPoint call
) {
String params
= " [";
for (int i
= 0; i
< call
.getArgs().length
; i
++) {
Object obj
= call
.getArgs()[i
];
if (null
!= obj
) {
if (obj
instanceof HttpServletRequest) {
continue;
}
String str
= obj
.toString();
if (obj
.getClass() != String
.class) {
str
= ToStringBuilder
.reflectionToString(obj
, ToStringStyle
.SHORT_PREFIX_STYLE
);
}
if (i
!= call
.getArgs().length
- 1) {
params
+= str
+ ",";
} else {
params
+= str
+ " ]";
}
}
if (params
.length() == 1) {
params
+= "]";
}
}
return params
;
}
private static String
getRandom(int length
) {
StringBuilder ret
= new StringBuilder();
for (int i
= 0; i
< length
; ++i
) {
boolean isChar
= random
.nextInt(2) % 2 == 0;
if (isChar
) {
int choice
= random
.nextInt(2) % 2 == 0 ? 65 : 97;
ret
.append((char) (choice
+ random
.nextInt(26)));
} else {
ret
.append(Integer
.toString(random
.nextInt(10)));
}
}
return ret
.toString();
}
}
效果示例
2.自定义注解切面处理
背景:在某些业务场景下需要某方法做一些特殊的处理,可以通过该方式 以自定义注解捕获异常为例 注:当然springBoot 统一异常处理应该使用@ControllerAdvice,以下示例,只是举了个例子,用来说明切面的作用
自定义注解
@Target({ElementType
.PARAMETER
, ElementType
.METHOD
})
@Retention(RetentionPolicy
.RUNTIME
)
@Documented
public @
interface ApiAnnotation {
String
description() default "业务接口";
}
切面代码
package com
.example
.aspect
;
import com
.example
.annotation
.ApiAnnotation
;
import com
.example
.bean
.result
.Response
;
import lombok
.extern
.slf4j
.Slf4j
;
import org
.apache
.commons
.lang3
.exception
.ExceptionUtils
;
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
.stereotype
.Component
;
import java
.text
.MessageFormat
;
@Slf4j
@Aspect
@Component
public class ApiExceptionAdviceAspect {
@Pointcut("@annotation(com.example.annotation.ApiAnnotation)")
public void pointCut() {
}
@Around("pointCut()&&@annotation(adviceLog)")
public Object
logAround(ProceedingJoinPoint joinPoint
, ApiAnnotation adviceLog
) {
Object result
= null
;
try {
log
.info("【Api-切面处理】>> {} >> 处理开始 ", adviceLog
.description());
result
= joinPoint
.proceed();
log
.info("【Api-切面处理】>> {} >> 处理结束 ", adviceLog
.description());
return result
;
} catch (Throwable e
) {
Response apiResult
= new Response();
log
.error("【Api-切面处理】>> {} >> 发生异常 , stack = {}", adviceLog
.description(), ExceptionUtils
.getStackTrace(e
));
String errorMsg
= MessageFormat
.format("{0} >> 发生异常了", adviceLog
.description());
apiResult
.setSuccess(false);
apiResult
.setErrorMsg(errorMsg
);
return apiResult
;
}
}
}
源码地址 关注程序员小强公众号更多编程趣事,知识心得与您分享