java - 在 Groovy 中重新加载类

标签 java class groovy

我有一个扩展 GroovyClassLoader 的自定义 ClassLoader,它将源代码编译为磁盘上的 .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: loader (instance of com/my/MyClassLoader): attempted duplicate class definition for name some/pckg/SomeClass

我阅读了互联网的大部分内容并找到了为每个类初始化类加载器的“解决方案”:

MyClassLoader myClassLoader = new MyClassLoader()
Class clazz = myClassLoader.loadClass 'some.pckg.SomeClass'

这似乎可行,但引起了我对性能的担忧...

重新加载类的正确方法是什么?如何重用相同的类加载器?我错过了什么?

最佳答案

其实有个技巧可以用

本来,当你打电话的时候

classLoader.defineClass(className, classBytes, 0, classBytes.length)

它调用 java 本地方法 defineClass1,实际上调用 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:不确定是否需要特权访问

关于java - 在 Groovy 中重新加载类,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58373661/

相关文章:

java - 如何将 JLabel 添加到扩展 JPanel 的类

c++ - 重载赋值运算符还是使用默认运算符?

c# - 方法中类名的别名

groovy - 如何在Griffon中轻松显示/隐藏 View

java - 如何在没有前缀的情况下从嵌入式 groovy 调用 Java 方法并访问属性

java - 如何使用pdfbox检查文本是否透明

java - 图形不显示在 JFrame 上

java - 如何从 drawableLeft android 中删除下划线

java - 如何使用同名字符串引用类

list - 如何在 Groovy 中用逗号分隔 List 元素?