Java进阶 - AOP
概念
AOP(Aspect-Oriented Programming),面向切面编程。旨在不改变程序现有代码的前提下,可以设置某方法运行前或运行后新增额外代码的操作,减少对代码的入侵。
AOP并不是Spring框架独有的,而是从AspectJ框架中借鉴而来。包括过滤器、拦截器都是一种AOP的思想。主要应用于:日志收集、事务管理、安全检查、缓存、对象池管理等。
AOP基本概念:
- 切面(
Aspect
):是一个可以加入额外代码运行的特定位置,一般指方法之间的调用,可以在不修改原代码的情况下,添加新的代码,对现有代码进行升级维护和管理。 - 织入(
Weaving
):选定一个切面,利用动态代理技术,为原有的方法的持有者生成动态对象,然后将它和切面关联,在运行原有方法时,就会按织入之后的流程运行了。 - 目标对象(
Target
):需要被加强的业务对象 - 代理类(
Proxy
):一个类被AOP织入通知后,就产生了一个代理类。 - 切点(
PointCut
):每个程序的连接点有多个,如何定位到某个感兴趣的连接点,就需要通过切点来定位。 - 连接点(
Joinpoint
):程序执行的某个特定位置,如某个方法调用前,调用后,方法抛出异常后,这些代码中的特定点称为连接点。连接点表示具体要拦截的方法,切点是定义一个范围,而连接点是具体到某个方法。 - 通知(
Advice
):原意为增强,是织入到目标类连接点上的一段程序代码。
AspectJ框架
AspectJ是一个面向切面的框架,是目前最好用,最方便的AOP框架,和Spring中的aop可以集成在一起使用,通过Aspectj提供的一些功能实现aop代理变得非常方便。
AspectJ使用步骤:
- 创建一个类,使用@Aspect标注
- @Aspect标注的类中,通过@Pointcut定义切入点
- @Aspect标注的类中,通过AspectJ提供的一些通知相关的注解定义通知
- 使用AspectJProxyFactory结合@Ascpect标注的类,来生成代理对象
Spring AOP
SpringAOP作为AOP的一种实现,基于动态代理的实现AOP,意味着实现目标对象的切面会创建一个代理类,代理类的实现有两种不同的模式,分为两种不同的代理。
Spring AOP利用的是运行时织入,在SpringAOP中连接点是方法的执行。在实际生产中,我们用得最多的还是 Spring AOP,其底层采用的是JDK/CGLIB动态代理。
引入AspectJ依赖
另外,在SpringAOP的实现中,借用了AspectJ的一些功能,比如@Aspcet、@Before、@PonitCut这些注解,都是AspectJ中的注解。在使用SpringAOP的时候需要引入AspectJ的依赖。
@Aspect注解方式
@Aspect注解方式,它的概念都是来自于 AspectJ,但是功能的实现是纯 Spring AOP 自己实现的,主要有两大核心:
- 定义[切入点]:使用 @Pointcut 切点表达式。
- 定义[切入时机] 和 [增强处理逻辑]:五种通知Advice注解 对[切入点]执行增强处理, 包括:@Before、@After、@AfterRunning、@AfterThrowing、@Around
@Pointcut
@Pointcut用来标注在方法上来定义切入点。格式:@ 注解(value=“表达标签 (表达式格式)”),如:
表达式标签(10种)
- execution:用于匹配方法执行的连接点
- within:用于匹配指定类型内的方法执行
- this:用于匹配当前AOP代理对象类型的执行方法;注意是AOP代理对象的类型匹配,这样就可能包括引入接口也类型匹配
- target:用于匹配当前目标对象类型的执行方法;注意是目标对象的类型匹配,这样就不包括引入接口也类型匹配
- args:用于匹配当前执行的方法传入的参数为指定类型的执行方法
- @within:用于匹配所以持有指定注解类型内的方法
- @target:用于匹配当前目标对象类型的执行方法,其中目标对象持有指定的注解
- @args:用于匹配当前执行的方法传入的参数持有指定注解的执行
@annotation
:用于匹配当前执行方法持有指定注解的方法
- bean:Spring AOP扩展的,AspectJ没有对于指示符,用于匹配特定名称的Bean对象的执行方法
@Before 前置通知
前置通知在被切的方法执行之前执行,比如获取连接对象等。
这里有个非常重要参数JoinPoint:连接点 。因为Spring只支持方法类型的连接点,所以在Spring中连接点指的就是被拦截到的方法. 里面有三个常用的方法,基于这3个方法,可以轻松打印:被切的类名、方法名、方法参数值、方法参数类型等。
- getSignature()获取签名
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
通过signature可以获取名称 getName() 和 参数类型 getParameterTypes()
- getTarget()获取目标类
Class<?> clazz = joinPoint.getTarget().getClass(); 如果被切的类是被别的切面切过的类,可以使用AopUtils.getTargetClass获取一个数组,再从数组中找你期望的类。
- getArgs()获取入参值
Object[] args = joinPoint.getArgs()
@After 后置通知
后置通知在被切的方法执行之后执行,无论被切方法是否异常都会执行,比如关闭连接对象等。
@AfterRunning 返回通知
返回通知在被切的方法return后执行,带有返回值,如果被切方法异常则不会执行。这里多了一个参数Object result
,注解上也多了一个参数:returning
。
@AfterThrowing 异常通知
异常通知只在被切方法异常时执行,否则不执行,比如回滚事务。
这里多了一个参数Exception e
,表示捕获所有异常,也可以设置为具体某一个异常,例如NullPointerException、RpcException等等。注解上也多了一个参数:throwing
@Aroud 环绕通知
环绕通知方法可以包含上面四种通知方法,是最全面最灵活的通知方法。可以决定目标方法是否执行,什么时候执行,执行时是否需要替换方法参数,执行完毕是否需要替换返回值。
这里的参数类型和其它通知方法不同,从JoinPoint变为ProceedingJoinPoint
。
AopContext
参考资料:AopContext.currentProxy() - 简书
Spring AOP模式是不暴露出来的,也就是默认AopContext.currentProxy()方法默认返回的都是null,需要配置Aop代理对象暴露,需要通过 @EnableAspectJAutoProxy(exposeProxy = true) 注解开启。