在阅读此文之前请你熟悉一些IOC的知识,同时了解AOP的概念。
在
Spring
中所有的通知都是以
Java
类的形式编写的。
Spring
是采用运行期的方式来将切面织入到系统中的。
代理
Bean
只有在第一次被应用系统需要的时候才被创建。
Spring
有两种方式创建代理:
Proxy
类创建代理
(
实现过接口的目标类
)
和运用
CGLIB
库创建代理
(
没有实现过任何接口的目标类
)
。需要注意两点:
1
、对接口创建代理优于对类创建代理,因为这样会产生更加松耦合的系统,
2
、标记为
final
的方法是不能被通知的,因为
Spring
在实现的时候是为目标类产生子类。
Spring
只支持方法联接点。
Spring
中通知的类型:
1
、
Around org.aopalliance.intercept.MethodInterceptor
栏截对目标对象方法的调用
2
、
Before org.springframework.aop.MethodBeforAdvice
在目标方法被调用之前调用
3
、
After org.springframework.aop.AfterReturningAdvice
当目标方法被调用之后调用
4
、
Thorws org.springframework.aop.ThrowsAdvice
当目标方法抛出异常时调用
前置通知
实现
MethodBeforeAdvice
接口,该接口有以下方法:
void befor(Method method,Object []args,Object target) throws Throwable;
例:
public class WelcomeAdvice implements MethodBeforeAdvice{
public void before(Method method,Object []args,Object target){
System.out.println("Hello,"+((Customer)args[0]).getName());
}
}
配置文件如下:
<beans>
<bean id="aaaTargetObject" class="AAA"/> //
目标类
,
这是一个实现某个接口的类
<bean id="welcomeAdvice" class="WelcomeAdvice"/> //
通知类
<bean id="aaa" class="org.springframework.aop.framework.proxyFactoryBean">
<property name="interceptorNames">
<list>
<value>welcomAdvice</value>
</list>
</property>
<property name="target">
<ref bean="aaaTargetObject"/>
</property>
</bean>
</beans>
后置通知
实现
AfterReturningAdvice
接口,该接口有以下方法:
void afterReturning(Object returnValue,Method method,Object []args,Object target) throws Throwable;
例:
public class ThankYouAdvice implements AfterReturningAdvice{
public void afterReturning(Object returnValue,Method method,Object []args,Object target){
System.out.println("Thank you, come again.");
}
}
在前置通知和后置通知中都不能改变参数中传进来的值,改变执行流程的惟一方法就是抛出异常。
环绕通知
实现
MethodInterceptor
接口,该接口有以下方法:
Object invoke(MethodInvocation invocation) throws Throwable;
环绕通知能够控制目标方法是否真的被调用,通过调用
MethodInvocation.proceed()
方法来调用目标方法。它还可以让你控制返回的对象。
例:
public class ExampleAroundInterceptor implements MethodInterceptor{
public Object invoke(MethodInvocation invocation) throws Throwable{
//
调用之前可以做一些处理
Object returnObject=invocation.proceed();//
调用目标方法
//
调用之后还可以做一些处理
return returnObject;
}
}
异常通知
实现
ThrowsAdvice
接口,此接口是一个标示接口,没有定义必须实现的方法,但是下面两个方法中必须实现一个:
void afterThrowing(Throwable throwable);
void afterThrowing(Method method,Object []args,Object target,Throwable throwable);
ThrowsAdvice
要处理的异常取决于你的方法定义的异常类型,在一个实现类中,也可以实现多个
afterThrowing
方法,根据抛出的异常类型调用适当的方法。
引入通知
给目标对象添加新的方法
(
以及属性
)
。
定义切入点
切入点
就是应用通知的地方,切入点决定了一个特定的类的特定方法是否满足一条特定的规则,如果一个方法符合的话,通知就应用到该方法上。
Spring
的切入点可以让我们以一种灵活的方式定义在什么地方将通知织入到我们的类中。
切入点的核心接口是
Pointcut
接口,同时它也需要两个接口支持,如下:
public interface Pointcut{
ClassFilter getClassFilter(); //
决定一个类是否符合要求
MethodMatcher getMethodMatcher(); //
决定一个方法是否符合要求
}
public interface ClassFilter{
boolean matches(Class clazz);
}
此接口总是包含一个简单的
ClassFilter
接口的实现
--ClassFilter.TRUE
,它是规范的适合任何类的
ClassFilter
实例,它适合于只根据方法决定什么时候符合要求的切入点。
public interface MethodMatcher{
boolean matches(Method m,class targetClass);//
静态的决定一个方法是否被通知织入
public boolean isRuntime();//
决定是静态的还是动态的织入
public boolean mathes(Method m,Class targetClass,Object []args);//
动态的决定一个方法是否被通知织入,系统开销会增加,不推荐使用
}
大多数的切面是由定义切面行为的通知和定义切面在什么地方执行的切入点给合而成的,
Spring
为此提供了
Advisor
,它把通知和切入点组合到一个对象中。接口
PointcutAdvisor
提供了这些功能,接口如下:
public interface PointcutAdvisor{
Pointcut getPointcut();
Advice getAdvice();
}
大多数
Spring
自带的切入点都有一个对应的
PointcutAdvisor
。这样方便你在一个地方定义通知和切入点。
使用
Spring
的静态切入点
:
Spring
为创建静态切入点提供了方便的父类:
StaticMethodMatcherPointcut
。如果想自定义静态切入点的话,继承这个类,并实现
isMatch
方法就
OK
了。
Spring
提供了一个静态切入点的实现类:
NameMatchMethodPointcut
。它有如下两个方法:
Public void setMappedName(String);
Public void setMappedNames(String []);
这个类通过名字映射,上面两个方法的参数中均可以使用通配符
*
。
规则表达式切入点,
Spring
提供了
RegexpMethodPointcut
让你利用正则表达式的力量来定义切入点。
符号
|
描述
|
示例
|
.
|
匹配任何单个字符
|
setFoo.
匹配
setFooB,
但不匹配
setFoo
或
setFooBar
|
+
|
匹配前一个字符一次或多次
|
setFoo.+
匹配
setFooBar
和
setFooB,
但不匹配
setFoo
|
*
|
匹配前一个字符
0
次或多次
|
setFoo.*
匹配
setFoo,setFooB
和
setFooBar
|
\
|
匹配任何正则表达式符号
|
\.setFoo.
匹配
bar.setFoo,
但不匹配
setFoo
|
如果你想匹配所有
setXxx
方法,我们需要使用
.*set*.
模版
(
第一个通配符匹配任何类名。笔者认为此处《
Spring in action
》一书中有误,我认为此处应为
.*set.*)
。如果使用
RegexpMethodPointcut
,你需要在你的应用系统中引入
ORO
类库。
<bean id=”queryPointcutAdvisor”
Class=”org.springframework.aop.support.RegExPointcutAdvisor”>
<property name=”pattern”>
<value>.*get.+By.+</value>
</property>
<property name=”advice”>
<ref bean=”queryInterceptor”/>
</property>
</bean>
使用动态切入点
Spring
提供了一种内置的动态切入点:
ControlFlowPointcut
。这个切入点根据线程调用堆栈的信息来匹配方法。也就是说,它可以配置成只有当指定方法或类能在当前线程执行堆栈中找到时,返回
true
。
<bean id=”servletPointcut” class=”org.springframework.aop.support.ControlFlowPointcut”>
<construct-arg>
<value>javax.servlet.http.HttpServlet</value>
</construct-arg>
</bean>
<bean id=”servletAdvisor” class=”org.springframework.aop.support.DefaultPointcutAdvisor”>
<property name=”advice”>
<ref bean=”servletInterceptor”/>
</property>
<property name=”pointcut”>
<ref bean=”servletPointcut”/>
</property>
</bean>
注:
ControlFlowPointcut
明显比其他的动态切入点慢。
切入点实施
Spring
支持在切入点上进行操作
—
合并与产叉
—
来创建新的切入点。只有当切入点都匹配时交叉集合才匹配,任何一个切入点匹配都会使合并集合匹配。
Spring
为创建这两种切入点提供了两个类:第一个类是
ComposablePointcut
。通过将已有的
ComposablePointcut
、切入点、
MethodMatcher
以及
ClassFilter
对象进行合并或交叉,组装成一个新的
ComposablePointcut
对象。这可以通过调用
ComposablePointcut
实例的
intersection
()
或
union
()
方法实现。
ComposablePointcut cp=new ComposablePoint()
cp=p.intersection(myPointcut).union(myMethodmatcher);
为了对两个
Pointcut
对象进行合并,必须使用
Pointcuts
类。这是一个工具类,它拥有很多操作
Pointcut
对象的静态方法。如:
Pointcut union=Pointcuts.union(pointcut1,pointcut2);
创建引入
引入与其他类型的
Spring
通知有所不同。其它类型通知是在方法调用的周围织入到不同的连接点,而引入则是影响整个类,他们通过给需要消息的类型添加方法和属性来实现。也就是说,你可以用一个已存在的类让它实现另外的接口,维持另外的状态
(
这也叫混合
)
。换句话说,引入让你能够动态地建立复合对象,提供了多态继承的好处。
实现
IntroductionInterceptor
Spring
通过一个特殊的方法拦截器接口
IntroductionMethodInterceptor
来实现引入。这个接口有一个方法:
Boolean implementsInterface(Class intf);
如果
IntroductionMethodInterceptor
是为了实现指定接口,那么方法
implementsInterface
应该返回
true
。就是说,对用这个接口声明的方法的任何调用将被委托给
IntroductionMethodInterceptor
的
invoke()
方法。
Invoke()
方法负责实现这个方法,不能调用
MethodInvocation.proceed()
。它引入了新的接口,调用目标对象是没有用的。
使用
ProxyBeanFactory
BeanFactory
对象是一个负责创建其他
JavaBean
的
JavaBean
。属性列表如下:
属性
|
使 用
|
target
|
代理的目标对象
|
proxyInterfaces
|
代理应该实现的接口列表
|
interceptorNames
|
需要应用到目标对象上的通知
Bean
的名字。可以是拦截器,
Advisor
或其他通知类型的名字。这个属性必须按照在
BeanFactory
中使用的顺序设置。
|
singleton
|
是否返回的是同一个代理实例。如果使用的是状态通知,应该设置为
false
|
aopProxyFactory
|
使用的
ProxyFactoryBean
实现
|
exposeProxy
|
目标对象是否需要得到当前的代理。通过调用
AopContext.getCurrentProxy
实现。记住这样做会在你的代码中引入
Spring
专有的
AOP
代码,所以,尽量避免使用。
|
frozen
|
一旦工厂被创建,是否可以修改代理的通知。
|
optimize
|
是否对创建的代理进行优化
(
仅适用于
CGLIB)
。
|
ProxyTargetClass
|
是否代理目标类,而不是实现接口。
(
需要
CGLIB
支持
)
|
大多数情况下我们只用到前三个属性。
如果想避免将目标对象暴露给系统中其他
Bean
的话,可以将它声明为一个内部
Bean
。
proxyInterfaces
属性指定了从工厂中创建的
Bean
需要实现的接口。如:
<property name=”proxyInterfaces”>
<value>com.springinaction.service.CourseService</value>
</property>
这样就让
ProxyBeanFactory
知道它创建的所有
Bean
都要实现
CourseService
接口。可以像这样只提供一个接口,也可以用
<list>
提供多个接口。
interceptorNames
属性定义了一个应用到目标对象上的
Advisor
或通知
Bean
的列表。目标
Bean
也可以放在此属性的
<list>
列表的最后,但是最好还是用
Target
属性设置目标
Bean
。
自动代理
Spring
有一个自动代理机制,它可以让容器为我们产生代理。
Spring
有两个类提供这种服务:
BeanNameAutoProxyCreator
和
DefaultAdvisorAutoProxyCreator
。
BeanNameAutoProxyCreator
为匹配一系列名字的
Bean
自动创建代理。它允许在名字的两端进行通配符匹配。通常用于为符合相同命名规则的
Bean
应用一个或一组切面
。
(
主要是解决
target
属性配置时目标类过多的问题。
)
如:
<bean id=”preformanceThresholdProxyCreator
Class=”org.springframework.aop.framework.autoproxy.BeanNameAutoProxyProlyCreator”>
<property name=”beanNames”>
<list>
<value>*Service</value>
</list>
</property>
<propery name=”interceptorNames”>
<value>performaceThresholdInterceptor</value>
</property>
</bean>
如果
Bean
是一个
Advisor
或拦截器,它将应用到代理对象的所有方法上。如果是通知的话,
Advisor
切入点会根据不同
Bean
将通知应用到不同的地方。
自动代理框架对于代理需要暴露哪些接口作了一些假设。目标对象实现的任何接口代理对象都要暴露出来。如果目标类没有实现任何接口,那么应用于前面讨论过的
ProxyFactoryBean
一样的规则
—
动态生成一个子类。
DefaultAdvisorAutoProxyCreator
这个类的奇妙之处在于它实现了
BeanPostProcessor
接口。当
ApplicationContext
读入所有
Bean
的配置信息后,
DefaultAdvisorAutoProxyCreator
将扫描上下文,寻找所有的
Advisor
。它将这些
Advisor
应用到所有符合
Advisor
切入点的
Bean
中。这个代理创建器只能与
Advisor
配合使用
。
(
我们知道,一个
Advisor
是一个切入点和一个通知的结合体。
DefaultAdvisorAutoProxyCreator
需要
Advisor
来知道哪些
Bean
需要通知。
)
<bean id=”advisor” class=”org.springframework.aop.support.RegexpMethodPointcutAdvisor”>
<property name=”advice”>
<bean class=”performaceThresholdInterceptor”/>
</property>
<property name=”pattern”>
<value>.+Service\..+</value>
</property>
</bean>
<bean id=”autoProxyCreator”
class=”org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator”/>
在读入所有
Bean
的定义信息后,
BeanFactory
中的所有
Advisor
将被释放出来。它们会将它们的通知配置到任何匹配它们的切入点的
Bean
上
(
这就是它真正的威力
)
。
不用显示地将
Advisor
与其他东西结合,现在只要简单地定义它们,然后让它们自动地应用到它们匹配的地方。这样松耦合
Bean
以及它们的通知就实现了。
Spring
也支持通过元数据
(Metadata)
驱动自动代理。这种类型自动代理,代理配置是通过源代码属性而不是外部配置文件获得的。最常见的元数据自动配置是声明式事务支持。
posted on 2006-08-15 10:17
Robin's Programming World 阅读(4479)
评论(5) 编辑 收藏 所属分类:
Java 、
读书