网站毕设,网站的优势是什么,wordpress上传图片x,中文一级a做爰片免费网站引言
Java Compiler API 是 Java 提供的一套用于在运行时编译 Java 源代码的工具。Java Compiler API的最大应用场景之一是jsp页面的编译。Tomcat把jsp编译为java文件#xff0c;然后再编译为class文件。 除了 JSP 编译#xff0c;Java Compiler API 还广泛应用于#xff1…引言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建议捕获此流以分析编译错误。参数 4arguments(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{finalJavaCompilersystemJavaCompilerToolProvider.getSystemJavaCompiler();finalintresultsystemJavaCompiler.run(null,null,null,-sourcepath,.,fibonacci.java,-d,.);if(result!0){System.out.println(编译失败);return;}URLurlnewURL(file://./);finalClass?aClassnewURLClassLoader(newURL[]{url}).loadClass(cn.edu.ncepu.Fibonacci);finalMethodfibonacciaClass.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.codecode;}privatestaticURIgetUri(StringfullClassName){// 1. 提取单纯的类名 (去掉包路径)// 找到最后一个点取后面的部分intdotIndexfullClassName.lastIndexOf(.);StringclassNameOnly(dotIndex-1)?fullClassName:fullClassName.substring(dotIndex1);// 2. 关键修改URI 中只使用单纯的类名不要带路径// 原来可能是: string:/// fullClassName Kind.SOURCE.extension// 现在改为: string:/// classNameOnly Kind.SOURCE.extensionURIuriURI.create(string:///classNameOnlyKind.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:///classNameKind.CLASS.extension),Kind.CLASS);}/** * 3. 核心重写方法 * 当编译器JavaCompiler需要写入字节码时会调用这个方法获取输出流 * return OutputStream 编译器会把字节码写入这个流 * throws IOException */OverridepublicOutputStreamopenOutputStream()throwsIOException{// 每次调用时初始化或清空流outputStreamnewByteArrayOutputStream();returnoutputStream;}/** * 4. 提供给外部获取字节码的方法 * 当编译完成后我们通过这个方法拿到字节数组用于加载类 * return 字节码数组 */publicbyte[]getCompiledBytes(){if(outputStreamnull){returnnewbyte[0];}returnoutputStream.toByteArray();}}第三步 自定义JavaFileManager自定义一个JavaFileManager,重写getJavaFileForOutput方法。但是写入编译结果之后是很难找到编译结果的所以使用一个HashMap去存储结果。publicclassCustomJavaFileManagerextendsForwardingJavaFileManager{privateHashMapString,ByteArrayJavaClasscachenewHashMap();/** * Creates a new instance of ForwardingJavaFileManager. * * param fileManager delegate to this file manager */protectedCustomJavaFileManager(JavaFileManagerfileManager){super(fileManager);}OverridepublicJavaFileObjectgetJavaFileForOutput(Locationlocation,StringclassName,JavaFileObject.Kindkind,FileObjectsibling){finalByteArrayJavaClassbyteArrayJavaClassnewByteArrayJavaClass(className);cache.put(className,byteArrayJavaClass);returnbyteArrayJavaClass;}publicHashMapString,ByteArrayJavaClassgetCache(){returncache;}}第四步 自定义CassLoaderpublicclassMemoryClassLoaderextendsClassLoader{privateCustomJavaFileManagerfileManager;publicMemoryClassLoader(CustomJavaFileManagerfileManager){this.fileManagerfileManager;}OverrideprotectedClass?findClass(Stringname)throwsClassNotFoundException{// 1. 从文件管理器的 Map 中获取编译好的类对象ByteArrayJavaClassjavaClassfileManager.getCache().get(name);if(javaClassnull){// 如果找不到尝试加载系统类比如 Object, String 等returnsuper.findClass(name);}// 2. 获取字节码byte[]byteCodejavaClass.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{finalJavaCompilersystemJavaCompilerToolProvider.getSystemJavaCompiler();try(finalCustomJavaFileManagerfileManagernewCustomJavaFileManager(systemJavaCompiler.getStandardFileManager(null,null,null))){StringjavaCodeFiles.readString(Paths.get(Fibonacci.java));finalStringclassNamecn.edu.ncepu.Fibonacci;finalStringJavaSourcesourcenewStringJavaSource(className,javaCode);finalJavaCompiler.CompilationTasktasksystemJavaCompiler.getTask(null,fileManager,null,null,null,Arrays.asList(source));task.call();finalClass?aClassnewMemoryClassLoader(fileManager).loadClass(cn.edu.ncepu.Fibonacci);finalMethodfibonacciaClass.getDeclaredMethod(fibonacci,int.class);System.out.println(fibonacci.invoke(null,5));}}}