在阅读此文之前请你熟悉一些IOC的知识,同时了解AOP的概念。
在 Spring 中所有的通知都是以 Java 类的形式编写的。 Spring 是采用运行期的方式来将切面织入到系统中的。
代理 Bean 只有在第一次被应用系统需要的时候才被创建。 Spring 有两种方式创建代理: Proxy 类创建代理 ( 实现过接口的目标类 ) 和运用 CGLIB 库创建代理 ( 没有实现过任何接口的目标类 ) 。需要注意两点: 1 、对接口创建代理优于对类创建代理,因为这样会产生更加松耦合的系统, 2 、标记为 final 的方法是不能被通知的,因为 Spring 在实现的时候是为目标类产生子类。
Spring 只支持方法联接点。
<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" /?>
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) 驱动自动代理。这种类型自动代理,代理配置是通过源代码属性而不是外部配置文件获得的。最常见的元数据自动配置是声明式事务支持。