java - 在 Groovy 中重新加载类
问题描述
我有一个自定义的 ClassLoader 扩展GroovyClassLoader
,它将源代码编译为.class
磁盘上的文件,然后加载生成的类:
class MyClassLoader extends GroovyClassLoader {
File cache = new File( './cache' )
Compiler compiler
MyClassLoader() {
CompilerConfiguration cc = new CompilerConfiguration( targetDirectory:cache )
compiler = new Compiler( cc )
addClasspath cache.path
}
@Override
Class findClass( name ) {
try{
parent.findClass name
}catch( ClassNotFoundException e ){
compiler.compile name, getBodySomehow()
byte[] blob = loadFromFileSystem name
Class c = defineClass name, blob, 0, blob.length
setClassCacheEntry c
c
}
}
@Override
void removeClassCacheEntry(String name) {
Class c = cache[ name ]
super.removeClassCacheEntry(name)
GroovySystem.metaClassRegistry.removeMetaClass c
deleteFiles name
}
}
Class clazz = myClassLoader.loadClass 'some.pckg.SomeClass'
现在,如果我更改源代码,请致电myClassLoader.removeClassCacheEntry(name)
并重试myClassLoader.loadClass()
,我得到:
java.lang.LinkageError:加载程序(com/my/MyClassLoader 的实例):尝试为名称 some/pckg/SomeClass 重复类定义
我阅读了互联网的大半部分,并找到了一个“解决方案”来为每个类初始化一个类加载器:
MyClassLoader myClassLoader = new MyClassLoader()
Class clazz = myClassLoader.loadClass 'some.pckg.SomeClass'
这似乎有效,但引起了我的性能问题......
重新加载类的正确方法是什么?如何重用相同的类加载器?我错过了什么?
解决方案
其实有一个技巧可以使用
原来,当你打电话
classLoader.defineClass(className, classBytes, 0, classBytes.length)
defineClass1
它调用实际调用方法的java本机loadClass
方法。
因此,可以拦截此方法并对其进行处理,使其与原始方法有所不同。
在包含缓存的类文件的文件夹中,我将以下 groovy 编译为类:A.class
println "Hello World!"
B.class
检查依赖类加载
class B extends A {
def run(){
super.run()
println "Hello from ${this.getClass()}!"
}
}
并C.class
检查多级类加载
我用这个jar编译下面的类并运行类重新加载示例
class C extends org.apache.commons.lang3.RandomUtils {
def rnd(){ nextInt() }
}
以下类+代码加载并重新加载同一个类:
import java.security.PrivilegedAction;
import java.security.AccessController;
import org.codehaus.groovy.control.CompilationFailedException;
@groovy.transform.CompileStatic
class CacheClassLoader extends GroovyClassLoader{
private File cacheDir = new File('/11/tmp/a__cache')
private CacheClassLoader(){throw new RuntimeException("default constructor not allowed")}
public CacheClassLoader(ClassLoader parent){
super(parent)
}
public CacheClassLoader(Script parent){
this(parent.getClass().getClassLoader())
}
@Override
protected Class getClassCacheEntry(String name) {
Class clazz = super.getClassCacheEntry(name)
if( clazz ){
println "getClassCacheEntry $name -> got from memory cache"
return clazz
}
def cacheFile = new File(cacheDir, name.tr('.','/')+'.class')
if( cacheFile.exists() ){
println "getClassCacheEntry $name -> cache file exists, try to load it"
//clazz = getPrivelegedLoader().defineClass(name, cacheFile.bytes)
clazz = getPrivelegedLoader().defineClass(name, cacheFile.bytes)
super.setClassCacheEntry(clazz)
}
return clazz
}
private PrivelegedLoader getPrivelegedLoader(){
PrivelegedLoader loader = AccessController.doPrivileged(new PrivilegedAction<PrivelegedLoader>() {
public PrivelegedLoader run() {
return new PrivelegedLoader();
}
});
}
public class PrivelegedLoader extends CacheClassLoader {
private final CacheClassLoader delegate
public PrivelegedLoader(){
super(CacheClassLoader.this)
this.delegate = CacheClassLoader.this
}
public Class loadClass(String name, boolean lookupScriptFiles, boolean preferClassOverScript, boolean resolve) throws ClassNotFoundException, CompilationFailedException {
Class c = findLoadedClass(name);
if (c != null) return c;
return delegate.loadClass(name, lookupScriptFiles, preferClassOverScript, resolve);
}
}
}
def c=null
//just to show intermediate class loaders could load some classes that will be used in CacheClassLoader
def cl_0 = new GroovyClassLoader(this.getClass().getClassLoader())
cl_0.addClasspath('/11/tmp/a__cache/commons-lang3-3.5.jar')
//create cache class loader
def cl = new CacheClassLoader(cl_0)
println "---1---"
c = cl.loadClass('A')
c.newInstance().run()
println "---2---"
c = cl.loadClass('A')
c.newInstance().run()
println "---3---"
cl.removeClassCacheEntry('A')
c = cl.loadClass('A')
c.newInstance().run()
println "---4---"
c = cl.loadClass('B')
c.newInstance().run()
println "---5---"
cl.removeClassCacheEntry('A')
cl.removeClassCacheEntry('B')
c = cl.loadClass('B')
c.newInstance().run()
println "---6---"
c = cl.loadClass('C')
println c.newInstance().rnd()
结果:
---1---
getClassCacheEntry A -> cache file exists, try to load it
Hello World!
---2---
getClassCacheEntry A -> got from memory cache
Hello World!
---3---
getClassCacheEntry A -> cache file exists, try to load it
Hello World!
---4---
getClassCacheEntry B -> cache file exists, try to load it
getClassCacheEntry A -> got from memory cache
Hello World!
Hello from class B!
---5---
getClassCacheEntry B -> cache file exists, try to load it
getClassCacheEntry A -> cache file exists, try to load it
Hello World!
Hello from class B!
---6---
getClassCacheEntry C -> cache file exists, try to load it
226399895
PS:不确定是否需要特权访问
推荐阅读
- azure - Azure VM 规模集无法通过 Azure 负载均衡器访问
- javascript - 验证 MusicKit 实例时出错
- python - 调用多处理队列工作者的类属性不能正常工作
- sql - 如何获取列出的存储过程的自定义用户类型列表?
- r - R POST() 状态 422 错误(msg 'value is not a valid list')
- reactjs - 安装 webpack css-loader 时出现 React-redux 错误
- ruby-on-rails - Rails 索引搜索只返回所有元素
- javafx - 如何在 TableView 中设置一次可见的行数(javafx)
- data-warehouse - 将来自非 GL 活动的事实与总帐帐户集成的最佳方式?
- html - 100% 页脚宽度左右有间隙