您的位置:首页 > 其它

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

2010-05-01 00:49 603 查看
两种动态加载类的方法
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);
本文出自 “ni掌柜的IT专栏” 博客,请务必保留此出处http://nileader.blog.51cto.com/1381108/307975
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐