引言
AOP是软件开发思想发展到一定阶段的产物,AOP的出现并不是为了代替OOP,仅作为OOP的有益补充,在下面的例子中这个概念将会得到印证。AOP的应用场合是受限制的,一般适用于那些具有横切逻辑的应用场合,例如性能监测,访问控制,事务管理,日志记录。在平常的应用开发中AOP很难被使用到,但是AOP是Spring的亮点之一,有必要一看。
一 AOP以及术语
AOP是Aspect Oriented Programing的简称,被译为面向切面编程。AOP希望将散落在业务逻辑函数中的相同代码抽取到一个独立的模块中。举个例子:
class A{ public void run() { doSomething(); doAthings(); doOtherthing(); } } class B{ public void run() { doSomething(); doBthings(); doOtherthing(); } } class C{ public void run() { doSomething(); doCthings(); doOtherthing(); doMorethings(); } }
例如上述三个类中的run方法,都有doSomething和doOtherthing代码,AOP就是要把这些代码抽取出来。我们知道,抽取代码很容易,但是如何将这些独立的逻辑代码再融合到业务类中完成和原来一样的业务操作,这才是AOP要解决的问题。
术语
Joinpoint连接点:程序执行的某个特定位置,例如类初始化前,类初始化后,方法执行前,方法执行后等,Spring只支持方法的连接点,即方法执行前,方法执行后,方法抛出异常时。
Pointcut切点:每个类中都拥有多个连接点,如拥有两个方法的类,这两个方法都是连接点,切点可以在连接点中定位一个或多个连接点。
Advice增强:增强是织入目标类连接点上的一段程序代码,即当程序到达一个执行点后会执行相对应的一段代码,Spring提供的Advice都带有接入点方位,例如BeforeAdvice,AftereturningAdvice,ThrowsAdvice等。只有结合切点和通知才能确定特定的连接点并执行对应代码。
Target目标对象:被嵌入代码的类。
Introductiony引介:可以为类添加属性和方法。
Weaving织入:AOP就像一台织布机,将增强代码嵌入到对应的类中。AOP有三种织入方式:
- 编译期织入:这要求是用特殊的Java编译器
- 类装载期织入:要求使用特殊的类装载器
- 动态代理织入:在运行时期为目标对象生成代理对象的方式实现织入
Spring采用动态代理织入,AspectJ采用编译期织入和类装载织入。
Proxy代理:一个类被AOP织入后,就会产生一个代理对象,他融合了原有类代码和增强代码。
Aspect切面:由切点和增强组成,他包括了连接点定义和横切逻辑代码的定义,SpringAOP就是负责实施切面的框架。
二 Advice增强
Spring使用增强类定义横切逻辑,增强类还包括了在方法的哪一点加入代码的信息。
增强类型
- 前置增强:在方法执行前实施增强
- 后置增强:在方法返回后实施增强
- 异常抛出增强:在目标方法抛出异常后实施增强
- 环绕增强:在方法执行前和执行后实施增强
- 引介增强:在目标类中加入新的方法和属性
接下来,我以简单的示例解释前四种增强
//Dog.java //定义狗的接口 package test.aop; /** * Created by gavin on 15-7-18. */ public interface Dog { public void shout(String name); public void sleep(String name); } //ChinaDog.java //设计中华田园犬 package test.aop; /** * Created by gavin on 15-7-18. */ public class ChinaDog implements Dog { @Override public void shout(String name) { System.out.println("中华田园犬"+name+" 叫了一声"); } @Override public void sleep(String name) { System.out.println("中华田园犬"+name+" 睡着了"); } public void error() throws Exception { throw new Exception("shout exception"); } } //BeforeAdvice.java //前置增强类 package test.aop; import org.springframework.aop.MethodBeforeAdvice; import java.lang.reflect.Method; /** * Created by gavin on 15-7-18. */ public class BeforeAdvice implements MethodBeforeAdvice { @Override public void before(Method method, Object[] objects, Object o) throws Throwable { String name = (String)objects[0]; //参数获取 System.out.println("中华田园犬"+name+" 前置增强"); } } //AfterReturnAdvice.java //后置增强类 package test.aop; import org.springframework.aop.AfterReturningAdvice; import java.lang.reflect.Method; /** * Created by gavin on 15-7-18. */ public class AfterReturnAdvice implements AfterReturningAdvice { @Override public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable { String name = (String)objects[0]; System.out.println("中华田园犬"+name+" 后置增强"); } } //SurroundAdvice.java //环绕增强类 package test.aop; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; /** * Created by gavin on 15-7-18. */ public class SurroundAdvice implements MethodInterceptor { @Override public Object invoke(MethodInvocation methodInvocation) throws Throwable { Object[] objects = methodInvocation.getArguments(); String name = (String)objects[0]; System.out.println("环绕增强----前"+name); Object object = methodInvocation.proceed(); //此处执行的是原有函数 System.out.println("环绕增强----后"+name); return object; } } //ThrowAdvice.java //异常抛出增强类 package test.aop; import org.springframework.aop.ThrowsAdvice; import java.lang.reflect.Method; /** * Created by gavin on 15-7-18. */ public class ThrowAdvice implements ThrowsAdvice { @Override public void afterThrowing(Method method,Object[] args, Object obj ,Exception ex)throws Throwable { System.out.println("------------------------"); System.out.println("------异常抛出增强"); System.out.println("------method "+method.getName()); System.out.println("------exception "+ex.getMessage()); System.out.println("------------------------"); } } //AopTest.java //AOP测试类 package test.aop; import org.aopalliance.aop.Advice; import org.springframework.aop.framework.ProxyFactory; import org.springframework.context.ApplicationContext; import org.springframework.context.support.FileSystemXmlApplicationContext; import org.testng.annotations.BeforeTest; import org.testng.annotations.Test; /** * Created by gavin on 15-7-18. */ public class AopTest { private Dog dog; private Advice advice; private ProxyFactory pf; @BeforeTest public void init() { dog = new ChinaDog(); pf = new ProxyFactory(); pf.setTarget(dog); } @Test public void beforeAdvice() { advice = new BeforeAdvice(); pf.addAdvice(advice); Dog dog = (Dog)pf.getProxy(); dog.shout("jim"); dog.sleep("tom"); } @Test public void afterReturndvice() { advice = new AfterReturnAdvice(); pf.addAdvice(advice); Dog dog = (Dog)pf.getProxy(); dog.shout("jim"); dog.sleep("tom"); } @Test public void arroundAdvice() { SurroundAdvice advice = new SurroundAdvice(); pf.addAdvice(advice); Dog dog = (Dog)pf.getProxy(); dog.shout("jim"); dog.sleep("tom"); } @Test public void throwAdvice() { advice = new ThrowAdvice(); pf.addAdvice(advice); ChinaDog dog = (ChinaDog)pf.getProxy(); try { dog.error(); } catch (Exception e) { e.printStackTrace(); } } }
分别对四种增强方式进行测试,得到以下结果:
前置增强
后置增强
环绕增强
异常抛出增强
三 创建切面
看完上面的代码,我们可能会注意一个问题,那就是增强类被织入到了目标类的所有方法中,即shout和sleep方法都会被增强。这时就需要用切点来定位了。
Spring通过org.springframework.aop.Pointcut接口描述切点,通过这个接口的ClassFilter定位到特定的类,通过MethodMatcher定位到特定的方法。
通过静态正则表达式方法匹配切面
在applicationContext.xml中加入以下xml语句:
<!--aop--> <!-- 被代理对象 --> <bean class="test.aop.ChinaDog" id="chinaDog"/> <!-- 前置通知 --> <bean class="test.aop.BeforeAdvice" id="beforeAdvice"/> <!-- 定义切面 --> <bean id="beforeAdviceFilter" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor"> <property name="advice" ref="beforeAdvice"/> <property name="patterns"> <list> <value>s.*</value> </list> </property> </bean> <!-- 代理对象 --> <bean class="org.springframework.aop.framework.ProxyFactoryBean" id="proxyFactoryBean"> <!-- 设置代理接口集合 --> <property name="proxyInterfaces"> <list> <!--接口要写全--> <value>test.aop.Dog</value> </list> </property> <!-- 把增强织入代理对象 --> <property name="interceptorNames"> <list> <value>beforeAdviceFilter</value> </list> </property> <!-- 配置被代理对象 --> <property name="target" ref="chinaDog"/> </bean>
在AopTest.java中调用applicationContext.xml:
public static void main(String[] args) { //PropertyConfigurator.configure("web/WEB-INF/log4j.properties"); ApplicationContext ac = new FileSystemXmlApplicationContext("web/WEB-INF/applicationContext.xml"); Dog dog = (Dog)ac.getBean("proxyFactoryBean"); dog.shout("jim"); dog.sleep("tom"); }
结果同前置增强的结果。
四 基于@AspectJ配置切面
在第三节中我们使用Pointcut和Advice接口来描述切点和增强,并用Advisor整合描述切面,@AspectJ则使用注解来描述切点和增强,描述的内容完全相同,只不过方式不同。
我们先用@AspectJ定义一个切面:
package test.aop; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; /** * Created by gavin on 15-7-18. */ @Aspect public class PreAspect { @Before("execution(* shout(..))")//意思是返回值任意 参数任意 public void beforeShout() { System.out.println("准备叫"); } }
在applicationContext.xml中设置:
<!--AOP--> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd"> <!--基于@AspectJ切面的驱动器--> <aop:aspectj-autoproxy/> <bean class="test.aop.PreAspect" />
在AopTest.java中进行测试,代码如下:
public static void main(String[] args) { //PropertyConfigurator.configure("web/WEB-INF/log4j.properties"); ApplicationContext ac = new FileSystemXmlApplicationContext("web/WEB-INF/applicationContext.xml"); Dog dog = (Dog)ac.getBean("chinaDog"); //注意这里 dog.shout("jim"); dog.sleep("tom"); }
结果为:
只有shout方法前调用了beforeShout方法。
总结
AOP是OOP的有益补充,他为程序开发提供了一个新的思考角度,可以将重复性的横切逻辑抽取到统一的模块中。只有通过OOP的纵向抽象和AOP的横向抽取,程序才可以真正的解决重复性代码问题。
Spring采用JDK动态代理和CGLib动态代理技术在运行期间织入增强。所以用户不用装备特殊的编译器或类装载器。要使用JDK动态代理,目标对象必须实现接口,而CGLib不对目标类采取限制。
JDK创建代理对象效率比CGLib高,而CGLib创建的代理对象运行效率比JDK的要高。