news 2026/4/17 1:38:30

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/4/15 22:47:30

终极指南:vue-esign电子签名的10个高效应用场景

在数字化办公时代&#xff0c;电子签名已成为企业信息化建设的重要环节。vue-esign作为一款基于Vue.js的Canvas手写签字组件&#xff0c;凭借其出色的兼容性和丰富的自定义选项&#xff0c;正在成为前端开发者的首选解决方案。本文将深入探讨该组件的核心功能架构、行业应用实践…

作者头像 李华
网站建设 2026/4/13 13:42:54

如何高效管理Mac多窗口:Topit窗口置顶工具完全指南

如何高效管理Mac多窗口&#xff1a;Topit窗口置顶工具完全指南 【免费下载链接】Topit Pin any window to the top of your screen / 在Mac上将你的任何窗口强制置顶 项目地址: https://gitcode.com/gh_mirrors/to/Topit 你是否在Mac上处理多任务时感到窗口混乱不堪&…

作者头像 李华
网站建设 2026/4/16 1:14:47

Mixamo动画转换器:从Blender到Unreal Engine的根运动完整解决方案

Mixamo动画转换器&#xff1a;从Blender到Unreal Engine的根运动完整解决方案 【免费下载链接】mixamo_converter Blender addon for converting mixamo animations to Unreal 4 rootmotion 项目地址: https://gitcode.com/gh_mirrors/mi/mixamo_converter 想要让Mixamo…

作者头像 李华
网站建设 2026/4/12 22:23:57

Java工程师Python实战教程:通过MCP服务器掌握Python核心语法

核心目标 本指南专为Java工程师设计&#xff0c;通过使用Python构建MCP&#xff08;Model Context Protocol&#xff09;服务器这一实际项目&#xff0c;系统讲解Python语法要点。我们将采用"结果导向"模式&#xff1a;先展示完整代码&#xff0c;再逐行解析Python语…

作者头像 李华
网站建设 2026/4/6 22:53:03

R3nzSkin英雄联盟皮肤修改器:免费体验全英雄皮肤的秘密武器

R3nzSkin英雄联盟皮肤修改器&#xff1a;免费体验全英雄皮肤的秘密武器 【免费下载链接】R3nzSkin Skin changer for League of Legends (LOL).Everyone is welcome to help improve it. 项目地址: https://gitcode.com/gh_mirrors/r3n/R3nzSkin 还在为英雄联盟中那些昂…

作者头像 李华
网站建设 2026/4/16 6:26:56

突破语言壁垒:jsPDF多语言PDF文档生成实战指南

突破语言壁垒&#xff1a;jsPDF多语言PDF文档生成实战指南 【免费下载链接】jsPDF 项目地址: https://gitcode.com/gh_mirrors/jsp/jsPDF 你是否曾经遇到过这样的场景&#xff1a;精心生成的PDF文档在海外客户那里显示异常&#xff0c;阿拉伯语文本错乱不堪&#xff0c…

作者头像 李华