spring aop要点分析
spring aop要点分析
在阅读此文之前请你熟悉一些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 中使用的顺序设置。 |