• Java_动态加载


    Java类动态加载(一)——java源文件动态编译为class文件
    最近在做java动态加载这方面的工作,起初也遇到了很多困难。网上关于这方便的东西很零散,为了便于日后回过头来再看,于是我将这几天的心得体会总结如下。

    什么情况下会需要用java程序动态的编译java源文件,动态的加载java类文件呢?如果很少遇到这样的需求的兄弟们可能不会清楚动态的编译、动态的加载用在一个什么样的场景。下面我将我遇到的场景描述下。
    Sdl说明:
    为了更好的说明需求,先解释下,我这里的sdl文件是干什么用的。

    sdl文件里面主要是定义了一些远程调用接口的相关信息,根据这些信息我们可以自己手动生成java版本的远程调用接口。具体有些什么东西呢?比如说,接口的名称、所在包路径、接口参数、接口用的java bean等等。我们得到这些sdl文件后,可以利用sdl2java.exe或者sdl2java.sh工具将文件编译成java源文件,利用这些源文件就可以进行远程接口调用。

    需求说明:
    用户只提供一个sdl文件,需要程序能够根据这个sdl文件,提取出所有远程调用接口,让用户在前端输入参数,然后进行远程调用。

    实现方案:
    用户上传一个sdl文件到工程临时目录,然后程序自动的调用sdl2java.exe或者sdl2java.sh命令将sdl文件动态编译成一系列的java源文件,然后程序动态的将这些java文件编译成class文件,最后再动态加载到项目中。

    整个实现方案有三个难点:

    • 用java程序调用sdl2java.exe或者sdl2java.sh命令解析sdl文件,生成一系列的java源文件
    • 动态编译上述java源文件为class类文件
    • 动态加载class类文件



    本文讲解的实现方案的前提:
    本文主要讲解第二个难点如何实现,因此假设程序已经实现了将sdl文件转换成了一系列的java文件,并存放到服务器中根目录的tempsdlsrc目录中

    动态将java文件编译为class文件解决方案:
    将tempsdlsrc目录中的java源文件编译成class文件,并存放到tempsdlclasses目录中。

    java中早就提供了用java方式去动态编译java源文件的接口,有关java动态编译的API都在javax.tools包中。本文主要使用jdk1.6以上版本提供的JavaCompiler工具来动态编译java源文件。
    我们可以通过ToolProvider类的静态方法getSystemJavaCompiler得到JavaCompiler对象实例。

    // 获取编译器实例  
    JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); 
    


    得到JavaCompiler对象实例后,我们可以调用该工具的getTask(Writer out, JavaFileManager fileManager, DiagnosticListener<? super JavaFileObject> diagnosticListener, Iterable<String> options, Iterable<String> classes, Iterable<? extends JavaFileObject> compilationUnits) 方法获取一个编译任务对象。

    CompilationTask compilationTask = compiler.getTask(null, fileManager, diagnostics, options, null, compilationUnits); 
    


    该方法的第一个参数为文件输出,这里我们可以不指定,我们采用javac命令的-d参数来指定class文件的生成目录。
    第二个参数为文件管理器实例

    // 获取标准文件管理器实例  
    StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
    


    该文件管理器实例的作用就是将我们需要动态编译的java源文件转换为getTask需要的编译单元。

    // 获取要编译的编译单元  
    Iterable<? extends JavaFileObject> compilationUnits = fileManager.getJavaFileObjectsFromFiles(sourceFileList);
    


    第三个参数DiagnosticCollector<JavaFileObject> diagnostics是在编译出错时,存放编译错误信息。
    第四个参数为编译命令选项,就是javac命令的可选项,这里我们主要使用了-d和-sourcepath这两个选项。

    /** 
    * 编译选项,在编译java文件时,编译程序会自动的去寻找java文件引用的其他的java源文件或者class。 -sourcepath选项就是定义java源文件的查找目录, -classpath选项就是定义class文件的查找目录,-d就是编译文件的输出目录。 
    */  
    Iterable<String> options = Arrays.asList("-d", targetDir, "-sourcepath", sourceDir);
    


    第五个参数为类名称,具体作用没研究清楚。
    第六个参数为上面提到的编译单元,就是我们需要编译的java源文件

    当我们得到CompilationTask compilationTask编译任务后,我们就可以调用compilationTask.call()方法进行编译工作

    // 运行编译任务 
    compilationTask.call()
    




    下面代码的运行前提条件:
    首先需要将附件中的sdl.rar文件解压到F:亚信工作SDL文件目录下,sdl目录结构见下图



    由于编译这些java文件需要用到两个与sdl相关的jar包,因此在运行下面的代码前,需要将sdl.rar中lib目录下的jar包导入到工程里面来。

    源代码如下,直接运行里面的main方法即可。运行后,如果输出:编译成功,则程序正常运行,可以通过查看sdlclasses目录中是否有class文件检查运行结果。

    package util;
    
    import java.io.File;
    import java.io.FileFilter;
    import java.io.FilenameFilter;
    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.List;
    
    import javax.tools.Diagnostic;
    import javax.tools.DiagnosticCollector;
    import javax.tools.JavaCompiler;
    import javax.tools.JavaFileObject;
    import javax.tools.StandardJavaFileManager;
    import javax.tools.ToolProvider;
    import javax.tools.JavaCompiler.CompilationTask;
    
    import org.apache.commons.lang.StringUtils;
    
    /**
     * @author zhengtian
     * 
     * @date 2012-4-17 下午07:24:24
     */
    @SuppressWarnings("all")
    public class DynamicCompilerUtil {
    
        /**
         * 编译java文件
         * 
         * @param filePath
         *            文件或者目录(若为目录,自动递归编译)
         * @param sourceDir
         *            java源文件存放目录
         * @param targetDir
         *            编译后class类文件存放目录
         * @param diagnostics
         *            存放编译过程中的错误信息
         * @return
         * @throws Exception
         */
        public static boolean compiler(String filePath, String sourceDir, String targetDir, DiagnosticCollector<JavaFileObject> diagnostics)
                throws Exception {
            // 获取编译器实例
            JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
            // 获取标准文件管理器实例
            StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
            try {
                if (StringUtils.isEmpty(filePath) && StringUtils.isEmpty(sourceDir) && StringUtils.isEmpty(targetDir)) {
                    return false;
                }
                // 得到filePath目录下的所有java源文件
                File sourceFile = new File(filePath);
                List<File> sourceFileList = new ArrayList<File>();
                getSourceFiles(sourceFile, sourceFileList);
                // 没有java文件,直接返回
                if (sourceFileList.size() == 0) {
                    System.out.println(filePath + "目录下查找不到任何java文件");
                    return false;
                }
                // 获取要编译的编译单元
                Iterable<? extends JavaFileObject> compilationUnits = fileManager.getJavaFileObjectsFromFiles(sourceFileList);
                /**
                 * 编译选项,在编译java文件时,编译程序会自动的去寻找java文件引用的其他的java源文件或者class。 -sourcepath选项就是定义java源文件的查找目录, -classpath选项就是定义class文件的查找目录。
                 */
                Iterable<String> options = Arrays.asList("-d", targetDir, "-sourcepath", sourceDir);
                CompilationTask compilationTask = compiler.getTask(null, fileManager, diagnostics, options, null, compilationUnits);
                // 运行编译任务
                return compilationTask.call();
            } finally {
                fileManager.close();
            }
        }
    
        /**
         * 查找该目录下的所有的java文件
         * 
         * @param sourceFile
         * @param sourceFileList
         * @throws Exception
         */
        private static void getSourceFiles(File sourceFile, List<File> sourceFileList) throws Exception {
            if (sourceFile.exists() && sourceFileList != null) {// 文件或者目录必须存在
                if (sourceFile.isDirectory()) {// 若file对象为目录
                    // 得到该目录下以.java结尾的文件或者目录
                    File[] childrenFiles = sourceFile.listFiles(new FileFilter() {
                        public boolean accept(File pathname) {
                            if (pathname.isDirectory()) {
                                return true;
                            } else {
                                String name = pathname.getName();
                                return name.endsWith(".java") ? true : false;
                            }
                        }
                    });
                    // 递归调用
                    for (File childFile : childrenFiles) {
                        getSourceFiles(childFile, sourceFileList);
                    }
                } else {// 若file对象为文件
                    sourceFileList.add(sourceFile);
                }
            }
        }
    
        public static void main(String[] args) {
            try {
                // 编译F:\亚信工作\SDL文件\sdl\src目录下的所有java文件
                String filePath = "F:\亚信工作\SDL文件\sdl\src";
                String sourceDir = "F:\亚信工作\SDL文件\sdl\src";
                String targetDir = "F:\亚信工作\SDL文件\sdl\classes";
                DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<JavaFileObject>();
                boolean compilerResult = compiler(filePath, sourceDir, targetDir, diagnostics);
                if (compilerResult) {
                    System.out.println("编译成功");
                } else {
                    System.out.println("编译失败");
                    for (Diagnostic diagnostic : diagnostics.getDiagnostics()) {
                        // System.out.format("%s[line %d column %d]-->%s%n", diagnostic.getKind(), diagnostic.getLineNumber(),
                        // diagnostic.getColumnNumber(),
                        // diagnostic.getMessage(null));
                        System.out.println(diagnostic.getMessage(null));
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    转自:http://www.myexception.cn/program/627493.html

  • 相关阅读:
    数组操作方法和迭代方法
    三元运算符
    数组求和/去重
    javascript保留字
    window.onload和document.ready区别
    alert()和consloe.log()区别
    Eventutil函数封装
    前端中的事件流
    react的生命周期
    小程序初体验
  • 原文地址:https://www.cnblogs.com/gisblogs/p/5504124.html
Copyright © 2020-2023  润新知