两种动态加载类的方法
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);