在传统的编写业务逻辑处理代码时,我们通常会习惯性地做几件事情:日志记录、事务控制及权限控制等,然后才是编写核心的业务逻辑处理代码。当代码编写完成回头再看时,不禁发现,扬扬洒洒上百行代码中,真正用于核心业务逻辑处理才那么几行,如图6-4所示。方法复方法,类复类,就这样子带着无可奈何遗憾地度过了多少个春秋。这倒也罢,倘若到了项目的尾声,突然决定在权限控制上需要进行大的变动时,成千上万个方法又得一一"登门拜访",痛苦"雪上加霜"。
如果能把图6-4中众多方法中的所有共有代码全部抽取出来,放置到某个地方集中管理,然后在具体运行时,再由容器动态织入这些共有代码的话,最起码可以解决两个问题:
Java EE程序员在编写具体的业务逻辑处理方法时,只需关心核心的业务逻辑处理,既提高了工作效率,又使代码变更简洁优雅。
在日后的维护中由于业务逻辑代码与共有代码分开存放,而且共有代码是集中存放的,因此使维护工作变得简单轻松。
面向切面编程AOP技术就是为解决这个问题而诞生的,切面就是横切面,如图6-5所示,代表的是一个普遍存在的共有功能,例如,日志切面、权限切面及事务切面等。
下面我们以用户管理业务逻辑组件UserService的AOP实现过程(见图6-6)为例,深度剖析一下AOP技术的实现原理。AOP技术是建立在Java语言的反射机制与动态代理机制之上的。业务逻辑组件在运行过程中,AOP容器会动态创建一个代理对象供使用者调用,该代理对象已经按Java EE程序员的意图将切面成功切入到目标方法的连接点上,从而使切面的功能与业务逻辑的功能同时得以执行。从原理上讲,调用者直接调用的其实是AOP容器动态生成的代理对象,再由代理对象调用目标对象完成原始的业务逻辑处理,而代理对象则已经将切面与业务逻辑方法进行了合成。
现将图6-6中涉及到的一些概念解释如下。
切面(Aspect):其实就是共有功能的实现。如日志切面、权限切面、事务切面等。在实际应用中通常是一个存放共有功能实现的普通Java类,之所以能被AOP容器识别成切面,是在配置中指定的。
通知(Advice):是切面的具体实现。以目标方法为参照点,根据放置的地方不同,可分为前置通知(Before)、后置通知(AfterReturning)、异常通知(AfterThrowing)、最终通知(After)与环绕通知(Around)5种。在实际应用中通常是切面类中的一个方法,具体属于哪类通知,同样是在配置中指定的。
连接点(Joinpoint):就是程序在运行过程中能够插入切面的地点。例如,方法调用、异常抛出或字段修改等,但Spring只支持方法级的连接点。
切入点(Pointcut):用于定义通知应该切入到哪些连接点上。不同的通知通常需要切入到不同的连接点上,这种精准的匹配是由切入点的正则表达式来定义的。
目标对象(Target):就是那些即将切入切面的对象,也就是那些被通知的对象。这些对象中已经只剩下干干净净的核心业务逻辑代码了,所有的共有功能代码等待AOP容器的切入。
代理对象(Proxy):将通知应用到目标对象之后被动态创建的对象。可以简单地理解为,代理对象的功能等于目标对象的核心业务逻辑功能加上共有功能。代理对象对于使用者而言是透明的,是程序运行过程中的产物。
织入(Weaving):将切面应用到目标对象从而创建一个新的代理对象的过程。这个过程可以发生在编译期、类装载期及运行期,当然不同的发生点有着不同的前提条件。譬如发生在编译期的话,就要求有一个支持这种AOP实现的特殊编译器;发生在类装载期,就要求有一个支持AOP实现的特殊类装载器;只有发生在运行期,则可直接通过Java语言的反射机制与动态代理机制来动态实现。
3.典型的Spring切面
- package com.sin90lzc.test;
- //观众类
- public class Audience {
- public Audience() {
- }
- //表演前找座位
- public void takeSeats() {
- System.out.println("The audience is taking their seats.");
- }
- //找到座位后关掉手机
- public void turnOffCellPhones() {
- System.out.println("The audience is turning off their cellphones");
- }
- //表演精彩时鼓掌
- public void applaud() {
- System.out.println("CLAP CLAP CLAP CLAP CLAP");
- }
- //表演槽糕时要求退票
- public void demandRefund() {
- System.out.println("Boo!We want our money back!");
- }
- }
package com.sin90lzc.test;
//观众类
public class Audience {
public Audience() {
}
//表演前找座位
public void takeSeats() {
System.out.println("The audience is taking their seats.");
}
//找到座位后关掉手机
public void turnOffCellPhones() {
System.out.println("The audience is turning off their cellphones");
}
//表演精彩时鼓掌
public void applaud() {
System.out.println("CLAP CLAP CLAP CLAP CLAP");
}
//表演槽糕时要求退票
public void demandRefund() {
System.out.println("Boo!We want our money back!");
}
}
由Spring容器管理Audience:
- <bean id="audience" class="com.springinaction.springido1.Audience" />
<bean id="audience" class="com.springinaction.springido1.Audience" />
3.1创建通知
通知类型 | 接口 |
---|---|
Before | org.springframework.aop.MethodBeforeAdvice |
After-returning | org.springframework.aop.AfterReturningAdvice |
After-throwing | org.springframework.aop.ThrowsAdvice |
Around | org.aopalliance.intercept.MethodInterceptor |
Introduction | org.springframework.aop.IntroductionInterceptor |
- package com.sin90lzc.test;
- import java.lang.reflect.Method;
- import org.springframework.aop.AfterReturningAdvice;
- import org.springframework.aop.MethodBeforeAdvice;
- import org.springframework.aop.ThrowsAdvice;
- public class AudienceAdvice implements MethodBeforeAdvice,
- AfterReturningAdvice, ThrowsAdvice {
- private Audience audience;
- public void afterReturning(Object arg0, Method arg1, Object[] arg2,
- Object arg3) throws Throwable {
- audience.applaud();
- }
- public void before(Method arg0, Object[] arg1, Object arg2)
- throws Throwable {
- audience.takeSeats();
- audience.turnOffCellPhones();
- }
- public void afterThrowing(Throwable throwable) {
- }
- public Audience getAudience() {
- return audience;
- }
- public void setAudience(Audience audience) {
- this.audience = audience;
- }
- }
package com.sin90lzc.test;
import java.lang.reflect.Method;
import org.springframework.aop.AfterReturningAdvice;
import org.springframework.aop.MethodBeforeAdvice;
import org.springframework.aop.ThrowsAdvice;
public class AudienceAdvice implements MethodBeforeAdvice,
AfterReturningAdvice, ThrowsAdvice {
private Audience audience;
public void afterReturning(Object arg0, Method arg1, Object[] arg2,
Object arg3) throws Throwable {
audience.applaud();
}
public void before(Method arg0, Object[] arg1, Object arg2)
throws Throwable {
audience.takeSeats();
audience.turnOffCellPhones();
}
public void afterThrowing(Throwable throwable) {
}
public Audience getAudience() {
return audience;
}
public void setAudience(Audience audience) {
this.audience = audience;
}
}
这里一个类中实现了3种不同类型的AOP通知。
- public void before(Method arg0, Object[] arg1, Object arg2)
- throws Throwable {}
public void before(Method arg0, Object[] arg1, Object arg2)
throws Throwable {}
第一个参数代表要使用这个通知的方法。第二个参数是方法被调用时要传递的参数。最后一个参数是方法调用的目标(也就是被调用方法所在的对象)。
- public void afterReturning(Object arg0, Method arg1, Object[] arg2,
- Object arg3) throws Throwable {}
public void afterReturning(Object arg0, Method arg1, Object[] arg2,
Object arg3) throws Throwable {}
aferReturning方法的参数与MethodBeforeAdvice的before()方法的参数区别不大,只是第一个参数是从被调用方法返回的值。
与MethodBeforeAdvice和AfterReturningAdvice不同的是,ThrowsAdvice不需要实现任何方法,它只是一个标记接口,告诉Spring相应的通知要求处理被抛出的异常。
周围通知相当于前通知、返回后通知、抛出后通知的结合。AudienceAdvice类可以用周围通知来重写。如:
- package com.sin90lzc.test;
- import org.aopalliance.intercept.MethodInterceptor;
- import org.aopalliance.intercept.MethodInvocation;
- public class AudienceAroundAdvice implements MethodInterceptor {
- public Object invoke(MethodInvocation invocation) throws Throwable {
- try {
- audience.takeSeats();
- audience.turnOffCellPhones();
- Object returnValue = invocation.proceed();
- audience.applaud();
- return returnValue;
- } catch (Exception e) {
- audience.demandRefund();
- throw e;
- }
- }
- public Audience getAudience() {
- return audience;
- }
- public void setAudience(Audience audience) {
- this.audience = audience;
- }
- private Audience audience;
- }
package com.sin90lzc.test;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
public class AudienceAroundAdvice implements MethodInterceptor {
public Object invoke(MethodInvocation invocation) throws Throwable {
try {
audience.takeSeats();
audience.turnOffCellPhones();
Object returnValue = invocation.proceed();
audience.applaud();
return returnValue;
} catch (Exception e) {
audience.demandRefund();
throw e;
}
}
public Audience getAudience() {
return audience;
}
public void setAudience(Audience audience) {
this.audience = audience;
}
private Audience audience;
}
使用周围通知的好处之一是能以简洁的方式在一个方法里定义前通知和后通知。
利用周围通知还可以检查和修改被通知方法的返回值,让我们可以在把方法的返回值传递给调用者之前对其进行一些后处理。AfterReturningAdvice只能对返回值进行检查,但不能修改它。
3.2 定义切入点和通知者
3.2.1声明正则表达式切点
- <bean id="performancePointcut" class="org.springframework.aop.support.JdkRegexpMethodPointcut">
- <property name="pattern" value=".*perform" />
- </bean>
<bean id="performancePointcut" class="org.springframework.aop.support.JdkRegexpMethodPointcut">
<property name="pattern" value=".*perform" />
</bean>
- <bean id="audienceAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
- <property name="advice" ref="audienceAdvice" />
- <property name="pointcut" ref="performancePointcut" />
- </bean>
<bean id="audienceAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
<property name="advice" ref="audienceAdvice" />
<property name="pointcut" ref="performancePointcut" />
</bean>
DefaultPointcutAdvisor是个通知者类,它只是把通知关联到切点。
- <bean id="audienceAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
- <property name="advice" ref="audienceAdvice" />
- <property name="pattern" value=".*perform" />
- </bean>
<bean id="audienceAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<property name="advice" ref="audienceAdvice" />
<property name="pattern" value=".*perform" />
</bean>
3.2.2定义AspectJ切点
- <bean id="performancePointcut" class="org.springframework.aop.aspectj.AspectJExpressionPointcut">
- <property name="expression" value="execution(* Performer+.perform(..))" />
- </bean>
<bean id="performancePointcut" class="org.springframework.aop.aspectj.AspectJExpressionPointcut">
<property name="expression" value="execution(* Performer+.perform(..))" />
</bean>
- <bean id="audienceAdvisor" class="org.springframework.aop.aspectj.AspectJExpressionPointcutAdvisor">
- <property name="advice" ref="audienceAdvice" />
- <property name="expression" value="execution(* Performer+.perform(..))" />
- </bean>
<bean id="audienceAdvisor" class="org.springframework.aop.aspectj.AspectJExpressionPointcutAdvisor">
<property name="advice" ref="audienceAdvice" />
<property name="expression" value="execution(* Performer+.perform(..))" />
</bean>
通知者把通知与切点关联起来,从而完整地定义一个切面。但是,切面在Spring里是以代理方式实现的,所以仍然需要代理目标Bean才能让通知者发挥作用。
3.3 使用ProxyFactoryBean
- <bean id="duke" class="org.springframework.aop.framework.ProxyFactoryBean">
- <property name="target" ref="dukeTarget" />
- <property name="interceptorNames">
- <list>
- <value>audienceAdvisor</value>
- </list>
- </property>
- <property name="proxyInterfaces">
- <list>
- <value>com.springinaction.springido1.Performer</value>
- </list>
- </property>
- </bean>
<bean id="duke" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target" ref="dukeTarget" />
<property name="interceptorNames">
<list>
<value>audienceAdvisor</value>
</list>
</property>
<property name="proxyInterfaces">
<list>
<value>com.springinaction.springido1.Performer</value>
</list>
</property>
</bean>