AOP(Aspect Oriented Programming),即面向切面编程,可以说是OOP(Object Oriented Programming,面向对象编程)的补充和完善。OOP引入封装、继承、多态等概念来建立一种对象层次结构,用于模拟公共行为的一个集合。不过OOP允许开发者定义纵向的关系,但并不适合定义横向的关系,例如日志功能。日志代码往往横向地散布在所有对象层次中,而与它对应的对象的核心功能毫无关系对于其他类型的代码,如安全性、异常处理和透明的持续性也都是如此,这种散布在各处的无关的代码被称为横切(cross cutting),在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。
AOP 技术恰恰相反,它利用一种称为"横切"的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为"Aspect",即切面。所谓"切面",简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。
AOP 使用"横切"技术,AOP把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处基本相似,比如权限认证、日志、事物。AOP的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。
AOP术语
- 切面(Aspect)
类是对物体特征的抽象,切面就是对横切关注点的抽象,例如数据库的事务贯穿了整个代码层面,这就是一个切面,它可以定义后面需要的各类通知、切点和引入内容。 - 通知(Advice)
通知是切面开启后,切面的方法。它根据在代理对象真实方法调用前后的顺序和逻辑区分。
前置通知(before):在动态代理反射原有对象方法或者执行环绕通知前执行的通知功能。
后置通知(after):在动态代理反射原有对象方法或者执行环绕通知后执行的通知功能。不论是否抛出异常都会执行。
返回通知(afterReturning):在动态代理反射原有对象方法或者执行环绕通知后正常返回(无异常)执行的通知功能。
异常通知(afterThrowing):在动态代理反射原有对象方法或者执行环绕通知产生异常后执行的通知功能。
环绕通知(around):在动态代理中,它可以取代当前被拦截对象的方法,提供回调原有被拦截对象的方法。 - 引入(Introduction)
引入允许我们在现有的类里添加自定义的类和方法。 - 切点(Pointcut)
这是一个告诉 Spring AOP 在什么时候启动拦截并织入对应的流程中,因为并不是所有的开发都需要启动 AOP,它往往通过切点表达式进行限定。 - 连接点(Join point)
连接点对应的是具体需要拦截的东西,比如通过切点的切点表达式去判断哪个方法是连接点,从而织入对应的通知。 - 织入(Weaving)
织入是一个生成代理对象并将切面内容放入到流程中的过程。实际代理的方法分为动态代理和静态代理。
XML配置开发Spring AOP
Spring 提供了两种方式配置 AOP,注解配置和XML文件配置,在这我们只介绍XML文件配置方式。
AOP配置元素 | 用途 | 备注 |
---|---|---|
aop:advisor | 定义AOP的通知器 | 一种较老的方式,目前很少使用 |
aop:aspect | 定义一个切面 | / |
aop:before | 定义前置通知 | / |
aop:after | 定义后置通知 | / |
aop:around | 定义环绕方式 | / |
aop:after-returning | 定义返回通知 | / |
aop:after-throwing | 定义异常通知 | / |
aop:config | 顶层的AOP配置元素 | AOP的配置根节点 |
aop:declare-parents | 给通知引入新的额外接口,增强功能 | / |
aop:pointcut | 定义切点 | / |
简单的 AOP 案例:
定义接口
public interface PhoneService{
public void call();
public void send_sms();
}
实现类
public class PhoneServiceImpl{
public void call(){
System.out.println("***打电话");
}
public void send_sms(){
System.out.println("***发短信");
}
}
通知类
pulbic class MyAdvice{
public void beforeLog(){
System.out.println(“准备执行移动业务======”);
}
public void afterLog(){
System.out.println("本次业务需要10元======");
}
}
XML配置
<bean id="phone" class="com.etc.service.impl.PhoneServiceImpl"></bean>
<bean id="myAdvice" class="com.etc.common.MyAdvice"></bean>
<aop:config>
<!—配置切点,即需要将方法织入的位置-->
<aop:pointcut id="pc" expression=“execution(* com.etc.service.impl.PhoneServiceImpl.*(...))”/>
<!—配置切面feeAdvice是Bean的id -->
<aop:aspect ref="myAdvice">
<aop:before method="beforLog" pointcut-ref="pc"/>
<aop:after method="afterLog" pointcut-ref="pc"/>
</aop:aspect>
</aop:config>
< aop:aspect >:用于定义切面类
< aop:before >:定义前置通知
< aop:after >:定义后置通知
< aop:after-throwing >:定义异常通知
< aop:after-retruning >:定义返回通知