面向切面编程,缩写为 AOP,在程序开发中主要用来解决一些系统层面上的问题,比如日志、事务、权限等。在阿里体系中,AOP 广泛应用于天梭日志、本地缓存、doom 增强等场景。
AOP基本概念
为什么需要面向切面编程?面向对象编程解决了封装问题,但同时也带来了新问题,如何增强对象的方法?比如,一个接口 A 可能有多个实现类 A1、A2、******、An,如何为多个实现类的同一个方法打印日志或者做权限管理?普通的面向对象思路一般是封装一个工具类,然后加到所有方法里,一旦需要做的增强比较多代码就会很臃肿,难以维护。因此,才有了面向切面编程。
下面的图展示了面向切面编程的思想,面向对象编程通过封装、继承和多态实现了业务逻辑,而面向切面编程把业务流程横刀切开在切面上处理特定事务。一横一竖,切面的称呼就是这样来的。
从上图也可以看出面向切面编程不属于业务逻辑的一部分,而是与具体业务无关的特定事务,如日志、权限等。面向切面编程有以下几个概念需要理解:
(1)Aspect(切面):通常是一个类,里面可以定义切入点和通知;
(2)JointPoint(连接点):程序执行过程中明确的点,一般是方法的调用;
(3)Advice(通知):AOP在特定的切入点上执行的增强处理,可以是前置处理、后置处理、异常处理等;
(4)Pointcut(切入点):就是带有通知的连接点,在程序中主要体现为书写切入点表达式;
(5)AOP代理:AOP框架创建的对象,代理就是目标对象的加强。
(6)织入:把切面应用到目标对象来创建新的代理对象的过程。
织入一般发生在如下几个时机:
(1)编译时:当一个类文件被编译时进行织入,这需要特殊的编译器才可以做的到,例如AspectJ的织入编译器;
(2)类加载时:使用特殊的ClassLoader在目标类被加载到程序之前增强类的字节代码;
(3)运行时:切面在运行的某个时刻被织入。
Spring AOP
Spring 提供了专门的依赖包来支持 AOP 。Spring中的AOP代理可以是JDK动态代理,也可以是CGLIB代理。代理的生成,管理及其依赖关系都是由IOC容器负责,Spring默认使用JDK动态代理,在需要代理类而不是代理接口的时候,Spring会自动切换为使用CGLIB代理。
Spring 提供了多种实现AOP的方式:
-
基于接口实现的切面:实现接口的方法,定义切入点和通知;
-
基于注解的切面:基于 @Aspect、@Pointcut、@Before、@AfterReturning 等注解的 AOP 方式;
-
基于 XML 的切面:使用 xml 中的 aop 命名空间,如 aop:aspect;
这里介绍使用比较多比较简单的基于接口方式,其他几种类型不做介绍。Spring 使用 Advisor 接口表示一般切面,包含了 Advice,但是没有切入点。
public interface Advisor {
//获取 Advice
Advice getAdvice();
//暂不用
boolean isPerInstance();
}
PointcutAdvisor 接口继承了 Advisor,代表具有切入点的切面,它包含Advice 和 Pointcut 两个类。基于 PointcutAdvisor 接口即可定义切面,可以通过类、方法名、注解等信息灵活地定义切面的连接点,提供更具适用性的切面。
public interface PointcutAdvisor extends Advisor {
//获取 Pointcut
Pointcut getPointcut();
}
Spring 提供了 PointcutAdvisor 接口的 6 个实现类,定义了 6 种切面类型,如下:
DefaultPointcutAdvisor:最常用的切面类型,它可以通过任意Pointcut和Advice定义一个切面;
NameMatchMethodPointcutAdvisor:通过该类可以定义按方法名定义切点的切面:
RegexpMethodPointcutAdvisor:对于按正则表达式匹配方法名进行切点定义的切面,可以通过扩展该实现类进行操作;
StaticMethodMatcherPointcutAdvisor:静态方法匹配器切点定义的切面;
AspecJExpressionPointcutAdvisor:用于Aspecj切点表达式定义切点的切面;
AspecJPointcutAdvisor:用于AspecJ语法定义切点的切面。
Spring AOP 提供了 Pointcut 接口多种内置实现,我们可以基于这些实现定义切入点。常用的实现包括:
StaticMethodMatcherPointcut:静态匹配方法,实现方法级别的切入,非运行时切入,默认情况下匹配所有的类;
DynamicMethodMatcherPointcut:动态匹配方法,实现方法级别的切入,运行时切入,默认情况下匹配所有的类;
AnnotationMatchingPointcut:基于注解匹配;
ExpressionPointcut:支持AspectJ切点表达式语法;
ControlFlowPointcut:控制流程切点,根据程序执行堆栈的信息查看目标方法是否由某一个方法直接或间接发起调用,以此判断是否为匹配的连接点;
ComposablePointcut:复合切点,为创建多个切点而提供的方便操作类。它所有的方法都返回ComposablePointcut类,这样,我们就可以使用链接表达式对其进行操作。
Spring AOP 提供了 Advice 接口多个子接口来支持增强。如下所示:
接口 MethodBeforeAdvice:在目标方法调用之前调用的Advice;
接口 AfterReturningAdvice:在目标方法调用并返回之后调用的Advice;
接口 MethodInterceptor:在目标方法的整个执行前后有效,并且有能力控制目标方法的执行;
接口 ThrowsAdvice:在目标方法抛出异常时调用的Advice;
基于接口的AOP使用方式
基于 PointcutAdvisor 接口实现 AOP 只需要定义切入点 Pointcut 和通知 Advice 即可,其他交给 Spring AOP 框架去处理。在基于接口的实现中,PointcutAdvisor 的实现类定义了一个切面。
在 getPointcut 中定义切入点。如下面的示例,定义了切入点是所有拥有 MyTestAnnotation 注解的方法,凡是有注解的方法都是我们的切入点。
@Getter
public Pointcut pointcut = new StaticMethodMatcherPointcut() {
@Override
public boolean matches(Method method, Class<?> targetClass) {
return method.getAnnotation(MyTestAnnotation.class) != null;
}
};
在 getAdvice() 中定义需要做的增强。如下面的示例,实现了 MethodInterceptor 接口来进行方法增强,入参为 MethodInvocation 对象,该对象携带了切面的信息,如正在调用的方法、入参等。可以在方法中直接调用原来的方法,并在调用方法前进行前置处理,或者在调用方法后进行后置处理。
@Getter
public Advice advice = (MethodInterceptor)methodInvocation -> {
// TODO 增强前置处理
// 调用原来的方法
Object result = methodInvocation.proceed();
// TODO 增强后置处理
return result;
};
AOP注意事项
AOP 不宜处理耗时太久的操作:AOP 作为切面应该专注于处理简单的事务,如打印日志。耗时过多的操作不宜放在 AOP 中进行,如果一定需要处理,应该做异步处理。
AOP 不宜抛出异常:AOP 的操作不能影响正常的业务逻辑,因此一定要加 catch,确保 AOP 本身的异常不影响正常业务。
AOP 不能出现循环:尤其是打印日志的切面,一定要避免循环。AOP 中出现错误需要打印日志的时候,尽量单独打印,避免循环。
总结
本文介绍了 AOP 基本概念,介绍了基于 Spring AOP 接口实现面向切面编程的方法。合理地使用 AOP 可以降低系统复杂度,无侵入地增强系统的功能,在 Java 企业应用中有着广泛的应用。
关注公众号“程序之心”(ID:chengxuzhixin),每天给你诚意满满的干货!