Spring4学习:SpringAOP基础

AOP是有特定的应用场合的,它只适合那些具有横切逻辑的应用场合,如性能检测、访问控制、事务管理及日志记录

一、AOP概述

1、AOP术语

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

(2)切点(Pointcut):在Spring中,切点通过org.springframework.aop.Pointcut接口进行描述,它使用类和方法作为连接点的查询条件,Spring AOP的规则解析引擎负责解析切点所设定的查询条件,找到对应的连接点。确切地说,应该是执行点而非连接点,因为连接点是方法执行前、执行后等包括方位信息的具体程序执行点,而切点只定位到某个方法上,所以如果希望定位到具体的连接点上,还需要提供方位信息。

(3)增强(Advice):是织入目标类连接点上的一段程序代码。在Spring中,增强除用于描述一段程序代码外,还拥有另一个和连接点相关的信息,这便是执行点的方位。结合执行点的方位信息和切点信息,就可以找到特定的连接。

(4)目标对象(Target):增强逻辑的织入目标类。如果没有AOP,那么目标业务类需要自己实现所有的逻辑。

(5)引介(Introduction):引介是一种特殊的增强,它为类添加一些属性和方法。这样,即使一个业务类原本没有实现某个接口,通过AOP的引介功能,也可以动态地为该业务类添加接口的实现逻辑,让业务类成为这个接口的实现类。

(6)织入(Weaving):是将增强添加到目标类的具体连接点上的过程。AOP有3种织入方式:编译期织入、类装载期织入、动态代理织入,Spring采用动态代理织入,而AspectJ采用编译期织入和类装载期织入。

(7)代理(Proxy):一个类被AOP织入增强后,就产生了一个结果类,它是融合了原类和增强逻辑的代理类。

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

二、基础知识

Spring AOP使用了两种代理机制:一种是基于JDK的动态代理;另一种是基于CGLib的动态代理。

1、JDK动态代理

JDK的动态代理主要涉及java.lang.reflect包中的两个类:Proxy和InvocationHandler。其中,InvocationHandler是一个接口,可以通过实现该接口定义横切逻辑,并通过反射机制调用目标类的代码,动态地将横切逻辑和业务逻辑编织在一起。而Proxy利用InvocationHandler动态创建一个符合某一接口的实例,生成目标类的代理对象。

ForumServiceImpl是业务逻辑类,如下代码:

public class ForumServiceImpl implements ForumService {
	public void removeTopic(int topicId) {
		System.out.println("模拟删除Topic记录:"+topicId);
		try {
			Thread.currentThread().sleep(20);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}		
	}
	public void removeForum(int forumId) {
		System.out.println("模拟删除Forum记录:"+forumId);
		try {
			Thread.currentThread().sleep(40);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}		
	}
}

在PerformanceMonitor中实现类横切逻辑,如下代码:

public class PerformanceMonitor {
	private static ThreadLocal performaceRecord = new ThreadLocal();
	public static void begin(String method) {
		System.out.println("begin monitor...");
		MethodPerformace mp = performaceRecord.get();
		if(mp == null){
			mp = new MethodPerformace(method);
			performaceRecord.set(mp);
		}else{
		    mp.reset(method);	
		}
	}
	public static void end() {
		System.out.println("end monitor...");
		MethodPerformace mp = performaceRecord.get();
		mp.printPerformace();
	}
}
PerformanceHandler实现了InvocationHandler接口,该接口定义了一个invoke(Object proxy,Method method,Object[] args)方法,其中,proxy是最终生成的代理实例,method是被代理目标实例的某个具体方法,通过它可以发起目标实例方法的反射调用;args是被代理实例某个方法的入参,在方法反射调用时使用。如下代码所示,PerformanceHandler类将横切逻辑代码和业务逻辑代码编织在一起。

public class PerformanceHandler implements InvocationHandler {
    private Object target;
	public PerformaceHandler(Object target){
		this.target = target;
	}
	public Object invoke(Object proxy, Method method, Object[] args)
			throws Throwable {
		PerformanceMonitor.begin(target.getClass().getName()+"."+ method.getName());
		Object obj = method.invoke(target, args);
		PerformanceMonitor.end();
		return obj;
	}
}
下面通过Proxy结合PerformanceHandler创建ForumService接口的代理实例。

public void proxy(){
	ForumService target = new ForumServiceImpl();
	PerformanceHandler handler = new PerformanceHandler(target);
	ForumService proxy = (ForumService)Proxy.newProxyInstance(
		target.getClass().getClassLoader(),
		target.getClass().getInterfaces(),
		handler);
	proxy.removeForum(10);
	proxy.removeTopic(1012);
}

2、CGLib动态代理

使用JDK创建代理有一个限制,即它只能为接口创建代理实例,这一点从Proxy的方法newProxyInstance(ClassLoader loader,Class[] interfaces,InvacationHandler h)中可以看出,第二个参数interfaces就是需要代理实例实现的接口列表。

CGLib采用底层的字节码技术,可以为一个类创建子类,在子类中采用方法拦截的技术拦截所有父类方法的调用并顺势织入横切逻辑。下面是用CGLib技术编写的可以为任何类创建织入横切逻辑代理对象的代理创建器。

public class CglibProxy implements MethodInterceptor {
	private Enhancer enhancer = new Enhancer();

	public Object getProxy(Class clazz) {
		enhancer.setSuperclass(clazz);
		enhancer.setCallback(this);
		return enhancer.create();//通过字节码技术动态创建子类实例
	}

	public Object intercept(Object obj, Method method, Object[] args,
			MethodProxy proxy) throws Throwable {
		PerformanceMonitor.begin(obj.getClass().getName()+"."+method.getName());
		Object result=proxy.invokeSuper(obj, args);//通过代理类调用父类中的方法
		PerformanceMonitor.end();
		return result;
	}
}
下面通过CglibProxy为ForumServiceImpl类创建代理对象,并测试代理对象的方法

public void proxy(){
	CglibProxy proxy = new CglibProxy();
	ForumServiceImpl forumService = (ForumServiceImpl)proxy.getProxy(ForumServiceImpl.class);
	forumService.removeForum(10);
	forumService.removeTopic(1023);
}
由于CGlib采用动态创建子类的方式生成代理对象,所以不能对目标类中的final或private方法进行代理。

CGLib所创建的动态代理对象的性能比JDK所创建的动态代理的对象的性能高,但CGLib在创建代理对象时所花费的时间比JDK动态代理多。对于Singleton的代理对象或者具有实例池的代理,因为无须频繁地创建代理对象,所以比较适合采用CGLib动态代理技术;反之则采用JDK动态代理技术。

三、创建增强类

1、增强类型

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

(1)前置增强:org.springframework.aop.BeforeAdvice代表前置增强,表示在目标方法执行前实施增强。因为Spring只支持方法级的增强,所以MethodBeforeAdvice是目前可用的前置增强。

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

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

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

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

2、前置增强

如下是业务逻辑类:

public class NativeWaiter implements Waiter{
	public void greetTo(String name){
		System.out.println("greet to "+name+"...");
	}
	public void serveTo(String name){
		System.out.println("serving "+name+"...");
	}
}
GreetingBeforeAdvice实现了MethodBeforeAdvice接口的before(Method method,Object[] args,Object obj)throws Throwable方法,实现横切逻辑,如下代码:

public class GreetingBeforeAdvice implements MethodBeforeAdvice {
	public void before(Method method, Object[] args, Object obj) throws Throwable {
		String clientName = (String)args[0];
		System.out.println(obj.getClass().getName()+"."+method.getName());
		System.out.println("How are you!Mr."+clientName+".");
	}
}
使用ProxyFactory将业务逻辑和横切逻辑编织在一起,如下代码:

@Test
public void before() {
        Waiter target = new NaiveWaiter();
        BeforeAdvice  advice = new GreetingBeforeAdvice();
        ProxyFactory pf = new ProxyFactory();
        pf.setInterfaces(target.getClass().getInterfaces());
        pf.setOptimize(true);
        pf.setTarget(target);
        pf.addAdvice(advice);

        Waiter proxy = (Waiter)pf.getProxy(); 
        proxy.greetTo("John");
        proxy.serveTo("Tom");
}
ProxyFactory内部使用JDK或CGLib动态代理技术将增强应用到目标类中。通过ProxyFactory的setInterfaces(Class[] interfaces)方法指定目标接口进行代理,则ProxyFactory使用JDK动态代理技术,如果是针对类的代理,则使用CGLib动态代理技术。通过ProxyFactory的setOptimize(true)方法让ProxyFactory启动优化代理方式,这样针对接口的代理也会使用CGLib。

在Spring中配置增强:

Spring4学习:SpringAOP基础_第1张图片

ProxyFactoryBean是FactoryBean接口的实现类,负责为其他Bean创建代理实例,它在内部使用ProxyFactory来完成这项工作。ProxyFactoryBean中的可配置属性如下:

target:代理的目标对象

proxyInterfaces:代理所要实现的接口,可以是多个接口。该属性还有一个别名属性interfaces。

interceptorNames:需要织入目标对象的Bean列表,采用Bean的名称指定。这些Bean必须是实现了org.aopalliance.intercept.MethodInterceptor或org.springframeword.aop.Advisor的Bean,配置中的顺序对应调用的顺序。

singleton:返回的代理是否是单实例,默认为单实例。

optimize:当设置为true时,强制使用CGLib动态代理。

proxyTargetClass:是否对类进行代理,设置为true时,使用CGLib动态代理。

3、后置增强

通过实现AfterReturningAdvice来定义后置增强的逻辑,AfterReturningAdvice接口也仅定义了唯一的方法afterReturning(Object returnObj,Method method,Obeject[] args,Object obj)throws Throwable。其中,returnObj为目标实例方法返回的结果;method为目标类的方法;args为目标实例方法的入参;而obj为目标类实例。如果在后置增强中抛出异常,如果该异常是目标方法声明的异常,则异常归并到目标方法中;如果不是目标方法所声明的异常,则Spring将其转为运行期异常抛出。

4、环绕增强

Spring使用MethodInterceptor作为环绕增强的接口。该接口拥有唯一的接口方法Object invoke(MethodInvocation invocation)throws Throwable。MethodInvocation 不但封装了目标方法及其入参数组,还封装了目标方法所在的实例对象,通过MethodInvocation 的getArguments()方法可以获取目标方法的入参数组,通过proceed()方法反射调用目标实例相应的方法。

5、异常抛出增强

ThrowsAdvice异常抛出增强接口没有定义的任何方法,它是一个标签接口,在运行期Spring使用反射机制自行判断,必须采用一些签名形式定义异常抛出的增强方法:

void afterThrowing(Method method,Object[] args,Object target,Throwable),方法名必须为afterThrowing,方法入参规定如下:前3个入参是可选的(要么都提供,要么都不提供),而最后一个入参是Throwable或其子类。

目标方法抛出异常后,优先选取拥有异常入参和抛出的异常相似度最高的afterThrowing()方法。

6、引介增强

引介增强是为目标类创建新的方法和属性,引介增强的连接点是类级别的,而非方法级别的。通过引介增强,可以为目标类添加一个接口实现,即原来目标类未实现某个接口,通过引介增强可以为目标类创建实现某接口的代理。
Spring定义了引介增强接口IntroductionInterceptor,该接口没有定义任何方法,Spring为该接口提供了DelegatingIntroductionInterceptor实现类。通过扩展该实现类可以定义自己的引介增强类。以下是引介增强的实例:
首先定义一个用于标示目标类是否支持性能监视的接口:
public interface Monitorable {
   void setMonitorActive(boolean active);
}
下面通过扩展DelegatingIntroductionInterceptor为目标类引入性能监视的可控功能

public class ControllablePerformaceMonitor extends DelegatingIntroductionInterceptor implements Monitorable, Testable {
	private ThreadLocal MonitorStatusMap = new ThreadLocal();
	public void setMonitorActive(boolean active) {
		MonitorStatusMap.set(active);
	}
	public Object invoke(MethodInvocation mi) throws Throwable {
		Object obj = null;
		if (MonitorStatusMap.get() != null && MonitorStatusMap.get()) {
			PerformanceMonitor.begin(mi.getClass().getName() + "."+ mi.getMethod().getName());
			obj = super.invoke(mi);
			PerformanceMonitor.end();
		} else {
			obj = super.invoke(mi);
		}
		return obj;
	}
}

下面通过Spring的配置,将这个引介增强织入业务类ForumService中

Spring4学习:SpringAOP基础_第2张图片

引介增强的配置与一般配置的区别:

一是需要指定引介增强所实现的接口,如上图(1)所示

二是由于只能通过为目标类创建子类的方式生成引介增强的代理,所以必须将proxyTargetClass设置为true。

四、创建切面

增强提供了连接点方位信息,如织入到方法前面、后面等,而切点进一步描述了织入哪些类的哪些方法上。
Spring通过org,springframework.aop.Pointcut接口描述切点,Pointcut由ClassFilter和MethodMatcher构成,它通过ClassFilter定位到某些特定类上,通过MethodMatcher定位到某些特定方法上。ClassFilter只定义了一个方法matches(Class class),其参数代表一个被检测类,该方法判别被检测的类是否匹配过滤条件。
Spring支持两种方法匹配器:静态方法匹配器和动态方法匹配器。
静态方法匹配器:仅对方法名签名(包括方法名和入参类型及顺序)进行匹配,仅会判别一次。
动态方法匹配器:会在运行期检查方法入参的值,每次调用方法都必须判断。动态匹配不常使用。
方法匹配器的类型由isRuntime()方法的返回值决定,返回false表示是静态方法匹配器,返回true表示是动态方法匹配器。

1、切点类型

(1)静态方法切点:org.springframework.aop.support.StaticMethodMatcherPointcut是静态方法切点的抽象基类,默认情况下它匹配所有的类。StaticMethodMatcherPointcut包括两个主要的子类,分别是NameMatchMethodPointcut和AbstractRegexpMethodPointcut,前者提供简单字符串匹配方法签名,而后者使用正则表达式匹配方法签名。
(2)动态方法切点:org.springframework.aop.support.DynamicMethodMatcherPointcut是动态方法切点的抽象基类,默认匹配所有类。
(3)注解切点:使用AnnotationMatchingPointcut支持在Bean中直接通过Java5.0注解标签定义的切点。
(4)表达式切点:ExpressionPointcut接口是为了支持AspectJ切点表达式语法而定义的接口
(5)流程切点:ControlFlowPointcut根据程序执行堆栈的信息查看目标方法是否由某一个方法直接或间接发起调用,以此判断是否为匹配的连接点。
(6)复合切点:ComposablePointcut是为创建多个切点而提供的方便操作类。

2、切面类型

切点仅代表目标类连接点的类和方法的定位信息,而切面是切点和增强的结合。切面类型如下:
(1)Advisor:包含了横切代码和连接点信息,但它代表的横切的连接点是所有目标类的所有方法。
(2)PointcutAdvisor:代表具有切点的切面,包含Advice和Pointcut两个类。它具有6个具体的实现类:
A、DefaultPointcutAdvisor:可以通过任意Pointcut和Advice定义一个切面,不支持引介的切面类型。
B、NameMatchMethodPointcutAdvisor:可以定义按方法名定义切点的切面。
C、RegexpMethodPointcutAdvisor:允许用户以正则表达式模式串定义方法匹配的切点。
D、StaticMethodMatcherPointcutAdvisor:静态方法匹配器切点定义的切面。
  E、AspectJExpressionPointcutAdvisor:用于AspectJ切点表达式定义切点的切面。
F、AspectJPointcutAdvisor:用于AdpectJ与发电用切点的切面。
(3)IntroductionAdvisor:代表引介切面,应用于类层面上,使用ClassFilter进行定义。

3、静态普通方法名匹配切面

下面是一个例子,给Waiter类的greetTo方法织入一个增强,首先定义业务类:
public class Waiter {
	private Waiter waiter;
	public void serveTo(String name){
		System.out.println("waiter serving "+name+"...");
		//waiter.greetTo(name);
	}
	public void greetTo(String name) {
		System.out.println("waiter greet to "+name+"...");
	}
}
public class Seller {
	public void greetTo(String name) {
		System.out.println("seller greet to "+name+"...");
	}
}
下面是切面类的代码,StaticMethodMatcherPointcutAdvisor默认可以匹配所有类,这里覆盖了getClassFilter方法,让它仅匹配Waiter类及其子类
public class GreetingAdvisor extends StaticMethodMatcherPointcutAdvisor {
	public boolean matches(Method method, Class clazz) {
		return "greetTo".equals(method.getName());
	}	
	public ClassFilter getClassFilter(){
		return new ClassFilter(){
			public boolean matches(Class clazz){
				return Waiter.class.isAssignableFrom(clazz);
			}
		};	
	}
}
Advisor还需要一个增强类,下面是一个前置增强:
public class GreetingBeforeAdvice implements MethodBeforeAdvice {
	public void before(Method method, Object[] args, Object obj) throws Throwable {
		String clientName = (String)args[0];
		System.out.println(obj.getClass().getName()+"."+method.getName());
		System.out.println("How are you!Mr."+clientName+".");
	}
}
最后使用Spring配置来定义切面:
Spring4学习:SpringAOP基础_第3张图片

StaticMethodMatcherPointcutAdvisor除了advice属性外还有
classFiler:类匹配过滤器,这里在GreetingAdvisor中用编码的方式设定了。
order:切面织入时的顺序。

4、静态正则表达式方法匹配切面

Spring4学习:SpringAOP基础_第4张图片
pattern:如果只有一个匹配模式串,则可以使用该属性进行配置。patterns属性用于定义多个匹配模式串,这些匹配模式串之间是或的关系

order:切面在织入时对应的顺序。

5、动态切面

在定义动态切点时,要同时覆盖getClassFilter()和matches(Method method,Class clazz)方法,通过静态切点检查排除大部分方法。

Spring4学习:SpringAOP基础_第5张图片
在Spring中配置动态切面


	
		
	
	
		
	

6、流程切面

Spring的流程切面由DefaultPointAdvisor和ControlFlowPointcut实现。流程切点代表由某个方法直接或间接发起调用的其他方法。下面的例子实现所有由WaiterDelegate#service()方法发起调用的其他方法都织入增强。

public class WaiterDelegate {
	private Waiter waiter;
	public void service(String clientName) {
		waiter.greetTo(clientName);
		waiter.serveTo(clientName);
	}
	public void setWaiter(Waiter waiter) {
		this.waiter = waiter;
	}
}
下面使用DefaultPointAdvisor配置一个流程切面:

Spring4学习:SpringAOP基础_第6张图片
ControlFlowPointcut有两个构造函数,分别是ControlFlowPointcut(Class clazz)和ControlFlowPointcut(Class clazz,String methodName)。第一个构造函数指定一个类作为流程切点;第二个构造函数指定一个类和某一个方法作为流程切点。

7、复合切点切面
8、引介切面






你可能感兴趣的:(Spring)