论坛首页 Java企业应用论坛

拿到spring proxy的target class

浏览 5420 次
该帖已经被评为良好帖
作者 正文
   发表时间:2008-11-19   最后修改:2008-11-19
前两天ahuaxuan同学的帖子aop cache再讨论,讲述了利用AOP来实现method cache,写的很好,但是有一个遗憾,就是那个代码不能对代理对象实现cache,下面是ahuaxuan的代码
if (invocation.getThis().getClass().getCanonicalName().contains("$Proxy")) {  
	if (logger.isWarnEnabled()) {  
	      logger.warn("----- The object has been proxyed and method " +  
                  "cache interceptor can't get the target, " +  
                  "so the method result can't be cached which name is ------" + methodName);  
        }  
               
return invocation.proceed();  

上述代码中,如果invocation.getThis()得到的对象是一个spring利用JDK dynamic proxy创建的代理,那么其getClass().getName()方法返回的是一种类似$Proxy21之类的名字,就无法利用其class name配置相应的cache了,所以ahuaxuan才写了上面的代码,碰到是dynamic proxy,则跳过cache处理,造成了遗憾,我们就试着弥补一下ahuaxuan的遗憾,经过观察spring代码,找到了类org.springframework.aop.support.AopUtils的方法getTargetClass,其java doc也说明了该方法可以根据proxy获得其target class,代码如下,
public static Class getTargetClass(Object candidate) {
	Assert.notNull(candidate, "Candidate object must not be null");
	if (candidate instanceof TargetClassAware) {
		return ((TargetClassAware) candidate).getTargetClass();
	}
	if (isCglibProxyClass(candidate.getClass())) {
		return candidate.getClass().getSuperclass();
	}
	return candidate.getClass();
}

短短几行代码,我们发现,这个方法对dynamic proxy根本不起作用,它只适用于实现了TargetClassAware接口的对象,或者由cglib产生的代理对象,但是jdk提供的dynamic proxy是我们最常用的创建代理的方式,是spring的默认行为,难道就不行了吗?当然不是了!(好老套的句型。。。),我们知道在spring中,创建proxy主要有两种方式,一种是cglib,其核心类为org.springframework.aop.framework.Cglib2AopProxy,另一种是最常用的jdk提供的dynamic proxy机制,核心类是org.springframework.aop.framework.JdkDynamicAopProxy,而这个JdkDynamicAopProxy类也是创建dynamic proxy的核心,既它实现了java.lang.reflect.InvocationHandler接口,而通过java.lang.reflect.Proxy的静态方法getInvocationHandler可以从一个proxy得到其对应的InvocationHandler,这就是问题关键了,拿到了InvocationHandler的实例,也就是JdkDynamicAopProxy,就可以通过反射拿到它携带的AdvisedSupport对象,从而拿到target class!

JdkDynamicAopProxy的主要代码如下:
class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializable {
	/** Config used to configure this proxy */
	private final AdvisedSupport advised;


而我们实现的代码如下
private static final String ADVISED_FIELD_NAME = "advised";

private static final String 
CLASS_JDK_DYNAMIC_AOP_PROXY = "org.springframework.aop.framework.JdkDynamicAopProxy";

public static Class getTargetClass(Object candidate) {
	if (!org.springframework.aop.support.AopUtils.isJdkDynamicProxy(candidate)) {
		return org.springframework.aop.support.AopUtils.getTargetClass(candidate);
	}

	return getTargetClassFromJdkDynamicAopProxy(candidate);
}

private static Class getTargetClassFromJdkDynamicAopProxy(Object candidate) {
	try {
		InvocationHandler invocationHandler = Proxy.getInvocationHandler(candidate);
		if (!invocationHandler.getClass().getName().equals(CLASS_JDK_DYNAMIC_AOP_PROXY)) {
			//在目前的spring版本,这处永远不会执行,除非以后spring的dynamic proxy实现变掉
			log.warn("the invocationHandler of JdkDynamicProxy isn`t the instance of "
 + CLASS_JDK_DYNAMIC_AOP_PROXY);
			return candidate.getClass();
		}
		AdvisedSupport advised = (AdvisedSupport) new DirectFieldAccessor(invocationHandler).getPropertyValue(ADVISED_FIELD_NAME);
		Class targetClass = advised.getTargetClass();
		if (Proxy.isProxyClass(targetClass)) {
			// 目标类还是代理,递归
			Object target = advised.getTargetSource().getTarget();
			return getTargetClassFromJdkDynamicAopProxy(target);
		}
		return targetClass;
	} catch (Exception e) {
		log.error("get target class from " + CLASS_JDK_DYNAMIC_AOP_PROXY + " error", e);
		return candidate.getClass();
	}
}


这样就OK了,扩展了AopUtils.getTargetClass方法,从而支持获得dynamic proxy的target class,上述代码里的JdkDynamicAopProxy不是public的,是我们通过反射来强行拿到其里面的代理信息,这种方式属于很猥琐的方式,如果以后spring升级了,那个JdkDynamicAopProxy内部的代码改了,这段代码就行不通了,呵呵,这个原理很简单,雕虫小技而已,个人认为spring完全可以把那个getTargetClass方法实现的完美一点,可以让我们直接使用。

所有代码在附件中,谢谢。
   发表时间:2008-11-19   最后修改:2008-11-19
根本在于,武断的将classname、methodname、parametervalue来做cache的key要素,是很“猥琐”的方案,以至于又出现这些“猥琐”的补丁。

那帖我也建议了,完全可以在method上指定@Cache(key="pattern{0}{1}")这样的方式,可惜没人听啊
0 请登录后投票
   发表时间:2008-11-19   最后修改:2008-11-19
sorphi 写道
根本在于,武断的将classname、methodname、parametervalue来做cache的key要素,是很“猥琐”的方案,以至于又出现这些“猥琐”的补丁。

那帖我也建议了,完全可以在method上指定@Cache(key="pattern{0}{1}")这样的方式,可惜没人听啊


你说的有道理,cache那个地方可以有很多种处理方式,但这个不仅限于method cache的补丁,在不少的情况下,需要拿到proxy的target class,都可以用这样的方式,在这里只是从method cache那篇文章引出来而已。
0 请登录后投票
   发表时间:2008-11-19  
哦了
spring 2.5加入aspectj的支持后,ProceedingJoinPoint更方便拿到target了
0 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics