Spring——创建代理对象-Java Proxy方法分析(JdkDynamicAopProxy)

news/2024/7/6 6:33:33 标签: java, spring, 代理模式

Spring——创建代理对象-Java Proxy方法分析(JdkDynamicAopProxy)

开始从JdkDynamicAopProxy分析

在这里插入图片描述

上面的类图是主体的思想。

  1. 首先要创建代理对象,JDK创建代理对象的时候要传递一个InvocationHandle,JdkDynamicAopProxy自己实现了InvocationHandle。在调用invoke方法的时候,会通过AdvisedSupport调用AdvisorChainFactory获取一个由MethodIntercept组成的列表,
  2. 调用的时候,将所需要的对象封装为MethodInvocation,做调用。这个调用主要是针对上一步封装的好的MethodIntercept的集合。

带着总纲,开始代码分析

getProxy方法分析

获取代理对象,本质是通过Proxy来创建代理对象,通过Proxy来创建代理对象的时候,需要传递要实现的接口和InvocationHandler.

InvocationHandler就是当前的JdkDynamicAopProxy。

所以,下面的代码的主要功能就是确定接口,并且通过Proxy来创建代理对象。

通过AopProxyUtils.completeProxiedInterfaces(this.advised, true)来确定要实现的接口。这个方法里面通过传递进来的AdvisedSupport来确定接口,并且按照情况添加几个特殊的接口,(SpringProxy,Advised,DecoratingProxy)。AdvisedSupport里面接口的来源就是通过ProxyFactory添加进来的。

并且在findDefinedEqualsAndHashCodeMethods方法设置hashcode和equals的标志位。

问题

  1. 为啥要添加那几个特殊的接口?
  2. 为啥要单独的找到equals和hashcode的标志位。如果说采取单独处理的话?会怎么处理?为啥要单独处理。
java">	@Override
	public Object getProxy(@Nullable ClassLoader classLoader) {
		if (logger.isTraceEnabled()) {
			logger.trace("Creating JDK dynamic proxy: " + this.advised.getTargetSource());
		}
    // 确定接口,获取Advised里面的配置信息,并且对于不同的情况添加SpringProxy和Advised和DecoratingProxy
		Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);
    // 设置是否有equal和hashcode的方法,对应有两个标志位
		findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
     // 这就是正常的通过Proxy来创建代理对象。
		return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
	} 

invoke方法分析

在JdkDynamicAopProxy里,他自己就是InvocationHandler,所以,重点要看他的invoke方法。

方法的主要作用在一开始的时候已经交代清楚了,主要就是构建MethodIntercept chain。构建MethodInvocation来调用。

此外,在这个里面会对hashcode,equals,还有DecoratingProxy,Advised的接口的特殊的处理。

每个通过Spring创建的代理对象,都会给一个SpringProxy接口,如果opaque为false。就会给一个Advised接口,此外,如果decoratingProxy为true,就会在给一个DecoratingProxy接口。

opaque是ProxyConfig的属性,他表示是否要给代理类添加一个Advised接口,true表示不添加,false表示添加。

decoratingProxy是AopProxyUtils#completeProxiedInterfaces的参数,true表示会添加一个DecoratingProxy接口,否则就不会。通过这个接口会拿到被代理类的类对象。

问题

  1. 下面对Advised接口方法的处理,Advised是什么时候添加进来的?调用这个方法的时候具体调用的是什么?

    Advised是在构建代理对象的时候添加进来的(具体在AopProxyUtils#completeProxiedInterfaces方法里面)

    Advised接口本身的意思就是创建代理对象时候的配置信息。

    实际的调用是 调用了一开始传递给JdkDynamicAopProxy的AdvisedSupport。因为在创建代理的时候,ProxyFactory本身就是一个实现了Advised接口的类,在创建的时候,就会将自己传递给JdkDynamicAopProxy。因为创建代理对象是线程安全的,也就是说配置不是共享的,所以,对于一个代理对象来说,实现了这个接口,获取关于自己的配置信息,就直接调用AdvisedSupport的方法就好了。

java">	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		Object oldProxy = null;
		boolean setProxyContext = false;
    // 这是目标bean,TargetSource这个接口的功能是,他持有一个被代理的Bean
		TargetSource targetSource = this.advised.targetSource;
		Object target = null;

		try {
      // 如果接口方法里面没有equals,但是他调用的确实equals方法,
			if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) {
				// The target does not implement the equals(Object) method itself.
				return equals(args[0]);
			}
      // 如果接口里面没有hashcode,但是他调用的确实hashcode方法
			else if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) {
				// The target does not implement the hashCode() method itself.
				return hashCode();
			}
      // 如果是调用的方法是DecoratingProxy
			else if (method.getDeclaringClass() == DecoratingProxy.class) {
				// There is only getDecoratedClass() declared -> dispatch to proxy config.
				return AopProxyUtils.ultimateTargetClass(this.advised);
			}
 
      // 调用的方法如果是Advised接口里面,因为Advised是在创建代理对象的时候调用的。
      // 其实这里调用的是AdvisedSuooper的方法。因为他也实现了这个方法
			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) {
        // 将当前的代理对象设置在ThreadLocal里面
				oldProxy = AopContext.setCurrentProxy(proxy);
				setProxyContext = true;
			}

			 // 拿到被代理的类
			target = targetSource.getTarget();
			Class<?> targetClass = (target != null ? target.getClass() : null);

     // 重点,拿到拦截器组成的链
			List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);

			
			if (chain.isEmpty()) {
         // 因为没有,所以这里就不创建MethodInvocation,只是直接的调用目标方法。
			
				Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
				retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse); // 如果没有找到合适的intercept,直接调用的是target(别代理的bean的方法)
			}
			else {
      // 创建MethodInvocation,开始调用
				MethodInvocation invocation =
						new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
				retVal = invocation.proceed();
			}

     // 拿到方法返回的类型
			Class<?> returnType = method.getReturnType();
      //方法返回了this对象,并且当前的代理对象是否是returnType的实例对象,并且方法还不是RawTargetAccess的,直接返回代理对象
			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 {
       // 释放targetSource里面的target
			if (target != null && !targetSource.isStatic()) {
				// Must have come from TargetSource.
				targetSource.releaseTarget(target);
			}
			if (setProxyContext) {
         // 设置为null
				AopContext.setCurrentProxy(oldProxy);
			}
		}
	}

下面的分析分为两个部分:1. 创建MethodIntercept chain,2. 构建MethodInvocation 调用。

构建MethodIntercept chain

从上面的类图可以看出,主要是通过AdvisorChainFactory来创建MethodIntercept chain。他的实现类就一个DefaultAdvisorChainFactory

代码主要流程如下:

  1. 遍历Advisor(从Advised中获取到的)

  2. 在遍历的时候判断不同的情况。

    1. PointcutAdvisor(这个处理就比较复杂)

      拿到他的切入点里面的ClassFilter做匹配。在后去joinPoint里面的MethodMatch做匹配。如果MethodMatch是IntroductionAwareMethodMatcher,就会从Advisor中判断是否有IntroductionAdvisor。

      在两者都满足的情况下,通过适配器做适配,添加到结果里面返回。

    2. IntroductionAdvisor

      只是通过ClassFilter来做匹配,这个默认值是True。通过适配器做适配,添加到结果里面返回

    3. 其他

      剩下的直接通过适配器做适配。添加到结果返回

  3. 如果适配器里面没有找到,就会报错。

问题?

  1. 适配器长什么样子?

在这里插入图片描述

适配器是要提供了两个方法。

  1. Support,这个本质是instanceof来判断
  2. getInterceptor,本质就是获取Advisor里面的Advice,然后自己包装了一下,创建对应的MethodIntercept。
  1. 为啥对于MethodMatch为IntroductionAwareMethodMatcher要做处理,并且hasMatchingIntroductions方法里面是做了什么事情?

    IntroductionAwareMethodMatcher继承了MethodMatcher,在匹配的时候考虑到了Introductions。具体的判断就要在子类里面去判断了。

    hasMatchingIntroductions里面主要配置里面的advisors是否有IntroductionAdvisor类型,并且IntroductionAdvisor里面的classFilter是否是满足的。

  2. Advised的isPreFiltered的作用什么什么?因为在做类适配的时候,有两个或关系的条件。它就是第一个。

    设置为true的时候,可以跳过之后的ClassFilter的检查,他们叫做预先过滤。如果跳过的话,其实没有太大的影响,只是跳过了ClassFilter的检查,但是如果Advice不合理,还是会报错。因为适配器里面不支持,直接接报错。

  3. mm.isRuntime()的意思是什么?

    如果是true,对已经找到的拦截器,用InterceptorAndDynamicMethodMatcher封装,在利用InvocationHandle做调用的时候,会再次判断一次,这个判断会会将参数传递过去,调用的是boolean matches(Method method, Class<?> targetClass, Object... args);做判断

java">@Override
	public List<Object> getInterceptorsAndDynamicInterceptionAdvice(
			Advised config, Method method, @Nullable Class<?> targetClass) {
     // 拿到适配器的Registry。在spring里面,用来持有对象的一般都叫做Registery
		AdvisorAdapterRegistry registry = GlobalAdvisorAdapterRegistry.getInstance();
    // 拿到所有的Advisor,添加进去的Advice会被包装为Advisor。所以,在创建代理对象的时候主要是Advisor在起作用
		Advisor[] advisors = config.getAdvisors();
    // 
		List<Object> interceptorList = new ArrayList<>(advisors.length);
		Class<?> actualClass = (targetClass != null ? targetClass : method.getDeclaringClass());
		Boolean hasIntroductions = null;

		for (Advisor advisor : advisors) {
      // 对于PointcutAdvisor的话,就需要拿到切入点,来做匹配。
			if (advisor instanceof PointcutAdvisor) {
				// Add it conditionally.
				PointcutAdvisor pointcutAdvisor = (PointcutAdvisor) advisor;
        // 拿到切入点做匹配
         // 先看class
				if (config.isPreFiltered() || pointcutAdvisor.getPointcut().getClassFilter().matches(actualClass)) {
          // 在看method的匹配器,做匹配。
					MethodMatcher mm = pointcutAdvisor.getPointcut().getMethodMatcher();
					boolean match;
           // 如果当前的Advisor的方法的匹配器是IntroductionAwareMethodMatcher。
					if (mm instanceof IntroductionAwareMethodMatcher) {
						if (hasIntroductions == null) {
              // 先判断advisors里面自己的IntroductionAdvisor
							hasIntroductions = hasMatchingIntroductions(advisors, actualClass);
						}
            // 在判断外面的
						match = ((IntroductionAwareMethodMatcher) mm).matches(method, actualClass, hasIntroductions);
					}
					else {
             // 就走正常的判断,比如AspectJ,注解等等匹配器
						match = mm.matches(method, actualClass);
					}
					if (match) {
            // 找到了,就通过适配器来做适配,
						MethodInterceptor[] interceptors = registry.getInterceptors(advisor);
						if (mm.isRuntime()) { 
						 // 如果是runtime还会在InvocationHandle里面判断一次。所以,这里又封装了一下InterceptorAndDynamicMethodMatcher。
							for (MethodInterceptor interceptor : interceptors) {
								interceptorList.add(new InterceptorAndDynamicMethodMatcher(interceptor, mm));
							}
						}
						else {
							interceptorList.addAll(Arrays.asList(interceptors));
						}
					}
				}
			}
      // 如果是IntroductionAdvisor,这个匹配不像上面的那样复杂,通过getClassFilter来做匹配。但是这个默认的匹配器返回值都是true
			else if (advisor instanceof IntroductionAdvisor) {
				IntroductionAdvisor ia = (IntroductionAdvisor) advisor;
				if (config.isPreFiltered() || ia.getClassFilter().matches(actualClass)) {
					Interceptor[] interceptors = registry.getInterceptors(advisor);
					interceptorList.addAll(Arrays.asList(interceptors));
				}
			}
			else {
        // 利用适配器做兼容。
				Interceptor[] interceptors = registry.getInterceptors(advisor);
				interceptorList.addAll(Arrays.asList(interceptors));
			}
		}

		return interceptorList;
	}

结束了,构建MethodIntercept chain结束了,下面来看看几个适配器里面的几个适配器的类

DefaultAdvisorAdapterRegistry#getInterceptors方法来看,通过ProxyFactory创建代理对象的时候,添加的Advice必须是MethodInterceptor的子类。否则就会报错。

此外,在获取MethodIntercept chain的时候还带有缓存,key是方法,v是chain。

构建MethodInvocation调用

到这里,MethodIntercept Chain就构建好了,可以构建MethodInvocation来做调用的

还有一个点忘了说了

如果MethodIntercept chain 为空,就会直接调用被代理对象的方法。在不为空的时候,才会构建MethodInvocation

还是从类图开始分析

在这里插入图片描述

根接口是JoinPoint。MethodInvocation继承与Invocation,Invocation继承与JoinPoint。ReflectiveMethodInvocation实现了ProxyMethodInvocation接口,并且ReflectiveMethodInvocation依赖ProxyMethodInvocation。同样的cglibMethodInvocation也是。

这里面重要的方法是proceed(),得在此强调接口的功能呢,接口是用来做功能的,具体的实现的细节是在实现类里面。面向接口编程,可以让主体的功能不受影响。在不同的接口里面添加不同的功能。慢慢的套,具体的功能就出来了。

比如下面我们要分析ReflectiveMethodInvocation,他实现了ProxyMethodInvocation,他在MethodInvocation的基础上,增加了对代理对象的持有。

先看ReflectiveMethodInvocation的构造方法和属性

在这里插入图片描述

通过构造方法设置属性,Proxy表示代理对象,target表示被代理的对象,method:当前调用的方法,arguments表示当前调用这个方法传递的参数,

targetClass:被代理对象的类对象,interceptorsAndDynamicMethodMatchers就是上一步组装好的MethodIntercept chain。currentInterceptorIndex:表示的是当前chain的下标。

重点还是看Proceed方法

Proceed方法分析

java">public Object proceed() throws Throwable {
    // 如果是到最后一个了。所有的MethodInterceptor会调用完了之后,才会调用真正的方法,也就是被代理的方法,
   if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
      // 才会调用真正的方法,还是通过 AopUtils.invokeJoinpointUsingReflection()不过,这一次,传递的是target
      return invokeJoinpoint();
   }
    // 拿到当前的MethodIntercept,currentInterceptorIndex默认值是-1,++为0;
   Object interceptorOrInterceptionAdvice =
         this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
  // InterceptorAndDynamicMethodMatcher,这是在上一步构建MethodInceptor chain构建的,如果method match的isRuntime方法返回true,就会构建InterceptorAndDynamicMethodMatcher,这里会再次判一次,
   if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) { 
      InterceptorAndDynamicMethodMatcher dm =
            (InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
      // 
      Class<?> targetClass = (this.targetClass != null ? this.targetClass : this.method.getDeclaringClass());
      if (dm.methodMatcher.matches(this.method, targetClass, this.arguments)) {
         // 开始调用MethodIntercept。
         return dm.interceptor.invoke(this); 
      }
      else {
         // 如果上一步是匹配,就跳过这个拦截器。
         return proceed();
      }
   }
   else {
       // 这里就调用拦截器了
      return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
   }
}

在这里插入图片描述

从类图来看,ReflectiveMethodInvocation依赖MethodInterceptor,MethodInterceptor依赖MethodInvocation。本质就是两者互相依赖,拦截器链里面所有的拦截器都调用结束了,才会调真正的方法。

下面画个图说明一下

比如,现在有两个methodIntercept,一个是MethodBeforeAdviceInterceptor,一个是ThrowsAdviceInterceptor。他俩组成了一个拦截器的链

来看看他两的重点方法
MethodBeforeAdviceInterceptor

java">@Override
public Object invoke(MethodInvocation mi) throws Throwable {
   this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis());
   return mi.proceed();
}

ThrowsAdviceInterceptor

java">@Override
public Object invoke(MethodInvocation mi) throws Throwable {
   try {
      return mi.proceed();
   }
   catch (Throwable ex) {
      Method handlerMethod = getExceptionHandler(ex);
      if (handlerMethod != null) {
         invokeHandlerMethod(mi, ex, handlerMethod);
      }
      throw ex;
   }
}

在这里插入图片描述

代码表现来看,就是这个样子,那么这个在模式在日后是可以直接仿照的,直接写起来。

问题?

  1. 这里有拦截器的顺序吗?

    没有,从ProxyFactory添加Advice一来,没看到排序的操作,也就是说,顺序就是添加时候的顺序,但是需要注意,添加的时候他提供了可以指定下标的。

可以在下面的一个例子里面跑demo看看,debug下面看的很清楚。代理对象的创建,MethodIntercept的构成,MethodInvocation调用对Method,看的很清楚。

java">
public class TestProxyFactory {
	public static void main(String[] args) {
		try {
			T t = new T();
			ProxyFactory proxyFactory = new ProxyFactory();
			proxyFactory.setTarget(t);
			Class<?>[] interfaces = t.getClass().getInterfaces();
			for (Class<?> anInterface : interfaces) {
				proxyFactory.addInterface(anInterface);
			}
			ThrowsAdviceInterceptor throwsAdviceInterceptor = new ThrowsAdviceInterceptor(new MethodException());

			proxyFactory.addAdvice(throwsAdviceInterceptor);
			proxyFactory.addAdvice(new MyMethodBeforeTest());

			Do proxy = (Do) proxyFactory.getProxy();
			String test = proxy.ddo("test");
			System.out.println(test);
		} catch (Throwable e) {
			e.printStackTrace();
		}
	}

	public static class MethodException implements ThrowsAdvice {
		public void afterThrowing(Exception ex) throws Throwable {
			System.out.println(ex + "exexex");
		}
	}

	public static class MyMethodBeforeTest implements MethodBeforeAdvice {
		@Override
		public void before(Method method, Object[] args, Object target) throws Throwable {
			System.out.println("------------------------------");
			System.out.println(method.getName());
			System.out.println(target.getClass());
			System.out.println("------------------------------");
		}
	}

}

到这里,spring中利用JDK来创建代理对象的方法分析结束了。

补充说明

  1. 添加到ProxyFactory的Advice会被包装为啥?

    有两种,DefaultIntroductionAdvisor和DefaultPointcutAdvisor,如果添加的advice实现了IntroductionInfo接口,就会获取IntroductionInfo#getInterfaces来获取需要引入的接口,添加到DefaultIntroductionAdvisor的interfaces里面。

  2. 之前说IntroductionInfo接口,可以添加接口。上面没有添加接口的操作,接口是在哪里添加的?

    通过IntroductionInfo添加接口,理论上来说,必须在创建代理对象的时候将接口确定好,所以,这个添加操作必须是在这个前面,在添加advice的时候添加的。对于实现了IntroductionInfo的接口,会构建DefaultIntroductionAdvisor,然后会在 AdvisedSupport#validateIntroductionAdvisor里面会添加到interfaces里面去。

到此,结束了。

关于博客这件事,我是把它当做我的笔记,里面有很多的内容反映了我思考的过程,因为思维有限,不免有些内容有出入,如果有问题,欢迎指出。一同探讨。谢谢。


http://www.niftyadmin.cn/n/1201661.html

相关文章

细粒度图像分类_我院细粒度图像分类研究成果被多媒体领域顶级会议ACM Multimedia 2019录用...

近日&#xff0c;大连理工大学-立命馆大学国际信息与软件学院几何计算与智能媒体技术研究所李豪杰教授课题组研究工作Weakly Supervised Fine-grained Image Classification via Correlation-guided Discriminative Learning被多媒体领域顶级学术会议ACM Multimedia录用。ACM是…

www.python123.com_马上学123

技术交流学习 加QQ微信: 631531977,公众号: mashangxue123tensorflow2.0官方教程目录导航Get started with TensorFlow 2.0Effective TensorFlow 2.0(高效的tensorflow 2.0)Migrate from TF 1 to TF 2Convert with the upgrade scriptGet started for beginners (初学者入门 Te…

cglib创建代理对象(1)

cglib创建代理对象 还是从一个的小demo开始 例子 被代理的类 public class Bean{public String sayHello(String name) {return "Bean.sayHello";}private String privateSayHello(String name){return "Bean.privateSayHello";}public String lipu1(){…

python 关键字在第几行_Python从入门到精通第007课--标识符和关键字

标识符计算机编程语言中&#xff0c;标识符是用户编程时使用的名字&#xff0c;用于给变量、常量、函数、语句块等命名&#xff0c;以建立起名称与使用之间的关系。命名规则标识符由字母、下划线和数字组成&#xff0c;且数字不能开头。严格区分大小写。不能使用关键字。思考&a…

cglib创建代理对象(2)

首先得从类图开始&#xff0c; 总体流程说明&#xff1a; 通常的使用是直接通过Enhancer来创建代理对象&#xff0c;Enhancer继承于AbstractClassGenerator&#xff0c;AbstractClassGenerator实现了ClassGenerator&#xff0c;ClassGenerator接口的作用是能够通过一个ClassVi…

对数坐标归一化_利用Matlab提取图片中曲线数据(线性修正,支持对数坐标)

0,y0)(x2,y2)(x1,y1)(xi,yi)ΔxΔy1.在坐标轴上取三点以定位坐标系。如图中红色点所示。2.在曲线上选取若干个点&#xff0c;如图中蓝色点所示。3.设定坐标轴选取点x和y的实际值。4.选取坐标系类型。5.变换。6.保存数据。7.数据后处理。在变换过程中程序首先计算(xi,yi)到(x1,y…

cglib创建代理对象(3)

MethodProxy调用分析 代码的开始&#xff0c;还是从第一篇的那个例子开始&#xff0c;代码我就不贴了。直接看MethodProxy 他是在MethodInterceptor里面的入参&#xff0c;通过它可以调用原始的方法&#xff0c;也可以调用父类的方法&#xff0c;也可以调用同一个类型的不同对…

table中加表单元素怎么验证_element-ui 解决 table 里包含表单验证的问题!

实际项目中的场景&#xff0c;需要在table里做表单的验证&#xff0c;如图效果&#xff1a;其实问题关键就在于如何给el-form-item动态绑定prop:prop"tableData. scope.$index .字段名"详见代码&#xff1a;border:data"model.tableData"style"widt…