SpringBoot项目切面编程之@Aspect

场景:
aop切面编程想必大家都不陌生了,aspect可以很方便开发人员对请求指定拦截层,一般是根据条件切入到controller控制层,做一些鉴权、分析注解、获取类名方法名参数、记录操作日志等

引入 aop 依赖包:



    org.springframework.boot
    spring-boot-starter-aop

切入流程:
1、创建组件类并标记注解@Aspect
2、定义切入点标记注解@Pointcut
3、使用切入点切入标记注解@Before、@After、@Around等

常用的增强类型:

1、@Before:前置增强,在某个JoinPoint执行前的增强

2、@After:final增强,不管抛异常还是正常退出都执行的增强

3、@AfterReturning:后置增强,方法正常退出时执行

4、@AfterThrowing:异常抛出增强,抛出异常后执行

5、@Around:环绕增强,包围一个连接点的增强,最强大的一个方式,且常用

常用方法:

方法 说明
Object[] getArgs 返回目标方法的参数数组
Signature getSignature 返回目标方法所在类信息,可强制转换为MethodSignature等
Object getTarget 返回被织入增强处理的目标对象
Object getThis 返回AOP代理对象
Object proceed(Object[] args) 利用反射执行目标方法并返回结果

代码案例:

package com.hkl.mpjoin.aspect;

import com.hkl.annotation.FieldConvert;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
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;

/**
 * 

ClassName:AopConfigure

*

Description:切面配置类

*

Author:hkl

*

Date:2022/11/8

*/ @Component @Aspect @Slf4j public class AopConfigure { private long startTime = 0; /** * 指定切入点表达式 * public * com.hkl.modules.*.controller..*(..)) */ @Pointcut("execution( * com.hkl.mpjoin.modules.finance.controller..*(..))") public void getMethods() { } /** * 指定注解切入 * @param @annotation(xxx):xxx是自定义注解的全路径 */ @Pointcut("@annotation(com.hkl.annotation.FieldConvert)") public void withAnnotationMethods() { } /*** * 方法执行之前切入控制层 * 表达式和注解方式同时满足才会切入 * @param joinPoint */ @Before(value = "getMethods() && withAnnotationMethods()") public void doBefore(JoinPoint joinPoint){ //获取Servlet容器 ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); //获取request请求 HttpServletRequest request = attributes.getRequest(); //执行方法对象 MethodSignature signature = (MethodSignature) joinPoint.getSignature(); Method method = signature.getMethod(); //判断切入的方法是否标记xxx注解 //boolean flag = method.isAnnotationPresent(xxx.class); //CollUtil.toList(role).contains(userType); /** 可做操作说明 start */ //1、鉴权、解析request请求对象中设置的属性 //2、反射解析注解、记录操作日志等 /** 可做操作说明 end */ log.info("测试切入{}成功,方法名:"+method.getName(), "@Before"); startTime = System.currentTimeMillis(); } /*** * 方法执行之后切入控制层 * 表达式和注解方式同时满足才会切入 * @param joinPoint */ //@After(value = "getMethods() && withAnnotationMethods()") public void doAfter(JoinPoint joinPoint) { //业务操作同 @Before 方式 MethodSignature methodSignature = (MethodSignature)joinPoint.getSignature(); Method method = methodSignature.getMethod(); log.info("测试切入{}成功,是否包含注解:"+method.isAnnotationPresent(FieldConvert.class), "@After"); log.info("注解中的属性值:"+method.getAnnotation(FieldConvert.class).codeType()); //log.info("执行方法耗时为:" + (System.currentTimeMillis() - startTime)); } /** *

环绕增强切入

* 表达式和注解方式同时满足才会切入 * @author hkl * @date 2022/11/9 */ @Around(value = "getMethods() && withAnnotationMethods()") public Object around(ProceedingJoinPoint point) throws Throwable { //业务操作同 @Before 方式 long startTime = System.currentTimeMillis(); //方法执行之前动作,等效于@Before Object res = point.proceed(); //方法执行之后动作,等效于@After MethodSignature methodSignature = (MethodSignature)point.getSignature(); Method method = methodSignature.getMethod(); log.info("测试切入{}成功,是否包含注解:"+method.isAnnotationPresent(FieldConvert.class), "@Around"); log.info("注解中的属性值:"+method.getAnnotation(FieldConvert.class).codeType()); log.info("执行方法耗时(毫秒)为:" + (System.currentTimeMillis() - startTime)); return res; } }

测试方法:

@FieldConvert(codeType = "aa")
@ApiOperation(value = "保存账户信息")
@PostMapping("/saveAccountBalanceInfo")
public CommonResult saveAccountBalanceInfo(@RequestBody @Validated AccountBalance accountBalance) throws BizException, InterruptedException {
    //accountBalanceService.saveAccountBalanceInfo(accountBalance);
    Thread.sleep(1000);
    return success("保存成功!");
}

测试结果成功切入: 
SpringBoot项目切面编程之@Aspect_第1张图片

 

总结和注意:
1、定义切入点,支持指定包按路径切入,也支持指定是否标记某个注解切入
2、aop默认无法切入 private 修饰的方法,切入点表达式定义的修饰符要和被切入的方法修饰符一致,否则无法切入
3、如下切入点规则,可以切入 public 修饰或者省略修饰符的方法,推荐此方式

@Pointcut("execution( * com.hkl.modules.controller..*(..))")

4、如果配置成 @Pointcut("execution(public * com.hkl.modules.controller..*(..))") 只能切入 public 修饰的方法

原理:
AOP切面的底层原理是cglib动态代理,mybatis的映射文件也是动态代理

你可能感兴趣的:(SpringBoot,aop,spring,boot)