前言
前几天突然接到一个技术需求,想要做一个功能。前端有一个表单,在页面上可以直接写java代码,写完后就能保存到数据库,并且这个代码实时生效。这岂非是不用发版就可以随时改代码了吗?而且有bug也不怕,随时改。
适用场景:代码逻辑需要经常变动的业务。
核心思想
页面改动java代码字符串
java代码字符串编译成class
动态加载到jvm
实现重点
JDK提供了一个工具包javax.tools让使用者可以用简易的API进行编译。
这些工具包的使用步骤:
获取一个javax.tools.JavaCompiler实例。
基于Java文件对象初始化一个编译任务CompilationTask实例。
因为JVM里面的Class是基于ClassLoader隔离的,所以编译成功之后可以通过自定义的类加载器加载对应的类实例
使用反射API进行实例化和后续的调用。
1.代码编译
这一步需要将java文件编译成class,其实平常的开发过程中,我们的代码编译都是由IDEA、Maven等工具完成。
内置的SimpleJavaFileObject是面向源码文件的,而我们的是源码字符串,所以需要实现JavaFileObject接口自定义一个JavaFileObject。
publicclassCharSequenceJavaFileObjectextendsSimpleJavaFileObject{publicstaticfinalStringCLASS_EXTENSION=".class";publicstaticfinalStringJAVA_EXTENSION=".java";privatestaticURIfromClassName(StringclassName){try{returnnewURI(className);}catch(URISyntaxExceptione){thrownewIllegalArgumentException(className,e);}}privateByteArrayOutputStreambyteCode;privatefinalCharSequencesourceCode;publicCharSequenceJavaFileObject(StringclassName,CharSequencesourceCode){super(fromClassName(className+JAVA_EXTENSION),Kind.SOURCE);this.sourceCode=sourceCode;}publicCharSequenceJavaFileObject(StringfullClassName,Kindkind){super(fromClassName(fullClassName),kind);this.sourceCode=null;}publicCharSequenceJavaFileObject(URIuri,Kindkind){super(uri,kind);this.sourceCode=null;}
OverridepublicCharSequencegetCharContent(booleanignoreEncodingErrors)throwsIOException{returnsourceCode;}OverridepublicInputStreamopenInputStream(){returnnewByteArrayInputStream(getByteCode());}//注意这个方法是编译结果回调的OutputStream,回调成功后就能通过下面的getByteCode()方法获取目标类编译后的字节码字节数组OverridepublicOutputStreamopenOutputStream(){returnbyteCode=newByteArrayOutputStream();}publicbyte[]getByteCode(){returnbyteCode.toByteArray();}}如果编译成功之后,直接通过CharSequenceJavaFileObject#getByteCode()方法即可获取目标类编译后的字节码对应的字节数组(二进制内容)
实现ClassLoader
因为JVM里面的Class是基于ClassLoader隔离的,所以编译成功之后得通过自定义的类加载器加载对应的类实例,否则是加载不了的,因为同一个类只会加载一次。
主要