SpringBoot中使用AOP
AOP主要实现的目的是针对业务处理过程中的切面进行提取,它所面对的是处理过程中的某个步骤或阶段,以获得逻辑过程中各部分之间低耦合性的隔离效果。
对于我们开发中最常见的可能就是日志记录,事务处理,异常处理等等。。。
原理
AOP是通过动态代理实现的,动态代理又分为两个部分:JDK动态代理和CGLIB动态代理,AOP功能的使用还是比较简单的,把相关bean注入到Spring容器中,编写好相应的Aspect类即可,以下两点需要记住:
动态代理模式
2、AOP是
方法级别
的AOP术语
- 切面(Aspect)
一个关注点的模块化,这个关注点可能会横切多个对象。事务管理是J2EE应用中一个关于横切关注点的很好的例子。在Spring AOP中,切面可以使用基于模式或者基于
@Aspect
注解的方式来实现- 连接点(JoinPoint)
在程序执行过程中某个特定的点,比如某方法调用的时候或者处理异常的时候。在SpringAOP中,一个连接点总是表示一个方法的执行
- 通知(Advice)
在切面的某个特定的连接点上执行的动作。其中包括了Around
、Before
和After
等不同类型的通知。许多AOP框架(包括Spring)都是以拦截器做通知模型,并维护一个以连接点为中心的拦截器链
注解 | 描述 |
---|---|
@Before | 前置通知, 在方法执行之前执行 |
@After | 后置通知, 在方法执行之后执行 |
@AfterReturn | 返回通知, 在方法返回结果之后执行 |
@AfterThrowing | 异常通知, 在方法抛出异常之后 |
@Around | 环绕通知,围绕方法的执行 |
- 切入点(PointCut)
匹配连接点的断言。通知和一个切入点表达式关联,并在满足这个切入点的连接点上运行(例如,当执行某个特定名称的方法时),切入点表达式如何和连接点匹配是AOP的核心:Spring
缺省使用AspectJ切入点语法
常用切入点指示符
1、execution表达式:用于匹配方法执行的连接点,属于方法级别
语法:execution(修饰符 返回值类型 方法名(参数)异常)
语法参数 | 描述 |
---|---|
修饰符 | 可选,如public,protected,写在返回值前,任意修饰符填* 号就可以 |
返回值类型 | 必选 ,可以使用* 来代表任意返回值 |
方法名 | 必选 ,可以用* 来代表任意方法 |
参数 | () 代表是没有参数,(..) 代表是匹配任意数量,任意类型的参数,当然也可以指定类型的参数进行匹配,如要接受一个String类型的参数,则(java.lang.String) , 任意数量的String类型参数:(java.lang.String..) 等等。。。 |
异常 | 可选,语法:throws 异常 ,异常是完整带包名,可以是多个,用逗号分隔 |
符号:
符号 | 描述 |
---|---|
* | 匹配任意字符 |
.. | 匹配多个包或者多个参数 |
+ | 表示类及其子类 |
条件符:
符号 | 描述 |
---|---|
&&、and | 与 |
|| | 或 |
! | 非 |
案例:
- 拦截
com.dw.study
包下的所有子包里的任意类的任意方法
execution(* com.dw.study..*.*(..))
-
拦截
com.dw.study
.Test2Controller下的任意方法
execution(*
com.dw.study
.Test2Controller.*(..)) -
拦截任何修饰符为public的方法
execution(public * * (..))
-
拦截
com.dw.study
下的所有子包里的以ok开头的方法
execution(*
com.dw.study
..*.ok*(..))
2、@annotation
根据所应用的注解对方法进行过滤
语法:
实例:
对用了com.dw.annotations.Test注解的所有方法进行拦截
语法:
@annotation(注解全路径)
实例:
对用了com.dw.annotations.Test注解的所有方法进行拦截
@annotation(com.dw.annotations.Test)
- 目标对象(Target Object)
被一个或者多个切面所通知的对象。也被称做被通知对象。既然Spring AOP是通过运行时代理实现的,这个对象永远是一个被代理对象
maven 依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<version>2.2.2.RELEASE</version>
</dependency>
使用:
@Aspect @Component public class MyCustomerAspect { /** * 定义切面,com.dengwei.springaopdemo.controller下面所有的方法进行拦截 */ @Pointcut("execution(public * com.dengwei.springaopdemo.controller.*.*(..))") public void myAspect(){} /** * 对于注解了 @MethodAspect的方法进行拦截 */ @Pointcut("@annotation(com.dengwei.springaopdemo.annotation.MethodAspect)") public void testMyAnnotationAspect() {} /** * 前置通知,在方法执行之前执行 * @param joinPoint * @throws Throwable */ @Before("myAspect()") public void doBefore(JoinPoint joinPoint) throws Throwable { System.out.println(1 / 0); // 接收到请求,记录请求内容 ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); // 记录下请求内容 System.out.println("URL : " + request.getRequestURL().toString()); System.out.println("HTTP_METHOD : " + request.getMethod()); System.out.println("IP : " + request.getRemoteAddr()); System.out.println("CLASS_METHOD : " + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName()); System.out.println("ARGS : " + Arrays.toString(joinPoint.getArgs())); } /** * 前置通知,在方法执行之前执行 * @param joinPoint */ @Before("testMyAnnotationAspect()") public void doBeforeForAnnotation(JoinPoint joinPoint) { // 接收到请求,记录请求内容 System.out.println("我是注解切面,被成功执行了"); } /** * 后置最终通知,final增强,不管是抛出异常或者正常退出都会执行 * @param joinPoint 连接点 */ @After("myAspect()") public void doAfter(JoinPoint joinPoint){ System.out.println("方法最后执行....."); } /** * 后置通知,方法返回结果后执行 *returning属性指定连接点方法返回的结果放置在returnResult变量中 * @param returnResult 返回结果 * @param joinPoint 连接点 */ @AfterReturning(returning = "returnResult", pointcut = "myAspect()") public void doAfterReturning(JoinPoint joinPoint, Object returnResult){ // 处理完请求,返回内容 System.out.println("方法的返回值 : " + returnResult); } /** * 后置通知,当方法抛出异常时执行 * @param joinPoint 连接点 * @param runtimeException 当切点方法抛出runtimeException时执行 */ @AfterThrowing(value = "myAspect()", throwing = "runtimeException") public void doThrowsIng(JoinPoint joinPoint, RuntimeException runtimeException){ System.out.println("方法异常时执行了我....."); } /** * 环绕通知,环绕增强,相当于MethodInterceptor * @param joinPoint * @return * @throws Throwable */ @Around("myAspect()") public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable { System.out.println("注解方式AOP拦截开始进入环绕通知......."); Object proceed = joinPoint.proceed(); System.out.println("准备退出环绕......"); return proceed; } }
自定义注解:
/** * @Author dw * @ClassName MethodAspect * @Description 注解在方法上 * @Date 2020/7/10 14:39 * @Version 1.0 */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface MethodAspect { }
controller:
@RestController public class TestAopController { @RequestMapping("/first") public Object first() { return "first controller"; } @RequestMapping("/doError") public Object error() { return "如果报错了,是否会打印我"; } @RequestMapping("/annotationAspectTest") @MethodAspect public Object annotationAspectTest(){ return "测试注解切面"; } }