news 2026/4/3 3:21:32

Java Compiler API使用

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Java Compiler API使用

引言

Java Compiler API 是 Java 提供的一套用于在运行时编译 Java 源代码的工具。Java Compiler API的最大应用场景之一是jsp页面的编译。Tomcat把jsp编译为java文件,然后再编译为class文件。
除了 JSP 编译,Java Compiler API 还广泛应用于:

  • 代码生成工具:如 Lombok、MapStruct 等。
  • 动态脚本引擎:在运行时动态加载和执行 Java 代码。
  • 热部署:在不重启应用的情况下更新代码。

编译磁盘源码

最简单的使用方法是获取JavaCompiler对象,然后编译一个java文件。
在使用 JavaCompiler.run(InputStream in, OutputStream out, OutputStream err, String… arguments) 方法时,很多开发者对前三个流参数和后面的命令行参数感到困惑。为了让你的代码更加健壮并便于调试,我们需要彻底理解它们的机制。该方法接收 3个固定流参数 + N个可变的命令行参数。

参数位置参数名称作用说明推荐用法
参数 1in(InputStream)为编译器提供输入(如交互式输入)。通常传null,使用系统默认System.in
参数 2out(OutputStream)接收编译器的正常输出信息(如-verbose信息)。null使用System.out,或传自定义流捕获日志。
参数 3err(OutputStream)接收编译器的错误和警告信息。null使用System.err,建议捕获此流以分析编译错误。
参数 4+arguments(String…)标准的javac命令行参数列表。见下文详细拆解。

arguments 的执行逻辑:

  • “-sourcepath”, “.”
    • 含义:指定源文件的查找路径。
    • 原理:告诉编译器去哪里找引用的源文件(非 .class 文件,而是 .java 文件)。这里设置为当前目录。
    • 注意:如果不指定,默认在当前目录查找。但在复杂项目中,显式指定可以避免 找不到符号 的错误。
  • “fibonacci.java”
    • 含义:要编译的目标文件。
    • 关键点:这是参数列表中唯一的“非选项”参数(non-option argument)。编译器会把所有无法识别为选项(即不以 - 开头的参数)都视为要编译的源文件。
    • 扩展:这里可以传入多个 .java 文件路径。
  • “-d”, “.”
    • 含义:指定编译生成的 .class 文件的输出目录。
    • 原理:-d 是一个选项,它后面紧跟的 . 就是该选项的值(输出目录)。
    • 重要性:如果不加 -d,编译器默认会将 .class 文件生成在与 .java 文件同级的目录,这通常会污染源码目录。
/** * 编译外部文件demo * 2025-12-22 * * @author 醒过来摸鱼 */publicclassMain{publicstaticvoidmain(String[]args)throwsMalformedURLException,NoSuchMethodException,ClassNotFoundException,InvocationTargetException,IllegalAccessException{finalJavaCompilersystemJavaCompiler=ToolProvider.getSystemJavaCompiler();finalintresult=systemJavaCompiler.run(null,null,null,"-sourcepath",".","fibonacci.java","-d",".");if(result!=0){System.out.println("编译失败");return;}URLurl=newURL("file://./");finalClass<?>aClass=newURLClassLoader(newURL[]{url}).loadClass("cn.edu.ncepu.Fibonacci");finalMethodfibonacci=aClass.getDeclaredMethod("fibonacci",int.class);System.out.println(fibonacci.invoke(null,5));}}

追踪源码发现,最终调用的是com.sun.tools.javac.main.JavaCompiler#compile方法。
但是这种方式有局限性:

  • 只能编译已经存在磁盘里的java文件。
  • 是只能把编译结果存入磁盘文件。
  • 无法处理内存中的源代码或动态生成的代码。

编译内存源码

如果源码来源于网络、内存、或者其他地方,而编译后的字节码,不存储在磁盘,就需要用另外一种方式,这也是JAVA compiler API最难的地方。如果要编译任意来源的java源码,比如内存里的java代码,需要五大步骤。

步骤一 自定义JavaFileObject以支持内存源码

JavaFileObject即可以代表java源码,也可以代表java class文件。前两个步骤都是继承SimpleJavaFileObject类。
新建一个类继承SimpleJavaFileObject并重写getCharContent方法,以支持内存源码。

/** * * 2025-12-22 * * @author 醒过来摸鱼 */publicclassStringJavaSourceextendsSimpleJavaFileObject{// 存储源代码的字符串privatefinalStringcode;/** * 构造函数 * @param fullClassName 类的全限定名,例如 "com.example.Hello" * @param code 源代码字符串 */publicStringJavaSource(StringfullClassName,Stringcode){super(getUri(fullClassName),Kind.SOURCE);this.code=code;}privatestaticURIgetUri(StringfullClassName){// 1. 提取单纯的类名 (去掉包路径)// 找到最后一个点,取后面的部分intdotIndex=fullClassName.lastIndexOf('.');StringclassNameOnly=(dotIndex==-1)?fullClassName:fullClassName.substring(dotIndex+1);// 2. 关键修改:URI 中只使用单纯的类名,不要带路径// 原来可能是: "string:///" + fullClassName + Kind.SOURCE.extension// 现在改为: "string:///" + classNameOnly + Kind.SOURCE.extensionURIuri=URI.create("string:///"+classNameOnly+Kind.SOURCE.extension);returnuri;}/** * 2. 核心重写方法 * 当编译器需要读取源代码时,会调用这个方法 * @param ignoreEncodingErrors 是否忽略编码错误 * @return CharSequence 返回源代码字符序列 */@OverridepublicCharSequencegetCharContent(booleanignoreEncodingErrors){// 直接返回内存中的字符串returncode;}}

步骤二 自定义JavaFileObject 以存储编译结果

如果要自定义一个类,来存储编译编译结果,就必须新建一个类继承SimpleJavaFileObject,然后重写openOutputStream方法。JDK自带的编译器会调用这个方法,将字节码,也就是byte数组写入这个流中。

/** * * 2025-12-22 * * @author 醒过来摸鱼 */publicclassByteArrayJavaClassextendsSimpleJavaFileObject{// 1. 定义一个输出流,用于接收编译器写入的字节码protectedByteArrayOutputStreamoutputStream;/** * 构造函数 * @param className 类的全限定名,例如 "com.example.Hello" */publicByteArrayJavaClass(StringclassName){// 2. 调用父类构造器// URI: 定义一个假的 URI,协议用 "byte://" 或 "string://" 都可以,主要是为了符合规范// Kind: 指定这是一个 CLASS 文件(而不是 SOURCE 源文件)super(URI.create("byte:///"+className+Kind.CLASS.extension),Kind.CLASS);}/** * 3. 核心重写方法 * 当编译器(JavaCompiler)需要写入字节码时,会调用这个方法获取输出流 * @return OutputStream 编译器会把字节码写入这个流 * @throws IOException */@OverridepublicOutputStreamopenOutputStream()throwsIOException{// 每次调用时,初始化或清空流outputStream=newByteArrayOutputStream();returnoutputStream;}/** * 4. 提供给外部获取字节码的方法 * 当编译完成后,我们通过这个方法拿到字节数组,用于加载类 * @return 字节码数组 */publicbyte[]getCompiledBytes(){if(outputStream==null){returnnewbyte[0];}returnoutputStream.toByteArray();}}

第三步 自定义JavaFileManager

自定义一个JavaFileManager,重写getJavaFileForOutput方法。
但是写入编译结果之后,是很难找到编译结果的,所以使用一个HashMap去存储结果。

publicclassCustomJavaFileManagerextendsForwardingJavaFileManager{privateHashMap<String,ByteArrayJavaClass>cache=newHashMap<>();/** * Creates a new instance of ForwardingJavaFileManager. * * @param fileManager delegate to this file manager */protectedCustomJavaFileManager(JavaFileManagerfileManager){super(fileManager);}@OverridepublicJavaFileObjectgetJavaFileForOutput(Locationlocation,StringclassName,JavaFileObject.Kindkind,FileObjectsibling){finalByteArrayJavaClassbyteArrayJavaClass=newByteArrayJavaClass(className);cache.put(className,byteArrayJavaClass);returnbyteArrayJavaClass;}publicHashMap<String,ByteArrayJavaClass>getCache(){returncache;}}

第四步 自定义CassLoader

publicclassMemoryClassLoaderextendsClassLoader{privateCustomJavaFileManagerfileManager;publicMemoryClassLoader(CustomJavaFileManagerfileManager){this.fileManager=fileManager;}@OverrideprotectedClass<?>findClass(Stringname)throwsClassNotFoundException{// 1. 从文件管理器的 Map 中获取编译好的类对象ByteArrayJavaClassjavaClass=fileManager.getCache().get(name);if(javaClass==null){// 如果找不到,尝试加载系统类(比如 Object, String 等)returnsuper.findClass(name);}// 2. 获取字节码byte[]byteCode=javaClass.getCompiledBytes();// 3. defineClass 是 ClassLoader 的 native 方法,用于将字节码转换为 Class 对象returndefineClass(name,byteCode,0,byteCode.length);}}

第五步 编译并反射

如果实现这种编译(有些地方叫动态编译)必须创建一个task,通过JavaCompiler#getTask方法来实现。

/** * 编译测试代码 * 2025-12-22 * * @author 醒过来摸鱼 */publicclassCompileMain{publicstaticvoidmain(String[]args)throwsIOException,NoSuchMethodException,InvocationTargetException,IllegalAccessException,ClassNotFoundException{finalJavaCompilersystemJavaCompiler=ToolProvider.getSystemJavaCompiler();try(finalCustomJavaFileManagerfileManager=newCustomJavaFileManager(systemJavaCompiler.getStandardFileManager(null,null,null))){StringjavaCode=Files.readString(Paths.get("Fibonacci.java"));finalStringclassName="cn.edu.ncepu.Fibonacci";finalStringJavaSourcesource=newStringJavaSource(className,javaCode);finalJavaCompiler.CompilationTasktask=systemJavaCompiler.getTask(null,fileManager,null,null,null,Arrays.asList(source));task.call();finalClass<?>aClass=newMemoryClassLoader(fileManager).loadClass("cn.edu.ncepu.Fibonacci");finalMethodfibonacci=aClass.getDeclaredMethod("fibonacci",int.class);System.out.println(fibonacci.invoke(null,5));}}}
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/3/31 19:55:19

canvas的画布尺寸

这个设置的是canvas的画布尺寸canvas.width myVideo.videoWidth || 900; // 视频原生宽度canvas.height myVideo.videoHeight || 500; // 视频原生高度这个设置的是canvas html 元素在页面上显示的尺寸canvas.style.width "900px"; // 保持显示尺寸canvas.style…

作者头像 李华
网站建设 2026/4/2 8:07:22

AcFunDown:零基础也能轻松掌握的A站视频下载神器

还在为无法离线保存AcFun精彩内容而困扰吗&#xff1f;AcFunDown作为一款完全免费的开源工具&#xff0c;凭借其强大的下载功能和简洁的操作界面&#xff0c;已经成为A站用户必备的视频保存利器。无论你是想收藏喜欢的视频还是备份学习资料&#xff0c;这款工具都能提供完美的解…

作者头像 李华
网站建设 2026/3/31 6:00:05

LangFlow与农业科技结合:作物病害识别与防治建议

LangFlow与农业科技结合&#xff1a;作物病害识别与防治建议 在广袤的农田里&#xff0c;一位农民举起手机&#xff0c;对着一片发黄卷曲的番茄叶拍照上传。几秒钟后&#xff0c;他的屏幕上弹出一份清晰报告&#xff1a;“疑似早疫病&#xff0c;建议使用代森锰锌喷雾&#xff…

作者头像 李华
网站建设 2026/3/27 21:58:32

VisualGGPK2终极指南:5步解锁PoE游戏资源编辑

想要为《流放之路》(Path of Exile)打造独特MOD却无从下手&#xff1f;VisualGGPK2这款专业工具正是你需要的解决方案。作为专门处理PoE游戏GGPK文件的完整工具集&#xff0c;它能让你轻松浏览、提取和修改游戏内的各种资源文件&#xff0c;从纹理图片到核心数据表格&#xff0…

作者头像 李华
网站建设 2026/3/23 22:00:09

精通Mod Organizer 2:虚拟文件系统与冲突管理深度解析

Mod Organizer 2作为专业级PC游戏模组管理工具&#xff0c;其核心技术架构基于创新的虚拟文件系统和智能冲突检测机制。对于已经具备基础模组管理经验的中级用户而言&#xff0c;深入理解这些技术原理能够显著提升模组配置的稳定性和管理效率。本文将重点剖析MO2的核心技术实现…

作者头像 李华
网站建设 2026/3/25 22:56:30

快速掌握vue-esign电子签名组件的核心技巧

快速掌握vue-esign电子签名组件的核心技巧 【免费下载链接】vue-esign canvas手写签字 电子签名 A canvas signature component of vue. 项目地址: https://gitcode.com/gh_mirrors/vu/vue-esign vue-esign是一个基于Vue.js的轻量级电子签名解决方案&#xff0c;它通过H…

作者头像 李华