您的位置:首页 > 其它

通过自定义类加载器进行动态编译与动态实现接口

2014-12-25 17:16 274 查看
两种动态加载类的方法
1. 使用java.lang.Class.forName(fullClassName ).
2. 使用自定义类加载器.
这里看看如何通过自定义加载器来进行java类加载.

通过默认使用的类加载器来加载类,一般都是通过如下方法:

Class obj = aLoader.loadClass(customFullClassName, true);



看一下loadClass方法的实现:

protected synchronized Class<?> loadClass(String name, boolean resolve) 
    throws ClassNotFoundException 
    { 
    Class c = findLoadedClass(name); 
    …………… 
……… 
}

这是一个采用双亲委派机制的实现,最主要的就是一个findLoadedClass方法,其它就是递归找父亲了..
所以要进行自定义扩展,就要从这个方法入手.
findLoadClass的默认实现如下:
这里我们看到了一个最让我们感兴趣的方法: defineClass
protected final Class<?> defineClass(String name, byte[] b, int off, int len);
这个方法将一个字节数据转换成一个Class并初始化.
当然如果不能转换的话,异常: ClassFormatError.

所以我们就有了一个自由加载文件系统中任意合法类文件的思路了.
写一个类继承ClassLoader-—将文件系统中的*.class文件以byte[]的方式传入---交给defineClass处理. 这样的做法似乎破坏了Java的双亲委派

所以类定义如下:




/** 
 * 一个自定义的类加载器. 
 * @author nileader 
 */ 
public class CustomClassLoader extends ClassLoader { 
 
    public static final String classPath = System.getProperty("user.dir") + "\\bin\\"; 
 
    /** 
     * 自定义的一个加载方法,这样的做法似乎破坏了Java的双亲委派 
     * @param classFullName 
     * @return 
     */ 
    protected Class customLoadClass(String classFullName){ 
        String filePath = classFullName2FileName(classFullName, classPath); 
        byte[] data = loadClassFromFS(filePath ); 
        //调用defineClass将一个字节数据转换成一个类并进行初始化工作. 
        return defineClass(classFullName, data, 0, data.length); 
    } 
 
    /** 
     * 从文件系统中加载类文件,生成一个byte[]. 
     * @param name   文件路径 
     * @return   类文件的字节码数组 
     */ 
    private byte[] loadClassFromFS(String filePath) { 
        FileInputStream fis = null; 
        byte[] byteSource = null; 
        try { 
            fis = new FileInputStream(new File(filePath ) ); 
            ByteArrayOutputStream tempSource = new ByteArrayOutputStream(); 
            int readChar= 0; 
            while ((readChar = fis.read()) != -1) { 
                tempSource.write(readChar ); 
            } 
            byteSource = tempSource.toByteArray(); 
        } catch (IOException e) { 
            //IO出错 
        } 
        return byteSource; 
    } 
     
    /** 
     * 将一个完整类名转换为一个当前工程classpath为基础的文件路径. 
     * @param classFullName     一个完整类名 
     * @param classPath         当前工程类路径 
     * @return 
     */ 
    public String classFullName2FileName(String classFullName, String classPath){ 
        classFullName = classFullName.replaceAll("[.]", "\\\\" ); 
        return classPath  + classFullName + ".class"; 
    }


现在,就可以使用修改后的类加载器来进行类的加载了.


CustomClassLoader customClassLoader = new CustomClassLoader(); 
 
Class obj = customClassLoader.customLoadClass("com.test.DemoInterface" );

加载完后就可以Class、Field和Method的一些方法来进行接口的“实现”.
方法很多.
这里所谓的”实现”是: 就是根据程序运行过程中的一些需求,进行具体方法的写. 如:
动态编译
动态编译的过程是:

生成一个临时的*.java文件, 使用com.sun.tools.javac.Main来模拟命令行中的java文件编译.





/** 
     * 将Java源程序生成一个类文件. 
     *  
     * @param sourceCode 
     *            要编译的Java源程序 
     * @throws Exception 
     */ 
    public static boolean compile(String sourceCode, String className) { 
        File file = null; 
        // 生成一个临时的*.java文件. 
        try { 
            file = File.createTempFile("JavaRuntime", ".java", new File( 
                    targetPath)); 
        } catch (IOException e) { 
            // 发生IO异常 
        } 
        String fileName = null; 
        if (null != file) { 
            // 取得类名 
            fileName = file.getName(); 
            file.deleteOnExit(); 
        } 
 
        // 将代码输出到java文件中去. 
        PrintWriter out = null; 
        try { 
            out = new PrintWriter(new FileOutputStream(file)); 
        } catch (FileNotFoundException e) { 
            // 发生 FileNotFoundException 异常. 
        } 
        if (null != out) { 
            out.write(sourceCode); 
            out.flush(); 
            out.close(); 
        } 
 
        /** 开始编译 */ 
        if (fileName != null) { 
            Main.compile(new String[] { "-d", 
                    System.getProperty("user.dir") + "\\bin\\", 
                    targetPath + fileName }); 
        } 
 
        return true; 
 
    }

生成好之后,就可以用同样的方式进行实现类的加载,通过如下方式进行
Class c1 = customClassLoader.customLoadClass(implementsFullName); 
        Constructor con = c1.getDeclaredConstructor(); 
        con.setAccessible(true ); 
        Object o = con.newInstance(); 
        // 调用封装bean的方法 
        Method method = c1.getMethod("print"); 
        method.setAccessible(true); 
        method.invoke(o);
本文出自 “专栏:Paxos与ZooKeeper” 博客,请务必保留此出处http://nileader.blog.51cto.com/1381108/307975
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: