SPRING AOP深刻理解

本文相关代码(来自官方源码spring-test模块)请参见spring-demysify org.springframework.mylearntest包下。

统称可以实现AOP的语言为AOL,即(Aspect-Oriented Language),其余Aspectj

  • AspectC
  • AspectC++
  • Aspect.Net
  • AspectL(Lisp)
  • AspectPHP
  • ......

JAVA中AOP实现方式java

  1. 动态代理git

    • 在运行期间,为响应的接口动态生成对应的代理对象,将横切关注点逻辑封装到动态代理的InvocationHandler中,而后在系统运行期间,根据横切关注点须要织入的模块位置,将横切逻辑织入到相应的代理类中。
  2. 动态字节码加强github

    • 使用ASM或者CGLIB等Java工具库,在程序运行期间,动态构建字节码的class文件。
    • 在运行期间经过动态字节码加强技术织入横切逻辑,为这些系统模块类生成相应的子类,而将横切逻辑加到这些子类中,让应用程序的执行期间使用的是这些动态生成的子类,从而达到将横切逻辑织入系统的目的。
    • 若是须要扩展的类以及类中的实例方法等声明为final的话,则没法对其进行子类化的扩展。Spring AOP在没法使用动态代理机制进行AOP功能的扩展的时候,会使用CGLIB库的动态字节码加强技术来实现AOP的扩展。
  3. java代码生成正则表达式

    • EJB容器根据部署描述符文件提供了织入信息,会为相应的功能模块类根据描述符所提供的信息生成对应的java代码,而后经过部署工具或者部署接口编译java代码生成对应的java类。以后部署到EJB容器的功能模块类就能够正常工做了。
  4. 自定义类加载器spring

    • 全部的java程序的class都要经过相应的类加载器(Classloader)架子啊到Java虚拟机以后才能够运行。默认的类加载器会读取class字节码文件,而后按照class字节码规范,解析并加载这些class文件到虚拟机运行。若是我梦可以在这个class加载到虚拟机运行期间,将横切逻辑织入到class文件的话,是否是就完成了AOP和OPP的融合呢?
    • 咱们能够经过自定义类加载器的方式完成横切逻辑到系统的织入,自定义类加载器经过读取外部文件规定的织入规则和必要信息,在加载class文件期间就能够将横切逻辑添加到系统模块类的现有逻辑中,而后将改动后的class交给java虚拟机运行。经过类加载器,咱们基本能够对大部分类以及相应的实例进行织入,功能于以前的几种方式相比固然强大不少。不顾哦这种方式最大的问题就是类加载器自己的使用。某些应用服务器会控制整个的类加载体系,因此,在这样的场景下会形成必定的问题。
    • Jboss AOP 和已经并入AspectJ项目的AspectWerkz框架都是采用自定义类加载器的方式实现。
  5. AOL扩展apache

    • AOL扩展是最强大、也是最难掌握的一种方式,咱们以前提到AspectJ就属于这种方式。在这种方式中,AOP的各类概念在AOL中大都有一一对应的实体。咱们可使用扩展过的AOL,实现任何AOP概念实体甚至OPP概念实体,好比一Aspect以及Class。全部的AOP概念在AOL中获得了最完美的表达。
    • 采用扩展的AOL,在AOP概念的表达上颇具实例,使得AOP涉及的全部横切关注点逻辑在进行织入以前,能够自由自在地存活在本身的“国度中”。而像“编译到静态类能够提高系统运行性能”,“java虚拟机能够像加载日常类那种,加载已经织入相应逻辑的AO组件所在的文件并运行”等特色。使用这种方式,须要学习一门扩展的AOL语言。


一些单词的含义:设计模式

  • Joinpoint 切点缓存

  • Pointcut 切点表达式:服务器

    • 直接指定Joinpoint所在的方法名称
    • 正则表达式:Jboss、Spring AOP、AspectWerkz等均支持
    • 使用特定的Pointcut表达语言:Spring 2.0之后,借助于AspectJ的Pointcut表述语言解释器,支持使用AspectJ的Pointcut表述语言来指定Pointcut。
  • Advice 切面微信

      1. Before Advice
      1. After Advice
      • After returning
      • After throwing
      • After Advice(finally)
      1. After Around
      1. Introduction
      • 在AspectJ中称Inter-Type Declaration,在JBoss AOP 中称Mix-in,都是指这同一种类型的Advice。与以前的几种Advice类型不一样,Introduction不是根据横切逻辑在Joinpoint处的执行时机来区分的,而是根据它能够完成的功能而区别于其余Advice类型。
      • AspectJ采用静态织入的形式,那么对象在使用的时候,Itroduction逻辑已是编译织入完成的。因此理论上来讲,AspectJ提供的Introduction类型的Advice,在现有Java平台上的AOP实现中是性能最好的;而像JBosss AOP或者Spring AOP等采用动态织入的AOP实现,Introduction的性能要稍逊一筹。

Aspect

Aspect是对系统中的横切关注点逻辑进行模块化封装的AOP的概念实体。一般状况下,Aspect能够包含多个Pointcut以及相关Advice定义。

设计模式之代理模式

  1. 静态代理
package org.springframework.mylearntest.aop.staticproxy;

public interface IRequestable {
	void request();
}
package org.springframework.mylearntest.aop.staticproxy;

public class RequestableImpl implements IRequestable{
	@Override
	public void request() {
		System.out.println(" request process in RequestableImpl");
	}
}
package org.springframework.mylearntest.aop.staticproxy;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class ServiceControlRequestableProxy implements IRequestable{
	private static final Logger logger = LoggerFactory.getLogger(ServiceControlRequestableProxy.class);
	private IRequestable requestable;

	public ServiceControlRequestableProxy(IRequestable target) {
		this.requestable = target;
	}

	@Override
	public void request() {
		System.out.println("request process in ServiceControlRequestableProxy");
		requestable.request();
	}

	public static void main(String[] args) {
		IRequestable target = new RequestableImpl();// 须要被代理的对象
		IRequestable proxy = new ServiceControlRequestableProxy(target); // 以构造方法形式将被代理对象传入代理者中
		proxy.request();// 让代理者去处理请求
	}
}
  1. 动态代理
  • 动态代理机制主要由一个类和一个接口组成,即java.lang.reflect.Proxy类和java.lang.reflect.InvocationHadler接口。
package org.springframework.mylearntest.aop.dynamicproxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class RequestCtrlInvocationHandler implements InvocationHandler {
	private Object target;

	public RequestCtrlInvocationHandler(Object target) {
		this.target = target;
	}

	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		System.out.println("reflect invoke before target method");
		if ("request".equals(method.getName())) {
			System.out.println("dynamic proxy target method");
			return method.invoke(target, args);
		}
		return null;
	}
}
package org.springframework.mylearntest.aop.dynamicproxy;

import org.springframework.mylearntest.aop.staticproxy.IRequestable;
import org.springframework.mylearntest.aop.staticproxy.RequestableImpl;

import java.lang.reflect.Proxy;

@SuppressWarnings("rawtypes")
public class Test4DynamicProxy {
	public static void main(String[] args) {
		// arg1:类加载器 arg2:接口信息 arg3:实现InvocationHandler的类 并传入须要代理的对象
		IRequestable requestable = (IRequestable) Proxy.newProxyInstance(
				Test4DynamicProxy.class.getClassLoader(),
				new Class[]{IRequestable.class},
				new RequestCtrlInvocationHandler(new RequestableImpl()));
		requestable.request();
	}
}

若是想深刻了解动态代理,请阅读《java reflect in action》。

  1. CGLIB字节码生成
  • 须要使用CGLIB扩展子类,首先须要实现一个net.sf.cglib.proxy.Callback,不过更多的时候,咱们会直接使用net.sf.cglib.proxy.MethodInterceptor接口(MethodInterceptor扩展了Callback接口)。
package org.springframework.mylearntest.aop.CGLIBClassGenerate;

public class Requestable {
	public void request(){
		System.out.println("req in requestable without implement any interface");
	}
}
package org.springframework.mylearntest.aop.CGLIBClassGenerate;

import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class RequestCtrlCallback implements MethodInterceptor {
	@Override
	public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
		if (method.getName().equals("request")) {
			System.out.println("proxy generated by cglib intercept method request");
			return methodProxy.invokeSuper(o, objects);
		}
		return null;
	}
}
package org.springframework.mylearntest.aop.CGLIBClassGenerate;

import org.springframework.cglib.proxy.Enhancer;

public class Test4CGLIB {
	public static void main(String[] args) {
		Enhancer enhancer = new Enhancer();
		enhancer.setSuperclass(Requestable.class);
		enhancer.setCallback(new RequestCtrlCallback());

		Requestable proxy = (Requestable) enhancer.create();
		proxy.request();
	}
}

AOP中的Pointcut

若是Pointcut类型为TruePointcut,默认会对系统中的全部对象,以及对象上全部被支持的Joinpoint进行匹配。

package org.springframework.aop;

springframework.aop.support.MethodMatchers

public interface Pointcut {

	ClassFilter getClassFilter();

	MethodMatcher getMethodMatcher();

	Pointcut TRUE = TruePointcut.INSTANCE;

}
package org.springframework.aop;

import java.io.Serializable;

@SuppressWarnings("serial")
final class TruePointcut implements Pointcut, Serializable {

	public static final TruePointcut INSTANCE = new TruePointcut();

	private TruePointcut() {
	}

	@Override
	public ClassFilter getClassFilter() {
		return ClassFilter.TRUE;
	}

	@Override
	public MethodMatcher getMethodMatcher() {
		return MethodMatcher.TRUE;
	}

	private Object readResolve() {
		return INSTANCE;
	}

	@Override
	public String toString() {
		return "Pointcut.TRUE";
	}

}

ClassFilter和MethodMatcher分别用于匹配将被执行织入操做的对象以及相应的方法。之因此将类型匹配和方法匹配分开定义,是由于能够重用不一样级别的匹配定义,而且能够在不一样级别或者相同级别上进行组合操做,或者强制让某个子类只覆盖(Override)相应方法定义等。

package org.springframework.aop;

@FunctionalInterface
public interface ClassFilter {

	boolean matches(Class<?> clazz);

	ClassFilter TRUE = TrueClassFilter.INSTANCE;

}
```java
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;

}
  • 当isRuntime返回false时,表示不会考虑具体Joinpoint的方法参数,这种类型的MethodMatcher称之为staticMethodMatcher。由于不用每次都检查参数,那么对于一样类型的方法匹配结果,就能够在框架内部缓存以提升性能。
  • 当isRuntime返回true时,代表MethodMatcher将会每次都对方法调用的参数进行匹配检查,这种类型的MethodMatcher称之为DynamicMethodMatcher。由于每次都要对方法参数进行检查,没法对匹配的结果进行缓存,因此,匹配效率相对于StaticMethodMatcher来讲要差。并且大部门状况下,staticMethodMatcher已经能够知足须要。最好避免使用DynamicMethodMatcher类型。
  • 若是boolean matches(Method method, Class targetClass);返回true时,三个参数的matches将会被执行,以进一步检查匹配条件;若是boolean matches(Method method, Class targetClass);返回false,那么无论这个MethodMatcher是staticMethodMatcher仍是DynamicMethodMatcher,该结果已是最终结果,三个参数的方法确定不会被执行了。

    常见pointcut

分述各类Pointcut

  1. NameMatchMethodPointcut
  • 最简单的Pointcut实现,属于StaticMethodMatcherPointcut的子类,能够根据自身指定一组方法名称与Joinpoint处的方法的方法名称进行匹配。
NameMatchMethodPointcut pointcut = new NameMatchMethodPointcut();
pointcut.setMappedName("matches");
// 或者传入多个方法名
pointcut.setMappedNames(new String[]{"matches", "isRuntime"});
// 简单模糊匹配
pointcut.setMappedNames(new String[]{"match*", "matches", "mat*es" });
  • 此方法没法对重载的方法名进行匹配,由于它仅对方法名进行匹配,不会考虑参数相关信息,并且也没有提供能够指定参数匹配信息的途径。
  1. JdkRegexpMethodPointcut和Perl5RegexpMethodPointcut
  • StaticMethodMatcherPointcut的子类有一个专门提供基于正则表达式的实现分支,以抽象类AbstractRegexpMethodPointcut为统帅,声明了pattern 和 patterns属性,能够指定一个或则和多个正则表达式的匹配模式。其下设JdkRegexpMethodPointcut和Perl5RegexpMethodPointcut两种具体实现。JdkRegexpMethodPointcut是在JDK 1.4以后引入JDK标准正则表达式。
JdkRegexpMethodPointcut pointcut = new JdkRegexpMethodPointcut();
pointcut.setPattern(".*match.*");
pointcut.setPatterns(new String[]{".*match.", ".*matches"});
  • 注意正则表达式匹配模式必须匹配整个方法签名(Method signature)的形式指定,而不能像NameMatchMethodPointcut那样仅给出匹配的方法名称。

  • Perl5RegexpMethodPointcut实现使用jakarta ORO提供正则表达式支持,

    1. 能够经过pattern或者patterns对象属性指定一个或者多个正则表达式
    2. 指定正则表达式匹配模式应该覆盖匹配整个方法签名,而不是只指定到方法名称部分。
  1. AnnotationMatchingPointcut
package org.springframework.mylearntest.aop.annotationmatchingpointcut;

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 org.springframework.mylearntest.aop.annotationmatchingpointcut;

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 {
}
package org.springframework.mylearntest.aop.annotationmatchingpointcut;

@ClassLevelAnnotation
public class GenericTargetObject {

	@MethodLevelAnnotation
	public void getMethod1() {
		System.out.println("getMethod1");
	}

	public void getMethod2() {
		System.out.println("getMethod2");
	}
}
AnnotationMatchingPointcut pointcut = new AnnotationMatchingPointcut(ClassLevelAnnotation.class);
	// 也能够经过静态方法
	AnnotationMatchingPointcut pointcut1 = AnnotationMatchingPointcut.forClassAnnotation(MethodLevelAnnotation.class);
	// 同时限定
	AnnotationMatchingPointcut pointcut2 = AnnotationMatchingPointcut.forClassAnnotation(ClassLevelAnnotation.class);
  1. ComposablePointcut
    Spring AOP提供Pointcut逻辑运算的Pointcut实现。它能够进行Pointcut之间的“并”以及“交”运算。
package org.springframework.mylearntest.aop.pointcut.composablePointcut;

import org.junit.Assert;
import org.springframework.aop.ClassFilter;
import org.springframework.aop.MethodMatcher;
import org.springframework.aop.Pointcut;
import org.springframework.aop.support.ComposablePointcut;
import org.springframework.aop.support.Pointcuts;

public class Test4ComposablePointcut {

	public static void main(String[] args) {
		ComposablePointcut pointcut1 = new ComposablePointcut(new ClassFilter() {
			@Override
			public boolean matches(Class<?> clazz) {
				return false;
			}
		}, MethodMatcher.TRUE);

		ComposablePointcut pointcut2 = new ComposablePointcut(new ClassFilter() {
			@Override
			public boolean matches(Class<?> clazz) {
				return false;
			}
		}, MethodMatcher.TRUE);

		// union intersection
		ComposablePointcut union = pointcut1.union(pointcut2);
		ComposablePointcut intersection = pointcut1.intersection(union);

		Assert.assertEquals(pointcut1,intersection);

		// combine classFilter with methodMatcher
		pointcut2.union(new ClassFilter() {
			@Override
			public boolean matches(Class<?> clazz) {
				return false;
			}
		}).intersection(MethodMatcher.TRUE);

		// just compute between pointcut, use org.springframework.aop.support.Pointcuts
		Pointcut pointcut3 = new Pointcut() {
			@Override
			public ClassFilter getClassFilter() {
				return null;
			}

			@Override
			public MethodMatcher getMethodMatcher() {
				return null;
			}
		};

		Pointcut pointcut4 = new Pointcut() {
			@Override
			public ClassFilter getClassFilter() {
				return null;
			}

			@Override
			public MethodMatcher getMethodMatcher() {
				return null;
			}
		};

		Pointcut union1 = Pointcuts.union(pointcut3, pointcut4);
		Pointcut intersection1 = Pointcuts.intersection(pointcut3, pointcut4);

	}
}
  1. ControlFlowPointcut
    ControlFlowPointcut匹配程序的调用流程,不是对某个方法执行所在Joinpoint处的单一特征进行匹配,而是要被特定的类执行时,才会进行方法拦截。
    由于ControlFlowPointcut类型的Pointcut 须要在运行期间检查程序的调用栈,并且每次方法调用都须要检查,因此性能比较差。

Spring Aop中的Advice

Spring 中各类Advice 和 Aop Alliance标准接口之间的关系。

  • 在Spring中,Advice按照其自身实例可否在目标对象类的全部实例中共享这一标准,能够划分为两大类,即per-calss类型的Advice 和 per-instance类型的Advice。

per-class

per-class的Advice是指,该类型的Advice的实例能够在目标对象类的全部实例之间共享。这种类型的Advice一般只是提供方法的拦截功能,不会对目标对象类保存任何状态或者添加新的特性。

  1. BeforeAdvice
package org.springframework.mylearntest.aop.advice;

import org.apache.commons.io.FileUtils;
import org.springframework.aop.MethodBeforeAdvice;
import org.springframework.core.io.Resource;

import java.lang.reflect.Method;

public class ResourceSetupBeforeAdvice implements MethodBeforeAdvice {
	private Resource resource;

	public ResourceSetupBeforeAdvice(Resource resource) {
		this.resource = resource;
	}

	@Override
	public void before(Method method, Object[] args, Object target) throws Throwable {
		if (!resource.exists()) {
			FileUtils.forceMkdir(resource.getFile());
		}
	}
}
  1. ThrowsAdvice
package org.springframework.mylearntest.aop.advice;

import org.omg.CORBA.portable.ApplicationException;
import org.springframework.aop.ThrowsAdvice;

import java.lang.reflect.Method;

public class ExceptionBarrierThrowsAdvice implements ThrowsAdvice {
	public void afterThrowing(Throwable t) {
		// 普通异常处理
	}

	public void afterThrowing(RuntimeException t) {
		// 运行时异常处理
	}

	public void afterThrowing(Method m, Object[] args, Object target, ApplicationException e) {
		// 处理应用程序生成的异常
	}
}
  1. AfterReturningAdvice
    此Advice能够访问到当前Joinpoint的方法返回值、方法、方法参数以及所在的目标对象,可是不能更改返回值,可使用Around Advice来更改返回值。

  2. Around Advice
    Spring中没有定义Around Advice ,而是直接使用AOP Alliance的标准接口,实现 MethodInterceptor便可。

per-instance

per-instance类型的Advice不会在目标类全部对象实例之间共享,而是会为不一样的实例对象保存它们各自的状态以及相关逻辑。在Spring中Introduction就是惟一的一种per-instance型Advice。

  • Introduction 能够在不改动目标类定义的状况下,为目标类添加新的属性以及行为。
  • 在Spring中,为目标对象添加新的属性和行为必须声明相应的接口以及相应的实现。这样,再经过特定的拦截器将新的接口定义以及实现类中的逻辑附加到目标对象之上。以后,目标对象就拥有了新的状态和行为。这个特定的拦截器是org.springframework.aop.IntroductionInterceptor。
  • Introduction继承了MethodInterceptor以及DynamicIntroductionAdvice,经过DynamicIntroductionAdvice,咱们能够界定当前的IntroductionInterceptor为哪些接口类提供相应的拦截功能。经过MethodInterceptor,IntroductionInterceptor就能够处理新添加的接口上的方法调用了。一般状况下,对于IntroductionInterceptor来讲,若是是新增长的接口上的方法调用,没必要去调用MethodInterceptor的proceed()方法。当前被拦截的方法其实是整个调用链中要最终执行的惟一方法。
    Introduction相关类图
DelegatingIntroductionInterceptor

DelegatingIntroductionInterceptor不会本身实现将要添加到目标对象上的新逻辑行为,而是委派给其余的实现类。

  • 使用DelegatingIntroductionInterceptor加强Developer。接口省略。
package org.springframework.mylearntest.aop.advice.perinstance.delegatingIntroductionInterceptor;

public class Developer implements IDeveloper{
	@Override
	public void developSoftware() {
		System.out.println(" do some developing ...");
	}
}
  1. 为新的状态和行为定义接口。咱们要为实现类添加加强的功能,首先须要将需求的职能以接口定义的形式声明。
package org.springframework.mylearntest.aop.advice.perinstance.delegatingIntroductionInterceptor;

public interface ITester {
	boolean isBusyAsTester();
	void testSoftware();
}
  1. 给出新的接口的实现类。接口实现类给出将要添加到目标对象的具体逻辑。当目标对象要行使新的职能的时候,会经过该实现类寻求帮忙。
package org.springframework.mylearntest.aop.advice.perinstance.delegatingIntroductionInterceptor;

public class Tester implements ITester{
	private  boolean busyAsTester;

	public void setBusyAsTester(boolean busyAsTester) {
		this.busyAsTester = busyAsTester;
	}

	@Override
	public boolean isBusyAsTester() {
		return busyAsTester;
	}

	@Override
	public void testSoftware() {
		System.out.println("do some developing and test ...");
	}
}
  1. 经过DelegatingIntroductionInterceptor进行Introduction拦截。有了新增长的职能的接口以及相应的实现类,使用DelegatingIntroductionInterceptor,咱们能够把具体的Introduction拦截委托给具体的实现类来完成。
ITester delegator = new Tester();
DelegatingIntroductionInterceptor interceptor = new DelegatingIntroductionInterceptor(delegator);
		
// 进行织入
ITester tester = (ITester)weaver.weave(developer).with(interceptor).getProxy();
tester.testSoftware();
  1. 虽然,DelegatingIntroductionInterceptor是Introduction型Advice的一个实现,但它的实现根本就有兑现Introduction做为per-instance型的承诺。实际上DelegatingIntroductionInterceptor会使用它所持有的同一个"delegate" 接口实例,供同一目标类的全部实例共享使用。若是要想严格达到Introduction型Advice的效果,咱们应该使用DelegatePerTargetObjectIntroductionInterceptor。
DelegatePerTargetObjectIntroductionInterceptor

与DelegatingIntroductionInterceptor不一样,DelegatePerTargetObjectIntroductionInterceptor会在内部持有一个目标对象与相应Introduction逻辑实现类之间的映射关系。当每一个对象上的新定义的接口方法被调用的时候,DelegatePerTargetObjectIntroductionInterceptor会拦截这些调用,而后以目标对象实例做为键,到它持有的那个映射关系中取得对应当前目标对象实例的Introduction实现实例。

DelegatePerTargetObjectIntroductionInterceptor interceptor1 =
				new DelegatePerTargetObjectIntroductionInterceptor(Tester.class,ITester.class);
  • 扩展DelegatingIntroductionInterceptor
package org.springframework.mylearntest.aop.advice.perinstance;

import org.aopalliance.intercept.MethodInvocation;
import org.apache.commons.lang3.StringUtils;
import org.springframework.aop.support.DelegatingIntroductionInterceptor;

public class TesterFeatureIntroductionInterceptor extends DelegatingIntroductionInterceptor implements ITester {

	public static final long serialVersionUID = -3387097489523045796L;
	private boolean busyAsTester;

	@Override
	public Object invoke(MethodInvocation mi) throws Throwable {
		if (isBusyAsTester() && StringUtils.contains(mi.getMethod().getName(), "developSoftware")) {
			throw new RuntimeException("I'am so tired");
		}
		return super.invoke(mi);
	}

	@Override
	public boolean isBusyAsTester() {
		return busyAsTester;
	}

	public void setBusyAsTester(boolean busyAsTester) {
		this.busyAsTester = busyAsTester;
	}

	@Override
	public void testSoftware() {
		System.out.println("I will ensure the quality");
	}
}

Spring AOP 中的Aspect

  • Advisor表明Spring中的Aspect,可是与正常的Aspect不一样,Advisor一般只持有一个Pointcut和一个Advice。而理论上,Aspect定义中能够有多个Pointcut和多个Advice,因此Advisor是一种特殊的Aspect。
PointcutAdvisor


  • 实际上,org.springframework.aop.PointcutAdvisor才是真正定义的有一个Pointcut和一个Advice的Advisor,大部分的Advisor实现所有是在PointcutAdvisor下的。
  1. DefaultPointcutAdvisor
<bean id="pointcut"
	  class="org.springframework.mylearntest.aop.pointcut.selfdefinepointcut.QueryMethodPointcut"/>
	<bean id="advice" class="org.springframework.mylearntest.aop.advice.perclass.DiscountMethodInterceptor"/>

	<bean id="advisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
		<property name="pointcut" ref="pointcut"/>
		<property name="advice" ref="advice"/>
	</bean>
  1. NameMatchMethodPointcutAdvisor
  • 此类内部持有一个NameMatchMethodPointcut类型的Pointcut实例。当经过NameMatchMethodPointcutAdvisor公开的setMappedName和setMappedNames方法设置将要被拦截的方法名的时候,实际上实在操做NameMatchMethodPointcutAdvisor内部的NameMatchMethodPointcut实例。
Advice advice = new DiscountMethodInterceptor();
NameMatchMethodPointcutAdvisor advisor = new NameMatchMethodPointcutAdvisor(advice);
advisor.setMappedName("invoke");
  1. RegexpMethodPointcutAdvisor
    只能经过正则表达式为其设置相应的Pointcut,内部持有一个AbstractRegexpMethodPointcut的实例。AbstractRegexpMethodPointcut有两个实现类,Perl5RegexpMethodPointcutAdvisor和JdkRegexpMethodPointcut。默认使用JdkRegexpMethodPointcut,若是强制使用Perl5RegexpMethodPointcutAdvisor,那么能够经过RegexpMethodPointcutAdvisor的setPerl5(boolean)实现。

  2. DefaultBeanFactoryPointcutAdvisor
    DefaultBeanFactoryPointcutAdvisor自身绑定到了BeanFactory,要使用DefaultBeanFactoryPointcutAdvisor,要绑定到Spring IoC容器。经过容器中的Advice注册的beanName来关联对应的Advice。只有当对应的Pointcut匹配成功以后,采起实例化对应的Advice,减小了容器启动初期Advisor和Advice之间的耦合性。

IntroductionAdvisor

IntroductionAdvisor只能应用于类级别的拦截,只能使用Introduction型的Advice,而不能像PointcutAdvisor那样,可使用任意类型的Pointcut,以及差很少任何类型的Advice。
IntroductionAdvisor类结构图

Order的做用
  • 大多数时候,会有多个关注横切点,那么,系统实现中就会有多个Advisor存在。当其中的某些Advisor的Pointcut匹配了同一个Joinpoint的时候,就会在这同一个Joinpoint处执行多个Advice的横切逻辑。一旦这几个须要在同一个Joinpoint处执行的Advice逻辑存在优先顺序依赖的话,就须要咱们来干预了。
  • Spring在处理同一Joinpoint处的多个Advisor的时候,会按照指定的顺序有优先级来执行他们。顺序号越小,优先级越高,优先级越高的,越先被执行。(默认状况下,Spring会按照它们的声明顺序来应用它们,最早声明的顺序号最小但优先级最大,其次次之)
    order图
  • 各个Advisor实现类,其实已经实现了Order接口。在使用的时候咱们能够直接指定便可
<bean id="permissionAuthAdvisor" class="...PermissionAuthAdvisor">
	<property name="order" value="1">
	...
<bean>

<bean id="exceptionBarrierAdvisor" class="...ExceptionBarrierAdvisor">
	<property name="order" value="0">
	...
<bean>

Spring AOP的织入

AspectJ采用ajc编译器做为它的织入器;JBoss AOP使用自定义的ClassLoader做为它的织入器;而在Spring AOP中,使用类org.springframework.aop.framework.ProxyFactory做为织入器。

  • 使用方法
    1. 传入须要织入的对象
      ProxyFactory weaver = new ProxyFactory(target);
    2. 将要应用到目标对象的Advisor绑定到织入器上
      • 若是不是Introduction的Advice类型,Proxy内部就会为这些Advice构造相应的Advisor,只不过在为它们构造Advisor中使用的Pointcut为Pointcut.TRUE。
      • 若是是Introduction类型,则会根据该Introduction具体类型进行区分;若是是Introduction的子类实现,框架内部会为其构造一个DefaultIntroductionAdvisor;若是是DynamicIntroductionAdvice的子实现类,框架内部将抛出AOPConfigException异常(由于没法从DynamicIntroductionAdvice取得必要的目标对象信息)
        weaver.addAdvisor(advisor);
    3. 获取代理对象
      Object proxyObject = weaver.getProxy();
基于接口的代理
package org.springframework.mylearntest.aop.weaver;

import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.NameMatchMethodPointcutAdvisor;

import java.util.Date;

/**
 * @Author: whalefall
 * @Date: 2020/7/15 22:53
 */

@SuppressWarnings({"rawtypes", "Deprecated"})
public class Test4ProxyFactory {
	public static void main(String[] args) {
		/*// 1. 传入须要织入的对象
		ProxyFactory weaver = new ProxyFactory(new Tester());
		// weaver.setTarget(new Tester());

		// 2. 将要应用到目标对象的Advisor绑定到织入器上
		ApplicationContext context = new ClassPathXmlApplicationContext("advisor/defaultadvisor/defaultadvisor.xml");
		Advisor advisor = (Advisor) context.getBean("advisor");
		weaver.addAdvisor(advisor);

		Object proxyObject =  weaver.getProxy();
		System.out.println(proxyObject.getClass());
		// out: class org.springframework.mylearntest.aop.advice.perinstance.Tester$$EnhancerBySpringCGLIB$$8e739b5b
		*/

		// 目标类有实现接口的用法
		// 只要不将ProxyFactory的optimize和proxyTargetClass设置为true
		// 那么ProxyFactory都会按照面向接口进行代理
		MockTask task = new MockTask();
		ProxyFactory weaver = new ProxyFactory(task);
		// weaver.setInterfaces(new Class[]{ITask.class});
		NameMatchMethodPointcutAdvisor advisor = new NameMatchMethodPointcutAdvisor();
		advisor.setMappedNames("execute");
		advisor.setAdvice(new PerformanceMethodInterceptor());
		weaver.addAdvisor(advisor);
		ITask proxyObj = (ITask)weaver.getProxy();
		// com.sun.proxy.$Proxy0
		// System.out.println(proxyObj.getClass());
		// 只能强制转化为接口类型,不能转化为实现类类型 不然会抛出ClassCastException
		// ITask proxyObj = (MockTask)weaver.getProxy();
		proxyObj.execute(new Date());

		// 目标类没有实现接口的用法


	}
}
基于类代理
package org.springframework.mylearntest.aop.weaver.baseonclass;

import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.NameMatchMethodPointcutAdvisor;
import org.springframework.mylearntest.aop.advice.perclass.PerformanceMethodInterceptor;

/**
 * @Author: whalefall
 * @Date: 2020/7/17 23:31
 */
public class Test4CGLib {
	public static void main(String[] args) {
		ProxyFactory weaver = new ProxyFactory(new Executable());
		NameMatchMethodPointcutAdvisor advisor = new NameMatchMethodPointcutAdvisor();

		advisor.addMethodName("execute");
		advisor.setAdvice(new PerformanceMethodInterceptor());
		weaver.addAdvisor(advisor);

		Executable proxyObject = (Executable)weaver.getProxy();
		proxyObject.execute();
		// org.springframework.mylearntest.aop.weaver.baseonclass.Executable$$EnhancerBySpringCGLIB$$37e40619
		System.out.println("proxyObject class: " + proxyObject.getClass());
	}
}
  • 若是目标类没有实现任何接口,无论proxyTargetClass的属性是什么,ProxyFactoy会采用基于类的代理
  • 若是ProxyFactoy的proxyTargetClass属性值被设置为true,ProxyFactoy会采用基于类的代理
  • 若是ProxyFactoy的optimize属性被设置为true,ProxyFactory会采用基于类的代理。
Introduction的织入
  • Introduction能够为已经存在的对象类型添加新的行为,只能应用于对象级别的拦截,而不是一般Advice的方法级别的拦截,因此在Introduction的织入过程当中,不须要指定Pointcut,而只须要指定目标接口类型。
  • Spring的Introduction支持只能经过接口定义为当前对象添加新的行为。因此,咱们须要在织入的时机,指定新织入的接口类型。
package org.springframework.mylearntest.aop.weaver.introduction;

import org.springframework.aop.framework.ProxyFactory;
import org.springframework.mylearntest.aop.advice.perinstance.Developer;
import org.springframework.mylearntest.aop.advice.perinstance.IDeveloper;
import org.springframework.mylearntest.aop.advice.perinstance.ITester;
import org.springframework.mylearntest.aop.advice.perinstance.TesterFeatureIntroductionInterceptor;

/**
 * @Author: whalefall
 * @Date: 2020/7/19 0:02
 */

@SuppressWarnings("rawtypes")
public class Test4Introduction {
	public static void main(String[] args) {
		ProxyFactory weaver = new ProxyFactory(new Developer());
		weaver.setInterfaces(new Class[]{IDeveloper.class, ITester.class});
		TesterFeatureIntroductionInterceptor advice = new TesterFeatureIntroductionInterceptor();
		weaver.addAdvice(advice);
		// DefaultIntroductionAdvisor advisor = new DefaultIntroductionAdvisor(advice,advice);
		// weaver.addAdvisor(advisor);

		Object proxy = weaver.getProxy();
		((ITester)proxy).testSoftware();
		((IDeveloper)proxy).developSoftware();
		System.out.println("proxy = " + proxy);

	}
}

ProxyFactory本质

  • Spring AOP框架内使用AopProxy对使用的不用的代理实现机制进行了适度的抽象,主要有针对JDK动态代理和CGLIB两种机制的AopProxy两种实现,分别是Cglib2AopProxy和JdkDynamicAopProxy两种实现。动态代理须要经过InvocationHandler提供调用拦截,因此JdkDynamicAopProxy同时实现了InvocationHandler接口。采用抽象工厂模式,经过org.springframework.aop.framework.AopProxyFactory进行。
pulic interface AopProxyFactory {
	AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException;
}
  • AopProxyFactory根据传入的AdvisedSupport实例提供的相关信息,来决定生成什么类型的AopProxy,具体的工做由AopProxyFactory具体的实现类来完成。即org.springframework.aop.framework.DefaultAopProxyFactory。
package org.springframework.aop.framework;

import java.io.Serializable;
import java.lang.reflect.Proxy;

import org.springframework.aop.SpringProxy;

@SuppressWarnings("serial")
public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {

	@Override
	public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
		// 若是传入的AdvisedSupport实例的isOptimize或者isProxyTargetClass方法返回true,
		// 或者目标对象没有实现任何接口,则采用CGLIB生成代理对象,不然使用动态代理。
		if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
			Class<?> targetClass = config.getTargetClass();
			if (targetClass == null) {
				throw new AopConfigException("TargetSource cannot determine target class: " +
						"Either an interface or a target is required for proxy creation.");
			}
			if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
				return new JdkDynamicAopProxy(config);
			}
			return new ObjenesisCglibAopProxy(config);
		}
		else {
			return new JdkDynamicAopProxy(config);
		}
	}

	private boolean hasNoUserSuppliedProxyInterfaces(AdvisedSupport config) {
		Class<?>[] ifcs = config.getProxiedInterfaces();
		return (ifcs.length == 0 || (ifcs.length == 1 && SpringProxy.class.isAssignableFrom(ifcs[0])));
	}

}

  • AdvisedSupport是一个生成代理对象所须要的信息的载体。一类为org.springframework.aop.framework.ProxyConfig为首的,记载生成代理对象的控制信息;一类以org.springframework.aop.framework.Advised为首,承载生成代理对象的所须要的必要信息,好比相关目标类、Advice、Advisor等。

  • ProxyConfig就是普通的JavaBean,定义了五个boolean型的属性,分别控制在生成代理代理对象的时候,应该采起哪些措施。

    1. ProxyTargetClass:若是这个属性设置如true,则ProxyFactory将会使用CGLIB对目标对象进行代理。默认值为false。
    2. optimize:该属性主要用于告知代理对象是否须要采起进一步的优化措施。若是代理对象生成以后,即便为其添加或者移除了相应的人Advice,代理对象也能够忽略这种变更。若是这个属性设置如true,则ProxyFactory将会使用CGLIB对目标对象进行代理。默认值为false。
    3. opaque:该属性用于控制生成的代理对象是否能够强制转化为Advised,默认值为false,表示任何生成的代理对象均可以强制转型为Advised,咱们能够经过Advised查询代理对象的一些状态。
    4. exposeProxy:设置exposeProxy,可让Spring AOP框架在生成代理对象时,将当前代理对象绑定到ThreadLocal。若是目标对象须要访问当前代理对象,能够经过AopContext.currentProxy()拿到代理对象。出于性能方面考虑,该属性默认为false。
    5. frozen:若是将frozen设置为true,那么一旦针对dialing对象生成的各项信息配置完成,则不允许更改。好比ProxyFactory的设置完毕,而且frozen为true,则不能对Advice进行任何变更,这样能够优化代理对象的性能,默认状况下为false。
  • 要生成代理对象,只有ProxyConfig提供的信息还不够,咱们还须要生成代理对象的一些具体信息,好比,要针对哪些目标类生成代理对象,要为代理对象加入何种横切逻辑等,这些信息能够经过org.springframework.aop.framework.Advised设置或者拆线呢。默认状况下Spring AOP框架返回的代理对象均可以强制转型为Advised,已查询代理对象的相关信息。

  • 咱们可使用Advised接口访问相应代理对象全部持有的Advisor,进行添加Advisor、一处Advisor等相关动做。即便代理对象已经生成完毕,也可对其进行操做,直接操做Advised,更多时候用于测试场景,能够帮助咱们检查生成的代理对象是否如所指望的那样。

AopProxy、AdvisedSupport、ProxyFactory之间的关系

  • ProxyFactory集AopProxy和AdvisedSupport于一身,能够经过AdvisedSupport设置生成代理对象所须要的相关信息,能够经过AopProxy生成代理对象。为了重用相关逻辑,Spring AOP框架在实现的时候,将一些公用的逻辑抽取到了org.springframework.aop.frameworkx.ProxyCreatorSuppport中,自身继承了AdvisedSupport,因此就能具备设置生成代理对象所须要的相关信息。
  • 为了简化生成不一样类型AopProxy的工做,ProxyCreatorSuppport内部持有一个AopProxyFactory实例,默认采用的是DefaultAopProxyFactory。

欢迎关注微信公众号哦~ ~

相关文章
相关标签/搜索