前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >技术分享——深入理解Java的动态编译

技术分享——深入理解Java的动态编译

作者头像
用户5546570
发布2020-06-09 10:53:19
1.2K0
发布2020-06-09 10:53:19
举报

写在前面:2020年面试必备的Java后端进阶面试题总结了一份复习指南在Github上,内容详细,图文并茂,有需要学习的朋友可以Star一下! GitHub地址:https://github.com/abel-max/Java-Study-Note/tree/master

前提

笔者很久之前就有个想法:参考现有的主流 ORM 框架的设计,造一个 ORM 轮子,在基本不改变使用体验的前提下把框架依赖的大量的反射设计去掉,这些反射 API 构筑的组件使用 动态编译 加载的实例去替代,从而可以得到接近于直接使用原生 JDBC 的性能。于是带着这样的想法,深入学习 Java 的动态编译。编写本文的时候使用的是 JDK11 。

基本原理

下面这个很眼熟的图来源于《深入理解Java虚拟机》前端编译与优化的章节,主要描述编译的过程:

技术分享——深入理解Java的动态编译

上图看起来只有三步,其实每一步都有大量的步骤,下图尝试相对详细地描述具体的步骤(图比较大难以分割,直接放原图):

技术分享——深入理解Java的动态编译

实际上,仅仅对于编译这个过程来说,开发者或者使用者不必要完全掌握其中的细节, JDK 提供了一个工具包 javax.tools 让使用者可以用简易的 API 进行编译(其实在大多数请下,开发者是面向业务功能开发,像编译和打包这些细节一般直接由开发工具、 Maven 、 Gradle 等工具完成):

技术分享——深入理解Java的动态编译

具体的使用过程包括:

  • 获取一个 javax.tools.JavaCompiler 实例。
  • 基于 Java 文件对象初始化一个编译任务 javax.tools.JavaCompiler$CompilationTask 实例。
  • CompilationTask 实例执行结果代表着编译过程的成功与否。

我们熟知的 javac 编译器其实就是 JavaCompiler 接口的实现,在 JDK11 中,对应的实现类为 com.sun.tools.javac.api.JavacTool 。在 JDK8 中不存在 JavaCompiler 接口,具体的编译入口类为 com.sun.tools.javac.main.JavaCompiler 。

因为 JVM 里面的 Class 是基于 ClassLoader 隔离的,所以编译成功之后可以通过自定义的类加载器加载对应的类实例,然后就可以应用反射 API 进行实例化和后续的调用。

JDK动态编译

JDK 动态编译的步骤在上一节已经清楚地说明,这里造一个简单的场景。假设存在一个接口如下:

代码语言:javascript
复制
package club.throwable.compile;public interface HelloService {        void sayHello(String name);}// 默认实现package club.throwable.compile;public class DefaultHelloService implements HelloService {    @Override    public void sayHello(String name) {        System.out.println(String.format("%s say hello [by default]", name));    }}

我们可以通过字符串 SOURCE_CODE 定义一个类:

代码语言:javascript
复制
static String SOURCE_CODE = "package club.throwable.compile;\n" +        "\n" +        "public class JdkDynamicCompileHelloService implements HelloService{\n" +        "\n" +        "    @Override\n" +        "    public void sayHello(String name) {\n" +        "        System.out.println(String.format(\"%s say hello [by jdk dynamic compile]\", name));\n" +        "    }\n" +        "}";// 这里不需要定义类文件,还原类文件内容如下package club.throwable.compile;public class JdkDynamicCompileHelloService implements HelloService{    @Override    public void sayHello(String name) {        System.out.println(String.format("%s say hello [by jdk dynamic compile]", name));    }}

在组装编译任务实例之前,还有几项工作需要完成:

  • 内置的 JavaFileObject 标准实现 SimpleJavaFileObject 是面向类源码文件,由于动态编译时候输入的是类源码文件的内容字符串,需要自行实现 JavaFileObject 。
  • 内置的 JavaFileManager 是面向类路径下的 Java 源码文件进行加载,这里也需要自行实现 JavaFileManager 。
  • 需要自定义一个 ClassLoader 实例去加载编译出来的动态类。

实现JavaFileObject

自行实现一个 JavaFileObject ,其实可以简单点直接继承 SimpleJavaFileObject ,覆盖需要用到的方法即可:

代码语言:javascript
复制
public class CharSequenceJavaFileObject extends SimpleJavaFileObject {    public static final String CLASS_EXTENSION = ".class";    public static final String JAVA_EXTENSION = ".java";    private static URI fromClassName(String className) {        try {            return new URI(className);        } catch (URISyntaxException e) {            throw new IllegalArgumentException(className, e);        }    }    private ByteArrayOutputStream byteCode;    private final CharSequence sourceCode;    public CharSequenceJavaFileObject(String className, CharSequence sourceCode) {        super(fromClassName(className + JAVA_EXTENSION), Kind.SOURCE);        this.sourceCode = sourceCode;    }    public CharSequenceJavaFileObject(String fullClassName, Kind kind) {        super(fromClassName(fullClassName), kind);        this.sourceCode = null;    }    public CharSequenceJavaFileObject(URI uri, Kind kind) {        super(uri, kind);        this.sourceCode = null;    }        @Override    public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {        return sourceCode;    }    @Override    public InputStream openInputStream() {        return new ByteArrayInputStream(getByteCode());    }        // 注意这个方法是编译结果回调的OutputStream,回调成功后就能通过下面的getByteCode()方法获取目标类编译后的字节码字节数组    @Override    public OutputStream openOutputStream() {        return byteCode = new ByteArrayOutputStream();    }    public byte[] getByteCode() {        return byteCode.toByteArray();    }}

如果编译成功之后,直接通过自行添加的 CharSequenceJavaFileObject#getByteCode() 方法即可获取目标类编译后的字节码对应的字节数组(二进制内容)。这里的 CharSequenceJavaFileObject 预留了多个构造函数用于兼容原有的编译方式。

实现ClassLoader

只要简单继承 ClassLoader 即可,关键是要覆盖原来的 ClassLoader#findClass() 方法,用于搜索自定义的 JavaFileObject 实例,从而提取对应的字节码字节数组进行装载,为了实现这一点可以添加一个哈希表作为缓存,键-值分别是全类名的别名( xx.yy.MyClass 形式,而非 URI 模式)和目标类对应的 JavaFileObject 实例。

代码语言:javascript
复制
public class JdkDynamicCompileClassLoader extends ClassLoader {    public static final String CLASS_EXTENSION = ".class";    private final Map<String, JavaFileObject> javaFileObjectMap = Maps.newConcurrentMap();    public JdkDynamicCompileClassLoader(ClassLoader parentClassLoader) {        super(parentClassLoader);    }    @Override    protected Class<?> findClass(String name) throws ClassNotFoundException {        JavaFileObject javaFileObject = javaFileObjectMap.get(name);        if (null != javaFileObject) {            CharSequenceJavaFileObject charSequenceJavaFileObject = (CharSequenceJavaFileObject) javaFileObject;            byte[] byteCode = charSequenceJavaFileObject.getByteCode();            return defineClass(name, byteCode, 0, byteCode.length);        }        return super.findClass(name);    }    @Nullable    @Override    public InputStream getResourceAsStream(String name) {        if (name.endsWith(CLASS_EXTENSION)) {            String qualifiedClassName = name.substring(0, name.length() - CLASS_EXTENSION.length()).replace('/', '.');            CharSequenceJavaFileObject javaFileObject = (CharSequenceJavaFileObject) javaFileObjectMap.get(qualifiedClassName);            if (null != javaFileObject && null != javaFileObject.getByteCode()) {                return new ByteArrayInputStream(javaFileObject.getByteCode());            }        }        return super.getResourceAsStream(name);    }    /**     * 暂时存放编译的源文件对象,key为全类名的别名(非URI模式),如club.throwable.compile.HelloService     */    void addJavaFileObject(String qualifiedClassName, JavaFileObject javaFileObject) {        javaFileObjectMap.put(qualifiedClassName, javaFileObject);    }    Collection<JavaFileObject> listJavaFileObject() {        return Collections.unmodifiableCollection(javaFileObjectMap.values());    }}

实现JavaFileManager

JavaFileManager 是 Java 文件的抽象管理器,它用于管理常规的 Java 文件,但是不局限于文件,也可以管理其他来源的 Java 类文件数据。下面就通过实现一个自定义的 JavaFileManager 用于管理字符串类型的源代码。为了简单起见,可以直接继承已经存在的 ForwardingJavaFileManager :

代码语言:javascript
复制
public class JdkDynamicCompileJavaFileManager extends ForwardingJavaFileManager<JavaFileManager> {    private final JdkDynamicCompileClassLoader classLoader;    private final Map<URI, JavaFileObject> javaFileObjectMap = Maps.newConcurrentMap();    public JdkDynamicCompileJavaFileManager(JavaFileManager fileManager, JdkDynamicCompileClassLoader classLoader) {        super(fileManager);        this.classLoader = classLoader;    }    private static URI fromLocation(Location location, String packageName, String relativeName) {        try {            return new URI(location.getName() + '/' + packageName + '/' + relativeName);        } catch (URISyntaxException e) {            throw new IllegalArgumentException(e);        }    }    @Override    public FileObject getFileForInput(Location location, String packageName, String relativeName) throws IOException {        JavaFileObject javaFileObject = javaFileObjectMap.get(fromLocation(location, packageName, relativeName));        if (null != javaFileObject) {            return javaFileObject;        }        return super.getFileForInput(location, packageName, relativeName);    }    /**     * 这里是编译器返回的同(源)Java文件对象,替换为CharSequenceJavaFileObject实现     */    @Override    public JavaFileObject getJavaFileForOutput(Location location, String className, JavaFileObject.Kind kind, FileObject sibling) throws IOException {        JavaFileObject javaFileObject = new CharSequenceJavaFileObject(className, kind);        classLoader.addJavaFileObject(className, javaFileObject);        return javaFileObject;    }    /**     * 这里覆盖原来的类加载器     */    @Override    public ClassLoader getClassLoader(Location location) {        return classLoader;    }    @Override    public String inferBinaryName(Location location, JavaFileObject file) {        if (file instanceof CharSequenceJavaFileObject) {            return file.getName();        }        return super.inferBinaryName(location, file);    }    @Override    public Iterable<JavaFileObject> list(Location location, String packageName, Set<JavaFileObject.Kind> kinds, boolean recurse) throws IOException {        Iterable<JavaFileObject> superResult = super.list(location, packageName, kinds, recurse);        List<JavaFileObject> result = Lists.newArrayList();        // 这里要区分编译的Location以及编译的Kind        if (location == StandardLocation.CLASS_PATH && kinds.contains(JavaFileObject.Kind.CLASS)) {            // .class文件以及classPath下            for (JavaFileObject file : javaFileObjectMap.values()) {                if (file.getKind() == JavaFileObject.Kind.CLASS && file.getName().startsWith(packageName)) {                    result.add(file);                }            }            // 这里需要额外添加类加载器加载的所有Java文件对象            result.addAll(classLoader.listJavaFileObject());        } else if (location == StandardLocation.SOURCE_PATH && kinds.contains(JavaFileObject.Kind.SOURCE)) {            // .java文件以及编译路径下            for (JavaFileObject file : javaFileObjectMap.values()) {                if (file.getKind() == JavaFileObject.Kind.SOURCE && file.getName().startsWith(packageName)) {                    result.add(file);                }            }        }        for (JavaFileObject javaFileObject : superResult) {            result.add(javaFileObject);        }        return result;    }    /**     * 自定义方法,用于添加和缓存待编译的源文件对象     */    public void addJavaFileObject(Location location, String packageName, String relativeName, JavaFileObject javaFileObject) {        javaFileObjectMap.put(fromLocation(location, packageName, relativeName), javaFileObject);    }}

注意在这个类中引入了自定义类加载器 JdkDynamicCompileClassLoader ,目的是为了实现 JavaFileObject 实例的共享以及为文件管理器提供类加载器实例。

动态编译和运行

前置准备工作完成,我们可以通过 JavaCompiler 去编译这个前面提到的字符串,为了字节码的兼容性更好,编译的时候可以指定稍低的 JDK 版本例如 1.6 :

代码语言:javascript
复制
public class Client {    static String SOURCE_CODE = "package club.throwable.compile;\n" +            "\n" +            "public class JdkDynamicCompileHelloService implements HelloService{\n" +            "\n" +            "    @Override\n" +            "    public void sayHello(String name) {\n" +            "        System.out.println(String.format(\"%s say hello [by jdk dynamic compile]\", name));\n" +            "    }\n" +            "}";    /**     * 编译诊断收集器     */    static DiagnosticCollector<JavaFileObject> DIAGNOSTIC_COLLECTOR = new DiagnosticCollector<>();    public static void main(String[] args) throws Exception {        // 获取系统编译器实例        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();        // 设置编译参数 - 指定编译版本为JDK1.6以提高兼容性        List<String> options = new ArrayList<>();        options.add("-source");        options.add("1.6");        options.add("-target");        options.add("1.6");        // 获取标准的Java文件管理器实例        StandardJavaFileManager manager = compiler.getStandardFileManager(DIAGNOSTIC_COLLECTOR, null, null);        // 初始化自定义类加载器        JdkDynamicCompileClassLoader classLoader = new JdkDynamicCompileClassLoader(Thread.currentThread().getContextClassLoader());        // 初始化自定义Java文件管理器实例        JdkDynamicCompileJavaFileManager fileManager = new JdkDynamicCompileJavaFileManager(manager, classLoader);        String packageName = "club.throwable.compile";        String className = "JdkDynamicCompileHelloService";        String qualifiedName = packageName + "." + className;        // 构建Java源文件实例        CharSequenceJavaFileObject javaFileObject = new CharSequenceJavaFileObject(className, SOURCE_CODE);        // 添加Java源文件实例到自定义Java文件管理器实例中        fileManager.addJavaFileObject(                StandardLocation.SOURCE_PATH,                packageName,                className + CharSequenceJavaFileObject.JAVA_EXTENSION,                javaFileObject        );        // 初始化一个编译任务实例        JavaCompiler.CompilationTask compilationTask = compiler.getTask(                null,                fileManager,                DIAGNOSTIC_COLLECTOR,                options,                null,                Lists.newArrayList(javaFileObject)        );        // 执行编译任务        Boolean result = compilationTask.call();        System.out.println(String.format("编译[%s]结果:%s", qualifiedName, result));        Class<?> klass = classLoader.loadClass(qualifiedName);        HelloService instance = (HelloService) klass.getDeclaredConstructor().newInstance();        instance.sayHello("throwable");    }}

输出结果如下:

代码语言:javascript
复制
编译[club.throwable.compile.JdkDynamicCompileHelloService]结果:truethrowable say hello [by jdk dynamic compile]

可见通过了字符串的类源码,实现了动态编译、类加载、反射实例化以及最终的方法调用。另外,编译过程的诊断信息可以通过 DiagnosticCollector 实例获取。为了复用,这里可以把 JDK 动态编译的过程抽取到一个方法中:

代码语言:javascript
复制
public final class JdkCompiler {    static DiagnosticCollector<JavaFileObject> DIAGNOSTIC_COLLECTOR = new DiagnosticCollector<>();    @SuppressWarnings("unchecked")    public static <T> T compile(String packageName,                                String className,                                String sourceCode,                                Class<?>[] constructorParamTypes,                                Object[] constructorParams) throws Exception {        // 获取系统编译器实例        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();        // 设置编译参数        List<String> options = new ArrayList<>();        options.add("-source");        options.add("1.6");        options.add("-target");        options.add("1.6");        // 获取标准的Java文件管理器实例        StandardJavaFileManager manager = compiler.getStandardFileManager(DIAGNOSTIC_COLLECTOR, null, null);        // 初始化自定义类加载器        JdkDynamicCompileClassLoader classLoader = new JdkDynamicCompileClassLoader(Thread.currentThread().getContextClassLoader());        // 初始化自定义Java文件管理器实例        JdkDynamicCompileJavaFileManager fileManager = new JdkDynamicCompileJavaFileManager(manager, classLoader);        String qualifiedName = packageName + "." + className;        // 构建Java源文件实例        CharSequenceJavaFileObject javaFileObject = new CharSequenceJavaFileObject(className, sourceCode);        // 添加Java源文件实例到自定义Java文件管理器实例中        fileManager.addJavaFileObject(                StandardLocation.SOURCE_PATH,                packageName,                className + CharSequenceJavaFileObject.JAVA_EXTENSION,                javaFileObject        );        // 初始化一个编译任务实例        JavaCompiler.CompilationTask compilationTask = compiler.getTask(                null,                fileManager,                DIAGNOSTIC_COLLECTOR,                options,                null,                Lists.newArrayList(javaFileObject)        );        Boolean result = compilationTask.call();        System.out.println(String.format("编译[%s]结果:%s", qualifiedName, result));        Class<?> klass = classLoader.loadClass(qualifiedName);        return (T) klass.getDeclaredConstructor(constructorParamTypes).newInstance(constructorParams);    }}

Javassist动态编译

既然有 JDK 的动态编译,为什么还存在 Javassist 这样的字节码增强工具?撇开性能或者效率层面, JDK 动态编译存在比较大的局限性,比较明显的一点就是无法完成字节码插桩,换言之就是无法基于原有的类和方法进行修饰或者增强,但是 Javassist 可以做到。再者, Javassist 提供的 API 和 JDK 反射的 API 十分相近,如果反射平时用得比较熟练, Javassist 的上手也就变得比较简单。这里仅仅列举一个增强前面提到的 DefaultHelloService 的例子,先引入依赖:

代码语言:javascript
复制
<dependency>    <groupId>org.javassist</groupId>    <artifactId>javassist</artifactId>    <version>3.27.0-GA</version></dependency>

编码如下:

代码语言:javascript
复制
public class JavassistClient {    public static void main(String[] args) throws Exception {        ClassPool pool = ClassPool.getDefault();        CtClass cc = pool.get("club.throwable.compile.DefaultHelloService");        CtMethod ctMethod = cc.getDeclaredMethod("sayHello", new CtClass[]{pool.get("java.lang.String")});        ctMethod.insertBefore("System.out.println(\"insert before by Javassist\");");        ctMethod.insertAfter("System.out.println(\"insert after by Javassist\");");        Class<?> klass = cc.toClass();        System.out.println(klass.getName());        HelloService helloService = (HelloService) klass.getDeclaredConstructor().newInstance();        helloService.sayHello("throwable");    }}

输出结果如下:

代码语言:javascript
复制
club.throwable.compile.DefaultHelloServiceinsert before by Javassistthrowable say hello [by default]insert after by Javassist

Javaassist 这个单词其实是 Java 和 Assist 两个单词拼接在一起,意为 Java 助手,是一个 Java 字节码增强类库:

  • 可以基于已经存在的类进行字节码增强,例如修改已经存在的方法、变量,甚至是直接在原有的类中添加新的方法等。
  • 可以完全像积木拼接一样,动态拼出一个全新的类。

不像 ASM ( ASM 的学习曲线比较陡峭,属于相对底层的字节码操作类库,当然从性能上来看 ASM 对字节码增强的效率远高于其他高层次封装的框架)那样需要对字节码编程十分了解, Javaassist 降低了字节码增强功能的入门难度。

进阶例子

现在定义一个接口 MysqlInfoMapper ,用于动态执行一条已知的 SQL ,很简单,就是查询 MySQL 的系统表 mysql 里面的用户信息 SELECT Host,User FROM mysql.user :

代码语言:javascript
复制
@Datapublic class MysqlUser {    private String host;    private String user;}public interface MysqlInfoMapper {    List<MysqlUser> selectAllMysqlUsers();}

假设现在只提供一个 MySQL 的驱动包( mysql:mysql-connector-java:jar:8.0.20 ),暂时不能依赖任何高层次的框架,要动态实现 MysqlInfoMapper 接口,优先整理需要的组件:

  • 需要一个连接管理器去管理 MySQL 的连接。
  • 需要一个 SQL 执行器用于执行查询 SQL 。
  • 需要一个结果处理器去提取和转换查询结果。

为了简单起见,笔者在定义这三个组件接口的时候顺便在接口中通过单例进行实现(部分配置完全写死):

代码语言:javascript
复制
// 连接管理器public interface ConnectionManager {    String USER_NAME = "root";    String PASS_WORD = "root";    String URL = "jdbc:mysql://localhost:3306/mysql?useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8&useSSL=false";    Connection newConnection() throws SQLException;    void closeConnection(Connection connection);    ConnectionManager X = new ConnectionManager() {        @Override        public Connection newConnection() throws SQLException {            return DriverManager.getConnection(URL, USER_NAME, PASS_WORD);        }        @Override        public void closeConnection(Connection connection) {            try {                connection.close();            } catch (Exception ignore) {            }        }    };}// 执行器public interface SqlExecutor {    ResultSet execute(Connection connection, String sql) throws SQLException;    SqlExecutor X = new SqlExecutor() {        @Override        public ResultSet execute(Connection connection, String sql) throws SQLException {            Statement statement = connection.createStatement();            statement.execute(sql);            return statement.getResultSet();        }    };}// 结果处理器public interface ResultHandler<T> {    T handleResultSet(ResultSet resultSet) throws SQLException;    ResultHandler<List<MysqlUser>> X = new ResultHandler<List<MysqlUser>>() {        @Override        public List<MysqlUser> handleResultSet(ResultSet resultSet) throws SQLException {            try {                List<MysqlUser> result = Lists.newArrayList();                while (resultSet.next()) {                    MysqlUser item = new MysqlUser();                    item.setHost(resultSet.getString("Host"));                    item.setUser(resultSet.getString("User"));                    result.add(item);                }                return result;            } finally {                resultSet.close();            }        }    };}

接着需要动态编译 MysqlInfoMapper 的实现类,它的源文件的字符串内容如下(注意不要在类路径下新建这个 DefaultMysqlInfoMapper 类):

代码语言:javascript
复制
package club.throwable.compile;import java.sql.Connection;import java.sql.ResultSet;import java.util.List;public class DefaultMysqlInfoMapper implements MysqlInfoMapper {    private final ConnectionManager connectionManager;    private final SqlExecutor sqlExecutor;    private final ResultHandler resultHandler;    private final String sql;    public DefaultMysqlInfoMapper(ConnectionManager connectionManager,                                  SqlExecutor sqlExecutor,                                  ResultHandler resultHandler,                                  String sql) {        this.connectionManager = connectionManager;        this.sqlExecutor = sqlExecutor;        this.resultHandler = resultHandler;        this.sql = sql;    }    @Override    public List<MysqlUser> selectAllMysqlUsers() {        try {            Connection connection = connectionManager.newConnection();            try {                ResultSet resultSet = sqlExecutor.execute(connection, sql);                return (List<MysqlUser>) resultHandler.handleResultSet(resultSet);            } finally {                connectionManager.closeConnection(connection);            }        } catch (Exception e) {            // 暂时忽略异常处理,统一封装为IllegalStateException            throw new IllegalStateException(e);        }    }}

然后编写一个客户端进行动态编译和执行:

代码语言:javascript
复制
public class MysqlInfoClient {    static String SOURCE_CODE = "package club.throwable.compile;\n" +            "import java.sql.Connection;\n" +            "import java.sql.ResultSet;\n" +            "import java.util.List;\n" +            "\n" +            "public class DefaultMysqlInfoMapper implements MysqlInfoMapper {\n" +            "\n" +            "    private final ConnectionManager connectionManager;\n" +            "    private final SqlExecutor sqlExecutor;\n" +            "    private final ResultHandler resultHandler;\n" +            "    private final String sql;\n" +            "\n" +            "    public DefaultMysqlInfoMapper(ConnectionManager connectionManager,\n" +            "                                  SqlExecutor sqlExecutor,\n" +            "                                  ResultHandler resultHandler,\n" +            "                                  String sql) {\n" +            "        this.connectionManager = connectionManager;\n" +            "        this.sqlExecutor = sqlExecutor;\n" +            "        this.resultHandler = resultHandler;\n" +            "        this.sql = sql;\n" +            "    }\n" +            "\n" +            "    @Override\n" +            "    public List<MysqlUser> selectAllMysqlUsers() {\n" +            "        try {\n" +            "            Connection connection = connectionManager.newConnection();\n" +            "            try {\n" +            "                ResultSet resultSet = sqlExecutor.execute(connection, sql);\n" +            "                return (List<MysqlUser>) resultHandler.handleResultSet(resultSet);\n" +            "            } finally {\n" +            "                connectionManager.closeConnection(connection);\n" +            "            }\n" +            "        } catch (Exception e) {\n" +            "            // 暂时忽略异常处理,统一封装为IllegalStateException\n" +            "            throw new IllegalStateException(e);\n" +            "        }\n" +            "    }\n" +            "}\n";    static String SQL = "SELECT Host,User FROM mysql.user";    public static void main(String[] args) throws Exception {        MysqlInfoMapper mysqlInfoMapper = JdkCompiler.compile(                "club.throwable.compile",                "DefaultMysqlInfoMapper",                SOURCE_CODE,                new Class[]{ConnectionManager.class, SqlExecutor.class, ResultHandler.class, String.class},                new Object[]{ConnectionManager.X, SqlExecutor.X, ResultHandler.X, SQL});        System.out.println(JSON.toJSONString(mysqlInfoMapper.selectAllMysqlUsers()));    }}

最终的输出结果是:

代码语言:javascript
复制
编译[club.throwable.compile.DefaultMysqlInfoMapper]结果:true[{"host":"%","user":"canal"},{"host":"%","user":"doge"},{"host":"localhost","user":"mysql.infoschema"},{"host":"localhost","user":"mysql.session"},{"host":"localhost","user":"mysql.sys"},{"host":"localhost","user":"root"}]

然后笔者查看本地安装的 MySQL 中的结果,验证该查询结果是正确的。

技术分享——深入理解Java的动态编译

这里笔者为了简化整个例子,没有在 MysqlInfoMapper#selectAllMysqlUsers() 方法中添加查询参数,可以尝试一下查询的 SQL 是 SELECT Host,User FROM mysql.user WHERE User = 'xxx' 场景下的编码实现。

如果把动态实现的 DefaultMysqlInfoMapper 注册到 IOC 容器中,就可以实现 MysqlInfoMapper 按照类型自动装配。

如果把 SQL 和参数处理可以抽离到单独的文件中,并且实现一个对应的文件解析器,那么就可以把类文件和 SQL 隔离, Mybatis 和 Hibernate 都是这样做的。

小结

动态编译或者更底层的面向字节码层面的编程,其实是一个十分有挑战性但是可以创造无限可能的领域,本文只是简单分析了一下 Java 源码编译的过程,并且通过一些简单的例子进行动态编译的模拟,离使用于实际应用中还有不少距离,后面需要花更多的时间去分析一下相关领域的知识。

来源: https://www.tuicool.com/articles/eQju6nb

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 基本原理
  • JDK动态编译
    • 实现JavaFileObject
      • 实现ClassLoader
        • 实现JavaFileManager
          • 动态编译和运行
          • Javassist动态编译
          • 进阶例子
          • 小结
          相关产品与服务
          云数据库 MySQL
          腾讯云数据库 MySQL(TencentDB for MySQL)为用户提供安全可靠,性能卓越、易于维护的企业级云数据库服务。其具备6大企业级特性,包括企业级定制内核、企业级高可用、企业级高可靠、企业级安全、企业级扩展以及企业级智能运维。通过使用腾讯云数据库 MySQL,可实现分钟级别的数据库部署、弹性扩展以及全自动化的运维管理,不仅经济实惠,而且稳定可靠,易于运维。
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档