原贴地址:http://book.csdn.net/bookfiles/111/1001113463.shtml
一.AOP概念
介绍完
IoC
之后,我们来介绍另外一个重要的概念:
AOP(Aspect Oriented Programming)
,也就是面向方面编程的技术。
AOP
基于
IoC
基础,是对
OOP
的有益补充。
AOP
将应用系统分为两部分,核心业务逻辑
(
Core business concerns
)及横向的通用逻辑,
也就是所谓的方面
Crosscutting enterprise concerns
,例如,所有大中型应用都要涉及到的持久化管理(
Persistent
)、事务管理(
Transaction Management
)、安全管理(
Security
)、
日志管理(
Logging
)和调试管理(
Debugging
)等。
AOP
正在成为软件开发的下一个光环。使用
AOP
,你可以将处理
aspect
的代码注入主程序,通常主程序的主要目的并不在于处理这些
aspect
。
AOP
可以防止代码混乱。
Spring framework
是很有前途的
AOP
技术。作为一种非侵略性的、轻型的
AOP framework
,你无需使用预编译器或其他的元标签,便可以在
Java
程序中使用它。这意味着开发团队里只需一人要对付
AOP framework
,其他人还是像往常一样编程。
6.3.1 A
OP
概念
让我们从定义一些重要的
AOP
概念开始。
—
方面(
Aspect
):一个关注点的模块化,这个关注点实现可能另外横切多个对象。事务管理是
J2EE
应用中一个很好的横切关注点例子。方面用
Spring
的
Advisor
或拦截器实现。
—
连接点(
Joinpoint
):程序执行过程中明确的点,如方法的调用或特定的异常被抛出。
—
通知(
Advice
):在特定的连接点,
AOP
框架执行的动作。各种类型的通知包括“
around
”、“
before
”和“
throws
”通知。通知类型将在下面讨论。许多
AOP
框架包括
Spring
都是以拦截器做通知模型,维护一个“围绕”连接点的拦截器链。
—
切入点(
Pointcut
):指定一个通知将被引发的一系列连接点的集合。
AOP
框架必须允许开发者指定切入点,例如,使用正则表达式。
—
引入(
Introduction
):添加方法或字段到被通知的类。
Spring
允许引入新的接口到任何被通知的对象。例如,你可以使用一个引入使任何对象实现
IsModified
接口,来简化缓存。
—
目标对象(
Target Object
):包含连接点的对象,也被称作被通知或被代理对象。
—
AOP
代理(
AOP Proxy
):
AOP
框架创建的对象,包含通知。在
Spring
中,
AOP
代理可以是
JDK
动态代理或
CGLIB
代理。
—
编织(
Weaving
):组装方面来创建一个被通知对象。这可以在编译时完成(例如使用
AspectJ
编译器),也可以在运行时完成。
Spring
和其他纯
Java AOP
框架一样,在运行时完成织入。
各种通知类型包括:
—
Around
通知:包围一个连接点的通知,如方法调用。这是最强大的通知。
Aroud
通知在方法调用前后完成自定义的行为,它们负责选择继续执行连接点或通过返回它们自己的返回值或抛出异常来短路执行。
—
Before
通知:在一个连接点之前执行的通知,但这个通知不能阻止连接点前的执行(除非它抛出一个异常)。
—
Throws
通知:在方法抛出异常时执行的通知。
Spring
提供强制类型的
Throws
通知,因此你可以书写代码捕获感兴趣的异常(和它的子类),不需要从
Throwable
或
Exception
强制类型转换。
—
After returning
通知:在连接点正常完成后执行的通知,例如,一个方法正常返回,没有抛出异常。
Around
通知是最通用的通知类型。大部分基于拦截的
AOP
框架(如
Nanning
和
Jboss 4
)
只提供
Around
通知。
如同
AspectJ
,
Spring
提供所有类型的通知,我们推荐你使用最为合适的通知类型来实现需要的行为。例如,如果只是需要用一个方法的返回值来更新缓存,你最好实现一个
after returning
通知,而不是
around
通知,虽然
around
通知也能完成同样的事情。使用最合适的通知类型使编程模型变得简单,并能减少潜在错误。例如,你不需要调用在
around
通知中所需使用的
MethodInvocation
的
proceed()
方法,因此就调用失败。
切入点的概念是
AOP
的关键,它使
AOP
区别于其他使用拦截的技术。切入点使通知独立于
OO
的层次选定目标。例如,提供声明式事务管理的
around
通知可以被应用到跨越多个对象的一组方法上。
因此切入点构成了
AOP
的结构要素。
下面让我们实现一个
Spring AOP
的例子。在这个例子中,我们将实现一个
before advice
,这意味着
advice
的代码在被调用的
public
方法开始前被执行。以下是这个
before advice
的实现代码。
package com.ascenttech.springaop.test;
import java.lang.reflect.Method;
import org.springframework.aop.MethodBeforeAdvice;
public class TestBeforeAdvice implements MethodBeforeAdvice {
public void before(Method m, Object[] args, Object target)
throws Throwable {
System.out.println("Hello world! (by "
+ this.getClass().getName()
+ ")");
}
}
接口
MethodBeforeAdvice
只有一个方法
before
需要实现,它定义了
advice
的实现。
before
方法共用
3
个参数,它们提供了相当丰富的信息。参数
Method m
是
advice
开始后执行的方法,方法名称可以用作判断是否执行代码的条件。
Object[] args
是传给被调用的
public
方法的参数数组。当需要记日志时,参数
args
和被执行方法的名称都是非常有用的信息。你也可以改变传给
m
的参数,但要小心使用这个功能;编写最初主程序的程序员并不知道主程序可能会和传入参数的发生冲突。
Object target
是执行方法
m
对象的引用。
在下面的
BeanImpl
类中,每个
public
方法调用前,都会执行
advice
,代码如下。
package com.ascenttech.springaop.test;
public class BeanImpl implements Bean {
public void theMethod() {
System.out.println(this.getClass().getName()
+ "." + new Exception().getStackTrace() [0].getMethodName ()
+ "()" + "says HELLO!");
}
}
类
BeanImpl
实现了下面的接口
Bean
,代码如下。
package com.ascenttech.springaop.test;
public interface Bean {
public void theMethod();
}
虽然不是必须使用接口,但面向接口而不是面向实现编程是良好的编程实践,
Spring
也鼓励这样做。
pointcut
和
advice
通过配置文件来实现,因此,接下来你只需编写主方法的
Java
代码,代码如下。
package com.ascenttech.springaop.test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;
public class Main {
public static void main(String[] args) {
//Read the configuration file
ApplicationContext ctx
= new FileSystemXmlApplicationContext("springconfig.xml");
//Instantiate an object
Bean x = (Bean) ctx.getBean("bean");
//Execute the public method of the bean (the test)
x.theMethod();
}
}
我们从读入和处理配置文件开始,接下来马上要创建它。这个配置文件将作为粘合程序不同部分的“胶水”。读入和处理配置文件后,我们会得到一个创建工厂
ctx
,任何一个
Spring
管理的对象都必须通过这个工厂来创建。对象通过工厂创建后便可正常使用。
仅仅用配置文件便可把程序的每一部分组装起来,代码如下。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework. org/dtd/spring-beans.dtd">
<beans>
<!--CONFIG-->
<bean id="bean" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces">
<value>com.ascenttech.springaop.test.Bean</value>
</property>
<property name="target">
<ref local="beanTarget"/>
</property>
<property name="interceptorNames">
<list>
<value>theAdvisor</value>
</list>
</property>
</bean>
<!--CLASS-->
<bean id="beanTarget" class="com.ascenttech.springaop.test.BeanImpl"/>
<!--ADVISOR-->
<!--Note: An advisor assembles pointcut and advice-->
<bean id="theAdvisor" class="org.springframework.aop.support.RegexpMethod PointcutAdvisor">
<property name="advice">
<ref local="theBeforeAdvice"/>
</property>
<property name="pattern">
<value>com\.ascenttech\.springaop\.test\.Bean\.theMethod</value>
</property>
</bean>
<!--ADVICE-->
<bean id="theBeforeAdvice" class="com.ascenttech.springaop.test.TestBefore Advice"/>
</beans>
4
个
bean
定义的次序并不重要。我们现在有了一个
advice
、一个包含了正则表达式
pointcut
的
advisor
、一个主程序类和一个配置好的接口,通过工厂
ctx
,这个接口返回自己本身实现的一个引用。
BeanImpl
和
TestBeforeAdvice
都是直接配置。我们用一个惟一的
ID
创建一个
bean
元素,并指定了一个实现类,这就是全部的工作。
advisor
通过
Spring framework
提供的一个
RegexMethodPointcutAdvisor
类来实现。我们用
advisor
的第一个属性来指定它所需的
advice-bean
,第二个属性则用正则表达式定义了
pointcut
,确保良好的性能和易读性。
最后配置的是
bean
,它可以通过一个工厂来创建。
bean
的定义看起来比实际上要复杂。
bean
是
ProxyFactoryBean
的一个实现,它是
Spring framework
的一部分。这个
bean
的行为通过以下的
3
个属性来定义。
—
属性
proxyInterface
定义了接口类。
—
属性
target
指向本地配置的一个
bean
,这个
bean
返回一个接口的实现。
—
属性
interceptorNames
是惟一允许定义一个值列表的属性,这个列表包含所有需要在
beanTarget
上执行的
advisor
。注意,
advisor
列表的次序是非常重要的。
二.Spring的切入点,通知,advisor
让我们看看
Spring
如何处理切入点这个重要的概念。
1
.概念
Spring
的切入点模型能够使切入点独立于通知类型被重用。
同样的切入点有可能接受不同的通知。
org.springframework.aop.Pointcut
接口是重要的接口,用来指定通知到特定的类和方法目标,完整的接口定义如下。
public interface Pointcut {
ClassFilter getClassFilter();
MethodMatcher getMethodMatcher();
}
将
Pointcut
接口分成两个部分有利于重用类和方法的匹配部分,并且组合细粒度的操作(如和另一个方法匹配器执行一个“并”的操作)。
ClassFilter
接口被用来将切入点限制到一个给定的目标类的集合。如果
matches()
永远返回
true
,所有的目标类都将被匹配。
public interface ClassFilter {
boolean matches(Class clazz);
}
MethodMatcher
接口通常更加重要,完整的接口如下。
public interface MethodMatcher {
boolean matches(Method m, Class targetClass);
boolean isRuntime();
boolean matches(Method m, Class targetClass, Object[] args);
}
matches(Method, Class)
方法被用来测试这个切入点是否匹配目标类的给定方法。这个测试可以在
AOP
代理创建的时候执行,避免在所有方法调用时都需要进行测试。如果
2
个参数的匹配方法对某个方法返回
true
,并且
MethodMatcher
的
isRuntime()
也返回
true
,那么
3
个参数的匹配方法将在每次方法调用的时候被调用。这使切入点能够在目标通知被执行之前立即查看传递给方法调用的参数。
大部分
MethodMatcher
都是静态的,意味着
isRuntime()
方法返回
false
。这种情况下,
3
个参数的匹配方法永远不会被调用。
2
.切入点的运算
Spring
支持的切入点的运算有并和交。
并表示只要任何一个切入点匹配的方法。交表示两个切入点都要匹配的方法。并通常比较有用。
切入点可以用
org.springframework.aop.support.Pointcuts
类的静态方法来组合,或者使用同一个包中的
ComposablePointcut
类。
3
.实用切入点实现
Spring
提供几个实用的切入点实现,一些可以直接使用,另一些需要子类化来实现应用相关的切入点。
(
1
)静态切入点
静态切入点只基于方法和目标类,而不考虑方法的参数。静态切入点足够满足大多数情况的使用。
Spring
可以只在方法第一次被调用的时候计算静态切入点,不需要在每次方法调用的时候计算。
让我们看一下
Spring
提供的一些静态切入点的实现。
—
正则表达式切入点
一个很显然的指定静态切入点的方法是正则表达式。除了
Spring
以外,其他的
AOP
框架也实现了这一点。
org.springframework.aop.support.RegexpMethodPointcut
是一个通用的正则表达式切入点,它使用
Perl 5
的正则表达式的语法。
使用这个类你可以定义一个模式的列表。如果任何一个匹配,那个切入点将被计算成
true
(所以,结果相当于是这些切入点的并集)。
用法如下。
<bean id="settersAndAbsquatulatePointcut"
class="org.springframework.aop.support.RegexpMethodPointcut">
<property name="patterns">
<list>
<value>.*get.*</value>
<value>.*absquatulate</value>
</list>
</property>
</bean>
RegexpMethodPointcut
一个实用子类,
RegexpMethodPointcutAdvisor
允许我们同时引用一个通知(通知可以是拦截器、
before
通知、
throws
通知等)。这简化了
bean
的装配,因为一个
bean
可以同时当作切入点和通知,如下所示。
<bean id="settersAndAbsquatulateAdvisor"
class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<property name="interceptor">
<ref local="beanNameOfAopAllianceInterceptor"/>
</property>
<property name="patterns">
<list>
<value>.*get.*</value>
<value>.*absquatulate</value>
</list>
</property>
</bean>
RegexpMethodPointcutAdvisor
可以用于任何通知类型。
—
属性驱动的切入点
一类重要的静态切入点是元数据驱动的切入点。它使用元数据属性的值,典型地,使用源代码级元数据。
(
2
)动态切入点
动态切入点的演算代价比静态切入点高得多。它们不仅考虑静态信息,还要考虑方法的参数。这意味着它们必须在每次方法调用的时候都被计算,并且不能缓存结果,因为参数是变化的。
—
控制流切入点
Spring
的控制流切入点概念上和
AspectJ
的
cflow
切入点一致,虽然没有其那么强大(当前没有办法指定一个切入点在另一个切入点后执行)。一个控制流切入点匹配当前的调用栈。例如,连接点被
com.mycompany.web
包或者
SomeCaller
类中一个方法调用的时候,触发该切入点。控制流切入点的实现类是
org.springframework.aop.support.ControlFlowPointcut
。
4
.切入点超类
Spring
提供非常实用的切入点的超类帮助你实现你自己的切入点。
因为静态切入点非常实用,你很可能子类化
StaticMethodMatcherPointcut
,如下所示。
这只需要实现一个抽象方法(虽然可以改写其他的方法来自定义行为)。
class TestStaticPointcut extends StaticMethodMatcherPointcut {
public boolean matches(Method m, Class targetClass) {
// return true if custom criteria match
}
}
当然也有动态切入点的超类。
Spring 1.0 RC2
或以上版本,自定义切入点可以用于任何类型的通知。
5
.自定义切入点
因为
Spring
中的切入点是
Java
类,而不是语言特性(如
AspectJ
),因此可以定义自定义切入点,无论静态还是动态。但是,没有直接支持用
AspectJ
语法书写的复杂的切入点表达式。不过,
Spring
的自定义切入点也可以任意的复杂。
现在让我们看看
Spring AOP
是如何处理通知的。
1
.通知的生命周期
Spring
的通知可以跨越多个被通知对象共享,或者每个被通知对象有自己的通知。这分别对应
per-class
或
per-instance
通知。
Per-class
通知使用最为广泛。它适合于通用的通知,如事务
adisor
。它们不依赖被代理的对象的状态,也不添加新的状态。它们仅仅作用于方法和方法的参数。
Per-instance
通知适合于导入,来支持混入(
mixin
)。在这种情况下,通知添加状态到被代理的对象。
可以在同一个
AOP
代理中混合使用共享和
per-instance
通知。
2
.
Spring
中通知类型
Spring
提供几种现成的通知类型并可扩展提供任意的通知类型。让我们看看基本概念和标准的通知类型。
(
1
)
Interception around advice
Spring
中最基本的通知类型是
interception around advice
。
Spring
使用方法拦截器的
around
通知是和
AOP
联盟接口兼容的。实现
around
通知的类需要实现接口
MethodInterceptor
。
public interface MethodInterceptor extends Interceptor {
Object invoke(MethodInvocation invocation) throws Throwable;
}
invoke()
方法的
MethodInvocation
参数暴露将被调用的方法、目标连接点、
AOP
代理和传递给被调用方法的参数。
invoke()
方法应该返回调用的结果:连接点的返回值。
一个简单的
MethodInterceptor
实现看起来如下。
public class DebugInterceptor implements MethodInterceptor {
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("Before: invocation=[" + invocation + "]");
Object rval = invocation.proceed();
System.out.println("Invocation returned");
return rval;
}
}
注意
MethodInvocation
的
proceed()
方法的调用。这个调用会应用到目标连接点的拦截器链中的每一个拦截器。大部分拦截器会调用这个方法,并返回它的返回值。但是,一个
MethodInterceptor
,和任何
around
通知一样,可以返回不同的值或者抛出一个异常,而不调用
proceed
方法。但是,没有好的原因你要这么做。
MethodInterceptor
提供了和其他
AOP
联盟的兼容实现的交互能力。这一节下面要讨论的其他的通知类型实现了
AOP
公共的概念,但是以
Spring
特定的方式。虽然使用特定通知类型有很多优点,但如果你需要在其他的
AOP
框架中使用,请坚持使用
MethodInterceptor around
通知类型。注意,目前切入点不能和其他框架交互操作,并且
AOP
联盟目前也没有定义切入点接口。
2
)
Before
通知
Before
通知
是一种简单的通知类型。
这个通知不需要一个
MethodInvocation
对象,因为它只在进入一个方法前被调用。
Before
通知的主要优点是它不需要调用
proceed()
方法,因此没有无意中忘掉继续执行拦截器链的可能性。
MethodBeforeAdvice
接口如下所示(
Spring
的
API
设计允许成员变量的
Before
通知,虽然一般的对象都可以应用成员变量拦截,但
Spring
有可能永远不会实现它)。
public interface MethodBeforeAdvice extends BeforeAdvice {
void before(Method m, Object[] args, Object target) throws Throwable;
}
注意,返回类型是
void
。
Before
通知可以在连接点执行之前插入自定义的行为,但是不能改变返回值。如果一个
Before
通知抛出一个异常,这将中断拦截器链的进一步执行。这个异常将沿着拦截器链后退着向上传播。如果这个异常是
unchecked
的,或者出现在被调用的方法的签名中,它将会被直接传递给客户代码;否则,它将被
AOP
代理包装到一个
unchecked
的异常里。
下面是
Spring
中一个
Before
通知的例子,这个例子计数所有正常返回的方法。
public class CountingBeforeAdvice implements MethodBeforeAdvice {
private int count;
public void before(Method m, Object[] args, Object target) throws Throwable {
++count;
}
public int getCount() {
return count;
}
}
Before
通知可以被用于任何类型的切入点。
(
3
)
Throws
通知
如果连接点抛出异常,
Throws
通知
在连接点返回后被调用。
Spring
提供强类型的
Throws
通知。注意,这意味着
org.springframework.aop.ThrowsAdvice
接口不包含任何方法,它是一个标记接口,标识给定的对象实现了一个或多个强类型的
Throws
通知方法。这些方法形式如下。
afterThrowing([Method], [args], [target], subclassOfThrowable)
只有最后一个参数是必需的。这样从
1
个参数到
4
个参数,依赖于通知是否对方法和方法的参数感兴趣。下面是
Throws
通知的例子。
如果抛出
RemoteException
异常(包括子类),这个通知会被调用。
public class RemoteThrowsAdvice implements ThrowsAdvice {
public void afterThrowing(RemoteException ex) throws Throwable {
// Do something with remote exception
}
}
如果抛出
ServletException
异常,下面的通知会被调用。和上面的通知不一样,它声明了
4
个参数,所以它可以访问被调用的方法、方法的参数和目标对象。
public static class ServletThrowsAdviceWithArguments implements ThrowsAdvice {
public void afterThrowing(Method m, Object[] args, Object target, ServletException ex) {
// Do something will all arguments
}
}
最后一个例子演示了如何在一个类中使用两个方法来同时处理
RemoteException
和
ServletException
异常。任意个数的
throws
方法可以被组合在一个类中。
public static class CombinedThrowsAdvice implements ThrowsAdvice {
public void afterThrowing(RemoteException ex) throws Throwable {
// Do something with remote exception
}
public void afterThrowing(Method m, Object[] args, Object target, ServletException ex){
// Do something will all arguments
}
}
Throws
通知可被用于任何类型的切入点。
(
4
)
After Returning
通知
Spring
中的
After Returning
通知必须实现
org.springframework.aop.AfterReturningAdvice
接口,如下所示。
public interface AfterReturningAdvice extends Advice {
void afterReturning(Object returnValue, Method m, Object[] args, Object target)
throws Throwable;
}
After Returning
通知可以访问返回值(不能改变)、被调用的方法、方法的参数和目标对象。
下面的
After Returning
通知统计所有成功的没有抛出异常的方法调用。
public class CountingAfterReturningAdvice implements AfterReturningAdvice {
private int count;
public void afterReturning(Object returnValue, Method m, Object[] args, Object target) throws Throwable {
++count;
}
public int getCount() {
return count;
}
}
这方法不改变执行路径。如果它抛出一个异常,这个异常而不是返回值将被沿着拦截器链向上抛出。
After returning
通知可被用于任何类型的切入点。
(
5
)
Introduction
通知
Spring
将
Introduction
通知看作一种特殊类型的拦截通知。
Introduction
需要实现
IntroductionAdvisor
和
IntroductionInterceptor
接口。
public interface IntroductionInterceptor extends MethodInterceptor {
boolean implementsInterface(Class intf);
}
继承自
AOP
联盟
MethodInterceptor
接口的
invoke()
方法必须实现导入,也就是说,如果被调用的方法是在导入的接口中,导入拦截器负责处理这个方法调用,它不能调用
proceed()
方法。
Introduction
通知不能被用于任何切入点,因为它只能作用于类层次上,而不是方法。你可以只用
InterceptionIntroductionAdvisor
来实现导入通知,它有下面的方法。
public interface InterceptionIntroductionAdvisor extends InterceptionAdvisor {
ClassFilter getClassFilter();
IntroductionInterceptor getIntroductionInterceptor();
Class[] getInterfaces();
}
这里没有
MethodMatcher
,因此也没有和导入通知关联的切入点,只有类过滤是合乎逻辑的。
getInterfaces()
方法返回
advisor
导入的接口。
让我们看看一个来自
Spring
测试套件中的简单例子。我们假设想要导入下面的接口到一个或者多个对象中。
public interface Lockable {
void lock();
void unlock();
boolean locked();
}
在这个例子中,我们想要能够将被通知对象类型转换为
Lockable
,不管它们的类型,并且调用
lock
和
unlock
方法。如果我们调用
lock()
方法,我们希望所有
setter
方法抛出
LockedException
异常。这样我们能添加一个方面使对象
不可变,而它们不需要知道这一点,这是一个很好的
AOP
例子。
首先,我们需要一个做大量转化的
IntroductionInterceptor
。在这里,我们继承
org.spring framework.aop.support.DelegatingIntroductionInterceptor
实用类。我们可以直接实现
Introduction
Interceptor
接口,但是,大多数情况下,
DelegatingIntroductionInterceptor
是最合适的。
DelegatingIntroductionInterceptor
的设计是将导入委托到真正实现导入接口的接口,隐藏完成这些工作的拦截器。委托可以使用构造方法参数设置到任何对象中,默认的委托就是自己(当无参数的构造方法被使用时)。这样,在下面的例子里,委托是
DelegatingIntroduction Interceptor
的子类
LockMixin
。给定一个委托(默认是自身)的
DelegatingIntroductionIntercepto
r
实例寻找被这个委托(而不是
IntroductionInterceptor
)实现的所有接口,并支持它们中任何一个导入。子类如
LockMixin
也可能调用
suppressInterflace (Class intf)
方法隐藏不应暴露的接口。然而,不管
IntroductionInterceptor
准备支持多少接口,
IntroductionAdvisor
将控制哪个接口将被实际暴露。一个导入的接口将隐藏目标的同一个接口的所有实现。
这样,
LockMixin
继承
DelegatingIntroductionInterceptor
并自己实现
Lockable
。父类自动选择支持导入的
Lockable
,所以我们不需要指定它。用这种方法我们可以导入任意数量的接口。
注意
locked
实例变量的使用,这有效地添加了额外的状态到目标对象。
public class LockMixin extends DelegatingIntroductionInterceptor
implements Lockable {
private boolean locked;
public void lock() {
this.locked = true;
}
public void unlock() {
this.locked = false;
}
public boolean locked() {
return this.locked;
}
public Object invoke(MethodInvocation invocation) throws Throwable {
if (locked() && invocation.getMethod().getName().indexOf("set") == 0)
throw new LockedException();
return super.invoke(invocation);
}
}
通常不要需要改写
invoke()
方法,实现
DelegatingIntroductionInterceptor
就足够了,如果是导入的方法,
DelegatingIntroductionInterceptor
实现会调用委托方法,否则继续沿着连接点处理。在现在的情况下,我们需要添加一个检查:在上锁状态下不能调用
setter
方法。
所需的导入
advisor
是很简单的。只有保存一个独立的
LockMixin
实例,并指定导入的接口,在这里就是
Lockable
。一个稍微复杂一点例子可能需要一个导入拦截器(可以定义成
prototype
)的引用:在这种情况下,
LockMixin
没有相关配置,所以我们简单地使用
new
来创建它。
public class LockMixinAdvisor extends DefaultIntroductionAdvisor {
public LockMixinAdvisor() {
super(new LockMixin(), Lockable.class);
}
}
我们可以非常简单地使用这个
advisor
,它不需要任何配置(但是,有一点是必要的,就是不可能在没有
IntroductionAdvisor
的情况下使用
IntroductionInterceptor
)。和导入一样,通常
advisor
必须是针对每个实例的,并且是有状态的。我们会有不同的
LockMixinAdvisor
每个被通知对象,会有不同的
LockMixin
。
advisor
组成了被通知对象的状态的一部分。
和其他
advisor
一样,我们可以使用
Advised.addAdvisor()
方法以编程地方式使用这种
advisor
,或者在
XML
中配置(推荐这种方式)。下面将讨论所有代理创建,包括“自动代理创建者”,选择代理创建以正确地处理导入和有状态的混入。
在
Spring
中,一个
advisor
就是一个
aspect
的完整的模块化表示。一般地,一个
advisor
包括通知和切入点。
撇开导入这种特殊情况,任何
advisor
可被用于任何通知。
org.springframework.aop. support.DefaultPointcutAdvisor
是最通用的
advisor
类。例如,它可以和
MethodInterceptor
、
BeforeAdvice
或
ThrowsAdvice
一起使用。
Spring
中可以将
advisor
和通知混合在一个
AOP
代理中。例如,你可以在一个代理配置中使用一个对
Around
通知、
Throws
通知和
Before
通知的拦截:
Spring
将自动创建必要的拦截器链。
三.用ProxyFactoryBean创建AOP代理
如果你在为你的业务对象使用
Spring
的
IoC
容器(例如,
ApplicationContext
或
BeanFactory
),你应该会或者你愿意会使用
Spring
的
AOP FactoryBean
(记住,
factory bean
引入了一个间接层,它能创建不同类型的对象)。
在
spring
中创建
AOP proxy
的基本途径是使用
org.springframework.aop.framework. ProxyFactoryBean
。这样可以对
pointcut
和
advice
做精确控制。但是,如果你不需要这种控制,那些简单的选择可能更适合你。
1
.基本概要
ProxyFactoryBean
和其他
Spring
的
FactoryBean
实现一样,引入一个间接的层次。如果你定义一个名字为
foo
的
ProxyFactoryBean
,引用
foo
的对象所看到的不是
ProxyFactoryBean
实例本身,而是由实现
ProxyFactoryBean
的类的
getObject()
方法所创建的对象。这个方法将创建一个包装了目标对象的
AOP
代理。
使用
ProxyFactoryBean
或者其他
IoC
可知的类来创建
AOP
代理的最重要的优点之一是
IoC
可以管理通知和切入点。这是一个非常的强大的功能,能够实现其他
AOP
框架很难实现的特定的方法。例如,一个通知本身可以引用应用对象(除了目标对象,它在任何
AOP
框架中都可以引用应用对象),这完全得益于依赖注入所提供的可插入性。
2
.
JavaBean
的属性
类似于
Spring
提供的绝大部分
FactoryBean
实现一样,
ProxyFactoryBean
也是一个
javabean
,我们可以利用它的属性来指定你将要代理的目标,指定是否使用
CGLIB
。
一些关键属性来自
org.springframework.aop.framework.ProxyConfig
,它是所有
AOP
代理工厂的父类。这些关键属性包括:
—
proxyTargetClass
:如果我们应该代理目标类,而不是接口,这个属性的值为
true
。如果这是
true
,我们需要使用
CGLIB
。
—
optimize
:是否使用强优化来创建代理。不要使用这个设置,除非你了解相关的
AOP
代理是如何处理优化的。目前这只对
CGLIB
代理有效,对
JDK
动态代理无效(默认)。
—
frozen
:是否禁止通知的改变,一旦代理工厂已经配置。默认是
false
。
—
exposeProxy
:当前代理是否要暴露在
ThreadLocal
中,以便它可以被目标对象访问(它可以通过
MethodInvocation
得到,不需要
ThreadLocal
)。如果一个目标需要获得它的代理,并且
exposeProxy
的值是
ture
,可以使用
AopContext.currentProxy()
方法。
—
AopProxyFactory
:所使用的
AopProxyFactory
具体实现。这个参数提供了一条途径来定义是否使用动态代理、
CGLIB
还是其他代理策略。默认实现将适当地选择动态代理或
CGLIB
。一般不需要使用这个属性;它的意图是允许
Spring 1.1
使用另外新的代理类型。
其他
ProxyFactoryBean
特定的属性包括:
—
proxyInterfaces
:接口名称的字符串数组。如果这个没有提供,
CGLIB
代理将被用于目标类。
—
interceptorNames: Advisor
、
interceptor
或其他被应用的通知名称的字符串数组。顺序是很重要的。这里的名称是当前工厂中
bean
的名称,包括来自祖先工厂的
bean
的名称。
—
Singleton
:工厂是否返回一个单独的对象,无论
getObject()
被调用多少次。许多
FactoryBean
的实现提供这个方法,默认值是
true
。如果你想要使用有状态的通知,(例如,用于有状态的
mixin
)将这个值设为
false
,使用
prototype
通知。
3
.代理接口
让我们来看一个简单的
ProxyFactoryBean
的实际例子。这个例子涉及到一个将被代理的目标
bean
,在这个例子里,这个
bean
被定义为“
personTarget
”;一个
advisor
和一个
interceptor
来提供
advice
;一个
AOP
代理
bean
定义,该
bean
指定目标对象(这里是
personTarget bean
),代理接口和使用的
advice
。
<bean id="personTarget" class="com.mycompany.PersonImpl">
<property name="name"><value>Tony</value></property>
<property name="age"><value>51</value></property>
</bean>
<bean id="myAdvisor" class="com.mycompany.MyAdvisor">
<property name="someProperty"><value>Custom string property value </value> </property>
</bean>
<bean id="debugInterceptor" class="org.springframework.aop.interceptor. NopInterceptor">
</bean>
<bean id="person"
class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces"><value>com.mycompany.Person</value></property>
<property name="target"><ref local="personTarget"/></property>
<property name="interceptorNames">
<list>
<value>myAdvisor</value>
<value>debugInterceptor</value>
</list>
</property>
</bean>
请注意,
person bean
的
interceptorNames
属性提供一个
String
列表,列出的是该
ProxyFactoryBean
使用的,在当前
bean
工厂定义的
interceptor
或者
advisor
的名字(
advisor
、
interceptor
、
before
、
after returning
和
throws advice
对象皆可)。
Advisor
在该列表中的次序很重要。
你也许会对该列表为什么不采用
bean
的引用存有疑问。原因就在于如果
ProxyFactory Bean
的
singleton
属性被设置为
false
,那么
bean
工厂必须能返回多个独立的代理实例。如果有任何一个
advisor
本身是
prototype
的,那么它就需要返回独立的实例,也就是有必要从
bean
工厂获取
advisor
的不同实例,
bean
的引用在这里显然是不够的。
上面定义的“
person
”
bean
定义可以作为
Person
接口的实现来使用,如下所示。
Person person = (Person) factory.getBean("person");
在同一个
IoC
的上下文中,其他的
bean
可以依赖于
Person
接口,就像依赖于一个普通的
java
对象一样。
<bean id="personUser" class="com.mycompany.PersonUser">
<property name="person"><ref local="person" /></property>
</bean>
在这个例子里,
PersonUser
类暴露了一个类型为
Person
的属性。只要是在用到该属性的地方,
AOP
代理都能透明地替代一个真实的
Person
实现。但是,这个类可能是一个动态代理类。也就是有可能把它类型转换为一个
Advised
接口(该接口在下面的章节中论述)。
4
.代理类
如果你需要代理的是类,而不是一个或多个接口,又该怎么办呢
?
想象一下我们上面的例子,如果没有
Person
接口,我们需要通知一个叫
Person
的类,而且该类没有实现任何业务接口。在这种情况下,你可以配置
Spring
使用
CGLIB
代理,而不是动态
代理。你只要在上面的
ProxyFactoryBean
定义中把它的
proxyTargetClass
属性改成
true
就可以。
只要你愿意,即使在有接口的情况下,你也可以强迫
Spring
使用
CGLIB
代理。
CGLIB
代理是通过在运行期产生目标类的子类来进行工作的。
Spring
可以配置这个生成的子类,来代理原始目标类的方法调用。这个子类是用
Decorator
设计模式置入到
advice
中的。
CGLIB
代理对于用户来说应该是透明的。然而,还有以下一些因素需要考虑。
(
1
)
Final
方法不能被通知,因为不能被重写。
(
2
)你需要在你的
classpath
中包括
CGLIB
的二进制代码,而动态代理对任何
JDK
都是可用的。
(
3
)
CGLIB
和动态代理在性能上有微小的区别,对
Spring 1.0
来说,后者稍快。另外,以后可能会有变化。在这种情况下,性能不是决定性因素。
posted on 2006-11-01 11:17
OMG 阅读(1384)
评论(0) 编辑 收藏 所属分类:
Spring