Spring(十九):AOP代理——JdkDynamicAopProxy、填坑:AOP代理与上循环依赖

回顾

之前我们已经详细说明了Bean是如何被代理的,最终Bean会被解析成JdkDynamicAopProxy或者ObjenesisCglibAopProxy对象了,对应JDK代理和Cglib代理,下面就来仔细看看这两个对象吧

还是回到之前DefaultAopProxyFactory的createAopProxy

在这里插入图片描述
这里的config已经有了之前解析出来的增强器和拦截器了(一个总的数组集合)、还有一些Bean的信息

JdkDynamicAopProxy

先进来看看这个类
在这里插入图片描述
可以看到这个类实现了InvocationHandler,这下估计都懂怎么回事了吧。。。

先看一下它的构建方法

	public JdkDynamicAopProxy(AdvisedSupport config) throws AopConfigException {
		Assert.notNull(config, "AdvisedSupport must not be null");
        //判断增强器和拦截器的数量
		if (config.getAdvisorCount() == 0 && config.getTargetSource() == AdvisedSupport.EMPTY_TARGET_SOURCE) {
            //如果为0就抛出异常
			throw new AopConfigException("No advisors and no TargetSource specified");
		}
        //注入配置
		this.advised = config;
        //看不懂。。。
		this.proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);
		//判断有没有定义的equals方法和hashCode方法
        findDefinedEqualsAndHashCodeMethods(this.proxiedInterfaces);
	}

可以看到,构造方法只是注入了一下属性,并且判断增强器和拦截器的数量而已,比较重要的是查找有有没有定义的equals方法和hashCode方法

JdkDynamicAopProxy就是一个JDK的proxy代理类,所以我们去关注它的invoke方法

源码如下,很长,一看就很复杂

@Override
	@Nullable
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		Object oldProxy = null;
		boolean setProxyContext = false;
		//获取被增强的对象的Class
        //这里比较特殊,之前的动态代理,我们都是直接new一个进来的
        //那么此时被代理的对象都还没有被IOC容器创建完,要怎么获取实例呢?
		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.
                //如果没有重写equals方法,那就使用默认的equals方法
				return equals(args[0]);
			}
            //对hashCode方法进行同样处理
			else if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) {
				// The target does not implement the hashCode() method itself.
				return hashCode();
			}
            //如果当前之心的方法的类型是DecoraingProxy.class
            //执行下面的逻辑
			else if (method.getDeclaringClass() == DecoratingProxy.class) {
				// There is only getDecoratedClass() declared -> dispatch to proxy config.
				return AopProxyUtils.ultimateTargetClass(this.advised);
			}
            //如果执行的方法是来自于接口的,并且方法属于的类还是一个Advised.class
            //证明了这是代理对象的代理对象(即被代理对象是一个代理对象,还实现了接口)
			else if (!this.advised.opaque && method.getDeclaringClass().isInterface() &&
					method.getDeclaringClass().isAssignableFrom(Advised.class)) {
				// Service invocations on ProxyConfig with the proxy config...
                //执行invokeJoinpointUsingReflection
				return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args);
			}

			Object retVal;
			//有时候目标对象内部的自我调用将无法实施切面中的增强,则需要通过此属性暴露代理
            //也就是说,代理了一个对象,并且去执行方法,如果方法里面又调用了内部的方法是会失效的
            //这是属于JDK动态代理的问题
            //Spring通过暴露oldProxy的方法去解决这个问题
            //如果配置允许暴露代理
			if (this.advised.exposeProxy) {
				// Make invocation available if necessary.
                //通过AopContext去获取之前的代理对象
                //并且会更新现在的代理对象
                //此时oldProxy就是上一个的代理对象,暴露了代理出来
				oldProxy = AopContext.setCurrentProxy(proxy);
      			//标志暴露了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);
            
            //如果拦截器链是空的
			if (chain.isEmpty()) {
				//直接调用被代理的方法,也就是切点的方法
                Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
				retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
			}
			else {
				// We need to create a method invocation...
                //如果不是空的使用MethodInvocation来链式执行了
				MethodInvocation invocation =
						new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
				// Proceed to the joinpoint through the interceptor chain.
				//链式执行,对应proceed方法
                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;
			}
            //如果返回值为null,并且返回类型不是void类型,并且返回类型还被定义为基本数据类型
			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);
			}
            //如果暴露过proxyContext
			if (setProxyContext) {
				// Restore old proxy.
                //要进行换回来
                //因为oldProxy记录了之前旧的Proxy
                //而AopContext记录了当前的Proxy,现在要还原回去?????
				AopContext.setCurrentProxy(oldProxy);
			}
		}
	}

从这个方法我们大致可以看懂代理对象执行方法的逻辑了

  • 首先针对equals和hashCode方法做处理
  • 针对代理方法中的方法又作处理
  • 判断配置文件中有没有设置曝光代理对象的设计
    • 如果有,就曝光了当前的代理对象,并使用oldProxy去存储旧的被曝光的代理对象
  • 判断拦截器链是否为空
    • 如果为空,直接执行被代理对象的方法
    • 如果不为空,使用MethodInvocation去执行拦截器链
  • 对返回值进行处理
    • 如果方法的返回值类型不是void,且返回值为null,且返回值类型是基本数据类型,抛错
  • 返回值
  • finally语句块去将提前暴漏的代理对象还原回来,因为已经执行完了,不需要再暴漏了,还原回旧值

这里比较重要的部分是针对于曝光代理对象的设计与拦截器链的执行

曝光代理对象

下面就来先看看曝光代理对象的设计吧

对应的代码是这部分

在这里插入图片描述
为什么要曝光代理对象呢?其实这是因为解决了内部调用方法无法被代理对象给拦截掉的问题,比如有A和B方法,A方法中调用了B方法,此时代理对象对于A、B方法单个执行都能被拦截掉的,但对于A中执行的B方法却不能够被拦截,这是一个this指针的问题,这是因为之所以可以被拦截,是因为执行A方法的是代理对象使用被代理对象来执行的,而A方法中的B方法,则是被代理对象调用自己的this指针来执行的,所以不会被代理对象给拦截到

下面看看是符合曝光代理对象的

可以看到,曝光代理对象是交由AopContext去执行的

在这里插入图片描述
源码如下

	static Object setCurrentProxy(@Nullable Object proxy) {
        //获取旧的曝光的代理对象
		Object old = currentProxy.get();
        //下面就是对曝光当前的代理对象了
        //判断是否为空
		if (proxy != null) {
            //如果不为空,存放进currentProxy里面
            //这样就完成曝光了
			currentProxy.set(proxy);
		}
		else {
            //如果为空,代表要曝光个空的代理对象
            //只需移除旧的代理对象即可
			currentProxy.remove();
		}
        //返回旧的曝光的代理对象
        //并且回归上层是对应oldProxy的
		return old;
	}

可以看到,代理对象其实被存放在currentProxy里面的

在这里插入图片描述
可以看到CurrentProxt竟然是一个ThreadLocal,Emmmm,这是就明白了具体曝光的操作其实只是将当前的代理对象存放进ThreadLocal里面,猜测是后面执行拦截器链的时候会从ThreadLocal里面拿出来使用,从而让内部方法也能调用到代理对象

拦截器链的执行

对应的部分代码如下
在这里插入图片描述
可以看到,是交由MethodInvocation来进行的

下面就来看看这个proceed方法吧

在这里插入图片描述
拓展一下:这个MethodInvocation里面的实现类有三个,一个是ReflectiveMethodInvocation(对应JDK的代理),另一个是CglibMethodInvocation(对应Cglib代理),另外一个是Anonymous

在这里插入图片描述
还是回归到我们的proceed代码上,源码如下

	public Object proceed() throws Throwable {
		// We start with an index of -1 and increment early.
    	//判断是不是已经执行完拦截器链上的方法了
		if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
			//如果已经执行完,就要去执行切点里面的方法了
            return invokeJoinpoint();
		}
		//去获取当前执行到哪个拦截器了
        //说白了就是去获取下一个要执行的拦截器
		Object interceptorOrInterceptionAdvice =
				this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
        //判断当前要执行的拦截器类型
        //InterceptorAndDynamicMethhodMatch是增强器
		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)) {
				return dm.interceptor.invoke(this);
			}
            //如果动态匹配失败,递归进行proceed
            //不匹配就不执行当前的拦截器
			else {
				return proceed();
			}
		}
        //如果不是增强器,只是一般的拦截器
		else {
			// It's an interceptor, so we just invoke it: The pointcut will have
			// been evaluated statically before this object was constructed.
            //直接执行即可
			return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
		}
	}

总结一下执行的流程

  • 首先判断是不是已经执行完拦截器链上的所有拦截器了,通过currentInterceptorIndex与拦截器链的长度进行比较

    • 如果已经执行完了,就要去执行切点自身的方法了,使用invokeJoinPoint方法

    • 如果未执行完,通过currentInterceptorIndex+1从拦截器链去获取下一个拦截器

      • 接下来就判断获取的拦截器是增强器还是一般的拦截器了

        • 如果是增强器,则要进行动态匹配,如果动态匹配成功,那就执行匹配的方法,如果动态匹配失败,递归调用proceed方法,既去寻找下一个拦截器

        • 如果获取的是一般的拦截器,直接执行即可

可以看到,对于规则链的延续下去,是交由各个增强器或者拦截器去进行的,就好像之前我们认识的一个前置增强器MethodBeforeAdviceInterceptor

在这里插入图片描述
可以看到这个前置增强器执行完了自己的逻辑之后,最终调用了传进来的MethodInvocation的proceed方法,此时相当于就发生了递归了,又返回了前面我们所讲的MethodInvocation的proceed方法,又去寻找下一个增强器或者拦截器了,如果拦截器链执行完了,就执行切点的方法了

那这里说的动态匹配是怎么回事呢?匹配的是什么呢?

从注释上看,这个匹配是检查此方法是否在运行时匹配,说白了该方法如果不在运行时匹配的话,那就不需要匹配了

下面我们来看一下MethodInvocation是怎么执行切点的方法的,对应的代码为如下这部分

在这里插入图片描述
源码如下

@Nullable
protected Object invokeJoinpoint() throws Throwable {
    //可以看到,对于切点的方法调用是交由AopUtils去做的
   return AopUtils.invokeJoinpointUsingReflection(this.target, this.method, this.arguments);
}

下面看一下AopUtils的invokeJoinpointUsingReflection方法

源码如下

@Nullable
	public static Object invokeJoinpointUsingReflection(@Nullable Object target, Method method, Object[] args)
			throws Throwable {

		// Use reflection to invoke the method.
		try {
            //让方法变得可以访问
			ReflectionUtils.makeAccessible(method);
            //调用Method对象的invoke
			return method.invoke(target, args);
		}
		catch (InvocationTargetException ex) {
			// Invoked method threw a checked exception.
			// We must rethrow it. The client won't see the interceptor.
			throw ex.getTargetException();
		}
		catch (IllegalArgumentException ex) {
			throw new AopInvocationException("AOP configuration seems to be invalid: tried calling method [" +
					method + "] on target [" + target + "]", ex);
		}
		catch (IllegalAccessException ex) {
			throw new AopInvocationException("Could not access method [" + method + "]", ex);
		}
	}

可以看到,这跟我平常添加代理对象是一样的,都是通过反射使用Method的invoke方法来执行方法的

前面不是提到过提前曝光代理对象的问题吗?下面就来看下是怎么运用这个被曝光代理对象的,下面依然使用MethodBeforeAdviceInterceptor为例(前置增强)

之前我们也提到过,MethodBeforeAdviceInterceptor对应执行的方法是组合进来的MethodBeforeAdvice的,对应的是AspectJMethodBeforeAdvice去实现的

在这里插入图片描述
可以看到,这有一个invokeAdviceMethod方法,我们进去看一下该方法需要的参数

在这里插入图片描述
可以看到该方法需要一个JoinPointMatch,也就是切入点匹配,也就是说这里就需要代理对象了,所以返回去查看getJoinPointMatch的细节

在这里插入图片描述
估计看到第一句代码,不少人应该懂了,通过ExposeInvocationInterceptor去获取MethodInvocation,MethodInvocation不就是前面提到的我们去执行任务链的对象吗?

继续进去ExposeInvocationInterceptor

在这里插入图片描述
在这里插入图片描述
可以看到是从ThreadLocal里面取出来的,到这里应该都懂了吧。

代理对象通过在ThreadLocal将自身曝光,然后拦截器、增强器会先去取曝光的代理,这样就可以去让增强器、拦截器中的方法也能使用代理对象执行了,可能说的有点绕。。。。。

不够感觉又有点不太对,因为这里取出来的是一个MethodInvocation,而在曝光代理对象的时候,曝光的是Proxy,MethodInvocation是在后面创建的。。。。。。

填坑:AOP曝光与创建Bean时的曝光

经过一番思考,总结了AOP曝光的作用

  • 解决循环依赖
  • 程序可以从ThreadLocal中获取曝光的代理对象进行操作,也就是说,我们可以手动的去获取代理对象,然后执行相应拦截操作(解决了内部调用的问题),对应的方法就是使用AopUtils.currentProxy()来获取当前的AOP对象(这样就可以让内部方法从这里取出当前的代理对象了)

对于循环依赖,这里重温一下三级缓存的知识

  • 一级缓存:存放已经创建好的Bean,可以理解成是单例缓存,只有单例Bean才会进来这里,所以创建Bean时会优先寻找一级缓存
  • 二级缓存:存放正在创建的Bean,找不到一级缓存就会找二级缓存
  • 三级缓存:存放创建Bean的BeanFactory

如果单纯解决单例Bean或者原型Bean的循环依赖问题,只需要一级、二级的缓存应该就足够了,那为什么要引入三级缓存呢?

所以很有必要这里再去认识一下三级缓存干啥了

回到原先我们创建Bean等待依赖注入的时的提前曝光,坐标AbstractAutowireCapableBeanFactory的doCreateBean

在这里插入图片描述
在这里插入图片描述
当初仅仅只是看了addSigletonFactory的细节而已,仅仅只是去更新二级缓存与三级缓存,而三级缓存里面要存储的对象则来源于一个getObject方法的具体实现是getEarlyBeanReference方法的ObjectFactory(当初真没想到这个方法这么重要,淦。。。。。。)

所以ObjectFactory的getObject方法的源码如下(这里要注意,其实这里相当于是一个固定的,因为传进来的参数都是来源于当前正在创建的Bean,也就是说beanName、RootBeanDefinition、bean都是固定的,这也是Lambda的一个好处,可以使用外部的变量)

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
		Object exposedObject = bean;
		if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
			for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) {
                //调用getEarlyBeanReference方法去获取曝光的Object
				exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);
			}
		}
		return exposedObject;
	}

可以看到具体返回的对象是SmartInstantiationAwareBeanPostProcessor的getEarlyBeanReference去获取的

该方法源码如下

在这里插入图片描述
我们回到了熟悉的AbstractAutoProxyCreator里面,可以看到竟然返回的是一个wrapIfNecessary。。。。。。并且还做了一个缓存处理,将bean存进去了earlyProxyReferences里面,标识了前面提前曝光的bean被AOP代理了

可以看到这个是去创建代理对象的,这里就不再赘述了,所以我们可以知道三级缓存存取的是一个可以给Bean创建代理对象的ObjectFactory。。。。。。。

前面我们看到过,当一级缓存没有、二级缓存也没有,最终是会使用三级缓存的ObjectFactory来创建Bean的,那假如真的出现这种情况,创建出来的是一个代理Bean,与原来的RootBeanDefinition还是同一个东西吗?显然不是同一个东西,也就是说问题在于此时要返回的是代理对象呢?还是返回原来的Bean呢?

这一切的答案都在后面的initializeBean中,前面虽然讲过了这个方法,但是我们是抛开了后置处理器的,而这个后置处理器就是对关于AOP代理对象的,这里补充一下

可以看到里面有各前处理方法和一个后处理方法

在这里插入图片描述
对于AOP的处理在于后处理方法,下面来看看后处理方法的逻辑,并且可以看到,这已经是最后一步了!也就是说,已经将里面的依赖都注入好了

在这里插入图片描述
可以看到关键其实在于processor的postProcessAfterInitialization方法

并且在这个方法的实现是交由AOP的AbstractAutoProxyCreator实现的

在这里插入图片描述
逻辑也很简单,首先说明一下这个方法也是对循环依赖有共享的,如果之前某个bean已经被增强了,那么就不应该再增强了,而是直接返回bean

  • 获取该Bean的缓存key
  • 通过该缓存Key从earlyProxyReferences里获取之前曝光的需要进行代理的bean出来(那时候bean是还没有发生代理的,也就是提前曝光了代理对象),然后移除
  • 紧接着比较当前bean与取出来的bean,如果是同一个对象,那代表已经增强过一次了,直接返回bean
  • 如果不相同,代表还没有进行代理增强,调用wrapIfNecessary方法来代理增强

说白了,最重要的一点就是earlyProxyReferences容器的作用就是提前曝光那些要被代理的Bean,只要这个容器里面没有,那就代表Bean还没被代理过

接下来doCreateBean后面就是检测循环依赖了

对应的源码如下

if (earlySingletonExposure) {
    		//从缓存中取出
    		//前面提到过,从缓存中取出如果是从三级缓存取出,是会填入到二级缓存然后清空三级缓存
    		//加入出现了循环依赖,那么这里将是从二级缓存中取出
			Object earlySingletonReference = getSingleton(beanName, false);
    		//如果从缓存中取出不为null,就需要区分是来自三级缓存的、还是二级缓存的
    		//如果来自二级缓存,那就证明有出现循环依赖了,循环依赖后面会使用三级缓存来创建二级缓存,并且清空三级缓存
    		//如果来自三级缓存,那就出现后面的依赖Bean没有循环依赖
			if (earlySingletonReference != null) {
                //判断经过initializeBean方法的Bean是不是仍为同一个
                //因为initializeBean是会执行applyBeanPostProcessorsAfterInitialization方法的
                //该方法会查上面提到的earlyBeanReference去当前Bean是不是被提前曝光的需要进行代理的Bean
                //如果不需要进行判断是否进行代理,那么exposedObject就没变
                //不需要进行判断是否进行代理有两种情况
                //第一种那就是后面有循环依赖触发了三级缓存,后面循环依赖导致已经被曝光了
                //第二种则是进入了判断是否进行代理,并且当前被earlyProxyReference曝光了
                //但因为没有增强器符合,所以不返回proxy
                //具体回看上面的postProcessAfterInitialization和wrapIfNecessary
				if (exposedObject == bean) {
                    //上面的两种情况都可以让当前的exposedObject等于earlySingletonReference
                    //第一种情况,被提前曝光,那就直接让当前为之前曝光的即可(从二级缓存中取)
                    //第二种情况,没有被提前曝光,是因为没有变成代理对象(从三级缓存中取,执行wrapIfNecessary方法)
                    //得出来的结果一样的,因为你被曝光也是去执行wrapIfNecessary,最终没被代理
                    //那这里从三级缓存中取,再次执行wrapIfNecessary得到的仍然是正确相同的结果
                    //这样就解决了循环依赖问题
					exposedObject = earlySingletonReference;
				}
                //如果出现了exposedObject != bean是什么情况呢?
                //很简单,就是没有出现循环依赖的,但是被代理了,exposed相比于bean被增强了
                //还有另外一种特殊情况,出现了循环引用,但一方是原始Bean,另一方是代理对象(Async代理,后面在研究)
                //并且这里要判断,是否有依赖,还要关闭了allowRawInjectionDespiteWrapping
                //这个属性代表了是否允许bean能不能注入自己,注意这里是注入自己,并不是循环依赖问题呀
                //简单来说就是BeanA里面有个成员属性也是BeanA
				else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
                    //如果不允许注入自己,并且还有依赖,要进行检查!!!
					String[] dependentBeans = getDependentBeans(beanName);
					Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
                    //循环判断依赖
					for (String dependentBean : dependentBeans) {
                        //判断是不是所有的依赖都create完了
                        //既在alreadyCreated数组里面是否有存在
                        //
						if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
							actualDependentBeans.add(dependentBean);
						}
					}
                    //当该bean的依赖并不是全部创建好的,那就代表可能出现自己注入自己了。
					if (!actualDependentBeans.isEmpty()) {
						throw new BeanCurrentlyInCreationException(beanName,
								"Bean with name '" + beanName + "' has been injected into other beans [" +
								StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
								"] in its raw version as part of a circular reference, but has eventually been " +
								"wrapped. This means that said other beans do not use the final version of the " +
								"bean. This is often the result of over-eager type matching - consider using " +
								"'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example.");
					}
				}
			}
		}

现在,我们终于把循环依赖的拼图都集齐了,真的复杂,都看了整整一天了,现在大概弄清楚了当AOP代理的时候是如何解决循环依赖的了,也终于理清楚了为什么要有三级缓存了。。。。。。

最重要的是两个概念

  • 三级缓存配合earlyProxyReference提前曝光AOP代理
  • 创建AOP代理对象时,如果已经被提前曝光就不会创建

具体的过程还是看方法上面的注释吧,太多内容了。。。下次再整理

getProxy

在这里插入图片描述
可以看到这个方法仅仅是简单地使用Proxy.newProxyInstance来返回JDK代理对象而已

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值