郑州最好的网站建设,wordpress获取文章二级菜单,简述建设一个网站的基本步骤,网站怎么做支付宝支付接口编译#xff0c;一般来说就是将源代码转换成机器码的过程#xff0c;比如在C语言中中#xff0c;将C语言源代码编译成a.out,#xff0c;但是在Java中的理解可能有点不同#xff0c;编译指的是将java 源代码转换成class字节码的过程#xff0c;而不是真正的机器码#xf…编译一般来说就是将源代码转换成机器码的过程比如在C语言中中将C语言源代码编译成a.out,但是在Java中的理解可能有点不同编译指的是将java 源代码转换成class字节码的过程而不是真正的机器码这是因为中间隔着一个JVM。虽然对于编译的理解不同但是编译的过程基本上都是相同的。但是我们熟悉的编译大都是点击一下Eclipse或者Intellij Idea的Run或者Build按钮但是在点击后究竟发生什么其实我没仔细了解过只是知道这个程序运行起来了但是如果你使用过javac命令去编译代码时可能了解的就更深一些据说印度的Java程序员最开始编程的时候使用的都是文本编辑器而不是IDE这样更能接触底层的过程。 除了使用javac命令编译Java程序从Java 1.6开始我们也可以在程序运行时根据程序实际运行来构建一些类并进行编译这需要JDK提供给我们一些可供调用的接口来完成编译工作。 一、编译源码需要啥 那么问题来了如果要了解运行时编译的过程和对应的接口首先要明白的就是编译这个过程都会涉及哪些工具和要解决的问题从我们熟悉的构建过程开始
编译工具编译器显然没有这个东西我们啥也干不了 要编译的源代码文件没有这个东西到底编啥呢 源代码、字节码文件的管理其实这里靠的是文件系统的支持包括文件的创建和管理 编译过程中的选项要编译的代码版本、目标源代码位置classpath和编码等等见相关文章 编译中编译器输出的诊断信息告诉你编译成功还是失败会有什么隐患提出警告信息 按照这些信息JDK也提供了可编程的接口对象上述信息这些API全部放在javax.tools包下对应上面的信息如下 编译器涉及到的接口和类如下
JavaCompiler JavaCompiler.CompilationTask ToolProvider 在上面的接口和类中ToolProvider类似是一个工具箱它可以提供JavaCompiler类的实例并返回然后该实例可以获取JavaCompiler.CompilationTask实例然后由JavaCompiler.CompilationTask实例来执行对应的编译任务其实这个执行过程是一个并发的过程。
源代码文件涉及到接口和类如下
FileObject ForwardingFileObject JavaFileObject JavaFileObject.Kind ForwardingJavaFileObject SimpleJavaFileObject 上述后面的4个接口和类都是FileObject子接口或者实现类FIleObject接口代表了对文件的一种抽象可以包括普通的文件也可以包括数据库中的数据库等其中规定了一些操作包括读写操作读取信息删除文件等操作。我们要用的其实是JavaFileObject接口其中还增加了一些操作Java源文件和字节码文件特有的API而SimpleJavaFileObject是JavaFileObject接口的实现类但是其中你可以发现很多的接口其实就是直接返回一个值或者抛出一个异常并且该类的构造器由protected修饰的所以要实现复杂的功能需要我们必须扩展这个类。ForwardingFileObject、ForwardingJavaFileObject类似其中都是包含了对应的FileObject和JavaFileObject并将方法的执行委托给这些对象它的目的其实就是为了提高扩展性。
文件的创建和管理涉及接口和类如下
JavaFileManager JavaFileManager.Location StandardJavaFileManager ForwardingJavaFileManager StandardLocation
JavaCompiler compiler ToolProvider.getSystemJavaCompiler();
DiagnosticCollectorJavaFileObject collector new DiagnosticCollector();
// 该JavaFileManager实例是com.sun.tools.javac.file.JavacFileManager
JavaFileManager manager compiler.getStandardFileManager(collector, null, null);JavaFileManager用来创建JavaFileObject包括从特定位置输出和输入一个JavaFileObjectForwardingJavaFileManager也是出于委托的目的。而StandardJavaFileManager是JavaFileManager直接实现类JavaFileManager.Location和StandardLocation描述的是JavaFileObject对象的位置由JavaFileManager使用来决定在哪创建或者搜索文件。由于在javax.tools包下没有JavaFileManager对象的实现类如果我们想要使用可以自己实现该接口也可以通过JavaCompiler类中的getStandardFileManager完成如下
编译选项的管理
OptionChecker 这个接口基本上没有用过。
诊断信息的收集涉及接口和类如下
Diagnostic DiagnosticListener Diagnostic.Kind DiagnosticCollector Diagnostic会输出编译过程中产生的问题包括问题的信息和出现问题的定位信息问题的类别则在Diagnostic.Kind中定义。DiagnosticListener则是从编译器中获取诊断信息当出现诊断信息时则会调用其中的report方法DiagnosticCollector则是进一步实现了DiagnosticListener并将诊断信息收集到一个list中以便处理。
在Java源码运行时编译的时候还会遇到一个与普通编译不同的问题就是类加载器的问题由于这个问题过大而且比较核心将会专门写一篇文章介绍。 二、如何在运行时编译源代码 好了说了这么多了其实都是为了下面的实例作为铺垫我们还是从上述的几个组件来说明。
1、准备编译器对象 这里只有一种方法如下
JavaCompiler compiler ToolProvider.getSystemJavaCompiler();
// ......
// 在其他实例都已经准备完毕后, 构建编译任务, 其他实例的构建见如下
Boolean result compiler.getTask(null, manager, collector, options,null,Arrays.asList(javaFileObject));2、诊断信息的收集
// 初始化诊断收集器
DiagnosticCollectorJavaFileObject collector new DiagnosticCollector();
// ......
// 编译完成之后获取编译过程中的诊断信息
collector.getDiagnostics().forEach(item - System.out.println(item.toString()))在这个过程中可以通过Diagnostic实例获取编译过程中出错的行号、位置以及错误原因等信息。 3、源代码文件对象的构建 由于JDK提供的FileObject、ForwardingFileObject、JavaFileObject、ForwardingJavaFileObject、SimpleJavaFileObject都无法直接使用所以我们需要根据需求自定义此时我们要明白SimpleJavaFileObject类中的哪些方法是必须要覆盖的可以看如下过程
下面是调用compiler中的getTask方法时的调用栈可以看出从main()方法中开始调用getTask方法开始直到编译工作开始进行首先读取源代码调用com.sun.tools.javac.main包中的readSource()方法源代码如下
public CharSequence readSource(JavaFileObject filename) {try {inputFiles.add(filename);return filename.getCharContent(false);} catch (IOException e) {log.error(error.reading.file, filename, JavacFileManager.getMessage(e));return null;}
}其中调用ClientCodeWrapper$WrappedFileObject对象中的filename.getCharContent(false)方法来读取要编译的源码源代码如下
public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {try {return clientFileObject.getCharContent(ignoreEncodingErrors);} catch (ClientCodeException e) {throw e;} catch (RuntimeException e) {throw new ClientCodeException(e);} catch (Error e) {throw new ClientCodeException(e);}
}而其中的clientFileObject.getCharContent(ignoreEncodingErrors)其实就是调用我们实现的自定义的JavaFIleObject对象因此源代码文本是必须的因此getCharContent方法是必须实现的另外在编译器编译完成之后要将编译完成的字节码输出如下图
这时调用writeClass()输出字节码通过打开一个输出流OutputStream来完成该过程因此openOutputStream()这个方法也是必须实现的。因此该类的实现如下
public static class MyJavaFileObject extends SimpleJavaFileObject {private String source;private ByteArrayOutputStream outPutStream;// 该构造器用来输入源代码public MyJavaFileObject(String name, String source) {// 1、先初始化父类由于该URI是通过类名来完成的必须以.java结尾。// 2、如果是一个真实的路径比如是file:///test/demo/Hello.java则不需要特别加.java// 3、这里加的String:///并不是一个真正的URL的schema, 只是为了区分来源super(URI.create(String:/// name Kind.SOURCE.extension), Kind.SOURCE);this.source source;}// 该构造器用来输出字节码public MyJavaFileObject(String name, Kind kind){super(URI.create(String:/// name kind.extension), kind);source null;}Overridepublic CharSequence getCharContent(boolean ignoreEncodingErrors){if(source null){throw new IllegalArgumentException(source null);}return source;}Overridepublic OutputStream openOutputStream() throws IOException {outPutStream new ByteArrayOutputStream();return outPutStream;}// 获取编译成功的字节码byte[]public byte[] getCompiledBytes(){return outPutStream.toByteArray();}
}4、文件管理器对象的构建 文件管理对象显然也是不能直接使用JDK提供的接口因为只有ForwardingJavaFileManager是一个类其他的都是接口而且在ForwardingJavaFileManager中构造器又是protected所以如果想定制化使用的话需要实现接口或者继承类如果只是简单使用可以如下
JavaCompiler compiler ToolProvider.getSystemJavaCompiler();
DiagnosticCollectorJavaFileObject collector new DiagnosticCollector();
// 该JavaFileManager实例是com.sun.tools.javac.file.JavacFileManager
JavaFileManager manager compiler.getStandardFileManager(collector, null, null);但是compiler.getStandardFileManager()返回的是com.sun.tools.javac.file.JavacFileManager实例这个不是公开的类所以我们无法直接使用只能通过这种调用返回实例。
但是我们课也可以构造自己的FileManager为了更好的构建需要理解JavaFileManager在内存中编译时的使用过程如下
在编译过程中首先是编译器会遍历JavaFileManager对象获取指定位置的所有符合要求的JavaFileObject对象甚至可以递归遍历这时调用的是list()方法该方法会扫面所有涉及的到的包包括一个类和它实现的接口和继承的类
之后根据获取到的JavaFileObject对象获取它的二进制表示的名称通过调用inferBinaryName()方法
之后是输出编译类而类的表示为JavaFileObject对象注意此时的JavaFileObject.Kind为CLASS调用的方法是getJavaFileForOutput()注意该方法的调用是在JavaFileObject中openOutputStream()方法之前如下图
既然了解了上述的流程我们自定义的文件管理器如下
private static MapString, JavaFileObject fileObjects new ConcurrentHashMap();
// 这里继承类不实现接口是为了避免实现过多的方法
public static class MyJavaFileManager extends ForwardingJavaFileManagerJavaFileManager {protected MyJavaFileManager(JavaFileManager fileManager) {super(fileManager);}Overridepublic JavaFileObject getJavaFileForInput(Location location, String className, JavaFileObject.Kind kind) throws IOException {JavaFileObject javaFileObject fileObjects.get(className);if(javaFileObject null){super.getJavaFileForInput(location, className, kind);}return javaFileObject;}Overridepublic JavaFileObject getJavaFileForOutput(Location location, String qualifiedClassName, JavaFileObject.Kind kind, FileObject sibling) throws IOException {JavaFileObject javaFileObject new MyJavaFileObject(qualifiedClassName, kind);fileObjects.put(qualifiedClassName, javaFileObject);return javaFileObject;}
}5、编译选项的选择 在使用javac命令的时候可以添加很多的选项在实现API完成编译的时候也可以提供参数比如编译目标输出路径以及类路径等等如下
ListString options new ArrayList();
options.add(-target);
options.add(1.8);
options.add(-d);
options.add(/);
// 省略......
compiler.getTask(null, javaFileManager, collector, options, null, Arrays.asList(javaFileObject));6、其他问题 想将编译完成的字节码输出为文件也不需要上面自定义JavaFileManager直接使用JavaCompiler提供的即可而且在自定义的JavaFileObject中也不需要实现OpenOutStream这种方法代替要提供options.add(“-d”)options.add(“/”)等编译选项如果不输出为文件按照上述的例子即可 StandardLocation中的元素可以代替真实的路径位置但是不会输出为文件可以为一个内存中的文件 在编译完成之后要将字节码文件加载进来因此就要涉及到类加载机制由于这也是一个很大的话题所以后面会专门总结一篇但是在这里还是要说明一下由于上面编译时没有额外的依赖包所以不用考虑加载依赖文件的问题但是当如果有这样的需求时我们可以利用类加载的委托机制将依赖文件的加载全部交给父加载器去做即可。 完整的代码如下
package com.wdx.compiler;import org.slf4j.Logger;
import org.slf4j.LoggerFactory;import javax.tools.Diagnostic;
import javax.tools.DiagnosticCollector;
import javax.tools.FileObject;
import javax.tools.ForwardingJavaFileManager;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileManager;
import javax.tools.JavaFileObject;
import javax.tools.SimpleJavaFileObject;
import javax.tools.ToolProvider;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;public class CubeJavaCompiler{private static final Logger logger LoggerFactory.getLogger(CubeJavaCompiler.class);private static final JavaCompiler _compiler ToolProvider.getSystemJavaCompiler();private static final DiagnosticCollectorJavaFileObject collector new DiagnosticCollector();private static final CubeJavaFileManager manager new CubeJavaFileManager(_compiler.getStandardFileManager(collector, null, null));private static final MapString, JavaFileObject fileObjectMap new ConcurrentHashMap();private static ListString options new ArrayList();static {options.add(-Xlint:unchecked);options.add(-target);options.add(1.8);}public static Class? compile(String code, String className) throws ClassNotFoundException{String qualified className.substring(className.lastIndexOf(.) 1, className.length());CubeJavaObject cubeJavaObject new CubeJavaObject(qualified, code);JavaCompiler.CompilationTask task _compiler.getTask(null, manager, collector, options, null, Arrays.asList(cubeJavaObject));task.call();//输出诊断信息for (Diagnostic? extends JavaFileObject diagnostic : collector.getDiagnostics()) {try {logger.error(编译错误:{}, diagnostic.toString());} catch (Exception e) {logger.error(输出内容错误, e);}}return cubeJavaClassLoader.loadClass(className);}private static ClassLoader cubeJavaClassLoader new ClassLoader() {Overrideprotected Class? findClass(String name) throws ClassNotFoundException {JavaFileObject fileObject fileObjectMap.get(name);if(fileObject ! null){byte[] bytes ((CubeJavaObject)fileObject).getCompiledBytes();return defineClass(name, bytes, 0, bytes.length);}try{return ClassLoader.getSystemClassLoader().loadClass(name);} catch (Exception e){logger.error(加载类失败,{}, name, e);return super.findClass(name);}}};private static class CubeJavaObject extends SimpleJavaFileObject{private String code;private ByteArrayOutputStream outPutStream;public CubeJavaObject(String qualified, String code) {super(URI.create(String:/// qualified Kind.SOURCE.extension), Kind.SOURCE);this.code code;}public CubeJavaObject(String qualified, Kind kind) {super(URI.create(String:/// qualified kind.extension), kind);}Overridepublic CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {if(code null){throw new IllegalArgumentException(code required);}return code;}Overridepublic OutputStream openOutputStream() throws IOException {outPutStream new ByteArrayOutputStream();return outPutStream;}public byte[] getCompiledBytes(){return outPutStream.toByteArray();}}private static class CubeJavaFileManager extends ForwardingJavaFileManagerJavaFileManager {public CubeJavaFileManager(JavaFileManager fileManager) {super(fileManager);}Overridepublic JavaFileObject getJavaFileForInput(Location location, String className, JavaFileObject.Kind kind) throws IOException {JavaFileObject javaFileObject fileObjectMap.get(className);if(javaFileObject null){super.getJavaFileForInput(location, className, kind);}return javaFileObject;}Overridepublic JavaFileObject getJavaFileForOutput(Location location, String className, JavaFileObject.Kind kind, FileObject sibling) throws IOException {JavaFileObject javaFileObject new CubeJavaObject(className, kind);fileObjectMap.put(className, javaFileObject);return javaFileObject;}}
}