Spring3.0读书笔记----(六)Spring AOP基础

 一、AOP概述

 (一)、AOP是什么

 AOP是(AspectOriented Programing)的简称,被译为“面向切面编程”。

 按照软件重构思想的理念,如果多个类中出现相同的代码,应该考虑定义一个共同的抽象类,将这些相同的代码提取到抽象类中。但是我们通过抽象父类的方式消除依附在业务方法中的重复代码。AOP独辟蹊径通过横向抽取机制为这类无法通过纵向继承体系进行抽象的重复性代码提供了解决方法。

 (二)、AOP术语

 (1)、连接点(Joinpoint)

 程序执行的某点特定位置:如类开始初始化前、类初始化后、类某个方法调用前、调用后、方法抛出异常后。一个类或一段程序代码拥有一些具有边界性质的特定点,这些代码中的特定点就称为”连接点”。Spring仅支持方法的连接点,即仅能在方法调用前、方法调用后、方法抛出异常时以及方法调用前后这些程序执行点织入增强。连接点就是AOP向目标类打入楔子的候选点。

 连接点由两个信息确定:第一是用方法表示的程序执行点;第二是用相对点表示的方位。如在Test.foo()方法执行前的连接点,执行点为Test.foo(),方位为该方法执行前的位置。Spring使用切点对执行点进行定位,而方位则在增强类型中定义。

 (2)、切点(Pointcut)

 每个程序类都拥有多个连接点,如一个拥有两个方法的类,这两个方法都是连接点,即连接点是程序类中客观存在的事物。但在这为数众多的连接点中,如何定位到某个感兴趣的连接点上呢?AOP通过“切点”定位特定连接点。通过数据库查询的概念来理解切点和连接点的关系再适合不过了:连接点相当于数据库中的记录,而切点相当于查询条件。切点和连接点不是一对一的关系,一个切点可以匹配多个连接点。

 (3)、增强(Advice)

 增强是织入到目标类连接点上的一段程序代码。在Spring中,增强除用于描述一段程序代码外,还拥有另一个和连接点相关的信息,这便是执行点的方位。结合执行点方位信息和切点信息,我们就可以找到特定的连接点了。Spring所提供的增强接口都是带方位名的:BeforAdvice、AfterRetueningAdvice、ThrowsAdvice等。只有结合切点和增强两者一起才能确定特定的连接点并实施增强逻辑。

 (4)、目标对象(Target)

 增强逻辑的织入目标类。如果没有AOP,目标业务类需要自己实现所有逻辑,在AOP帮助下,只实现哪些非横切逻辑的程序逻辑,而性能监视和事务管理等这些横切逻辑则可以使用AOP动态织入到特定的链接点上。

 (5)、引介(Introduction)

 引介是一种特殊的增强,它为类添加一些属性和方法。这样,既是一个业务类原本没有实现某个接口,通过AOP的引介功能,我们可以动态地为该业务类添加接口的实现逻辑,让业务类称为这个接口的实现类。

 (6)、织入(Weaving)

 织入是将增强添加对目标类具体连接点上的过程,AOP像一台织布机,将目标类、增强或者引介通过AOP这台织布机天衣无缝地编织到一起。AOP有三种织入方式:

 1)  编译器织入,这要求使用特殊的Java编译器;

 2)  类装载器织入,这要求使用特殊的类加载器;

 3)  动态代理织入,在运行期为目标类添加增强生成子类的方式。

 Spring采用动态代理织入,而AspectJ采用编译器织入和类装载器织入。

 (7)、代理(Proxy)

 一个类被AOP织入增强后,就产生一个结果类,它是融合了原类和强增逻辑的代理类。根据不同的代理方式,代理类既可能是和原类具有相同接口的类,也可能是原类的子类,所以我们可以采用调用原类相同的方式调用代理类。

 (8)、切面(Aspect)

 切面由切点和增强(引介)组成,它既包括了横切逻辑的定义,也包括了连接点的定义,Spring Aop就是负责实施切面的框架,它将切面所定义的横切逻辑织入到切面所指定的连接点中。

 AOP的工作重心在于如何将增强应用于目标对象的连接点上,这里首先包括两个工作:第一,如何通过切点和增强定位连接点上;第二,如何在增强中编写切面的代码。

 (三)、基础总结

 Spring AOP的底层就是通过使用JDK动态代理或CGLib动态代理技术为目标Bean织入横切逻辑。

 二、创建增强类

 (一)、增强类型

 按照增强在目标类方法的连接点位置,可以分为5类:

 (1)  前置增强:org.springframeword.aop.beforeAdvice代表前置增强,因为Spring只支持方法级增强,所以MethodBeforeAdvice是目前可用的前置增强,表示在目标方法执行前实施增强,而BeforeAdvice是为了将来版本扩展需要而定义的;

 (2)  后置增强:org.springframeword.aop.AfterReturningAdvice代表后增强,表示在目标方法执行后实施增强;

 (3)  环绕增强:org.aopalliance.intercept.MethodInterceptor代表环绕增强,表示在目标方法执行前后实施增强;

 (4)  异常抛出增强:org.springframework.aop.ThrowsAdvice代表抛出异常增强,表示在目标方法抛出异常后实施增强;

 (5)  引介增强:org.springframeword.aop.IntroductionInterceptor代表引介增强,表示在目标类中添加一些新的方法和属性。

 这些增强接口都有一些方法,通过实现这些接口方法,在接口方法中定义横切逻辑,就可以将他们织入到目标类方法的相应连接点的位置。

  (二)、前置增强

 一个实例。假设服务生只干两件事:第一,欢迎顾客;第二对顾客提供服务

 目标接口及目标实现类:

public interface Waiter {
	//向顾客打招呼
	void greetTo(String name);
	//向顾客提供服务
	void serveTo(String name);
}
public class NaiveWaiter implements Waiter {
	public void greetTo(String name) {
		System.out.println(name+"欢迎光临!");
	}
	public void serveTo(String name) {
		System.out.println(name+"请问有什么需要!");
	}
}
 前置增强实现类:

import java.lang.reflect.Method;
import org.springframework.aop.MethodBeforeAdvice;
public class GreetingBeforeAdvice implements MethodBeforeAdvice {
	//在目标类方法调用前执行
	public void before(Method method, Object[] args, Object obj) throws Throwable {
		String clientName = (String) args[0];
		System.out.println("您好!"+clientName+"是我们的上帝");
	}
}
 使用代理类融合原类和增强逻辑:

@Test
	public void test() {
		Waiter target = new NaiveWaiter();
		BeforeAdvice advice = new GreetingBeforeAdvice();
		//Spring提供的代理工厂
		ProxyFactory pf = new ProxyFactory();
		//设置代理目标
		pf.setTarget(target);
		//为代理目标添加增强
		pf.addAdvice(advice);
		//生成代理实例
		Waiter proxy= (Waiter) pf.getProxy();
		proxy.greetTo("Tom");
		proxy.serveTo("Jerry");
	}
 或者使用Spring配置代理:

<bean id="greetingAdvice" class="com.baobao.jdbcsoutce.advice.GreetingBeforeAdvice"/>
	<bean id="target" class="com.baobao.jdbcsoutce.advice.NaiveWaiter"/>
	<!-- ProxyFactoryBean是FactoryBean接口的实现类 -->
	<bean id="waiter" class="org.springframework.aop.framework.ProxyFactoryBean" 
		p:proxyInterfaces="com.baobao.jdbcsoutce.advice.Waiter" 
		p:interceptorNames="greetingAdvice" 
		p:target-ref="target"/>

proxyInterfaces:代理所要实现的接口,可以是多个接口,如果是多个接口可以使用<list>元素。该属性还有一个别名属性interfaces;

 (三)、后置增强

 通过实现AfterReturningAdvice来定义后置增强逻辑,后置逻辑方法void afterReturning(Object returnObj, Method method, Object[] arg,Object obj) throws Throwable中参数解释:

         ObjectreturnObj:为目标实例方法返回的结果;

         Methodmethod:为目标类的方法;

         Object[]arg:为目标实例的方法的入参;

         Objectobj:为目标类实例。

 具体代码如下:

 

import java.lang.reflect.Method;
import org.springframework.aop.AfterReturningAdvice;
public class GreetingAfterAdvice implements AfterReturningAdvice {
	//在目标方法调用后执行
	public void afterReturning(Object returnObj, Method method, Object[] arg, Object obj) throws Throwable {
		System.out.println("请您随意");
	}
}
 Spring中修改配置,多个织入增强可用“,”、“;”、“空格”号分隔,或者用<list>标签:
 
<bean id="greetingBefore" class="com.baobao.jdbcsoutce.advice.GreetingBeforeAdvice"/>
	<bean id="greetingAfter" class="com.baobao.jdbcsoutce.advice.GreetingAfterAdvice"/>
	<bean id="target" class="com.baobao.jdbcsoutce.advice.NaiveWaiter"/>
	<!-- ProxyFactoryBean是FactoryBean接口的实现类 -->
	<bean id="waiter" class="org.springframework.aop.framework.ProxyFactoryBean" 
		p:proxyInterfaces="com.baobao.jdbcsoutce.advice.Waiter" 
		p:interceptorNames="greetingBefore,greetingAfter" 
		p:target-ref="target"/>

 (四)、环绕增强

绕环增强达到了前置和后置联合增强的效果,代码如下:

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
public class GreetingInterceptor implements MethodInterceptor {
	//MethodInvocation是目标封装类包括:目标方法、入参数组、实例数组
	public Object invoke(MethodInvocation invocation) throws Throwable {
		//获取目标入参数组
		Object[] args = invocation.getArguments();
		String clientName = (String) args[0];
		System.out.println("您好!"+clientName+"是我们的上帝");
		//通过反射调用目标方法并保存返回值
		Object obj = invocation.proceed();
		System.out.println("请您随意");
		return obj;
	}
}
 (五)、异常抛出增强

 当事务中的某个Dao发生异常时,事务管理器就必须回滚事务。以下是一个业务类:

 

public class ForumService {
	public void removeForum(int forumld){
		throw new RuntimeException("运行异常");
	}
	public void updateForum(String forumName)throws Exception{
		throw new SQLException("数据更新操作异常");
	}
}

 异常增强实现类代码:

import java.lang.reflect.Method;
import org.springframework.aop.ThrowsAdvice;
//ThrowsAdvice标识接口
public class TransactionManage implements ThrowsAdvice {
	public void afterThrowing(Method method,Object[]args,Object target,
			Exception e)throws Throwable{
		System.out.println("----");
		System.out.println("目标方法名"+method.getName());
		System.out.println("异常信息"+e.getMessage());
		System.out.println("事务回滚");
	}
}
 Spring中配置:

<!-- 增强类 -->
	<bean id="transactionManage" class="com.baobao.jdbcsoutce.advice.TransactionManage"/>
	<!-- 目标类 -->
	<bean id="target" class="com.baobao.jdbcsoutce.advice.ForumService"/>
	<!-- ProxyFactoryBean是FactoryBean接口的实现类 -->
	<bean id="targetService" class="org.springframework.aop.framework.ProxyFactoryBean" 
		p:interceptorNames="transactionManage" 
		p:target-ref="target" 
		p:proxyTargetClass="true"/><!-- 目标类不是接口,使用CGLib代理 -->
 

 (六)、引介增强

 引介增强是一种比较特殊的增强类型,他不是在目标方法周围织入增强,而是为目标类创建新的方法和属性,所以引介增强的连接点是类级别的,而非方法级别的。通过引介增强,我们可以为目标类添加一个接口的实现,即原来目标类未实现某个接口,通过引介增强可以为目标类创建实现某接口的代理。这种功能富有吸引力,因为它能够在横向上定义接口的实现方法。下面用一个性能检测的例子理解。

         (1)、性能监视类

 
//性能监视类
public class PerformanceMonitor {
	//通过一个ThreadLocal保存调用线程相关的性能监视信息
	private static ThreadLocal<MethodPerformace> performanceRecord = 
			new ThreadLocal<MethodPerformace>();
	//启动对某一目标方法的性能监视
	public static void begin(String method){
		System.out.println("begin monitor...");
		MethodPerformace mp = new MethodPerformace(method);
		performanceRecord.set(mp);
	}
	public static void end(){
		System.out.println("end monitor...");
		MethodPerformace mp = performanceRecord.get();
		//打印方法性能监视的结果信息
		mp.printPerformance();
	}
}

ThreadLocal是将费线程安全类改造为线程安全类的法宝,通过调用begin(String method)方法对某个目标类方法的监视,method为目标类方法的全限定名;而end()方法节数对目标方法的监视,并给出性能监视的信息。

(2)、时间记录类

//记录类
public class MethodPerformace {
	private long begin;
	private long end;
	private String method;
	public MethodPerformace(String method) {
		this.method = method;
		//记录目标类方法开始执行点的系统时间
		this.begin = System.currentTimeMillis();
	}
	public void printPerformance() {
		//获取目标类方法执行完成后的系统时间
		end = System.currentTimeMillis();
		//保存目标方法执行完成的系统时间
		long elapse = end-begin;
		System.out.println(method+"花费"+elapse+"毫秒");
	}
}
 (3)、引介增强类

 Spring定义了引介增强接口IntroductionInterceptor,该接口没有定义任何方法,Spring为该接口提供了DelegatingIntroductionInterceptor实现类,通过扩展该类定义自己的引介增强类。

import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.support.DelegatingIntroductionInterceptor;
//引介增强类
import com.baobao.jdbcsoutce.proxy.PerformanceMonitor;
public class ControllablePerformanceMonitor extends DelegatingIntroductionInterceptor implements Monittorable {
	private ThreadLocal<Boolean> monitorStatusMap = new ThreadLocal<Boolean>();
	@Override
	public void setMonitorActive(boolean active) {
		monitorStatusMap.set(active);
	}
	//覆盖父类的invoke()方法,用于拦截目标类MethodInvocation
	public Object invoke(MethodInvocation mi) throws Throwable {
		Object obj = null;
		if(monitorStatusMap.get()!=null && monitorStatusMap.get()){
			//PerformanceMonitor:性能监测类,传入目标方法的全类名
			PerformanceMonitor.begin(mi.getClass().getName()+"."+
					mi.getMethod().getName());
			obj = super.invoke(mi);
			PerformanceMonitor.end();
		}else{
			obj = super.invoke(mi);
		}
		return obj;
	}
}
 定义一个接口,用于实现开启监视可控功能

public interface Monittorable {
	void setMonitorActive(boolean active);
}
 (4)、配置XML,使引介增强织入到目标业务类中

<!-- 引介增强扩展类 -->
	<bean id="pmonitor" class="com.baobao.jdbcsoutce.introduce.ControllablePerformanceMonitor"/>
	<!-- 目标对象 -->
	<bean id="target" class="com.baobao.jdbcsoutce.introduce.ForumService"/>
	<!-- ProxyFactoryBean是FactoryBean接口的实现类 -->
	<bean id="targetService" class="org.springframework.aop.framework.ProxyFactoryBean" 
		p:interfaces="com.baobao.jdbcsoutce.introduce.Monittorable" 
		p:target-ref="target" 
		p:interceptorNames="pmonitor" 
		p:proxyTargetClass="true"/><!-- 目标类不是接口,使用CGLib代理 -->
 (5)、测试代码
public void test() throws Exception {
		ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
		ForumService forum = (ForumService) ctx.getBean("targetService");
		forum.removeForum(7);
		forum.removeTopic("Tom");
		//开启性能检测
		Monittorable monittorable = (Monittorable) forum;
		monittorable.setMonitorActive(true);
		
		forum.removeForum(7);
		forum.removeTopic("Tom");
	}
PS:关于引介增强的另一套参考: 引介增强实例需求


 三、创建切面

 假设我们希望有选择地织入到目标类某些特定的方法中,就需要使用切点进行目标连接点的定位。描述连接点是进行AOP编程最主要的工作,为了突出强调这一点,我们再次给出Spring AOP如何定位连接点:

       增强提供了连接点方位信息:如织入到方法前面、后面等,而切点进一步描述织入到哪些类的哪些方法上。

      Spring通过org.springframeword.aop.Pointcut接口描述切点,Pointcut由ClassFilter和MethodMatcher构成,它通过ClassFilter定位到某些特定类上,通过MethodMatcher定位到某些特定方法上,这样Pointcut就拥有了描述某些类的某些特定方法的能力。Pointcut类关系图如下:


 

 我们可以看到ClassFilter只定义了一个方法matches(Class clazz),其参数代表一个被检测类,该方法判别被检测的类是否匹配过滤条件。

       Spring支持两种方法匹配器:静态方法匹配器和动态方法匹配器。所谓静态方法匹配器,它仅方法名签名(包括方法名和入参类型及顺序)进行匹配;而动态方法匹配器,会在运行期检查方法入参的值。静态匹配仅会判断一次;而动态匹配因为每次调用方法的入参都可能不一样,所以每次调用方法都必须判断,因此动态匹配对性能的影响很大。一般情况下,动态匹配不常使用。方法匹配器的类型由isRuntime()返回值决定,返回false表示是静态方法匹配器,返回true表示是动态方法匹配器。

 (一)、切点类型

       Spring提供了六种类型切点,下面分别对它们的用途进行介绍:

      (1)、静态方法切点:org.springframeword.aop.support.StaticMethodMatcherPointcut是静态方法切点额抽象基类,默认情况下它匹配所有的类;

      (2)、动态方法切点:org.springframework.aop.support.DynamicMethodMatcherPointcut是动态方法切点的抽象基类,默认情况下它匹配所有的类;

      (3)、注解切点:org.springframework.aop.support.annotation.AnnotationMatchingPointcut实现类表示注解切点。使用AnnotationMatchingPointcut支持在Bean中直接通过JDK5.0注解标签定义的切点;

      (4)、表达式切点:org.springframework.aop.support.ExpressionPointcut接口主要是为了支持AspectJ切点表达式语法而定义的接口;

  (5)、流程切点:org.springframework.aop.support.ControlFlowPointcut实现类表示控制流程切点。ControlFlowPointcut是一种特殊的切点,它根据程序执行堆栈的信息查看目标方法是否由一个方法直接或间接发起调用,以此判断是否为匹配的连接点;

    (6)、符合切点:org.springframework.aop.support.ComposablePointcut实现类是为创建多个切点而提供的方便操作类。它所有的方法都返回ComposablePointcut类,这样,我们就可以使用连接表达式对切点进行操作,形如:Pointcut pc = new ComposablePointcut().union(classFilter).intersection(methodMatcher).intersection(pointcut)。

     (二)、切面类型

 由于增强即包括横切代码,又包含部分的连接点信息(方法前,方法后主方法信息),所以我们可以仅通过增强类生成一个切面。但切点仅代表目标类连接点的部分信息(类和方法的定位),所以仅有切点,我们无法制作出一个切面,必须结合增强才能制作出切面。Spring使用org.springframework.aop.advisor接口表示切面的概念,一个切面同时包含横切代码和连接信息。切面可以分为三类:一般切面、切点切面和引介切面,我们可以通过Spring所定义的切面接口清楚地了解切面的分类。下面是切面类继承关系图:

 

 (1)、Advisor:代表一般切面,它仅包含一个Advice。因为Advice包含了横切代码和连接点的信息,所以Advice本身就是一个简单的切面,只不过它代表的横切的连接点是所有目标类的所有方法,因为这个横切面太宽泛,所以一般不会直接使用;

       (2)、PointcutAdvisor:代笔具有切点的切面,它包含Advice和Pointcut两个类,这样,我们就可以通过类、方法名以及方法方位等信息灵活地定义切面的连接点,提供更具适用性的切面;

       (3)、IntroductionAdvisor:代笔引介切面。引介切面是对应引介增强的特殊的切面,它应有于类层面上,所以引介切点使用ClassFilter进行定义。

 下面看一下PointcutAdvisor的主要实现类体系:

 

 (1)、DefaultPointcutAdvisor:最常用的切面类型,它可以通过任意Pointcut和Advice定义一个切面,唯一不支持的是引介的切面类型,一般可以通过扩展该类实现自定义的切面;

       (2)、NameMatchMethodPointcutAdvisor:通过该类可以定义按方法名定义切点的切面;

       (3)、RegexpMethodPointcutAdvisor:对于按正则表达式匹配方法名进行切点定义的切面,可以通过扩展该实现类进行操作。RegexpMethodPointcutAdvisor允许用户以正则表达式模式串定义方法匹配的切点,其内部通过JdkRegexpMethodPointcut构造出正则表达式方法名切点。

       (4)、StaticMethodMatcherPointcutAdvisor:静态方法匹配器切点定义的切面,默认情况下,匹配所有的目标类;

       (5)、AspectJExpressionPoincutAdvisor:用于AspectJ切点表达式定义切点的切面,它是Spring 2.0新提供的类;

       (6)、AspectJPointcutAdvisor:用于AspectJ语法定义切点的切面,它也是Spring2.0新提供的类。

 (三)具体用法

 (1)、静态普通方法名匹配切面

       StaticMethodMatcherPointcutAdvisor代表一个静态方法匹配切面,它通过StaticMethodMatcherPointcut定义切点,通过类过滤和方法名匹配定义切点。

 1)、两个业务类,具有相同的方法名,但是一个设置匹配另一个不匹配最后看效果

 

public class Waiter {
	public void greetTo(String name){
		System.out.println("waiter greet to "+name+"...");
	}
	public void serveTo(String name){
		System.out.println("waiter serving "+name+"...");
	}
}
public class Seller {
	public void greetTo(String name){
		System.out.println("waiter greet to "+name+"...");
	}
}
 

 2)、定义切面,在Waite#greetTo()方法调用前织入一个增强

 
public class GreetingAdvisor extends StaticMethodMatcherPointcutAdvisor {
	//切点方法名匹配,匹配规则为greetTo
	public boolean matches(Method method, Class clazz) {
		return "greetTo".equals(method.getName());
	}
	//切点类匹配,匹配规则为Waiter的类或子类
	public ClassFilter getClassFilter() {
		return new ClassFilter(){
			public boolean matches(Class clazz) {
				return Waiter.class.isAssignableFrom(clazz);
			}
		};
	}
	/*
	 *因为 StaticMethodMatcherPointcutAdvisor抽象类唯一需要定义的是matches()方法。
	 *在默认情况下,该切面匹配所有的类,这里通过覆盖getClassFilter()方法,让它仅匹配Waiter
	 *类及其子类
	 */
}

 3)、再定义前置增强,用来和切面配合

 
import java.lang.reflect.Method;
import org.springframework.aop.MethodBeforeAdvice;
//前置增强
public class GreetingBeforeAdvice implements MethodBeforeAdvice {
	public void before(Method method, Object[] args, Object obj) throws Throwable {
		//输出切点的全名
		System.out.println(obj.getClass().getName()+"."+method.getName());
		//保存切点参数
		String clientName = (String) args[0];
		System.out.println("How are you! Mr."+clientName+".");
	}
}

 4)配置Bean信息及代理

       由于需要分别为waiter和seller两个Bean定义代理器,两者有很多公共的配置信息,我们使用一个父<bean>简化配置,通过引用父<bean>定义织入切面的代理

 
<bean id="waiterTarget" class="com.baobao.springtest6.advisor.Waiter"/>
	<bean id="sellerTarger" class="com.baobao.springtest6.advisor.Seller"/>
	<!-- 增强 -->
	<bean id="greetingAdvice" class="com.baobao.springtest6.advisor.GreetingBeforeAdvice"/>
	<!-- 切面 -->
	<bean id="greetingAdvisor" class="com.baobao.springtest6.advisor.GreetingAdvisor" 
		p:advice-ref="greetingAdvice"/>
	<!-- 通过一个父<bean>的代理类定义公共配置信息 -->
	<bean id="parent" abstract="true" class="org.springframework.aop.framework.ProxyFactoryBean" 
		p:interceptorNames="greetingAdvisor" 
		p:proxyTargetClass="true"/>
	<!-- 2个子代理类分别配置代理 -->
	<bean id="waiter" parent="parent" p:target-ref="waiterTarget"/>
	<bean id="seller" parent="parent" p:target-ref="sellerTarger"/>
 5)测试
 
public void test() {
		ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
		Waiter waiter = (Waiter) ctx.getBean("waiter");
		Seller seller = (Seller) ctx.getBean("seller");
		waiter.greetTo("John");
		waiter.serveTo("John");
		seller.greetTo("John");
	}
 结果输出:

 com.baobao.springtest6.advisor.Waiter.greetTo
 How are you! Mr.John.
 waiter greet to John...
 waiter serving John...
 waiter greet to John...

 (2)、静态正则表达式方法匹配切面

 在StaticMethodMatcherPointcutAdvisor中,我们仅能通过方法名定义切点,这种描述不够灵活。假设目标类中有多个方法,且他们都满足一定的命名规范,使用正则表达式匹配藐视就灵活多了。RegexMethodPointcutAdvisor是正则表达式方法匹配的切面实现类,该类已经是功能齐备实现类,一般情况下,无需扩展该类。

 具体事例,直接通过配置的方式为目标类定义切面

 
<bean id="waiterTarget" class="com.baobao.springtest6.advisor.Waiter"/>
	<!-- 增强 -->
	<bean id="greetingAdvice" class="com.baobao.springtest6.advisor.GreetingBeforeAdvice"/>
	<!-- 正则表达式切面 -->
	<bean id="regexpAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor" 
		p:advice-ref="greetingAdvice">
		<!-- 使用正则表达式定义目标类全限定名的匹配模式串 -->
		<property name="patterns">
			<list>
				<!-- 匹配模式串 -->
				<value>.*greet.*</value>
			</list>
		</property>
	</bean>
	<!-- 通过一个父<bean>的代理类定义公共配置信息 -->
	<bean id="waiter1" class="org.springframework.aop.framework.ProxyFactoryBean" 
		p:interceptorNames="regexpAdvisor" 
		p:target-ref="waiterTarget" 
		p:proxyTargetClass="true"/>
 正则表达式语法


 
 (3)、动态切面

 我们使用DefaultPointcutAdvisor和Dy DynamicMethodMatcherPointcut来完成功能。DynamicMethodMatcherPointcut是一个抽象类,它将isRuntime()标识为final并且会犯true,这样其子类一定是一个动态的切点了,该抽象类默认匹配所有的类和方法,因此需要扩展该类编写符合要求的动态切点。

 Spring机制:在创建代理时对目标类的每个连接点使用静态切点检查,如果仅通过静态切点检查就可以知道连接点是不匹配的,则不再进行动态切点检查;如果静态切点检查是匹配的,在运行时才进行动态切点检查。

 测试代码:首先继承抽象类DynamicMethodMatcherPointcut

 

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import org.springframework.aop.ClassFilter;
import org.springframework.aop.support.DynamicMethodMatcherPointcut;
public class GreetingDynamicPointcut extends DynamicMethodMatcherPointcut {
	//动态切点过滤信息保存在List中
	private static List<String>specialClientList = new ArrayList<String>();
	static{
		specialClientList.add("John");
		specialClientList.add("Tom");
	}
	//重写静态切点检查
	public ClassFilter getClassFilter() {
		return new ClassFilter(){
			public boolean matches(Class clazz){
				System.out.println("调用getClassFilter()对"+clazz.getName()+"做静态检查.");
				return Waiter.class.isAssignableFrom(clazz);
			}
		};
	}
	//对方法进行静态切点检查
	public boolean matches(Method method, Class clazz) {
		System.out.println("调用matches(method,clazz)"+clazz.getName()+"."+
				method.getName()+"做静态检查.");
		return "greetTo".equals(method.getName());
	}
	//对方法进行动态切点检查
	public boolean matches(Method method, Class clazz, Object[] args) {
		System.out.println("调用matches(method,clazz)"+clazz.getName()+"."
				+method.getName()+"做动态检查.");
		String clientName = (String) args[0];
		//检查入参信息是否与List中的保存一致
		return specialClientList.contains(clientName);
	}
}
 然后在xml中配置切面和代理等,增强还是用前面的前置
 
<!-- 目标类 -->
	<bean id="waiterTarget" class="com.baobao.springtest6.advisor.Waiter"/>
	<!-- 切面 -->
	<bean id="dynamicAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
		<!-- 动态切点 -->
		<property name="pointcut">
			<bean class="com.baobao.springtest6.advisor.GreetingDynamicPointcut"/>
		</property>
		<!-- 增强 -->
		<property name="advice">
			<bean class="com.baobao.springtest6.advisor.GreetingBeforeAdvice"/>
		</property>
	</bean>
	<!-- 代理类定 -->
	<bean id="waiter2" class="org.springframework.aop.framework.ProxyFactoryBean" 
		p:interceptorNames="dynamicAdvisor" 
		p:target-ref="waiterTarget" 
		p:proxyTargetClass="true"/>

 我们发现每次在调用代理对象的任何一个方法,都会执行动态切点检查,这将导致很大的性能问题。所以,我们在定义动态切点时,切勿忘记同时覆盖getClassFilter()和matches(Method method,Class clazz)方法,通过静态切点检查排除大部分方法。

 (4)、流程切面

 Spring的流程切面由DefaultPointcutAdvisor和ContorFlowPointcut实现。流程切点代表由某个方法直接或间接发起调用的其他方法。假设我们通过一个类代理目标类所有的方法,下面看一个案例:

 
public class WaiterDelegate {
	private Waiter waiter;
	//waiter的方法通过该方面发起调用
	public void service(String clientName){
		waiter.greetTo(clientName);
		waiter.serveTo(clientName);
	}
	public Waiter getWaiter() {
		return waiter;
	}
	public void setWaiter(Waiter waiter) {
		this.waiter = waiter;
	}
}

 如果我们希望所有由WaiterDelegate#service()方法发起调用的其他方法都织入GreetingBeforeAdvice增强,就必须使用流程切面来完成目标。下面使用DefalutPointcutAdvisor配置一个流程切面来完成这一需求。

 
<!-- 增强 -->
	<bean id="greetingAdvice" class="com.baobao.springtest6.advisor.GreetingBeforeAdvice"/>
	<!-- 目标类 -->
	<bean id="waiterTarget" class="com.baobao.springtest6.advisor.Waiter"/>
	<!-- 流程切点 -->
	<bean id="controlFlowPointcut" class="org.springframework.aop.support.ControlFlowPointcut">
		<!-- 指定流程切点的类 -->
		<constructor-arg type="java.lang.Class" value="com.baobao.springtest6.advisor.WaiterDelegate"/>
		<!-- 指定流程切点的方法 -->
		<constructor-arg type="java.lang.String" value="service"/>
	</bean>
	<!-- 流程切面 -->
	<bean id="controFlowAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor" 
		p:pointcut-ref="controlFlowPointcut" 
		p:advice-ref="greetingAdvice"/>
	<!-- 代理 -->
	<bean id="waiter3" class="org.springframework.aop.framework.ProxyFactoryBean" 
		p:interceptorNames="controFlowAdvisor" 
		p:target-ref="waiterTarget" 
		p:proxyTargetClass="true"/>

 流程切面和动态切面从某种程度上说可以算是一类切面,因为这两都需要在运行期判断动态的环境。对于流程切面来说,代理对象在每次调用目标类方法时,都需要判断方法调用堆栈中是否满足流程切面的要求。因此,和动态切面一样,流程切面对性能的影响也很大,在JVM1.4上,流程切面通常比别的切点药慢5倍,在JVM1.3上要慢10倍。

 四、自动创建代理

 Spring为我们提供了自动代理机制,让容器为我们自动生成代理,把我们从繁琐的配置工作中解放出来。在内部,Spring使用BeanPostProcessor自动地完成这项工作。

 这些基于BeanPostProcessor的自动代理创建器的实现类,将根据一些规则自动在容器实例化Bean时为匹配的Bean生成代理实例。这些代理创建器可以分为以下三类:

 (1)基于Bean配置名规则的自动代理创建器:允许为一组特定配置名的Bean自动创建代理实例的代理创建器,实现类BeanNameAutoProxyCreator;

 (2)基于Advisor匹配机制的自动代理创建器:它会对容器中所有的Advisor进行扫描,自动将这些切面应用匹配到Bean中,实现类为DefaultAdvisorAutoProxyCreator;

 (3)基于Bean中AspjectJ注解标签的自动代理创建器:为包含AspectJ注解的Bean自动创建代理实例,它的实现类是AnnotationAwareAspectJAutoProxyCreator。

 (一)、使用BeanNameAutoProxyCreator配置代理
 
<!-- 目标类 -->
	<bean id="waiter" class="com.baobao.springtest6.advisor.Waiter"/>
	<bean id="seller" class="com.baobao.springtest6.advisor.Seller"/>
	<!-- 增强 -->
	<bean id="greetingAdvice" class="com.baobao.springtest6.advisor.GreetingBeforeAdvice"/>
	<!-- 代理 -->
	<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator" 
		p:beanNames="*er" 
		p:interceptorNames="greetingAdvice" 
		p:optimize="true"/>

 beanNameAutoProxyCreator有一个beanNames属性,,它允许用户指定一组需要自动代理的Bean名称,Bean名称可以使用*通配符。我们假设Spring容器中除了waiter和seller外还有其他的Bean,如果以“er”为后缀的规则可以将这两个Bean和容器中其他Bean区分开,并被自动代理。

 beanNameAutoProxyCreator的interceptorNames属性指定一个或多个增强Bean的名称。此外还有一个常用的optimize属性,如果将此属性设置为true将强制使用CGLib动态代理技术。

 (二)、使用DefaultAdvisorAutoProxyCreator配置代理

 我们知道切面Advisor是切点和增强的复合体,Advisor本身已经包含了足够的信息:横切逻辑(要织入什么)以及连接点(织入到哪里)。

 DefaultAdvisorAutoProxyCreator能够扫描容器中的Advisor,并将Advisor自动织入到匹配的目标Bean中,即为匹配的目标Bean自动创建代理。

<bean id="waiter" class="com.baobao.springtest6.advisor.Waiter"/>
	<bean id="seller" class="com.baobao.springtest6.advisor.Seller"/>
	<bean id="greetingAdvice" class="com.baobao.springtest6.advisor.GreetingBeforeAdvice"/>
	<!-- 正则表达式切面 -->
	<bean id="regexpAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor" 
		p:patterns=".*greet.*" 
		p:advice-ref="greetingAdvice"/>
	<!-- 自动扫描容器中切面的代理 -->
	<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>
 测试代码:

public void test() {
		ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext3.xml");
		Waiter waiter = (Waiter) ctx.getBean("waiter");
		Seller seller = (Seller) ctx.getBean("seller");
		waiter.greetTo("John");
		waiter.serveTo("serveTo");
		seller.greetTo("Tom");
	}

 Waiter#serveTo()方法没有织入增强,而Waiter和Seller的greetTo()方法都织入了增强,由此可见增强被正确地织入到匹配的连接点中。

 四、总结

 Spring只能在方法级别上织入增强,增强其他就是一种最简单的切面,它既包括横切代码也包括切点信息,只不过它的切点只是简单的方法相对位置的信息。所以增强一般需要和切点联合才可以表示一个更具适用性的切面。

 Spring中,普通的切点通过目标类名和方法名描述连接点的信息。流程切点是比较特殊的切点,它通过方法调用堆栈运行环境信息来决定连接点。有时,我们需要通过切点的检查或合并描述一个最终的切点,可以使用ComposablePointcut符合切点。

 切面是增强和切点和联合体,我们可以很方便地通过Spring提供ProxyBeanFactory将切面植入到不同的目标类中。也可以利用BeanPostProcessor干涉Bean生命周期的机制,提供了一些可以自动创建代理,织入切面的自动代理创建器,其中DafalutAdvisorAutoProxyCreator是功能强大的自动代理创建器,它将容器中所有Advisor自动织入到目标Bean中。









  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值