`

Spring AOP 剖析(5)

阅读更多

在动态代理 和 CGLIB 的支持下, Spring AOP 框架的实现经过了两代。

 

从 Spring AOP 框架第一次发布,到 Spring 2.0 发布之前的 AOP 实现,是 Spring 第一代 AOP 实现。

 

Spring 2.0 发布后的 AOP 实现是第二代。

 

但是,Spring AOP 的底层实现机制一直没有变,唯一改变的,是各种 AOP 概念实现的表现形式以及 Spring AOP  的使用


方式。

 

 

Spring AOP 的底层实现机制

 

 

1. Spring AOP 中的 Joinpoint

 

Spring AOP 中,仅支持方法级别的 Joinpoint ,更确切的说,只支持方法执行 (Method Execution )类型的 Joinpoint

 

虽然 Spring AOP 仅提供方法拦截,但是实际的开发过程中,这已经可以满足 80% 的开发需求了。Spring AOP 之所以

 

如此,主要有以下几个原因。

 

a.  Spring AOP 要提供一个简单而强大的 AOP 框架,并不想因大而全使得框架本身过于臃肿。能够仅付出 20% 的

 

努力,就能够得到 80% 的回报。否则,事倍功半,并不是想看到的结果。

 

b.  对于类中属性 (Field )级别的 Joinpoint ,如果提供这个级别的拦截,那么就破坏了面向对象的封装,而且,完全

 

可以通过 setter 和 getter 方法的拦截达到同样的目的。

 

c.   如果应用需求非常特殊,完全超出了 Spring AOP 提供的那 80% 的需求支持,可以求助于现有其他 AOP 实现产品,

 

如 AspectJ。 目前看来, AspectJ 是 Java 平台对 AOP 支持最完善的产品,同时,Spring AOP 也提供了对 Aspect

 

的支持。

 

 

 

2.  Spring AOP 中的 Pointcut

 

Spring 中以接口 org.springframework.aop.Pointcut 作为其 AOP 框架中的所有 Pointcut 的最顶层抽象。

 

package org.springframework.aop;

public interface Pointcut {

	ClassFilter getClassFilter();

	MethodMatcher getMethodMatcher();

	Pointcut TRUE = TruePointcut.INSTANCE;
}

 

ClassFilter  和  MethodMatcher 分别用于匹配被执行织入操作的对象以及相应的方法。

 

 

a.  ClassFilter

 

ClassFilter 接口的作用是对 Joinpoint 所处的对象进行 Class 级别的类型匹配。

 

package org.springframework.aop;

public interface ClassFilter {

	boolean matches(Class<?> clazz);

	ClassFilter TRUE = TrueClassFilter.INSTANCE;
}

 

当织入的目标对象的 Class 类型与 Pointcut 所规定的类型相符时,matchers 方法将会返回 true,否则返回 false。

 

即意味着不会对这个类型的目标对象进行织入操作。比如,如果仅希望对系统中的 Foo 类型的对象执行织入,则可以

 

package prx.aop.proxy;

import org.springframework.aop.ClassFilter;

public class FooClassFilter implements ClassFilter{

	public boolean matches(Class<?> clazz) {
		return Foo.class.equals(clazz);
	}

}

 

如果类型对所捕捉的 Joinpoint 无所谓,那么 Pointcut 中使用的 ClassFilter 可以直接使用

 

ClassFilter TRUE = TrueClassFilter.INSTANCE 。

 

当 Pointcut 中返回的 ClassFilter 类型为该类型实例时,Pointcut 的匹配将会针对系统中所有类的实例。

 


b.  MethodMatcher

 

MethodMatcher 接口的作用是描述 Pointcut 中 Method Execution 的 Joinpoint 的集合。即对象中的方法是否匹配

 

该 Pointcut 的而需要拦截。也就是 Spring 主要支持的 方法级别的拦截 的依据。

 

package org.springframework.aop;

import java.lang.reflect.Method;


public interface MethodMatcher {

	boolean matches(Method method, Class<?> targetClass);

	boolean isRuntime();

	boolean matches(Method method, Class<?> targetClass, Object[] args);

	MethodMatcher TRUE = TrueMethodMatcher.INSTANCE;

}
 

MethodMatcher 通过重载,定义了两个 matches 方法,而这两个方法的分界线就是 isRuntime 方法。

 

两个 mathcers 方法的区别在于:

 

在进行方法拦截的时候,可以选择忽略方法执行时的传入参数,也可以每次都检查方法执行时的传入参数。

 

 

比如:现在要对 登录方法 login(String username, String passwod) 进行拦截

 

1. 只想在 login 方法之前插入计数功能,那么 login 方法的参数对于 Joinpoint 捕捉就是可以忽略的。

 

2. 在用户登录的时候对某个用户做单独处理(拒绝登录 或 给予特殊权限),那么方法的参数在匹配 Joinpoint 时必须要考虑到

 

(1).  StaticMethodMatcher

 

前一种情况下, isRuntime 返回 false , 表示不会考虑具体 Joinpoint 的方法参数, 这种类型的 MethodMatcher

 

称之为 StaticMethodMatcher。因为不用每次都检查参数,那么对于同样的类型的方法匹配结果,就可以在框架内部

 

缓存以提高性能。isRuntime 方法返回 false 表明当前的 MethodMatcher 为 StaticMethodMatcher 的时候, 只有

 

boolean matches(Method method, Class<?> targetClass);

 方法将被执行, 它的匹配结果将会成为其所属的 Pointcut 主要依据。

 

package org.springframework.aop.support;

import java.lang.reflect.Method;

import org.springframework.aop.MethodMatcher;

public abstract class StaticMethodMatcher implements MethodMatcher {

	public final boolean isRuntime() {
		return false;
	}

	public final boolean matches(Method method, Class<?> targetClass, Object[] args) {
		// should never be invoked because isRuntime() returns false
		throw new UnsupportedOperationException("Illegal MethodMatcher usage");
	}

}
 

(2). DynamicMethodMatcher

 

当 isRuntime 方法返回 true 时, 表明 MethodMatcher 将会每次都对方法调用的参数进行匹配检查,这种类型的

 

MethodMatcher 称之为 DynamicMethodMatcher。 因为每次都要对方法参数进行检查,无法对匹配结果进行缓存,

 

所以,匹配效率相对 StatisMethodMatcher 来说要差。

 

大部分情况下, StaticMethodMatcher 已经够用了,最好避免使用 DynamicMethodMatcher 类型。

 

如果一个 MethodMatcher 为 DynamicMethodMatcher , 那么只有 isRuntime 返回 true, 而且

 

matchers(Method method, Class targetClass) 也返回 true 的时候, 三个参数的 matchers 方法将被执行,进行

 

进一步检查匹配条件。否则不会执行 三个参数的 matchers 方法,直接返回 false 了。

 

package org.springframework.aop.support;

import java.lang.reflect.Method;

import org.springframework.aop.MethodMatcher;

public abstract class DynamicMethodMatcher implements MethodMatcher {

	public final boolean isRuntime() {
		return true;
	}

	/**
	 * Can override to add preconditions for dynamic matching. This implementation
	 * always returns true.
	 */
	public boolean matches(Method method, Class<?> targetClass) {
		return true;
	}

}
 

在 MethodMatcher 类型的基础上, Pointcut 可以分为两类, 即 StaticMethodMatcherPointcut 和 DynamicMethodMatcherPointcut。

 

因为 StaticMethodMatcherPointcut 具有明显的性能优势, 所以, Spring 为其提供了更多的支持。

 

Spring 中 Pointcut  局部类结构图

 


 

部分资料说明  AbstractRegexpMethodPointcut 还有个 子类  Perl5RegexpMethodPointcut,但是我在

 

Spring 3.0.5 包中没有看到此类。

 

从上图中看出 Spring 提供了集中常见的 Pointcut 实现 (浅红色的类图 )。


1.  NameMatchMethodPointcut

 

最简单的 Pointcut 实现,根据自身指定的一组方法名称与 Joinpoint 处的方法的名称进行匹配,支持“*”通配符实现简单

 

的模糊匹配。

 

NameMatchMethodPointcut pointcut = new NameMatchMethodPointcut();

pointcut.setMappedName("methodName");

pointcut.setMappedNames(new String[]{"methodName1", "methodName2"});

pointcut.setMappedNames(new String[]{"method*", "*Name", "method*Num"});

 

但是, NameMatchMethodPointcut 无法对重载的方法名进行匹配, 因为它仅对方法名匹配,不考虑参数信息。

 


2.  JdkRegexpMethodPointcut

 

StaticMethodMatcherPointcut 中正则表达式的分支实现,基于 JDK1.4 之后引入的 JDK 标准正则表达式。

 

JdkRegexpMethodPointcut pointcut = new JdkRegexpMethodPointcut();

pointcut.setPattern(".*method.*");

pointcut.setPatterns(new String[]{".*method.*", ".*name.*"});

 

注意:使用正则表达式来匹配对应的 Joinpoint 所处的方法时, 正则表达式的匹配模式必须以匹配整个方法签名的形式


指定,而不能像 NameMatchMethodPointcut 那样仅给出匹配的方法名称。

 

 

package prx.aop.proxy;

public class Foo {
	public void doSomething() {
		
	}
}

 

如果使用正则表达式  .*doS.* 则会匹配 Foo 的 doSomething  方法, 即完整签名:

 

prx.aop.proxy.Foo.doSomething  。 但是如果 Pointcut 使用 doS.* 作为匹配的正则表达式模式,就无法捕捉到

 

Foo 的 doSomething 方法的执行。

 


3.  AnnotationMatchingPointcut

 

根据目标对象中是否存在指定类型的注解来匹配 Joinpoint , 只能使用在 JDK5 或更高版本中, 因为注解是 JDK5

 

发布后才有的。

 

AnnotationMatchingPointcut 根据目标对象中是否存在指定类型的注解来匹配 Joinpoint ,要使用该类型的 Pointcut

 

首先需要声明相应的注解。

 

例如:

 

package prx.aop.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ClassLevelAnnotation {

}

 

package prx.aop.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MethodLevelAnnotation {

}

 

注解定义中的 @Target 指定注解可以标注的类型。 ClassLevelAnnotation用于类层次,MethodLevelAnnotation

 

用于方法层次。

 

不同的 AnnotationMatchingPointcut 的定义方式会产生不同的匹配行为。

 

//仅指定类级别的注解, 标注了 ClassLevelAnnotation 注解的类中的所有方法执行的时候,将全部匹配。
AnnotationMatchingPointcut pointcut = new AnnotationMatchingPointcut(ClassLevelAnnotation.class);

//还可以使用静态方法创建 pointcut 实例
AnnotationMatchingPointcut pointcut = AnnotationMatchingPointcut.forClassAnnotation(ClassLevelAnnotation.class);


//仅指定方法级别的注解,标注了 MethodLeavelAnnotaion 注解的方法(忽略类匹配)都将匹配
AnnotationMatchingPointcut pointcut = AnnotationMatchingPointcut.forMethodAnnotation(MethodLevelAnnotation.class);


//同时限定类级别和方法级别的注解,只有标注了 ClassLevelAnnotation 的类中 同时标注了 MethodLevelAnnotation 的方法才会匹配
AnnotationMatchingPointcut pointcut = new AnnotationMatchingPointcut(ClassLevelAnnotation.class, MethodLevelAnnotation.class);

 

 讲了这么多,比较空泛了, 来个简单的实际例子看下: (注解的定义延用上面的代码)

 

package prx.aop.annotation;

@ClassLevelAnnotation
public class TargetObject {

	@MethodLevelAnnotation
	public void method1() {
		System.out.println("target : method1");
	}
	
	public void method2() {
		System.out.println("target : method2");
	}
}
package prx.aop.annotation;

import java.lang.reflect.Method;

import org.springframework.aop.BeforeAdvice;
import org.springframework.aop.MethodBeforeAdvice;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.aop.support.annotation.AnnotationMatchingPointcut;

public class Client {

	public static void main(String[] args) {	
		//pointcut 定义, 匹配方式可以按上面的说明修改,  这里是注解类的所有方法都匹配
		AnnotationMatchingPointcut pointcut = AnnotationMatchingPointcut.forClassAnnotation(ClassLevelAnnotation.class);
		
		// advice 定义, 根据前面的介绍知道 这个是 横切逻辑的定义, 这里是 方法执行前插入横切逻辑
		BeforeAdvice advice = new MethodBeforeAdvice() {
			public void before(Method method, Object[] args, Object target) throws Throwable {
				System.out.println(target.getClass().getSimpleName() + ":" + method.getName() + " - before logic ");
			}	
		};
		
		// Spring 中的 Aspect , pointcut 和 advice 的封装类
		DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor();
		advisor.setPointcut(pointcut);
		advisor.setAdvice(advice);
		
		// Spring 基本织入器 weaving 和 weaver
		ProxyFactory weaver = new ProxyFactory();
		weaver.setTarget(new TargetObject());	//指定代理目标对象
		weaver.addAdvisor(advisor);				//指定 Aspect
		
		Object proxyObject = weaver.getProxy();	//生成代理对象 (这里没接口, Spring 使用 CGLIB 创建子类)
		
		((TargetObject) proxyObject).method1();
		((TargetObject) proxyObject).method2();
	}
}
 

 


4.   ComposablePointcut

 

ComposablePointcut 是 Spring AOP 提供的可以进行 Pointcut 逻辑运算的 Pointcut 实现, 它可以进行 Pointcut

 

之间的 “并” 以及 “交” 运算。

 

ComposablePointcut pointcut1 = new ComposablePointcut(classFilter1, methodMatcher1);
ComposablePointcut pointcut2 = new ComposablePointcut(classFilter2, methodMatcher2);

//求并集
ComposablePointcut unitedPointcut 		= pointcut1.union(pointcut2);
//求交集
ComposablePointcut intersectionPointcut	= pointcut1.intersection(unitedPointcut);

assertEquals(pointcut1, intersectionPointcut);

 

同时, Spring AOP 还提供了 工具类: org.springframework.aop.support.Pointcuts

 

Pointcut pointcut1 = ...;
Pointcut pointcut2 = ...;

//求并集
Pointcut unitedPointcut = Pointcuts.union(pointcut1, pointcut2);
//求交集
Pointcut intersectionPointcut = Pointcuts.intersection(pointcut1, unitedPointcut);

assertEquals(pointcut1, intersectionPointcut);
 


5.   ControlFlowPointcut

 

非常有个性的 Pointcut 类型, 不是很常用。 指定只有当 Joinpoint 指定的某个方法 在 某个特定的 类中被调用时,才

 

对其进行拦截。而一般情况是,Joinpoint  指定的方法,无论被谁调用,都会被拦截。

 

package prx.aop.controlflow;

public class TargetObject {

	public void method1() {
		System.out.println("TargetObject : method1");
	}
}

 

package prx.aop.controlflow;

public class TargetCaller {
	
	private TargetObject target;
	
	public void callMethod() {
		target.method1();
	}
	
	public void setTarget(TargetObject target) {
		this.target = target;
	}
}

 

package prx.aop.controlflow;

import java.lang.reflect.Method;

import org.springframework.aop.BeforeAdvice;
import org.springframework.aop.MethodBeforeAdvice;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.ControlFlowPointcut;
import org.springframework.aop.support.DefaultPointcutAdvisor;

public class Client {

	public static void main(String[] args) {	
		//该 Pointcut 表示 Joinpoint 指定的方法 只有在 TargetCaller 类中被调用,才能拦截织入横切逻辑
		ControlFlowPointcut pointcut = new ControlFlowPointcut(TargetCaller.class);
		
		// advice 定义, 根据前面的介绍知道 这个是 横切逻辑的定义, 这里是 方法执行前插入横切逻辑
		BeforeAdvice advice = new MethodBeforeAdvice() {
			public void before(Method method, Object[] args, Object target) throws Throwable {
				System.out.println(target.getClass().getSimpleName() + ":" + method.getName() + " - before logic ");
			}	
		};
		
		// Spring 中的 Aspect , pointcut 和 advice 的封装类
		DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor();
		advisor.setPointcut(pointcut);
		advisor.setAdvice(advice);
		
		// Spring 基本织入器 weaving 和 weaver
		ProxyFactory weaver = new ProxyFactory();
		weaver.setTarget(new TargetObject());	//指定代理目标对象
		weaver.addAdvisor(advisor);				//指定 Aspect
		
		Object proxyObject = weaver.getProxy();	//生成代理对象 (这里没接口, Spring 使用 CGLIB 创建子类)
	
		//直接调用 method1 不会触发横切逻辑执行
		((TargetObject) proxyObject).method1();

		System.out.println("-----------------");
		
		//advice的逻辑在这里才能被触发执行
		//因为 TargetCaller 的 callMethod() 将调用 method1
		TargetCaller caller = new TargetCaller();
		caller.setTarget((TargetObject)proxyObject);
		caller.callMethod();

	}
}

 

如果在 ControlFlowPointcut 的构造方法中单独指定 Class 类型的参数,如上面的例子,那么 ControlFlowPointcut

 

将尝试匹配指定的 Class 中声明的所有方法,跟目标对象的 Joinpoint 处的方法流程组合。 所以,如果是想要做到

 

“只有 TargetCaller 类的 callMethod 方法调用 TargetObject.method1() 才拦截,而 TargetCaller 的其他方法

 

全都忽略” 的话,可以在构造时,传入第二个参数

 

ControlFlowPointcut pointcut = new ControlFlowPointcut(TargetCaller.class, "callMethod");

 

因为 ControlFlowPointcut 类型的 Pointcut 需要在运行期间检查程序的调用栈,而且每次方法调用都需要检查,所以


性能比较差,应该尽量避免使用。

 

 

太长了。。。下回待续。O(∩_∩)O~

  • 大小: 28.4 KB
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics