一、AOP的概念
AOP(Aspect-OrientedProgramming),叫做面向切面编程,利用一种称为“横切”的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其名为“Aspect”,即切面。所谓“切面”,简单地说,就是将那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可操作性和可维护性。AOP代表的是一个横向的关系,如果说“对象”是一个空心的圆柱体,其中封装的是对象的属性和行为;那么面向方面编程的方法,就仿佛一把利刃,将这些空心圆柱体剖开,以获得其内部的消息。而剖开的切面,也就是所谓的“切面”了。然后它又以巧夺天功的妙手将这些剖开的切面复原,不留痕迹。
使用“横切”技术,AOP把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处都基本相似。比如权限认证、日志、事务处理。Aop 的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。
实现AOP的技术,主要分为两大类:一是采用动态代理技术,利用截取消息的方式,对该消息进行装饰,以取代原有对象行为的执行;二是采用静态织入的方式,引入特定的语法创建“切面”,从而使得编译器可以在编译期间织入有关“切面”的代码。
二、常用的AOP应用
Authentication 权限
Caching 缓存
Context passing 内容传递
Error handling 错误处理
Lazy loading 懒加载
Debugging 调试
logging, tracing, profiling and monitoring 记录跟踪 优化 校准
Performance optimization 性能优化
Persistence 持久化
Resource pooling 资源池
Synchronization 同步
Transactions 事务
三、AOP的相关概念
切面(Aspect):一个关注点的模块化,这个关注点实现可能另外横切多个对象。事务管理是J2EE应用中一个很好的横切关注点例子。方面用spring的 Advisor或拦截器实现。切面的具体表现就是实现公共方法的类。
连接点(Joinpoint): 程序执行过程中明确的点,如方法的调用或特定的异常被抛出。连接点就是程序在运行过程中能够插入切面的地点。例如,方法调用、异常抛出或字段修改等,但spring只支持方法级的连接点。比如,调用update之前需要调用开启事务,则update方法这里就是一个连接点。
通知(Advice):通知就是,我们在连接点上,需要以什么形式做事情。根据放置的地方不同,可分为前置通知(Before)、后置通知(AfterReturning)、异常通知(AfterThrowing)、最终通知(After)与环绕通知(Around)5种。在实际应用中通常是切面类中的一个方法,具体属于哪类通知,同样是在配置中指定的。
切入点(Pointcut): 指定一个通知将被引发的一系列连接点的集合。就是我们需要指定一种通知,然后哪些连接点可以用,是一个集合,通常使用表达式来描述。
目标对象(Target):就是那些即将切入切面的对象,也就是那些被通知的对象。这些对象中已经只剩下干干净净的核心业务逻辑代码了,所有的共有功能代码等待AOP容器的切入。
AOP代理(AOP Proxy): AOP框架创建的对象,包含通知。 在Spring中,AOP代理可以是JDK动态代理或者CGLIB代理。
织入(Weaving): 组装方面来创建一个被通知对象。这可以在编译时完成(例如使用AspectJ编译器),也可以在运行时完成。Spring和其他纯Java AOP框架一样,在运行时完成织入。
四、基础接口和基础类
Pointcut:PointCut在Spring中抽象出来的一个类,定义了那些连接点需要被织入横切逻辑。Spring中PointCut结构如下
Advice系列:Spring抽象出来的通知接口和类,Adivce子接口分别有AfterAdvice,BeforeAdvice和Interceptor。Spring类图如下:
Advisor:Advisor是Spring AOP 的一个切面,但只包含一个PointCut和一个Advice。Advisor是切面的一个特例,通过跟踪AdvisedSupport源码可以发现,一个Advisor链表其实就是实现了Aspect的功能。所以Advisor可以看成是Aspect的一个单元吧;
SpringProxy:SpringAOP代理类的一个表示,所有Spring代理类都实现了这个接口;
Advised:AOP代理的一个接口,它的实现类管理AOP配置,如Interceptors,advice,Advisors和被代理的接口。Spring代理类都能够类型转换成Advised类型;
AdvisedSupport:AOP代理配置管理的基础类,继承ProxyConfig并且实现了Advised接口;
ProxyCreatorSupport:代理工程基础类,主要是支持配置和访问AopProxyFactory;
ProxyFactory:代理工厂,提供编程式的Srping AOP应用,其实声明式的Spring AOP照样是要引用它来实现的(这是显然的,声明了最后还是得通过编程式的类来执行的);
AopProxy:AOP代理的一个委托接口,CglibAopProxy,JdkDynamicAopProxy实现这个接口;
CglibAopProxy:基于Cglib代理的Spring AOP代理类,getProxy,getCallbacks是核心方法;
ObjenesisCglibAopProxy:CglibAopProxy的子类,定义了DynamicAdvisedInterceptor等回调类,其中intercept方法是核心。
JdkDynamicAopProxy:基于JDK 动态代理的Spring AOP代理类,其中getProxy,invoke是核心;
AopProxyFactory:被用来通过工厂模式实现基于(AdvisedSupport)配置创建对应代理类的基础接口。
DefaultAopProxyFactory:AopProxyFactory的默认实现类;
AdvisorChainFactory:advisor链的工厂接口。提供getInterceptorsAndDynamicInterceptionAdvice目标对象方法执行的切面;
DefaultAdvisorChainFactory:AdvisorChainFactory的默认实现类;
ReflectiveMethodInvocation:提供反射调用目标对象的功能。
ProxyFactoryBean:提供声明式的SpringAOP引用。
五、如何使用Spring AOP
可以通过配置文件或者编程的方式来使用Spring AOP。
配置可以通过xml文件来进行,大概有四种方式:
1. 配置ProxyFactoryBean,显式地设置advisors, advice, target等
2. 配置AutoProxyCreator,这种方式下,还是如以前一样使用定义的bean,但是从容器中获得的其实已经是代理对象
3. 通过来配置
4. 通过来配置,使用AspectJ的注解来标识通知及切入点
也可以直接使用ProxyFactory来以编程的方式使用Spring AOP,通过ProxyFactory提供的方法可以设置target对象, advisor等相关配置,最终通过 getProxy()方法来获取代理对象。
六、具体使用
1、通过ProxyFactoryBean
上代码,一个接口Person,一个实现Male
public interface Person {
public void sleep();
}
public class Male implements Person {
public void sleep() {
System.out.println("我要睡觉了!");
}
}
现在,我们要在调用sleep方法前和后,做一些事情,定义一个类,实现两个接口,这两个接口对应的就是上面的Before通知和After通知
/**
* 增强类
* 也就是AOP的Advice
* 继承了Before类型的通知和After类型的通知
* @author Errol
*
*/
public class SleepHelper implements MethodBeforeAdvice , AfterReturningAdvice{
public void afterReturning(Object returnValue, Method method,
Object[] args, Object target) throws Throwable {
System.out.println("睡觉前刷个牙!");
}
public void before(Method method, Object[] args, Object target)
throws Throwable {
System.out.println("睡觉后要关灯");
}
}
来看配置文件
<!-- target对象 -->
<bean id="male" class="com.errol.aop.Male"></bean>
<!-- Advice -->
<bean id="sleepHelper" class="com.errol.aop.SleepHelper"></bean>
<!-- PointCut切点 -->
<bean id="sleepPointCut" class="org.springframework.aop.support.JdkRegexpMethodPointcut">
<property name="pattern" value="com.errol.aop.*.sleep"></property>
</bean>
<!-- 切面Advisor -->
<bean id="sleepHelperAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
<property name="advice" ref="sleepHelper"></property>
<property name="pointcut" ref="sleepPointCut"></property>
</bean>
<!-- 代理对象 -->
<bean id="PersonProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<!-- 被代理的对象 -->
<property name="target" ref="male"></property>
<!-- 拦截器 -->
<property name="interceptorNames" value="sleepHelperAdvisor"></property>
<property name="proxyInterfaces" value="com.errol.aop.Person"></property>
</bean>
上面注释也表明,分别对应上面概念中的内容
下面来测试
@Test
public void test1(){
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
Person male = (Person) ac.getBean("PersonProxy");
male.sleep();
}
这里使用的动态代理,要获取代理对象,直接getBean传入FactoryBean的id就可以,如果要获取FactoryBean本身,则需要加一个&。
结果
睡觉后要关灯
我要睡觉了!
睡觉前刷个牙!
注意到,这里先走的是后置通知,原因是:AfterReturning类型的通知写在前面。
2、通过AutoProxyCreator
和第一种不一样的地方就是,配置上面的变化
<!-- 代理对象
<bean id="PersonProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
被代理的对象
<property name="target" ref="male"></property>
拦截器
<property name="interceptorNames" value="sleepHelperAdvisor"></property>
<property name="proxyInterfaces" value="com.errol.aop.Person"></property>
</bean>
-->
<!-- 自动代理 -->
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"></bean>
这里使用的是自动代理,Spring将会自动扫描完成我们的代理过程
测试的地方,getBean(“male”),这里直接去获取bean即可。
测试结果和上面是一样的。
3、@Aspect注解形式
加入两个jar包,aspectjrt.jar,aspectjweaver-1.6.10.RELEASE.jar,注意两个jar包和jdk的版本
/**
* 增强类
* 也就是AOP的Advice
* 继承了Before类型的通知和After类型的通知
* @author Errol
*
*/
@Aspect
public class SleepHelper{
@Pointcut("execution(* sleep*(..))")
public void sleepPoint(){};
@AfterReturning("sleepPoint()")
public void afterReturning(){
System.out.println("睡觉前刷个牙!");
}
@Before("sleepPoint()")
public void before() {
System.out.println("睡觉后要关灯");
}
}
配置文件
<aop:aspectj-autoproxy/>
<bean id="male1" class="com.errol.aop.Male"></bean>
<bean id="sleepHelper1" class="com.errol.aop.SleepHelper"></bean>
简单粗暴,重点在于PointCut()中切入点的正则表达式的写法。
测试结果与上面一致
4、通过来配置
<bean id="male1" class="com.errol.aop.Male"></bean>
<bean id="sleepHelper1" class="com.errol.aop.SleepHelper"></bean>
<aop:config>
<aop:aspect ref="sleepHelper1">
<aop:after-returning method="afterReturning" pointcut="execution(* sleep(..))"/>
<aop:before method="before" pointcut="execution(* sleep(..))"/>
</aop:aspect>
</aop:config>
这种配置最为灵活,不需要改动代码,只需要配置文件就可以完成AOP的功能。