面向切面编程(AOP)
1.横切关注点
在软件开发中,把散布在应用各处的功能称为横切关注点,比如日志、安全。
通常横切关注点是与我们的业务逻辑相互嵌套的,而AOP就是为了将横切关注点和业务逻辑分离。
2.切面
横切关注点可以模块化为特殊的类,这些类称为切面,它是通知和切点的结合。如下图所示:
切面取代了继承和委托,使用AOP,我们仍然在一个地方定义通用功能,但是可以通过声明的方式定义这个功能以何种方式在何处应用,而不修改受影响的类。
3.通知
在AOP术语中,切面的工作被称为通知,它定义了切面是什么以及何时使用。Spring切面定义了五种通知:
- Before:在目标方法调用前执行通知
- After:在目标方法调用后执行通知
- After-returning:在目标方法成功执行后执行通知
- After-throwing:在目标方法抛出异常时执行通知
- Around:在目标方法调用前和调用后执行通知
4.连接点
连接点是应用执行过程中能够插入切面的一个点,其可能是调用方法时、抛出异常时、甚至修改一个字段时。
5.切点
切点定义了切面所通知的连接点的范围,即在何处应用切面。
6.织入
织入是把切面应用到目标对象并创建新的代理对象的过程。
7.动态代理
Spring AOP是在运行期将切面织入到Spring管理的bean中的,即Spring是基于动态代理的。
代理类包裹了切面,封装了目标类,并会拦截对目标类的方法调用。当代理拦截到方法调用时,先执行切面逻辑,之后把调用转发给真正的目标bean。
注意:基于JDK的动态代理只能代理实现了接口的类,基于cglib的采用继承的方式,两种类都可以代理。因为final类和静态方法是不能被代理的,所以aop也不能织入静态方法。
配置AOP
Spring框架集合了ProxyFactory和Cglib两种方式来实现AOP。Spring AOP能够根据上下文环境选择其一,一般而言,当业务对象实现了接口时会选择基于JDK的动态代理;当业务对象没有实现接口时会选择Cglib动态对目标对象进行子类化;如果实现了接口却发生异常,需开启@EnableAspectJAutoProxy(proxyTargetClass = true)
强制使用Cglib。
1.在xml中配置
Spring的AOP配置元素能够以非侵入性的方式声明切面。
大多数的AOP配置元素必须 在<aop:config>
元素的上下文内使用。使用<aop:aspect>
元素中的ref属性可以声明一个简单的切面。部分配置元素如下:
<aop:after>
<aop:before>
<aop:around>
<aop:after-returning>
<aop:after-throwing>
<aop:aspect>
定义一个切面<aop:pointcut>
定义一个切点
2.使用注解配置
- 首先创建一个切面类,使用@Aspect注解此类声明为切面
- 创建无参无方法体的方法作为切点ID,并使用@Pointcut("execution(* com.crab.service..(..))")注解的形式声明一个切点
- 创建增强的方法体,添加如@Before("anyCut()")的注解,anyCut为上一步创建的切点
Spring AOP实战测试
在IOC的基础上需要的包如下:
1.使用xml配置
使用示例如下:
<!-- 启用注解配置 -->
<!-- <aop:aspectj-autoproxy /> -->
<!-- proxy-target-class属性默认为false,表示使用DynamicProxy实现AOP,设置为true表示使用Cglib实现AOP -->
<aop:config proxy-target-class="true">
<!-- 定义全局切点,后面使用id引入 -->
<aop:pointcut id="anyCut" expression="execution(* com.crab.service.Tom.*(..))"/>
<!-- 定义切面,ref指代理类的bean -->
<aop:aspect ref="aspectDemo">
<!-- 将切面织入切点,使用切面的method方法增强切点方法,在切点方法之前执行 -->
<aop:before pointcut-ref="anyCut" method="divBefore" />
<!-- 直接配置局部切点 -->
<aop:after pointcut="execution(* com.crab.service.Tom.say(..))" method="divAfter" />
</aop:aspect>
</aop:config>
其中限制切点范围的execution中的内容分别表示:execution(类的返回类型+空格+类的方法的全限定名+(方法参数)),以*开始,表明了我们不关心方法返回值的类型。然后,我们指定了全限定类名和方法名。对于方法参数列表,我们使用两个点号(..)表明切点要选择任意的相应方法,无论该方法参数是什么。
2.使用注解配置
使用示例如下:
//声明此类为切面
@Aspect
//告知Spring为此类创建bean
@Component
public class AnnoAspect {
//声明一个切入点,这里是service包里的所有方法,无参无方法体的方法名为切入点ID
@Pointcut("execution(* com.crab.service.*.*(..))")
private void anyCut() {}
@Pointcut("execution(* com.crab.service.Tom.say(..))")
private void roundCut() {}
@Before("anyCut()")
public void divBef() {
System.out.println("----------before-----------");
}
@Around("roundCut()")
public void divAft() {
System.out.println("----------round-----------");
}
}
参考书籍
《Spring In Action》