一个类被代理或多次代理之后,如何获取原来的类型?

在java的各种设计模式中,代理模式使用的非常广泛。比如目前作为Java的事实标准框架Spring框架中不得不谈的就是IOC和AOP,而AOP使用的就包含代理模式。
比如事务处理,我们只需要添加一个注解@Transactional就可以将复杂的事务处理交给Spring。在Spring中动态代理有两种模式:一个是JDK动态代理,一个是CGLIB.
比如有以下一个接口:

public interface OrderService {

    public void add(Order order);

}

实现类如下:

@Service
@Transactional
public class OrderServiceImpl implements OrderService {
    @Override
    public void add(Order order) {

    }
}

注意上面添加了注解@Transactional
开启事务并使用默认的代理模式(JDK)

@EnableTransactionManagement

此时获取这个接口的Bean的类型,结果会怎样呢?
在这里插入图片描述

然后再设置为CGLIB模式:

@EnableTransactionManagement(proxyTargetClass = true)

在这里插入图片描述
可以看出,经过代理之后获取的类型与原来相差很大。那如何才能获取到原来的类型呢?
当然,Spring中提供了一些工具类可以快速解决这个问题。
第一个工具方法,判断一个类是否为代理类
org.springframework.aop.support.AopUtils#isCglibProxy

/**
 * Check whether the given object is a CGLIB proxy.
 * <p>This method goes beyond the implementation of
 * {@link ClassUtils#isCglibProxy(Object)} by additionally checking if
 * the given object is an instance of {@link SpringProxy}.
 * @param object the object to check
 * @see ClassUtils#isCglibProxy(Object)
 */
public static boolean isCglibProxy(@Nullable Object object) {
	return (object instanceof SpringProxy && ClassUtils.isCglibProxy(object));
}

第二个工具方法,获取一个被代理对象的被代理前的类型targetClass

/**
 * Determine the target class of the given bean instance which might be an AOP proxy.
 * <p>Returns the target class for an AOP proxy or the plain class otherwise.
 * @param candidate the instance to check (might be an AOP proxy)
 * @return the target class (or the plain class of the given object as fallback;
 * never {@code null})
 * @see org.springframework.aop.TargetClassAware#getTargetClass()
 * @see org.springframework.aop.framework.AopProxyUtils#ultimateTargetClass(Object)
 */
public static Class<?> getTargetClass(Object candidate) {
	Assert.notNull(candidate, "Candidate object must not be null");
	Class<?> result = null;
	if (candidate instanceof TargetClassAware) {
		result = ((TargetClassAware) candidate).getTargetClass();
	}
	if (result == null) {
		result = (isCglibProxy(candidate) ? candidate.getClass().getSuperclass() : candidate.getClass());
	}
	return result;
}

使用以上方法得到结果如下:
在这里插入图片描述
看起来这个问题很容易就解决了。
其实这里有个坑,如果一个类被代理了一次,以上没有问题。如果一个被代理之后,又被代理,也就是多次代理,以上方法就不行了。但是只要在Spring中还是可以轻松解决的。如下所示:

Object target = bean;
// 如果Spring的版本为4.3.8或以上 直接调用AopProxyUtils.getSingletonTarget(target)就可以获取target对象了
while (target instanceof Advised) {
    TargetSource targetSource = ((Advised) target).getTargetSource();
    if (targetSource instanceof SingletonTargetSource) {
        target = ((SingletonTargetSource) targetSource).getTarget();
    }
}
Class<?> targetClass = AopUtils.getTargetClass(target);

无非就是循环获取到被代理的对象,如果还是被代理的,就不断获取目标类直到不是被代理的类。
是不是这个问题就这么结束了呢?
如果停步如此,其实我们并没有真正懂得什么是代理。
首先在JDK代理模式下,这个以Proxy开头的类是哪里来的?跟原来的类是什么关系?在CGLIB代理模式下,这个包含$$是哪里来的?跟原来的类有啥关系?
这个才是关键!!!

其实JDK代理就是在运行时动态创建了一个类名以Proxy开头的类实现了指定的接口,在上面那个例子中就是com.example.managingtransactions.OrderService。这也是为什么JDK动态代理必须有接口存在的原因。代理类实现了被代理类的接口,是implements关系。
获取这个代理类实现的接口:
在这里插入图片描述

而CGLIB代理模式呢?是在运行时动态创建一个类继承了目标类,注意,因为是继承(extends)关系,所以是类,而不是接口。
获取这个代理类的父类
在这里插入图片描述
此时是不是感觉豁然开朗的感觉?

下面通过JDK自带的工具HSDB在JVM中看看以上的结论是否正确。
在这里插入图片描述
通过jps查看java进程号

然后在jdk的lib目录下执行如下命令(我的目录是:jdk1.8.0_121/lib)

java -cp ./sa-jdi.jar sun.jvm.hotspot.HSDB

在这里插入图片描述

在这里插入图片描述
CGLIB生成的类关系如下所示:
在这里插入图片描述
JDK动态代理生成的类关系如下:
在这里插入图片描述
也就是说要想获得最初的那个被代理类,对于JDK代理,其实就包含在实现的接口当中。而对于CGLIB,可以通过不断获取父类直到Object.这种方式比起上面那些工具类的第一个优势是只要得到类就可以,不需要获得对象实体,对于一些只提供对象类型而不给对象实体的回调方法前面的方法就失效了。另外一个优势就是更加简单易懂,有没有觉得上面的一些工具方法很多时候会把问题越来越复杂化?

最后来个甜点,其实在Spring中有这样一个工具类方法的存在。哈哈!
org.springframework.util.ClassUtils#getUserClass(java.lang.Class<?>)
源码如下所示:

/** The CGLIB class separator character "$$" */
public static final String CGLIB_CLASS_SEPARATOR = "$$";
/**
 * Return the user-defined class for the given class: usually simply the given
 * class, but the original class in case of a CGLIB-generated subclass.
 * @param clazz the class to check
 * @return the user-defined class
 */
public static Class<?> getUserClass(Class<?> clazz) {
	if (clazz != null && clazz.getName().contains(CGLIB_CLASS_SEPARATOR)) {
		Class<?> superClass = clazz.getSuperclass();
		if (superClass != null && !Object.class.equals(superClass)) {
			return superClass;
		}
	}
	return clazz;
}

如果有看懂我上面关于代理的说明,看这个方法简直不要太easy!

  • 8
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lang20150928

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值