SPRING AOP

Posted on
2020-07-20 22:14 
WhaleFall541 
阅读(
评论(
编辑 
收藏

SpringAop

统称能够实现AOP的语言为AOL,即(Aspect-Oriented Language),其他Aspectj

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

JAVA中AOP实现方式

  1. 动态代理

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

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

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

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

    • 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);

	}
}

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

版权声明:本文为whalefall541原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/whalefall541/p/13348110.html