上文中我们了解了java中JDK和CGLIB两种动态代理的实现。
动态代理在Springboot中最大的应用就是AOP切面编程。
AOP其实我们在自定义注解中就已经应用过了。但是那只是简单的应用,这里我们来详细了解一下AOP编程。
AOP一种面向切面的编程思想。面向切面编程是将程序抽象成各个切面,即解剖对象的内部,将那些影响了多个类的公共行为抽取到一个可重用模块里,减少系统的重复代码,降低模块间的耦合度,增强代码的可操作性和可维护性。
AOP把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处都基本相似。比如权限认证、日志、事务处理、增强处理。
一:引入POM依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
二:定义一个切面类
我这里就定义一个切面类,定义跟注解相关的切面类,请移步《SpringBoot(二十六)SpringBoot自定义注解》
import com.alibaba.fastjson.JSONArray;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.JoinPoint;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class ControllerAspect {
/**
* 定义一个切点,匹配controller包下的所有方法
*/
@Pointcut("execution(* com.modules.controller..*.*(..)) ")
public void controllerMethods() {}
/**
* 在切点方法执行前执行(这个方法 不需要返回值)
* @param joinPoint
*/
@Before("controllerMethods()")
public void beforeControllerMethod(JoinPoint joinPoint)
{
System.out.println("进入了切面类的before");
// 这里可以编写你想在方法执行前调用的代码
System.out.println(joinPoint);
Object[] args = joinPoint.getArgs();
for (Object arg : args)
{
System.out.println("参数:" + arg);
}
}
/**
* 环绕,可以在切入点前后织入代码,并且可以自由的控制何时执行切点;
* @param point
* @return
* @throws Throwable
*/
@Around("controllerMethods()")
private Object testAop(ProceedingJoinPoint point) throws Throwable
{
System.out.println("======AopAspectJ执行环绕通知开始=========");
Object obj = point.proceed();
Object[] args = point.getArgs();
//方法名
String methodName = point.getSignature().getName();
//对象
Object target = point.getTarget();
//类名
String className = target.getClass().getName();
System.out.println("类:" + className + ";方法:" + methodName + ";参数:" + JSONArray.toJSONString(args));
System.out.println("======AopAspectJ执行环绕通知结束=========");
return obj;
}
/**
* 在切点方法执行后执行
* @param joinPoint
*/
@AfterReturning("controllerMethods()")
public void afterControllerMethod(JoinPoint joinPoint)
{
// 这里可以编写你想在方法执行后调用的代码
System.out.println("进入了切面类的after");
}
}
通过上方的代码,我们可以发现,定义一个切面类其实很简单,使用@Aspect、@Component注解定义就可以了。
上方的切面类匹配的是所有controller包下的所有方法:
我们随意访问一个方法。
http://127.0.0.1:7001/java/index/getData
控制台输出:
======AopAspectJ执行环绕通知开始=========
进入了切面类的before
execution(Map com.modules.controller.fontend.IndexController.getData(Integer,String))
参数:1
参数:
进入了index方法!
进入了切面类的after
类:com.modules.controller.fontend.IndexController;方法:getData;参数:[1,""]
======AopAspectJ执行环绕通知结束=========
三:常用AOP注解
1:通知方法、注解
1) 切面(Aspect):一般是指被@Aspect修饰的类,代表着某一具体功能的AOP逻辑。
2) 切入点(Pointcut):选择对哪些方法进行增强。
3) 通知(Advice):对目标方法的增强,有一下五种增强的类型。
² 环绕通知(@Around):内部执行方法,可自定义在方法执行的前后操作。
² 前置通知(@Before):在方法执行前执行。
² 后置通知(@After):在方法执行后执行。
² 返回通知(@AfterReturning):在方法返回后执行。
² 异常通知(@AfterThrowing):在方法抛出异常后执行。
4) 连接点(JoinPoint):就是那些被切入点选中的方法。这些方法会被增强处理。
2:匹配表达式
| 表达式类型 | 功能 |
|---|---|
| execution() | 匹配方法,最全的一个 |
| args() | 匹配入参类型 |
| @args() | 匹配入参类型上的注解 |
| @annotation() | 匹配方法上的注解 |
| within() | 匹配类路径 |
| @within() | 匹配类上的注解 |
| this() | 匹配类路径,实际上AOP代理的类 |
| target() | 匹配类路径,目标类 |
| @target() | 匹配类上的注解 |
切点表达式实例:
execution(方法修饰符 返回类型 方法所属的包.类名.方法名称(方法参数))
括号外的 ‘..’ 表示多级目录
括号内的 ‘..’ 表示多个参数
例子:在AspectJ中,execution是一个常用的切入点表达式,用于匹配特定的方法执行。它可以根据方法的修饰符、返回类型、所属的包、类名以及方法名称等信息来定义切入点。以下是几个使用execution表达式的例子,以及对它们的解释:
1:匹配特定类中的方法:
execution(* com.example.MyClass.myMethod(..))
*这个表达式匹配com.example包中MyClass类里的myMethod方法。表示任何修饰符,..表示任何参数。
2:匹配任何返回类型的方法:
execution(* *.methodName(..))
这里第一个代表任何返回类型,第二个代表任何类,methodName是你想要匹配的方法名。
3:匹配特定包及子包下所有类的方法:
execution(* com.example.*.*(..))
这个表达式匹配com.example包及其所有子包中所有类的方法。
4:匹配特定修饰符的方法:
execution(public * com.example.MyClass.myMethod(..))
这个表达式仅匹配公开(public)的myMethod方法。
5:匹配特定参数类型的方法:
execution(* com.example.MyClass.myMethod(java.lang.String, int))
这个表达式匹配MyClass中接受String和int作为参数的myMethod方法。
6:匹配继承自特定类的类中的方法:
execution(* com.example.*+.myMethod(..))
+表示匹配继承自com.example包中任何类的myMethod方法。
7:匹配注解了特定注解的方法:
execution(@com.example.MyAnnotation * *(..))
这个表达式匹配任何被@com.example.MyAnnotation注解的方法。
8:匹配特定异常类型的抛出:
execution(* *.*(..) throws java.lang.Exception)
这个表达式匹配任何可能抛出java.lang.Exception或其子类异常的方法。
9:组合使用多个条件:
execution(public * com.example.service.*.*(..))
这个表达式匹配com.example.service包中公开的任何类的方法。
在AspectJ中,execution表达式提供了强大的灵活性,允许你定义非常具体或非常广泛的切入点,以实现面向切面编程(AOP)的各种需求。
四:AOP注解失效的场景:
1:Spring的AOP只能拦截由Spring容器管理的Bean对象。如果您使用了非受Spring管理的对象,则AOP将无法对其进行拦截。注解也不会生效。
2:如果一个Bean内部的方法直接调用同一个Bean内部的另一个方法,AOP将无法拦截这个内部方法调用。因为AOP是基于代理的,只有通过代理对象才能触发AOP拦截。(@Transactional事务注解也是同理)
3:私有方法调用,Spring的AOP只能拦截public方法。
4:静态方法
Spring的AOP只能拦截非静态方法。如果您尝试拦截静态方法,AOP将无法生效。
5:final方法
AOP无法拦截final方法。final方法是不可重写的,因此AOP无法生成代理对象来拦截这些方法。直接在对象内部调用方法:如果您直接在对象内部调用方法而不通过代理对象,AOP将无法拦截。因此,建议始终通过代理对象调用方法以确保AOP的生效
6:异步方法
对于使用Spring的异步特性(如@Async注解)的方法,AOP拦截器可能无法正常工作。这是因为异步方法在运行时会创建新的线程或使用线程池,AOP拦截器无法跟踪到这些新线程中的方法调用。
以上大概就是AOP切面编程的基本使用。
有好的建议,请在下方输入你的评论。