前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【小家Spring】详解Spring AOP的底层代理JdkDynamicAopProxy和ObjenesisCglibAopProxy的源码分析(介绍CGLIB使用中的坑)

【小家Spring】详解Spring AOP的底层代理JdkDynamicAopProxy和ObjenesisCglibAopProxy的源码分析(介绍CGLIB使用中的坑)

作者头像
YourBatman
发布2019-09-03 16:24:09
3K1
发布2019-09-03 16:24:09
举报
前言

在前面的博文我们了解到,Spring所有的代理AopProxy的创建最后都是ProxyCreatorSupport#createAopProxy这个方法,而这个方法如下:

	protected final synchronized AopProxy createAopProxy() {
		if (!this.active) {
			activate();
		}
		return getAopProxyFactory().createAopProxy(this);
	}

显然它又是调用了AopProxyFactory#createAopProxy方法,它的唯一实现为DefaultAopProxyFactory。 它做了一个简单的逻辑判断:若实现类接口,使用JdkDynamicAopProxy最终去创建,否则交给ObjenesisCglibAopProxy

最终拿到AopProxy后,调用AopProxy#getProxy()就会拿到这个代理对象,从而进行相应的工作了。

我们基本有一共共识就是:默认情况下,若我们实现了接口,就实用JDK动态代理,若没有就实用CGLIB。那么就下来,就具体看看关乎到代理对象的创建、执行的一个具体过程原理

常识普及

AOP(Aspect Orient Programming),一般称为面向切面编程,作为面向对象的一种补充,用于处理系统中分布于各个模块的横切关注点,比如事务管理、日志、缓存等等。

AOP代理主要分为静态代理动态代理,静态代理的代表为AspectJ;而动态代理则以Spring AOP为代表。静态代理是编译期实现,动态代理是运行期实现,可想而知前者拥有更好的性能。

静态代理

静态代理是编译阶段生成AOP代理类,也就是说生成的字节码就织入了增强后的AOP对象;(并不会创建出多余的对象)

实现方式:

  • 包装器模式:持有目标对象的引用,然后实际上是调用目标对象的方法。 这种方式也可称为代理模式,但是有明显的缺点(比如一般都需要实现同一个接口,且它是以编码的方式去实现的,侵入性高)
  • AspectJ静态代理方式:非常非常强大。Aspectj并不是动态的在运行时生成代理类,而是在编译的时候就植入代码到class文件。由于是静态织入的,所以性能相对来说比较好Aspectj不受类的特殊限制,不管方法是private、或者static、或者final的,都可以代理,Aspectj不会代理除了限定方法之外任何其他诸如toString(),clone()等方法,唯一缺点就是必须有AspectJ自己的编译器的支持,所以其实很少使用 Spring也是提供了相关类支持的,比如:LoadTimeWeaverAwareProcessor

基于AspectJ的静态代理方式非常强大,但是它依赖于它自己的编译器。并且还有自己的个性化语言,使用起来不够方便,因此其实还是使用得较少的。主要还是以动态代理为主~~~

动态代理

动态代理则不会修改字节码,而是在内存中临时生成一个AOP对象,这个AOP对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法

这在我们平时使用中得到了大量的使用,因为使用简单并且还非常灵活,下面就重点介绍。

AopProxy:Aop代理接口

它是一个AOP代理的抽象接口。提供了两个方法,让我们可以获取对应 配置的AOP对象的代理:

public interface AopProxy {
	//Create a new proxy object. Uses the AopProxy's default class loader  ClassUtils.getDefaultClassLoader()
	Object getProxy();
	Object getProxy(@Nullable ClassLoader classLoader);
}

它的继承关系也很简单,就是接下来我们要说的那几个

在这里插入图片描述
在这里插入图片描述
环境构建

为了更好的做源码分析,因此此处我构建一个非常简单的Spring AOP的环境,来跟踪一下它的来龙去脉

public class Main {

    public static void main(String[] args) {
        ProxyFactory proxyFactory = new ProxyFactory(new Demo());
        proxyFactory.addAdvice((MethodBeforeAdvice) (method, args1, target) ->
                System.out.println("你被拦截了:方法名为:" + method.getName() + " 参数为--" + Arrays.asList(args1)));

        DemoInterface demo = (DemoInterface) proxyFactory.getProxy();
        //你被拦截了:方法名为:hello 参数为--[]
        //this demo show
        demo.hello();

        System.out.println(proxyFactory.getTargetClass()); //class com.fsx.maintest.Demo
        System.out.println(proxyFactory.getTargetSource()); //SingletonTargetSource for target object [com.fsx.maintest.Demo@643b1d11]
        System.out.println(Arrays.asList(proxyFactory.getProxiedInterfaces())); //[interface com.fsx.maintest.DemoInterface]
        System.out.println(Arrays.asList(proxyFactory.getAdvisors())); //[org.springframework.aop.support.DefaultPointcutAdvisor: pointcut [Pointcut.TRUE]; advice [com.fsx.maintest.Main$$Lambda$2/1349414238@2ef5e5e3]]

        // 获取类型,看看是JDK代理还是cglib的
        System.out.println(demo instanceof Proxy); //true  所有的JDK代理都是继承自Proxy的
        System.out.println(demo instanceof SpringProxy); //true
        System.out.println(demo.getClass()); //class com.fsx.maintest.$Proxy0
        System.out.println(Proxy.isProxyClass(demo.getClass())); //true
        System.out.println(AopUtils.isCglibProxy(demo)); //false

        //测试Advised接口、DecoratingProxy的内容
        Advised advised = (Advised) demo;
        System.out.println(Arrays.asList(advised.getProxiedInterfaces())); //[interface com.fsx.maintest.DemoInterface]
        System.out.println(Arrays.asList(advised.getAdvisors())); //[org.springframework.aop.support.DefaultPointcutAdvisor: pointcut [Pointcut.TRUE]; advice [com.fsx.maintest.Main$$Lambda$2/1349414238@2ef5e5e3]]
        System.out.println(advised.isExposeProxy()); //false
        System.out.println(advised.isFrozen()); //false

        //System.out.println(advised.removeAdvice(null));
        DecoratingProxy decoratingProxy = (DecoratingProxy) demo;
        System.out.println(decoratingProxy.getDecoratedClass()); //class com.fsx.maintest.Demo

        System.out.println("-----------------------------------------------------");

        // Object的方法 ==== 所有的Object方法都不会被AOP代理 这点需要注意
        System.out.println(demo.equals(new Object()));
        System.out.println(demo.hashCode());
        System.out.println(demo.getClass());

        // 其余方法都没被拦截  只有toString()被拦截了  咋回事呢?它也不符合切点表达式的要求啊  看下面的解释吧
        // 你被拦截了:方法名为:hello 参数为--[]
        // com.fsx.maintest.Demo@643b1d11
        System.out.println(demo.toString());
    }
}

interface DemoInterface {
    void hello();
}

class Demo implements DemoInterface {
    @Override
    public void hello() {
        System.out.println("this demo show");
    }
}

因为本文只讲述代理的创建,所以使用最为简单的ProxyFactory来创建代理,排除其它的干扰因素

JdkDynamicAopProxy

如上搭建的环境,生成的就是JDK的动态代理对象。这里面逻辑比较干净简单了:

  1. setTarget和setInterfaces交给ProxyCreatorSupport
  2. addAdvice:此处就是个Advice前置通知增强器。最终会被包装成DefaultPointcutAdvisor交给ProxyCreatorSupport
  3. getProxy()才是今天的关键,他内部会去new出来一个JdkDynamicAopProxy,然后调用其getProx()获取到动态代理出来的对象

那么接下来,就深入它的源码看看究竟:

// 我们发现它自己就实现了了InvocationHandler,所以处理器就是它自己。会实现invoke方法
// 它还是个final类  默认是包的访问权限
final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializable {

	private static final Log logger = LogFactory.getLog(JdkDynamicAopProxy.class);

	/** 这里就保存这个AOP代理所有的配置信息  包括所有的增强器等等 */
	private final AdvisedSupport advised;

	// 标记equals方法和hashCode方法是否定义在了接口上=====
	private boolean equalsDefined;
	private boolean hashCodeDefined;

	public JdkDynamicAopProxy(AdvisedSupport config) throws AopConfigException {
		Assert.notNull(config, "AdvisedSupport must not be null");
		// 内部再校验一次:必须有至少一个增强器  和  目标实例才行
		if (config.getAdvisors().length == 0 && config.getTargetSource() == AdvisedSupport.EMPTY_TARGET_SOURCE) {
			throw new AopConfigException("No advisors and no TargetSource specified");
		}
		this.advised = config;
	}


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

	// 真正创建JDK动态代理实例的地方
	@Override
	public Object getProxy(@Nullable ClassLoader classLoader) {
		if (logger.isDebugEnabled()) {
			logger.debug("Creating JDK dynamic proxy: target source is " + this.advised.getTargetSource());
		}
		// 这部很重要,就是去找接口 我们看到最终代理的接口就是这里返回的所有接口们(除了我们自己的接口,还有Spring默认的一些接口)  大致过程如下:
		//1、获取目标对象自己实现的接口们(最终肯定都会被代理的)
		//2、是否添加`SpringProxy`这个接口:目标对象实现对就不添加了,没实现过就添加true
		//3、是否新增`Adviced`接口,注意不是Advice通知接口。 实现过就不实现了,没实现过并且advised.isOpaque()=false就添加(默认是会添加的)
		//4、是否新增DecoratingProxy接口。传入的参数decoratingProxy为true,并且没实现过就添加(显然这里,首次进来是会添加的)
		//5、代理类的接口一共是目标对象的接口+上面三个接口SpringProxy、Advised、DecoratingProxy(SpringProxy是个标记接口而已,其余的接口都有对应的方法的)
		//DecoratingProxy 这个接口Spring4.3后才提供
		Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);
		findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
		// 第三个参数传的this,处理器就是自己嘛   到此一个代理对象就此new出来啦
		return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
	}

	// 找找看看接口里有没有自己定义equals方法和hashCode方法,这个很重要  然后标记一下
	// 注意此处用的是getDeclaredMethods,只会找自己的
	private void findDefinedEqualsAndHashCodeMethods(Class<?>[] proxiedInterfaces) {
		for (Class<?> proxiedInterface : proxiedInterfaces) {
			Method[] methods = proxiedInterface.getDeclaredMethods();
			for (Method method : methods) {
				if (AopUtils.isEqualsMethod(method)) {
					this.equalsDefined = true;
				}
				if (AopUtils.isHashCodeMethod(method)) {
					this.hashCodeDefined = true;
				}
				// 小技巧:两个都找到了 就没必要继续循环勒
				if (this.equalsDefined && this.hashCodeDefined) {
					return;
				}
			}
		}
	}

	 // 对于这部分代码和采用CGLIB的大部分逻辑都是一样的,Spring对此的解释很有意思:
	 // 本来是可以抽取出来的,使得代码看起来更优雅。但是因为此会带来10%得性能损耗,所以Spring最终采用了粘贴复制的方式各用一份
	 // Spring说它提供了基础的套件,来保证两个的执行行为是一致的。
	 //proxy:指的是我们所代理的那个真实的对象;method:指的是我们所代理的那个真实对象的某个方法的Method对象args:指的是调用那个真实对象方法的参数。

	// 此处重点分析一下此方法,这样在CGLIB的时候,就可以一带而过了~~~因为大致逻辑是一样的
	@Override
	@Nullable
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		// 它是org.aopalliance.intercept这个包下的  AOP联盟得标准接口
		MethodInvocation invocation;
		Object oldProxy = null;
		boolean setProxyContext = false;

		// 进入invoke方法后,最终操作的是targetSource对象
		// 因为InvocationHandler持久的就是targetSource,最终通过getTarget拿到目标对象
		TargetSource targetSource = this.advised.targetSource;
		Object target = null;

		try {
			//“通常情况”Spring AOP不会对equals、hashCode方法进行拦截增强,所以此处做了处理
			// equalsDefined为false(表示自己没有定义过eequals方法)  那就交给代理去比较
			// hashCode同理,只要你自己没有实现过此方法,那就交给代理吧
			// 需要注意的是:这里统一指的是,如果接口上有此方法,但是你自己并没有实现equals和hashCode方法,那就走AOP这里的实现
			// 如国接口上没有定义此方法,只是实现类里自己@Override了HashCode,那是无效的,就是普通执行吧
			if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) {
				return equals(args[0]);
			}
			else if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) {
				return hashCode();
			}


			// 下面两段做了很有意思的处理:DecoratingProxy的方法和Advised接口的方法  都是是最终调用了config,也就是this.advised去执行的~~~~
			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;

			//是否暴露代理对象,默认false可配置为true,如果暴露就意味着允许在线程内共享代理对象,
			//注意这是在线程内,也就是说同一线程的任意地方都能通过AopContext获取该代理对象,这应该算是比较高级一点的用法了。
			// 这里缓存一份代理对象在oldProxy里~~~后面有用
			if (this.advised.exposeProxy) {
				oldProxy = AopContext.setCurrentProxy(proxy);
				setProxyContext = true;
			}

			//通过目标源获取目标对象 (此处Spring建议获取目标对象靠后获取  而不是放在上面) 
			target = targetSource.getTarget();
			Class<?> targetClass = (target != null ? target.getClass() : null);

			// 获取作用在这个方法上的所有拦截器链~~~  参见DefaultAdvisorChainFactory#getInterceptorsAndDynamicInterceptionAdvice方法
			// 会根据切点表达式去匹配这个方法。因此其实每个方法都会进入这里,只是有很多方法得chain事Empty而已
			List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);

		
			if (chain.isEmpty()) {
				// 若拦截器为空,那就直接调用目标方法了
				// 对参数进行适配:主要处理一些数组类型的参数,看是表示一个参数  还是表示多个参数(可变参数最终到此都是数组类型,所以最好是需要一次适配)
				Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
				// 这句代码的意思是直接调用目标方法~~~
				retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
			}
			else {
				// 创建一个invocation ,此处为ReflectiveMethodInvocation  最终是通过它,去执行前置加强、后置加强等等逻辑
				invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
				// 此处会执行所有的拦截器链  交给AOP联盟的MethodInvocation去处理。当然实现还是我们Spring得ReflectiveMethodInvocation
				retVal = invocation.proceed();
			}

			// 获取返回值的类型
			Class<?> returnType = method.getReturnType();
			if (retVal != null && retVal == target &&
					returnType != Object.class && returnType.isInstance(proxy) &&
					!RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) {
				 // 一些列的判断条件,如果返回值不为空,且为目标对象的话,就直接将目标对象赋值给retVal
				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()) {
				targetSource.releaseTarget(target);
			}

			// 把老的代理对象重新set进去~~~
			if (setProxyContext) {
				AopContext.setCurrentProxy(oldProxy);
			}
		}
	}

	// AOP帮我们实现的CgLib方法
	@Override
	public boolean equals(@Nullable Object other) {
		if (other == this) {
			return true;
		}
		if (other == null) {
			return false;
		}

		JdkDynamicAopProxy otherProxy;
		if (other instanceof JdkDynamicAopProxy) {
			otherProxy = (JdkDynamicAopProxy) other;
		}
		else if (Proxy.isProxyClass(other.getClass())) {
			InvocationHandler ih = Proxy.getInvocationHandler(other);
			if (!(ih instanceof JdkDynamicAopProxy)) {
				return false;
			}
			otherProxy = (JdkDynamicAopProxy) ih;
		}
		else {
			// Not a valid comparison...
			return false;
		}

		// If we get here, otherProxy is the other AopProxy.
		return AopProxyUtils.equalsInProxy(this.advised, otherProxy.advised);
	}

	// AOP帮我们实现的HashCode方法
	@Override
	public int hashCode() {
		return JdkDynamicAopProxy.class.hashCode() * 13 + this.advised.getTargetSource().hashCode();
	}

}

细节:

  • 除了实现类里自己写的方法(接口上没有的),其余方法统一都会进入代理得invoke()方法里面。只是invoke上做了很多特殊处理,比如DecoratingProxyAdvised等等的方法,都是直接执行了。
  • object的方法中,toString()方法会被增强(至于为何,我至今还没找到原因,麻烦的知道的给个答案) 因为我始终不知道AdvisedSupport#methodCache这个字段事什么把toString()方法缓存上的,打断点都没跟踪上
  • 生成出来的代理对象,Spring默认都给你实现了接口:SpringProxy、DecoratingProxy、Advised - 说明:CGLIB代理出来的对象没有实现接口DecoratingProxy。(多谢评论区【神的力量】小伙伴的指正)

CglibAopProxy

ObjenesisCglibAopProxy

环境我们只需要让Demo不再实现接口就成了~

public class Main {

    public static void main(String[] args) {
        ProxyFactory proxyFactory = new ProxyFactory(new Demo());
        proxyFactory.addAdvice((MethodBeforeAdvice) (method, args1, target) -> {
                    System.out.println("你被拦截了:方法名为:" + method.getName() + " 参数为--" + Arrays.asList(args1));
                }
        );

        Demo demo = (Demo) proxyFactory.getProxy();
        //你被拦截了:方法名为:hello 参数为--[]
        //this demo show
        demo.hello();
    }
}

// 不要再实现接口,就会用CGLIB去代理
class Demo {
    public void hello() {
        System.out.println("this demo show");
    }
}

这个两个放在一起说,因为ObjenesisCglibAopProxy继承自CglibAopProxy,它只重写了createProxyClassAndInstance方法:

// 它是Spring4.0之后提供的
class ObjenesisCglibAopProxy extends CglibAopProxy {
	// 下面有解释,另外一种创建实例的方式(可议不用空的构造函数哟)
	private static final SpringObjenesis objenesis = new SpringObjenesis();
	
	public ObjenesisCglibAopProxy(AdvisedSupport config) {
		super(config);
	}

	// 创建一个代理得实例
	@Override
	protected Object createProxyClassAndInstance(Enhancer enhancer, Callback[] callbacks) {
		Class<?> proxyClass = enhancer.createClass();
		Object proxyInstance = null;
		
		// 如果为true,那我们就采用objenesis去new一个实例~~~
		if (objenesis.isWorthTrying()) {
			try {
				proxyInstance = objenesis.newInstance(proxyClass, enhancer.getUseCache());
			} catch (Throwable ex) {
				logger.debug("Unable to instantiate proxy using Objenesis, " +
						"falling back to regular proxy construction", ex);
			}
		}
		
		// 若果还为null,就再去拿到构造函数(指定参数的)
		if (proxyInstance == null) {
			// Regular instantiation via default constructor...
			try {
				Constructor<?> ctor = (this.constructorArgs != null ?
						proxyClass.getDeclaredConstructor(this.constructorArgTypes) :
						proxyClass.getDeclaredConstructor());
				
				// 通过此构造函数  去new一个实例
				ReflectionUtils.makeAccessible(ctor);
				proxyInstance = (this.constructorArgs != null ?
						ctor.newInstance(this.constructorArgs) : ctor.newInstance());
			} catch (Throwable ex) {
				throw new AopConfigException("Unable to instantiate proxy using Objenesis, " +
						"and regular proxy instantiation via default constructor fails as well", ex);
			}
		}

		((Factory) proxyInstance).setCallbacks(callbacks);
		return proxyInstance;
	}
}

本来要想使用ASM和CGLIB,我们是需要引入cglib相关的jar包的。但是从Spring3.2以后,我们就不用再单独因此此Jar了,因为Spring已经帮我们集成在Spring-core里面了

那么接下来,我们只需要看看CglibAopProxy内容即可,他处理的是核心内容。

class CglibAopProxy implements AopProxy, Serializable {

	// 它的两个getProxy()相对来说比较简单,就是使用CGLIB的方式,利用Enhancer创建了一个增强的实例
	// 这里面比较复杂的地方在:getCallbacks()这步是比较繁琐的
	// setCallbackFilter就是看看哪些方法需要拦截、哪些不需要~~~~
	@Override
	public Object getProxy() {
		return getProxy(null);
	}
	
	// CGLIB重写的这两个方法
	@Override
	public boolean equals(Object other) {
		return (this == other || (other instanceof CglibAopProxy &&
				AopProxyUtils.equalsInProxy(this.advised, ((CglibAopProxy) other).advised)));
	}
	@Override
	public int hashCode() {
		return CglibAopProxy.class.hashCode() * 13 + this.advised.getTargetSource().hashCode();
	}

	// 最后,所有的被代理得类的所有的方法调用,都会进入DynamicAdvisedInterceptor#intercept这个方法里面来(相当于JDK动态代理得invoke方法)
	// 它实现了MethodInterceptor接口
	private static class DynamicAdvisedInterceptor implements MethodInterceptor, Serializable {

		private final AdvisedSupport advised;

		public DynamicAdvisedInterceptor(AdvisedSupport advised) {
			this.advised = advised;
		}

		@Override
		@Nullable
		public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
			Object oldProxy = null;
			boolean setProxyContext = false;
			Object target = null;
			// 目标对象源
			TargetSource targetSource = this.advised.getTargetSource();
			try {
				if (this.advised.exposeProxy) {
					oldProxy = AopContext.setCurrentProxy(proxy);
					setProxyContext = true;
				}

				// 拿到目标对象   这里就是使用targetSource的意义,它提供多个实现类,从而实现了更多的可能性
				// 比如:SingletonTargetSource  HotSwappableTargetSource  PrototypeTargetSource  ThreadLocalTargetSource等等
				target = targetSource.getTarget();
				Class<?> targetClass = (target != null ? target.getClass() : null);
			
				// 一样的,也是拿到和这个方法匹配的 所有的增强器、通知们 和JDK Proxy中是一样的
				List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
				Object retVal;
				// 没有增强器,同时该方法是public得  就直接调用目标方法(不拦截)
				if (chain.isEmpty() && Modifier.isPublic(method.getModifiers())) {
					Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
					retVal = methodProxy.invoke(target, argsToUse);
				}
				else {
					// CglibMethodInvocation这里采用的是CglibMethodInvocation,它是`ReflectiveMethodInvocation`的子类   到这里就和JDK Proxy保持一致勒 
					retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();
				}
				retVal = processReturnType(proxy, target, method, retVal);
				return retVal;
			}
			finally {
				if (target != null && !targetSource.isStatic()) {
					targetSource.releaseTarget(target);
				}
				if (setProxyContext) {
					AopContext.setCurrentProxy(oldProxy);
				}
			}
		}

		@Override
		public boolean equals(Object other) {
			return (this == other ||
					(other instanceof DynamicAdvisedInterceptor &&
							this.advised.equals(((DynamicAdvisedInterceptor) other).advised)));
		}

		/**
		 * CGLIB uses this to drive proxy creation.
		 */
		@Override
		public int hashCode() {
			return this.advised.hashCode();
		}
	}
}

细节:

  • 和JDK的一样,Object的方法,只有toString()会被拦截(执行通知)
  • 生成出来的代理对象,Spring默认都给你实现了接口:SpringProxy、DecoratingProxy、Advised
  • 它和JDK不同的是,比如equals和hashCode等方法根本就不会进入intecept方法,而是在getCallbacks()那里就给特殊处理掉了



Objenesis:另一种实例化对象的方式

它专门用来创建对象,即使你没有空的构造函数,都木有问题~~ 可谓非常的强大 它不使用构造方法创建Java对象,所以即使你有空的构造方法,也是不会执行的。

Objenesis是一个Java的库,主要用来创建特定的对象。

由于不是所有的类都有无参构造器又或者类构造器是private,在这样的情况下,如果我们还想实例化对象,class.newInstance是无法满足的。

使用ObjenesisStd
public class MainTest {

    public static void main(String[] args) throws Exception {
        Objenesis objenesis = new ObjenesisStd();
        // 它竟然创建成功了
        MyDemo myDemo = objenesis.newInstance(MyDemo.class);
        System.out.println(myDemo); //com.fsx.maintest.MyDemo@1f32e575
        System.out.println(myDemo.code); //null  特别注意:这里是null,而不是10

        // 若直接这样创建 就报错 java.lang.InstantiationException: com.fsx.maintest.MyDemo
        System.out.println(MyDemo.class.newInstance());
    }
}

class MyDemo {
    public String code = "10";

    public MyDemo(String code) {
        this.code = code;
    }
}

实用ObjectInstantiator

    public static void main(String[] args) throws Exception {
        Objenesis objenesis = new ObjenesisStd();
        // 相当于生成了一个实例创建的工厂,接下来就可以很方便得创建实例了
        // 如果你要创建多个实例,建议这么来创建
        ObjectInstantiator<MyDemo> instantiator = objenesis.getInstantiatorOf(MyDemo.class);

        MyDemo myDemo1 = instantiator.newInstance();
        MyDemo myDemo2 = instantiator.newInstance();
        System.out.println(myDemo1);
        System.out.println(myDemo1.code); //null
        System.out.println(myDemo2);
    }
使用SpringObjenesis

这是Spring对Objenesis接口的一个实现。由Spring4.2之后提供的(ObjenesisCglibAopProxy可是Spring4.0就有了哦) 基本实用上,我们只需要换个实现就成:

Objenesis objenesis = new SpringObjenesis();

Spring为我们提供了一个isWorthTrying()方法:

	// 是否需要尝试:也就是说,它是否还没有被使用过,或者已知是否有效。方法返回true,表示值得尝试
	// 如果配置的Objenesis Instantiator策略被确定为不处理当前JVM。或者系统属性"spring.objenesis.ignore"值设置为true,表示不尝试了
	// 这个在ObjenesisCglibAopProxy创建代理实例的时候用到了。若不尝试使用Objenesis,那就还是用老的方式用空构造函数吧
	public boolean isWorthTrying() {
		return (this.worthTrying != Boolean.FALSE);
	}
Objenesis Vs class.newInstance

从以上代码可以发现class构造器需要参数,而Objenesis可以绕过去, Objenesis主要应用场景:

  1. 序列化,远程调用和持久化 -对象需要实例化并存储为到一个特殊的状态,而没有调用代码
  2. 代理,AOP库和Mock对象 -类可以被子类继承而子类不用担心父类的构造器。
  3. 容器框架 -对象可以以非标准的方式被动态实例化(比如Spring就是容器框架)。

Enhancer:CGLIB增强器

也是位于cglib相关的包内。org.springframework.cglib.proxy CGLIB是一个强大的高性能的代码生成包。它被许多AOP的框架(例如Spring AOP)使用,为他们提供方法的interception(拦截)

CGLIB包的底层是通过使用一个小而快的字节码处理框架ASM,来转换字节码并生成新的类。不鼓励直接使用ASM,因为它要求你必须对JVM内部结构包括class文件的格式和指令集都很熟悉

public class MainTest {

    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(MyDemo.class);
        // 注意此处得MethodInterceptor是cglib包下的   AOP联盟里还有一个MethodInterceptor
        enhancer.setCallback((MethodInterceptor) (o, method, args1, methodProxy) -> {
            System.out.println(method.getName() + "---方法拦截前");
            // 此处千万不能调用method得invoke方法,否则会死循环的 只能使用methodProxy.invokeSuper 进行调用
            //Object result = method.invoke(o, args1);
            Object result = methodProxy.invokeSuper(o, args1);
            System.out.println(method.getName() + "---方法拦截后");
            return result;
        });

        //MyDemo myDemo = (MyDemo) enhancer.create(); // 这里是要求必须有空的构造函数的
        MyDemo myDemo = (MyDemo) enhancer.create(new Class[]{String.class}, new Object[]{"fsx"});
        // 直接打印:默认会调用toString方法以及hashCode方法  此处都被拦截了
        System.out.println(myDemo);
        //System.out.println(myDemo.code);

    }
}

class MyDemo {
    public String code = "10";

    public MyDemo(String code) {
        this.code = code;
    }
}

输出:
toString---方法拦截前
hashCode---方法拦截前
hashCode---方法拦截后
toString---方法拦截后
com.fsx.maintest.MyDemo$$EnhancerByCGLIB$$b07b3819@7960847b
fsx

这样我们就简单的实现了,对一个对象进行增强。

还有一种创建代理实例的方式,就是我们只用Enhancer把Class类型创建出来,然后创建实例的工作交给Objenesis 这样我们就拜托了对构造函数的依赖

    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(MyDemo.class);

        // 如国实用createClass方式来创建代理的实例  是不能直接添加callback得
        //enhancer.setCallback();
        enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
        enhancer.setStrategy(new DefaultGeneratorStrategy());
        enhancer.setCallbackFilter(new CallbackHelper(MyDemo.class, null) {
            @Override
            protected Object getCallback(Method method) {
                return (MethodInterceptor) (o, method1, args1, methodProxy) -> {
                    System.out.println(method1.getName() + "---方法拦截前");
                    // 此处千万不能调用method得invoke方法,否则会死循环的 只能使用methodProxy.invokeSuper 进行调用
                    //Object result = method.invoke(o, args1);
                    Object result = methodProxy.invokeSuper(o, args1);
                    System.out.println(method1.getName() + "---方法拦截后");
                    return result;
                };
            }
        });
        enhancer.setCallbackTypes(new Class[]{MethodInterceptor.class});

        // 这里我们只生成Class字节码,并不去创建对象
        Class clazz = enhancer.createClass();
        // 创建对象的操作交给
        Objenesis objenesis = new SpringObjenesis();
        MyDemo myDemo = (MyDemo) objenesis.newInstance(clazz);

        System.out.println(myDemo);
        System.out.println(myDemo.code);

    }
输出:
com.fsx.maintest.MyDemo$$EnhancerBySpringCGLIB$$6558edaa@5700d6b1
null

这样即使你没有空的构造函数,我依然可议给你创建一个实例。

CGLIB整个过程如下
  1. Cglib根据父类,Callback, Filter 及一些相关信息生成key
  2. 然后根据key 生成对应的子类的二进制表现形式
  3. 使用ClassLoader装载对应的二进制,生成Class对象,并缓存
  4. 最后实例化Class对象,并缓存
生成二进制Class的方法

针对不同场景, CGlib准备了不同的Class生成方法

二进制文件存在在哪儿?

放在byte数组中,下面这行代码就截取于方法AbstractClassGenerator.create(Object key)

byte[] b = strategy.generate(this); 

然后通过 ReflectUtils.defineClass(className, b, loader)生成对应的Class实例,并缓存入cache2

Cglib如何把二进制Load生成的Class

上面说了,事ReflectUtils.defineClass这个方法:

    public static Class defineClass(String className, byte[] b, ClassLoader loader) throws Exception {
        return defineClass(className, b, loader, PROTECTION_DOMAIN);
    }

    public static Class defineClass(String className, byte[] b, ClassLoader loader, ProtectionDomain protectionDomain) throws Exception {
        Object[] args;
        Class c;
        if (DEFINE_CLASS != null) {
            args = new Object[]{className, b, new Integer(0), new Integer(b.length), protectionDomain};
            c = (Class)DEFINE_CLASS.invoke(loader, args);
        } else {
            if (DEFINE_CLASS_UNSAFE == null) {
                throw new CodeGenerationException(THROWABLE);
            }  

            args = new Object[]{className, b, new Integer(0), new Integer(b.length), loader, protectionDomain};
            c = (Class)DEFINE_CLASS_UNSAFE.invoke(UNSAFE, args);
        }

        Class.forName(className, true, loader);
        return c;
    }
注意事项

JDK代理只能针对实现了接口的类以反射的方式生成代理,而不能针对类 ,所以也叫接口代理。 CGLIB是针对类实现代理的,主要对指定的类以字节码转换的方式(ASM框架)生成一个子类,并重写其中的方法。

所以使用CGLIB做动态代理,必须要保证有一个空的构造函数。(那是之前,其实现在不需要了,因为我们有了Objenesis的帮助),但是类不能是Final的

  • 关于final方法 - JDK代理:因为接口的方法不能使用final关键字,所以编译器就过不去 - CGLIB代理:final修饰某个方法后,不报错。但也不会拦截了
  • 关于static方法 - JDK代理:static修饰接口上的方法,要求有body体(JDK8后支持)。但是因为子类不能@Override了,所以编译就报错了 - CGLIB代理:父类方法用static修饰后,子类也是无法进行重写的。因此不抱错,但也不会拦截了

使用代理的时候,尽量不要使用final和static关键字

  • 关于非public方法 - JDK代理:接口中的方法都是public的,所以对于它不存在这种现象 - CGLIB代理:记住结论 只有private的方法不能被代理(因为子类无法访问),其余的访问权限级别的,都能够被正常代理。 简单的说就是只要子类能够访问的权限,都能够被正常代理
关于代理出现的:类型转换常见错误

java.lang.ClassCastException: com.sun.proxy.$Proxy7 cannot be cast to XXXXX 这一看就知道是JDK的代理对象不能转换为xxx对象。这是由JDK动态代理可能导致的。比如:

public class Main {

    public static void main(String[] args) {
        ProxyFactory proxyFactory = new ProxyFactory(new Demo());
        proxyFactory.addAdvice((MethodBeforeAdvice) (method, args1, target) -> {
                    System.out.println("你被拦截了:方法名为:" + method.getName() + " 参数为--" + Arrays.asList(args1));
                }
        );

        // 如果使用的是JDK的动态代理,这里若实现实现类接收,就报错:java.lang.ClassCastException: com.fsx.maintest.$Proxy0 cannot be cast to com.fsx.maintest.Demo
        //Demo demo = (Demo) proxyFactory.getProxy();
        DemoInter demo = (DemoInter) proxyFactory.getProxy();
        //你被拦截了:方法名为:hello 参数为--[]
        //this demo show
        demo.hello();

    }
}

// 不要再实现接口,就会用CGLIB去代理
class Demo implements DemoInter{

    @Override
    public void hello() {
        System.out.println("this demo show");
    }
}

interface DemoInter{
    void hello();
}

这个例子就好比我们经常使用@Autowired自动注入的时候,建议注入接口(因为注入实现类且是JDK动态代理的话,是会出现类似错误的)

    @Autowired
    private HelloService helloService; // 建议注入接口而不是实现类
    //@Autowired
    //private HelloServiceImpl helloServiceImpl; //不建议直接注入实现类

上面例子,若采用的是CGLIB动态代理,不管是用接口还是实现类,都不会有问题。因此可见总体来说,CGLIb还是更加强大一些的

CGLIB动态代理 字段为null 导致的坑

先不说别的,看下面例子:(一定让你摸不着头脑)

public class Main {

    public static void main(String[] args) {
        ProxyFactory proxyFactory = new ProxyFactory(new Demo());
        proxyFactory.addAdvice((MethodBeforeAdvice) (method, args1, target) -> {
                    System.out.println("你被拦截了:方法名为:" + method.getName() + " 参数为--" + Arrays.asList(args1));
                }
        );

        Demo demo = (Demo) proxyFactory.getProxy();
        //你被拦截了:方法名为:setAge 参数为--[10]
        demo.setAge(10);

        //你被拦截了:方法名为:getAge 参数为--[]
        System.out.println(demo.getAge()); //10
        System.out.println(demo.age); //null 对你没看错,这里是null
        System.out.println(demo.findAge()); //null 对你没看错,这里是null

    }
}

// 不要再实现接口,就会用CGLIB去代理
class Demo {
    public Integer age;

    // 此处用final修饰了  CGLIB也不会代理此方法了
    public final Integer findAge() {
        return age;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }
}

what?what a fuck? 是不是此时你有此种感觉:不再相信java了。明明我都用set把age赋值了,为毛我拿出来却仍旧为null呢?(但通过getAge()方法获取正常)

如何解决?

最直接也是最推荐的方案,就是遵从上面所说得规范,小心谨慎为事。 另外,本处我也提供一个简单的工具,配置上就能就绝get/set的问题。(但知其然必须知其所以然,才能更好的解决一类问题,而不是这一个问题)。仅供参考:

@Aspect
@Order(Integer.MIN_VALUE)
public class SetterAspect {

    // 切所有的set方法
    @After(value = "execution(* *.set*(*)) && args(value)", argNames = "value")
    public void after(JoinPoint jp, Object value) {
        Object proxy = jp.getThis();

        // 拿到目标对象
        Object target = jp.getTarget();

        if (AopUtils.isAopProxy(proxy)) {//只有代理对象才需要处理

            try {
                Class<?> proxyClass = proxy.getClass();
                Class<?> targetClass = target.getClass();
                String methodName = jp.getSignature().getName();

                Method m = BeanUtils.findDeclaredMethod(proxyClass, methodName, new Class[]{value.getClass()});
                PropertyDescriptor descriptor = BeanUtils.findPropertyForMethod(m);
                String propName = descriptor.getName();

                // 调用目标对象的set方法
                Field f = targetClass.getClass().getDeclaredField(propName);
                if (f != null) {
                    f.setAccessible(true);
                    f.set(proxy, value);
                }
            } catch (Exception e) {
                e.printStackTrace();//记录好异常进行处理  
            }
        }
    }

}  
解释:

若了解CGLIB的原理,甚至看过它生成的代理类的源码的话,此原因就可一句道破。

原理简述:假设有个类A,会在字节码的层面上动态生成一个类B并加载进JVM里面。B继承自A同时又有对A的引用,B会重写所有的A类里面的非Final、非private方法,从而可以在目标方法调用前后进行对应的增强了。

本文中:demo.setAge(10);执行的是代理对象的setAge()方法,所以set进去的值是给了代理对象的,目标对象仍然我null。而我们findAge()方法因为我标注了final,因此不能被CGLIB代理,所以只能从目标对象里拿值。因此它也只能拿到null

而我们调用的getAge()方法它被代理过,所以他能拿到正确的值:10。

备注:若采用JDK动态代理不回存在此种现象,因为接口代理的都是方法。 另外:建议若代理的时候,final慎用。同时大多数情况下字段还是private掉吧,然后暴露出get方法给外部调用比较好。

总结

JdkDynamicAopProxy 入口方法是动态代理的 invoke() 方法,CGLIB 使用的是 DynamicAdvisedInterceptor.intercept()方法

JdkDynamicAopProxy使用的MethodInvocation 是: ReflectiveMethodInvocation 子类, CGLIB 使用的是CglibMethodInvocation

它俩都是ProxyMethodInvocation接口的实现类。并且CglibMethodInvocation是继承自ReflectiveMethodInvocation

CGLib更适合代理不需要频繁实例化的类,而Spring绝大多数Bean都是单例的,因此在Spring AOP中我极力推荐使用CGLib,它的功能更强大些

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2019年04月02日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 常识普及
  • AopProxy:Aop代理接口
  • 环境构建
  • JdkDynamicAopProxy
  • CglibAopProxy
  • ObjenesisCglibAopProxy
  • Objenesis:另一种实例化对象的方式
    • 使用ObjenesisStd
      • 使用SpringObjenesis
        • Objenesis Vs class.newInstance
        • Enhancer:CGLIB增强器
          • CGLIB整个过程如下
            • 注意事项
              • CGLIB动态代理 字段为null 导致的坑
                • 总结
                相关产品与服务
                容器服务
                腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
                领券
                问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档