欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

spring aop: ProxyFactory

程序员文章站 2022-04-02 16:25:19
...

简介

    在之前的文章里我们讨论过AOP技术的应用基础。在常用的几种方法里,我们比对了decorator pattern,InvocationHandler, CGLib和AspectJ。这些方法都是为了在现有设计和代码的基础上去增加新的功能而不需要对原有的部分做什么改动。但是从前面技术应用的实例来看,光有这些东西直接应用到实现里还是显得不太友好。于是spring AOP框架里就提供了一些相关的脚手架来方便我们使用它们。那么,我们该怎么使用这些基础代码呢?这些具体的基础代码里又是怎么实现的以达到我们使用方便的目的呢?这里针对常用的几个部分进行讨论。

 

几个基本概念

    刚看spring aop的时候,很多人会被里面提到的几个概念给弄迷糊。比如JointPoint, Advice, Pointcut, Aspect, Weaving, Target, Introduction。这些东西看起来比较含糊又有点不好理解。其实,抛开这些概念的表面,我们从一个创造者的角度来看他们就相对来说简单一些了。

    我们首先来看AOP的基本思想,它无非就是想在程序执行的某些点中间增加一些可以定义的逻辑。那么现在就有这么几个点需要考虑。一个是怎么定义程序中执行的点?是某个方法吗?应该怎么来描述这个程序中具体的点呢?另外一个,我们希望在特定的点增加什么样的逻辑呢?这些逻辑该定义在哪里?我们要添加的逻辑和程序执行的点应该耦合在一起吗?如果应该是松耦合的,那么该用什么办法把他们给结合在一起呢?还有一个就是,我们添加的逻辑和程序执行点之间的执行关系,我们是希望在这个点之前执行?还是之后?所有这些都需要通过某种方式用机器可以理解执行的方式来描述出来。

    在进一步澄清这些具体的概念之前,我们先看一个示例:

    假设我们定义有如下的类Agent:

package com.yunzero;

public class Agent {
	public void speak() {
		System.out.println("Bond");
	}
}

    类Agent很简单,就定义了一个speak方法。里面输出一个简单的字符串。

    接着,我们定义一个AgentDecorator类,它相当于是对Agent类的增强:

 

package com.yunzero;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

public class AgentDecorator implements MethodInterceptor {
	@Override
	public Object invoke(MethodInvocation invocation) throws Throwable {
		System.out.println("James ");
		
		Object retVal = invocation.proceed();
		
		System.out.println("!");
		return retVal;
	}
}

    这个类实现了一个特定的接口MethodInterceptor。在具体的实现里,我们加入了自定义的逻辑,也就是在invocation.proceed()方法的前后输出两行字符串。在上述代码里我们可以看到,我们相当于在方法的执行过程中间做的增强。而具体是对哪个方法做了增强呢?这里没有明确去指定哪个方法,而是对所有的方法都做了这个增强,我们在后续会接着讨论。

    现在,我们再看怎么把原有代码和增强部分给结合起来:

package com.yunzero;

import org.springframework.aop.framework.ProxyFactory;

public class App {
    public static void main( String[] args ) {
        Agent target = new Agent();
        
        ProxyFactory pf = new ProxyFactory();
        pf.addAdvice(new AgentDecorator());
        pf.setTarget(target);
        
        Agent proxy = (Agent) pf.getProxy();
        
        target.speak();
        System.out.println("");
        proxy.speak();
    }
}

     在实现里,我们创建了一个ProxyFactory对象,通过addAdvice将前面定义的AgentDecorator加入进去。然后通过setTarget方法将原有的Agent对象加入到其中。最后,通过getProxy方法返回一个同样Agent类型的对象,但是它却不一样了。在执行上述代码之后,会有如下的输出:

Bond

James 
Bond
!

    从上述代码的实现过程我们可以看到。我们通过程序的方式定义好原有的对象,这个对象有自己的实现和功能。像Agent对象,这就是前面提到的Target。所以Target无非就是一个我们需要去增强功能的目标对象。

   而上述我们实现的AgentDecorator则是增强的逻辑,它本身是对所有方法的实现做的一个增强。相当于前面提到的Advice。我们可以尝试修改一下原有的代码,比如说增加一个如下的类:

package com.yunzero;

public class Chatter {
	public void chat() {
		System.out.println("Knock knock");
	}
}

   然后在后面的组合代码里改成如下:

package com.yunzero;

import org.springframework.aop.framework.ProxyFactory;

public class App {
    public static void main( String[] args ) {
        Chatter target = new Chatter();
        
        ProxyFactory pf = new ProxyFactory();
        pf.addAdvice(new AgentDecorator());
        pf.setTarget(target);
        Chatter proxy = (Chatter) pf.getProxy();
        target.chat();
        System.out.println("");
        proxy.chat();
    }
}

    我们仅仅是把原有的Agent对象换成Chatter对象,它对应的输出则变成:

 

Knock knock

James 
Knock knock
!

    可见这里增强业务的逻辑和原有的业务逻辑是松耦合的,他们可以*组合。我们在程序里切入的点,在前面Agent对象里是speak方法,而在Chatter对象里是chat方法。它就是我们程序的切入点,也就是之前概念里提到的Pointcut。当然,这里似乎没有显式的指定Pointcut。

     综合前面的代码,我们其实只是定义了原有的逻辑和增强逻辑,然后通过ProxyFactory对象将他们组合在一起。可见,具体实现逻辑的组合的细节就隐藏在ProxyFactory里了。我们先针对这几个基本的概念进一步讨论一下,后续再来探索一下ProxyFactory的实现细节。

 

Advice

     在前面的讨论里,我们已经知道,advice是在某个切入点执行的代码。在示例代码里也显示了通过实现MethodInterceptor接口的一种Advice。在实际的框架里,根据我们的需要有很多种Advice。它们详情如下面的列表:

 

Advice Name Interface 描述
Before org.springframework.aop.MethodBeforeAdvice 在切入点之前执行自定义的逻辑。在Advice中出现异常的时候会后续的执行将会被跳过。
 After-Returning  org.springframework.aop.AfterReturningAdvice  
 After(finally) org.springframework.aop.AfterAdvice 这个Advice会在被切入的方法执行失败或者抛异常的情况下也执行。
Around org.aopalliance.intercept.MethodInterceptor  这是通过AOP Alliance标准的实现,因为是对原有方法的一个深度定义,我们甚至可以跳过原来的实现用以替换成我们自己的实现。
 Throws org.springframework.aop.ThrowsAdvice  在被切入的方法出现异常的时候执行。 
 Introduction org.springframework.aop.IntroductionInterceptor  

 

     我们在实际的使用中可以根据需要选择实现的接口就可以了。Advice接口以及相关的各种扩展接口的类关系如下图:

  spring aop: ProxyFactory
            
    
    博客分类: javaspringaop

    从上图的结构中也可以看到,在绝大多数的情况下Advice的执行情况都给定义好了。

 

Pointcut

    在前面的讨论里有提到,spring里的切入点就是某个方法。前面的示例只是给了一个笼统的对所有方法都进行织入的做法。在具体的实现里,我们还需要根据不同的要求来定义程序的切入点。比如说,有的时候我们需要指定针对某些特定的类下面的方法作为切入点。有的时候我们需要匹配某些符合特定命名规则的方法,等等。所有这一切都表明了,我们需要有比较丰富的描述手段来支持这些需求。

     在spring里,有专门的接口Pointcut来定义这一部分的描述。像Pointcut接口的定义如下:

 

public interface Pointcut {
	ClassFilter getClassFilter();

	MethodMatcher getMethodMatcher();

	Pointcut TRUE = TruePointcut.INSTANCE;
}

     这里定义了两个方法,一个返回ClassFilter对象,一个返回MethodMatcher对象。从名字上来看,这应该是分别用于过滤类名字和方法名字的。

    ClassFilter和MethodMatcher的定义分别如下:

 

@FunctionalInterface
public interface ClassFilter {
	boolean matches(Class<?> clazz);

	ClassFilter TRUE = TrueClassFilter.INSTANCE;

}

 

public interface MethodMatcher {
	boolean matches(Method method, @Nullable Class<?> targetClass);

	boolean isRuntime();

	boolean matches(Method method, @Nullable Class<?> targetClass, Object... args);

	MethodMatcher TRUE = TrueMethodMatcher.INSTANCE;
}

     很显然,ClassFilter里的matches方法就是根据class对象来判断,如果符合给定的要求,就返回true。否则就是false。而MethodMatcher里有两个matches方法,后面一个方法多了一系列的可选参数。这个方法可以用在动态运行的时候,根据方法调用的参数值进行检查判断。而前一个方法则是根据静态的方法属性和class对象来判断。

    既然要实现查找和定位执行点就是要实现Pointcut接口,而实现这个接口就牵涉到返回ClassFilter和MethodMatcher对象。总的来说,要自己手工实现这一系列的接口还是比较麻烦的。所以spring里提供了一系列的实现。如下表:

 

实现类 描述
org.springframework.aop.support.annotation.AnnotationMatchingPointcut 通过查找在类或者方法上定义的特定annotation来定位执行点。
org.springframework.aop.aspectj.AspectJExpressionPointcut 那个AspectJ weaver来检查aspectJ的pointcut表达式。这是一种看起来有点像正则表达式的描述形式。
org.springframework.aop.support.ComposablePointcut 将多个切入点组合到一起的pointcut。本身相当于一个辅助功能。
 org.springframework.aop.support.ControlFlowPointcut  符合某个方法里面控制流的切入点。
 org.springframework.aop.support.DynamicMethodMatcherPointcut  作为构造动态切入点的基类。
 org.springframework.aop.support.JdkRegexpMethodPointcut  通过构造正则表达式的方式来查找匹配切入点。
 org.springframework.aop.support.NameMatchMethodPointcut  通过简单的名字匹配来查找切入点。
 org.springframework.aop.support.StaticMethodMatcherPointcut  作为构造静态切入点的基类。

     这些详细的Pointcut与子类的关系结构如下图:

spring aop: ProxyFactory
            
    
    博客分类: javaspringaop

 

    有了这些描述之后,我们再来看看该怎么具体应用Pointcut接口的实现。

    假设我们有两个类GoodGuitarist 和GreatGuitarist,它们都实现接口Singer:

public interface Singer {
	void sing();
}

 

public class GoodGuitarist implements Singer {

	@Override
	public void sing() {
		System.out.println("Who says I can't be free \n From all of the things that I used to be");
	}

}

 

public class GreatGuitarist implements Singer {

	@Override
	public void sing() {
		System.out.println("I shot the sheriff, \n But I did not shoot the deputy");
	}
	
}

    这部分非常简单。然后我们定义一个Advice:

 

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

public class SimpleAdvice implements MethodInterceptor {
	@Override
	public Object invoke(MethodInvocation invocation) throws Throwable {
		System.out.println(">> Invoking " + invocation.getMethod().getName());
		Object retVal = invocation.proceed();
		System.out.println(">> Done\n");
		return retVal;
	}
}

   结合前面的讨论可以看到,这是一个Around advice。它主要打印当前调用的方法名。我们再定义一个Pointcut,这次是通过继承StaticMethodMatcherPointcut:

    

import java.lang.reflect.Method;

import org.springframework.aop.ClassFilter;
import org.springframework.aop.support.StaticMethodMatcherPointcut;

public class SimpleStaticPointcut extends StaticMethodMatcherPointcut {

	@Override
	public boolean matches(Method method, Class<?> targetClass) {
		return ("sing".equals(method.getName()));
	}

	@Override
	public ClassFilter getClassFilter() {
		return cls -> (cls == GoodGuitarist.class);
	}
}

    里面的实现也很直白。找到sing方法。并且在getClassFilter里判断要求是GoodGuitarist class的类型。

    然后,我们将它们两者结合起来使用:

    

public class App {
    public static void main( String[] args ) {
        GoodGuitarist johnMeyer = new GoodGuitarist();
        GreatGuitarist ericClapton = new GreatGuitarist();
        
        Singer proxyOne;
        Singer proxyTwo;
        Pointcut pc = new SimpleStaticPointcut();
        Advice advice = new SimpleAdvice();
        Advisor advisor = new DefaultPointcutAdvisor(pc, advice);
        
        ProxyFactory pf = new ProxyFactory();
        pf.addAdvisor(advisor);
        pf.setTarget(johnMeyer);
        proxyOne = (Singer)pf.getProxy();
        
        pf = new ProxyFactory();
        pf.addAdvisor(advisor);
        pf.setTarget(ericClapton);
        proxyTwo = (Singer)pf.getProxy();
        
        proxyOne.sing();
        proxyTwo.sing();
    }
}

    这里,我们定义了一个DefaultPointcutAdvisor,然后将前面构造好的Pointcut和Advice传入其中。ProxyFactory然后调用了addAdvisor。最后执行的时候输出如下:

 

>> Invoking sing
Who says I can't be free 
 From all of the things that I used to be
>> Done

I shot the sheriff, 
 But I did not shoot the deputy

   可见,虽然都设置了target,但是只有GoodGuitarist的sing方法被加入了这些内容,而另外一个类没有。这里就引出了另外一个东西,就是我们刚才看到的Advisor。我们定义了Advice,表示要加入的内容是什么。我们也定义了Pointcut,表示在哪个地方加。而Advisor就表示它们的一个组合。就是在哪个点做什么这么个意思。这里的Advisor就相当于书面上定义的aspect。

     在spring里,为了实现支持不同的Advice和Pointcut的组合,它里面也定义了一系列的Advisor。整体的结构如下图所示:

 

spring aop: ProxyFactory
            
    
    博客分类: javaspringaop

   这些新定义的Advisor实现是为了使得前面定义Advice, Pointcut再将它们组合起来的过程更加便利一些。

 

proxyfactory

     前面讨论了半天,基本上明白了Advice, Pointcut, Advisor的含义以及应用场景。从使用的角度来说,我们无非就是定义好它们,然后通过调用ProxyFactory的对象来设置Advice, Pointcut,target或者Advisor。那么,ProxyFactory到底是怎么做的才实现了对我们定义的这些对象的组合呢?我们进一步探索一下。

    我们首先看一下ProxyFactory相关的类结构图:

spring aop: ProxyFactory
            
    
    博客分类: javaspringaop

    我们在getProxy方法的时候返回的对象就好像有了特殊的功能。而从类结构图里可以看到,前面继承的接口Adviced就有设置Advisor, TargetSource等操作。这些就为了后面的设置打下基础。既然是在getProxy方法的时候发现它的变化,我们就从这个方法跟进去看一下。

   在ProxyFactory里,getProxy方法的定义如下:

 

public Object getProxy() {
	return createAopProxy().getProxy();
}

    而createAopProxy方法的实现如下:

    

protected final synchronized AopProxy createAopProxy() {
	if (!this.active) {
		activate();
	}
	return getAopProxyFactory().createAopProxy(this);
}
   getAopProxyFactory的方法返回了一个引用的接口AopProxyFactory:
public interface AopProxyFactory {
	AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException;

}
     所以说,真正创建Proxy的方法就取决于AopProxyFactory的实现。在spring里,有DefaultAopProxyFactory类,它实现了AopProxyFactory:
@Override
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
	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);
	}
}
     到这里,我们会发现最后创建proxy的责任最后就归结到这里返回的ObjenesisCglibAopProxy或者JdkDynamicAopProxy对象上来了。总的来说,当我们前面设置好了ProxyFactory之后,最后调用它的getProxy的时候,实际上是通过这两个对象的getProxy来返回特定的对象的。

    所以,具体怎么创建proxy,我们就只要看它们两个的详细实现了。这里,我们重点看一下JdkDynamicAopProxy的实现。它定义有两个getProxy的方法:

 

@Override
public Object getProxy() {
	return getProxy(ClassUtils.getDefaultClassLoader());
}

@Override
public Object getProxy(@Nullable ClassLoader classLoader) {
	if (logger.isDebugEnabled()) {
		logger.debug("Creating JDK dynamic proxy: target source is " + this.advised.getTargetSource());
	}
	Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);
	findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
	return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
}

    其中的AopProxyUtils.completeProxiedInterfaces方法将需要代理的对象接口都拿到。而findDefinedEqualsAndHashCodeMethods则判断这些要代理的接口里有没有equals和hashCode方法,有的话则将他们都标记出来。最后通过Proxy.newProxyInstance方法来构造代理对象。

    在之前讨论InvocationHandler的时候我们已经知道,这个被构造的对象要实现这个接口才行。而这里的定义里JdkDynamicAopProxy正好也将这个接口给实现了,它的定义如下:

final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializable

 

 实现的方法细节在这里:

@Override
	@Nullable
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		MethodInvocation invocation;
		Object oldProxy = null;
		boolean setProxyContext = false;

		TargetSource targetSource = this.advised.targetSource;
		Object target = null;

		try {
			if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) {
				// The target does not implement the equals(Object) method itself.
				return equals(args[0]);
			}
			else if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) {
				// The target does not implement the hashCode() method itself.
				return hashCode();
			}
			else if (method.getDeclaringClass() == DecoratingProxy.class) {
				// There is only getDecoratedClass() declared -> dispatch to proxy config.
				return AopProxyUtils.ultimateTargetClass(this.advised);
			}
			else if (!this.advised.opaque && method.getDeclaringClass().isInterface() &&
					method.getDeclaringClass().isAssignableFrom(Advised.class)) {
				// Service invocations on ProxyConfig with the proxy config...
				return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args);
			}

			Object retVal;

			if (this.advised.exposeProxy) {
				// Make invocation available if necessary.
				oldProxy = AopContext.setCurrentProxy(proxy);
				setProxyContext = true;
			}

			// Get as late as possible to minimize the time we "own" the target,
			// in case it comes from a pool.
			target = targetSource.getTarget();
			Class<?> targetClass = (target != null ? target.getClass() : null);

			// Get the interception chain for this method.
			List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);

			// Check whether we have any advice. If we don't, we can fallback on direct
			// reflective invocation of the target, and avoid creating a MethodInvocation.
			if (chain.isEmpty()) {
				// We can skip creating a MethodInvocation: just invoke the target directly
				// Note that the final invoker must be an InvokerInterceptor so we know it does
				// nothing but a reflective operation on the target, and no hot swapping or fancy proxying.
				Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
				retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
			}
			else {
				// We need to create a method invocation...
				invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
				// Proceed to the joinpoint through the interceptor chain.
				retVal = invocation.proceed();
			}

			// Massage return value if necessary.
			Class<?> returnType = method.getReturnType();
			if (retVal != null && retVal == target &&
					returnType != Object.class && returnType.isInstance(proxy) &&
					!RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) {
				// Special case: it returned "this" and the return type of the method
				// is type-compatible. Note that we can't help if the target sets
				// a reference to itself in another returned object.
				retVal = proxy;
			}
			else if (retVal == null && returnType != Void.TYPE && returnType.isPrimitive()) {
				throw new AopInvocationException(
						"Null return value from advice does not match primitive return type for: " + method);
			}
			return retVal;
		}
		finally {
			if (target != null && !targetSource.isStatic()) {
				// Must have come from TargetSource.
				targetSource.releaseTarget(target);
			}
			if (setProxyContext) {
				// Restore old proxy.
				AopContext.setCurrentProxy(oldProxy);
			}
		}
	}

     上面的代码比较长,我们一一看过来。

     第11到第18行的代码表示如果定义了equals方法或者hashCode方法,则直接返回JdkDynamicAopProxy里面定义的equals或hashCode方法。

    第19~23行的代码,表示如果当前方法的声明类是DecoratingProxy的话,则通过proxy config来处理代理的构造。

    第24行~第27行的代码,表示方法所属的Class是一个接口并且方法所属的Class是AdvisedSupport的父类或者父接口,直接通过反射调用该方法。

     第32行~第35行的代码,是用于判断是否将代理暴露出去的,由<aop:config>标签中的expose-proxy="true/false"配置。

    第44行的代码,获取AdvisedSupport中的所有拦截器和动态拦截器列表,用于拦截方法。

    第48行~第53行的代码,如果拦截器列表为空,很正常,因为某个类/接口下的某个方法可能不满足expression的匹配规则,因此此时通过反射直接调用该方法。

    第57行~第59行的代码,如果拦截器列表不为空,按照注释的意思,需要一个ReflectiveMethodInvocation,并通过proceed方法对原方法进行拦截。proceed方法对拦截器进行递归的调用,每次通过递归的时候修改当前索引来实现遍历的效果。

    后面剩下的代码主要是处理返回值的问题。

    这里主要是讨论了JdkDynamicAopProxy的实现,而另外使用到的ObjenesisCglibAopProxy在后续的文章里会进一步分析。

 

总结

   AOP 里的几个概念看似比较拗口而且不容易理解。但是,我们从一个使用者的角度来看的话,无非就是要定位执行点,也就是方法,所以对这个执行点的描述就对应到了Pointcut。而在这个执行点做什么,是在它执行之前还是之后做自己的业务逻辑,这些就对应到了Advice。在一个灵活设计的系统里,我们希望执行点的描述和添加的业务逻辑是相互分离的。所以我们在使用的时候需要通过ProxyFactory对象来设置这些。当然,我们也可以通过Advisor的具体实现对象来组合Pointcut和Advice。这里的这个Advisor就相当于概念里的aspect。

    至于ProxyFactory它真正构造出来的Proxy对象其实是代理到两个对象JdkDynamicAopProxy和ObjenesisCglibAopProxy来做的。通过它们的getProxy方法来构造真正的代理对象。JdkDynamicAopProxy是通过InvocationHandler的实现来达到添加功能的效果。而ObjenesisCglibAopProxy是通过实现MethodInterceptor来实现同样效果的。这就正好和我们之前文章里提到的AOP技术基础契合在一起了。

 

参考资料

pro spring 5 

http://www.cnblogs.com/xrq730/p/6757608.html

  • spring aop: ProxyFactory
            
    
    博客分类: javaspringaop
  • 大小: 107.9 KB
  • spring aop: ProxyFactory
            
    
    博客分类: javaspringaop
  • 大小: 68.8 KB
  • spring aop: ProxyFactory
            
    
    博客分类: javaspringaop
  • 大小: 72.7 KB
  • spring aop: ProxyFactory
            
    
    博客分类: javaspringaop
  • 大小: 53.2 KB