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

在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的类型,结果会怎样呢?
一个类被代理或多次代理之后,如何获取原来的类型?_第1张图片

然后再设置为CGLIB模式:

@EnableTransactionManagement(proxyTargetClass = true)

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

/**
 * Check whether the given object is a CGLIB proxy.
 * 

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.
 * 

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; }

使用以上方法得到结果如下:
一个类被代理或多次代理之后,如何获取原来的类型?_第3张图片
看起来这个问题很容易就解决了。
其实这里有个坑,如果一个类被代理了一次,以上没有问题。如果一个被代理之后,又被代理,也就是多次代理,以上方法就不行了。但是只要在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关系。
获取这个代理类实现的接口:
一个类被代理或多次代理之后,如何获取原来的类型?_第4张图片

而CGLIB代理模式呢?是在运行时动态创建一个类继承了目标类,注意,因为是继承(extends)关系,所以是类,而不是接口。
获取这个代理类的父类
一个类被代理或多次代理之后,如何获取原来的类型?_第5张图片
此时是不是感觉豁然开朗的感觉?

下面通过JDK自带的工具HSDB在JVM中看看以上的结论是否正确。
一个类被代理或多次代理之后,如何获取原来的类型?_第6张图片
通过jps查看java进程号

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

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

一个类被代理或多次代理之后,如何获取原来的类型?_第7张图片

一个类被代理或多次代理之后,如何获取原来的类型?_第8张图片
CGLIB生成的类关系如下所示:
一个类被代理或多次代理之后,如何获取原来的类型?_第9张图片
JDK动态代理生成的类关系如下:
一个类被代理或多次代理之后,如何获取原来的类型?_第10张图片
也就是说要想获得最初的那个被代理类,对于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!

你可能感兴趣的:(spring,设计思维,设计模式,java,spring,aop)