怎样拦截classloader实现类方法截获

07-04-23 leadyu
我目前正在开发的一个组件想实现一个功能: 就是通过配置文件配置类名,就能够拦截该类所有方法的调用,而该类可能没有实现任何接口,并且这个过程对于系统程序是透明的,我想在虚拟机这层解决这个问题,对于组件的部署都是非常有帮助的.

目前我的想法就是通过拦截classloader,或者实现自己的classloader,另外通过修改原class的字解码实现拦截方法。但是遇到2个难题:

1)classloader的拦截似乎并不那么听话,我是通过Thread.setContextClassLoader()来设置自己的classloader,但是似乎对于下面的程序,并不一定就会采用我设置的classloader工作,在多个容器和JDK版本下试过,行为有点不一样,而SUN在这方面的资料又非常匮乏。

2)字解码修改,往往我需要代理的类在我的组件运行时已经被上层classloader加载,这时试图修改该类的字解码,会引起JVM报错,比如重复加载之类的错误。

由于资料非常匮乏,所以,非常希望有这方面经验的人,能够给大家讲解一下虚拟机加载类的时序,已经类生存的周期,命名空间,以及安全控制等问题,当然最重要的是告诉我怎么实现我想要的功能,呵呵。

leadyu
2007-04-23 22:14
自己顶一个,麻烦BENQ大哥帮我的贴提一提,欢迎大家讨论。

leadyu
2007-04-23 22:21
之前,我通过修改cglib的代码,把字解码生成出来,但是把这个类的名称定义成和我要代理的类一样时,通过调用容器classloader的defineClass方法,会出现类重复定义的错误。

然后,我就通过定义自己的classLoader,把容器的ClassLoader定义成父,这样defineClass也许不会重复,还没试过。

我有个疑问,JVM对于已经加载的类是怎么管理的,是不是所有的classloader加载的类在JVM内部是共享的?因为defineClass本身是JVM内部实现的,是不是对于所有classloader定义一个类时,都是加载到JVM内部的一个共享的存储空间?不得而知。

leadyu
2007-04-23 22:23
望有缘人指条明路啊,头疼

leadyu
2007-04-23 23:38
现在,我设计的一个模型是:把自己定义的classloader定义为容器的classloader的子,那么怎么更新已经加载的类为通过字解码编辑后的类呢?

现在基本上没有想到办法在classloader上重新更新一个已经加载的类,重新LoadClass是没有意义的,无非从缓存里取出原来的类而以,重新defineClass就会收到类定义重复的错误,如果通过实现自己的classloader来define修改后的Class,是不会报错了,但是非常危险吧(我不知道,感觉而已),因为容器的classloader已经加载了该类,现在自定义的classloader也加载了更新后的类,但是系统在运行时不确定会使用哪一个classloader来得到该类,哪怕设置自定义classloader为线程ContextLoader,也没用,引用者类如果是容器的classloader加载的,那么它引用的类A也必然是通过容器classloader加载,只有新的才会使用我自定义的类来加载新的类A,那么系统就存在2个不同版本但是同名的类,危险自然不必说了。

没什么思路了。除非以我的classloader来启动整个容器?可怕的想法

banq
2007-04-24 17:00
目前我们实现类方法截获是使用动态代理,这是JDK提供的一个基础功能.

leebai
2007-04-24 18:18
这问题是有点难度。由于最终类可能不实现任何接口,板兄建议的动态代理技术也不适合该问题。

这个问题的本质可以描述为:如何在JVM中自由地载入和卸载类。

》》》现在基本上没有想到办法在classloader上重新更新一个已经加载的类。。。

我曾经试过在jetty服务器上添加 [servlet被改后自动重载] 的功能(与leadyu的问题相同),原先想得很简单,但后来发现很复杂。

JVM Spec中说:

* 2.17.8 Unloading of Classes and Interfaces

* A class or interface may be unloaded if and only if its class loader is unreachable. 。。。

照这个思路我找了很多资料,包括Tomcat的重载机制,最后写成的ReloadableClassLoader能实现[servlet被改后自动重载],但导致jsp运行报错,问题没有完全解决,后来由于时间关系没有深入研究。

to leadyu:

有兴趣可以下载我的7wxAop框架(xjawa.org),参考其中的org.xjawa.tools.ReloadableClassLoader。如果leadyu兄能把这个问题完全搞明白,不妨也贴出来让大家学习学习。

leadyu
2007-04-24 21:26
谢谢leebai的回复,

这样说吧,我现在已经编写了一个监控组件,可以部署在基于J2EE的WEB系统上,这个组件目前已经有不少系统再用了,不过我希望他能做到通过配置动态的监控某个类的运行,所以这就会涉及几个问题:

1)首先,我要能够实现动态代理,基本上JVM的Proxy不是很适合,主要是很多类没有接口,现在主要考虑CGLIB,通过改写CGLIB的代码,我已经能够通过它获得代理后的类的二进制流数组,但是好像生成的有点,问题,呵呵,还没完全调通。

2)那么下一步,我需要把生成的二进制流重新定义要替换的那个类,比如org.Test

我需要重新在JVM里面定义org.Test,这样系统以后对该类的调用自然就被我监控了,也就是我前面问的,类的重载问题,今天收获不小,在sourceforge上搜获一篇文章介绍他是怎么改写Tomcat代码实现类重载,地址忘了,收藏在公司,明天再转贴在这这里。原理就是通过自定义ClassLoader去define新的org.Test,我试过了,不再报duplicate define class exception了。

3)需要解决的一个问题就是,我不能破坏容器原来的类加载机制,否则有的头疼了,通过自定义classLoader,define了新的org.Test,那么在系统中就会存在2个版本org.Test,系统运行时很难说会用哪个classLoader去加载类,所以在org.Test的运行过程中就会有2套,自然会很危险。所以如果我能做到,容器的classLoader在加载org.Test时也能委派给我的classLoader,那么2个版本的问题就会小很多,只要原来老版本的org.Test所有实例都GC了,那么我就差不多实现了目标(但是如果老版本实例有静态的,呵呵,不知道会有什么情况)

leadyu
2007-04-24 21:31
说的有点罗嗦,呵呵,原来贴的一段论坛好像有点问题,没发出去,这个问题我感觉还是有可行性,就是太缺乏资料了,有些理解的得来甚至通过DEBUG这样的方式去观察JVM是怎么工作的

leadyu
2007-04-24 22:44
Name spaces in the JVM arise from a simple rule that all JVMs must follow when they resolve the symbolic references contained inside class files. Sometimes, an entry in a constant pool may refer symbolically to a type that hasn't yet been loaded. When such entries are resolved, the JVM must load the referred-to type. Because all types must be loaded by a class loader, the JVM must at that point decide which class loader to ask to load the type.

To choose a class loader, the JVM uses this simple rule:

The Resolution Rule - The JVM loads referenced types via the same class loader that loaded the referencing type.

谢谢leebai提供的文章。

leebai
2007-04-25 09:49
上面的规则是很自然的,只有被A类引用的B类载入之后,JVM才可能做链接、初试化。

这个规则要求你要么提前使用自己的classLoader;要么不在先加载的类中引用“动态类”。

leadyu
2007-04-25 12:20
发现JVM1.4,1.5一个差异:

1.4对于已经对于已经定义的类,程序引用到时,根本不调用ClassLoader.loadClassInternal,这个方法本来是由虚拟机加载类时调用的

而1.5对于所有程序执行到的类,全部委托给当前线程的ClassLoader.loadClass,策略由用户自己的ClassLoader决定。

这个是通过断点调试发现的,还没找到资料证明。

不过这就造成一个问题,我注册了新类,老的类版本在原来的ClassLoader命名空间还存在,执行时会抛出ClassCastException,也就是此类非彼类,虽然大家的名称都相同,如果JVM能够把所有执行到的类的加载全部使用Thread.getContextLoader,那么就好办,我把我的ClassLoader注册到线程环境,对于要更新的类,我采用自定义的ClassLoader加载,其他的委托给父ClassLoader,但是1.4好像不是这样的策略。

fantasia
2007-04-26 11:17
可以考虑采用aspectj,用通配符的方式对类的所有方法进行拦截

* * *.*(*),记不太清具体如何写,现在手边也没有书,反正就是匹配到所有包中所有方法,不管方法返回值和参数是什么都可进行拦截,具体看看aspectj的使用吧。

leadyu
2007-04-26 22:21
我不是很了解aspectj,我目的是需要对已有J2EE系统中的类进行动态拦截,并且对已有系统无侵入,类似jprofile之类监控工具的方法监控功能。aspectj能满足要求吗,我不清楚。

leadyu
2007-04-26 22:47
我现在觉得我之前想通过实现class的热部署来实现动态的类监控这个思路太难走了,又想对系统完全无侵入,困难太多,虽然已经有了很大进展。

不过,我突然想到一个新思路,容易很多,大家看可行不可行:

其实,我需要的不是类热部署,我需要的把一个现有的类,用相应的动态代理类去替换。

那么,如果我在容器还没有加载工程相关的业务类之前,先用临时的自定义ClassLoader,通过cglib或者ASM这样的字节码修改类库,动态生成代理类的二进制流,然后使用当前容器的ClassLoader,去defineClass这个流,使用同样的类名,那么以后系统对该类的调用不就自然成了调用我的代理类了吗?

当然,中间有个插曲,defineClass不是一个公开方法,不过这有什么关系,我完全可以通过这样的代码:

DEFINE_CLASS = loader.getDeclaredMethod("defineClass", new Class[]{ String.class,

byte[].class,

Integer.TYPE,

Integer.TYPE,

ProtectionDomain.class });

DEFINE_CLASS.setAccessible(true);

达到调用的目的。

猜你喜欢
2Go 1 2 下一页