Jdk19 动态编译 Java源码为 Class 文件

news2024/11/18 18:41:59

动态编译 Java 源码为 Class

  • 一.背景
    • 1.Jdk 版本
    • 2.需求
  • 二.Java 源码动态编译实现
    • 1.Maven 依赖
    • 2.源码包装类
    • 3.Java 文件对象封装类
    • 4.文件管理器封装类
    • 5.类加载器
    • 6.类编译器
  • 三.动态编译测试
    • 1.普通测试类
    • 2.接口实现类
    • 3.测试
  • 四.用动态编译 Class 替换 SpringBoot 的 Bean(未完)

一.背景

1.Jdk 版本

版本查看命令:java -version

在这里插入图片描述

2.需求

本来想看下项目热部署的实现,比如 SpringBoot 不停机热加载 Jar 实现功能修改;后来看到 Jdk 支持源码动态编译,如果可以实现,那么就可以在线直接修改代码,再利用 SpringBoot 管理起来,替换旧的 Bean,实现功能修改。可能实际应用场景不多,可以做应急修改,线上服务最终还是需要把修改后的代码重新部署更为稳妥。

其实动态修改代码还可以通过 Arthas 实现,包括反编译、编译等更多功能

二.Java 源码动态编译实现

源码编译需要用到的关键类:

说明
JavaCompiler编译器 ToolProvider.getSystemJavaCompiler();
SimpleJavaFileObject文件对象类,可以表示源码、类文件
ClassLoader顶层类加载器,抽象类
ForwardingJavaFileManager文件管理器

项目结构如图

在这里插入图片描述

1.Maven 依赖

暂时只是一个 Maven 项目,未引入其他依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>DynamicDemo</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>19</maven.compiler.source>
        <maven.compiler.target>19</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

</project>

2.源码包装类

基于 SimpleJavaFileObject 扩展,用于封装类名、源码信息

package org.example.demo.util;

import javax.tools.SimpleJavaFileObject;
import java.io.IOException;
import java.net.URI;

/**
 * @author moon
 * @date 2023-02-15 20:32
 * @since 1.8
 */
public class CustomSourceCode extends SimpleJavaFileObject {

    /**
     * 类名称
     */
    private String className;

    /**
     * 类源码
     */
    private String contents;

    /**
     * 源码初始化
     * @param className
     * @param contents
     */
    public CustomSourceCode(String className, String contents) {
        super(URI.create("string:///" + className.replace('.', '/')
                + Kind.SOURCE.extension), Kind.SOURCE);
        this.contents = contents;
        this.className = className;
    }

    /**
     * 获取类名
     * @return
     */
    public String getClassName() {
        return className;
    }

    /**
     * 源码字符序列
     * @param ignoreEncodingErrors ignore encoding errors if true
     * @return
     * @throws IOException
     */
    public CharSequence getCharContent(boolean ignoreEncodingErrors)
            throws IOException {
        return contents;
    }
}

3.Java 文件对象封装类

基于 SimpleJavaFileObject 实现,封装了类名、类字节输出流信息

package org.example.demo.util;

import javax.tools.SimpleJavaFileObject;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URI;
import java.net.URISyntaxException;

/**
 * @author moon
 * @date 2023-02-15 20:52
 * @since 1.8
 */
public class CustomJavaFileObject extends SimpleJavaFileObject {

    /**
     * 类名称
     */
    private String className;

    /**
     * 输出的字节码流
     */
    private ByteArrayOutputStream toByteArray = new ByteArrayOutputStream();

    /**
     * Construct a SimpleJavaFileObject of the given kind and with the
     * given URI.
     *
     * @param className
     */
    public CustomJavaFileObject(String className) throws URISyntaxException {
        super(new URI(className), Kind.CLASS);
        this.className = className;
    }

    @Override
    public OutputStream openOutputStream() throws IOException {
        return toByteArray;
    }

    /**
     * 获取类名
     * @return
     */
    public String getClassName() {
        return className;
    }

    /**
     * 获取字节信息
     * @return
     */
    public byte[] getByteCode() {
        return toByteArray.toByteArray();
    }
}

4.文件管理器封装类

package org.example.demo.util;

import javax.tools.FileObject;
import javax.tools.ForwardingJavaFileManager;
import javax.tools.JavaFileManager;
import javax.tools.JavaFileObject;

/**
 * @author moon
 * @date 2023-02-15 20:00
 * @since 1.8
 */
public class CustomJavaFileManager extends ForwardingJavaFileManager<JavaFileManager> {

    /**
     * 自定义类加载器
     */
    private CustomClassLoader loader;

    /**
     * 初始化
     * @param fileManager
     * @param loader
     */
    protected CustomJavaFileManager(JavaFileManager fileManager, CustomClassLoader loader) {
        super(fileManager);
        this.loader = loader;
    }

    @Override
    public JavaFileObject getJavaFileForOutput(
            JavaFileManager.Location location, String className,
            JavaFileObject.Kind kind, FileObject sibling) {

        try {
            CustomJavaFileObject innerClass = new CustomJavaFileObject(className);
            loader.addJavaCode(innerClass);
            return innerClass;
        } catch (Exception e) {
            throw new RuntimeException("exception when creating in-memory output stream for  " + className, e);
        }
    }

    @Override
    public ClassLoader getClassLoader(JavaFileManager.Location location) {
        return loader;
    }

}

5.类加载器

用于从 CustomJavaFileObject 获取字节流,并通过 ClassLoader.defineClass 生成类

package org.example.demo.util;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author moon
 * @date 2023-02-15 20:50
 * @since 1.8
 */
public class CustomClassLoader extends ClassLoader{

    /**
     * 缓存源代码对象
     */
    private Map<String, CustomJavaFileObject> fileCacheMap = new ConcurrentHashMap<>(16);

    /**
     * 初始化类加载器
     * @param parent
     */
    public CustomClassLoader(ClassLoader parent) {
        super(parent);
    }

    /**
     * 添加源码缓存
     * @param obj
     */
    public void addJavaCode(CustomJavaFileObject obj) {
        fileCacheMap.put(obj.getName(), obj);
    }

    /**
     * 获取类
     * @param className
     *          The <a href="#binary-name">binary name</a> of the class
     *
     * @return
     * @throws ClassNotFoundException
     */
    @Override
    protected Class<?> findClass(String className) throws ClassNotFoundException {
        if (fileCacheMap.containsKey(className)){
            byte[] byteCode = fileCacheMap.get(className).getByteCode();
            return defineClass(className, byteCode, 0, byteCode.length);
        } else {
            return super.findClass(className);
        }
    }
}

6.类编译器

简要说明一下调用流程:读取源码 -> 编译 -> 加载为 Class => 构建对象及使用

package org.example.demo.util;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author moon
 * @date 2023-02-15 20:50
 * @since 1.8
 */
public class CustomClassLoader extends ClassLoader{

    /**
     * 缓存源代码对象
     */
    private Map<String, CustomJavaFileObject> fileCacheMap = new ConcurrentHashMap<>(16);

    /**
     * 初始化类加载器
     * @param parent
     */
    public CustomClassLoader(ClassLoader parent) {
        super(parent);
    }

    /**
     * 添加源码缓存
     * @param obj
     */
    public void addJavaCode(CustomJavaFileObject obj) {
        fileCacheMap.put(obj.getName(), obj);
    }

    /**
     * 获取类
     * @param className
     *          The <a href="#binary-name">binary name</a> of the class
     *
     * @return
     * @throws ClassNotFoundException
     */
    @Override
    protected Class<?> findClass(String className) throws ClassNotFoundException {
        if (fileCacheMap.containsKey(className)){
            byte[] byteCode = fileCacheMap.get(className).getByteCode();
            return defineClass(className, byteCode, 0, byteCode.length);
        } else {
            return super.findClass(className);
        }
    }
}

三.动态编译测试

1.普通测试类

定义一个普通测试类,包含:有、无参构造初始化,有、无参方法调用

用户积分器

package org.example.demo.common;

public class UserSort {

    private String name;
    private int score;

    public UserSort (){
    }

    public UserSort (String name, int sort){
        this.name = name;
        this.score = sort;
    }

    public void reset(){
        this.score = 0;
        System.out.println("姓名: " + this.name + " 积分重置: " + this.score);
    }

    public void insert(int score){
        this.score += score;
        System.out.println("姓名: " + this.name + " 加分结果: " + this.score);
    }

    public void reduce(int score){
        this.score -= score;
        System.out.println("姓名: " + this.name + " 减分结果: " + this.score);
    }
}

封装一个静态方法:

public static void commonClass(CustomClassCompiler compiler) throws Exception{

        String sourceCode = "package org.example.demo.common;\n" +
                "\n" +
                "public class UserSort {\n" +
                "\n" +
                "    private String name;\n" +
                "    private int score;\n" +
                "\n" +
                "    public UserSort (){\n" +
                "    }\n" +
                "\n" +
                "    public UserSort (String name, int sort){\n" +
                "        this.name = name;\n" +
                "        this.score = sort;\n" +
                "    }\n" +
                "\n" +
                "    public void reset(){\n" +
                "        this.score = 0;\n" +
                "        System.out.println(\"姓名: \" + this.name + \" 积分重置: \" + this.score);\n" +
                "    }\n" +
                "\n" +
                "    public void insert(int score){\n" +
                "        this.score += score;\n" +
                "        System.out.println(\"姓名: \" + this.name + \" 加分结果: \" + this.score);\n" +
                "    }\n" +
                "\n" +
                "    public void reduce(int score){\n" +
                "        this.score -= score;\n" +
                "        System.out.println(\"姓名: \" + this.name + \" 减分结果: \" + this.score);\n" +
                "    }\n" +
                "}";

        String className = "org.example.demo.common.UserSort";

        compiler.addSource(className, sourceCode);

        compiler.compile(className);

        Class<?> clazz = compiler.getClassByName(className);

        System.out.println("无参构造及重置分数-----------------------");

        Object object = clazz.getDeclaredConstructor().newInstance();

        Method method = clazz.getDeclaredMethod("reset");

        method.invoke(object);

        System.out.println("有参构造及重置分数-----------------------");

        object = clazz.getDeclaredConstructor(String.class,int.class).newInstance("张三",0);

        method.invoke(object);

        System.out.println("加分-----------------------------------");

        method = clazz.getDeclaredMethod("insert",int.class);

        method.invoke(object,10);

        System.out.println("减分-----------------------------------");

        method = clazz.getDeclaredMethod("reduce",int.class);

        method.invoke(object,2);
    }

2.接口实现类

定义一个处理器接口,用于处理数据

package org.example.demo.handler;

/**
 * @author moon
 * @date 2023-02-15 20:55
 * @since 1.8
 */
public interface BaseHandler {

    /**
     * 处理器
     * @param content
     */
    void deal(String content);
}

封装一个静态方法,实现类不再单独贴出,简单加了个打印,输出【春江花月夜】

public static void interfaceClass(CustomClassCompiler compiler) throws Exception {

        String sourceCode = "package com.demo.handler;\n" +
                "import org.example.demo.handler.BaseHandler;\n" +
                "public class DynamicHandler implements BaseHandler {\n" +
                "    \n" +
                "    @Override\n" +
                "    public void deal(String content) {\n" +
                "        System.out.println(content);\n" +
                "    }\n" +
                "}";

        String className = "com.demo.handler.DynamicHandler";

        compiler.addSource(className, sourceCode);

        compiler.compile(className);

        BaseHandler handler = (BaseHandler) compiler.getClassByName(className).getDeclaredConstructor().newInstance();

        handler.deal("春江花月夜");
    }

3.测试

在 App 类内直接定义一个 main 方法,调用上面两个静态方法

package org.example.demo;

import org.example.demo.handler.BaseHandler;
import org.example.demo.util.CustomClassCompiler;

import java.lang.reflect.Method;

/**
 * @author moon
 * @date 2023-02-15 20:42
 * @since 1.8
 */
public class App {

    public static void main(String[] args) throws Exception {

        CustomClassCompiler compiler = CustomClassCompiler.newInstance(null);

        commonClass(compiler);

        System.out.println("\n--------------------------------------------------\n");
        
        interfaceClass(compiler);
        
    }

	//TODO 静态方法 commonClass
	
	//TODO 静态方法 interfaceClass
	
}

调用效果如下:

在这里插入图片描述

四.用动态编译 Class 替换 SpringBoot 的 Bean(未完)

未完待续 . . .

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/348565.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Hexo博客搭建部署GitHub

Hexo博客 Hexo是一个简洁的静态博客页面&#xff0c;使用markdown渲染文件&#xff0c;在本地生产静态文件后可以部署到GitHub上&#xff0c;这样不需要占用自己的域名和服务器&#xff0c;其实我在很早之前就换用了hexo&#xff0c;原来的博客在csdn&#xff0c;oceansec.blo…

康耐视智能相机Insight-选择型号方式

一&#xff1a;了解仿真中每种型号。 1.首先需要知道每种仿真代表的是多少万像素的相机&#xff0c;然后根据具体的分辨率去选择相应的型号&#xff0c;具体型号如下。 IS2000 30万相机 分辨率&#xff1a;640480 IS5000 30万相机 分辨率&#xff1a;640480 IS5001 130万相机 分…

[数据结构] 深入理解什么是跳表及其模拟实现

跳表定义优化实现基本框架定义跳表结点实现基础结构构造函数实现基本操作查找操作插入数据删除某结点打印跳表跳表与平衡搜索树和哈希表的对比定义 每相邻两个节点升高一层&#xff0c;增加一个指针&#xff0c;让指针指向下下个节点&#xff1b;上面每一层链表的节点个数&…

Xshell 7 连接云服务器的步骤和出现的错误

一、工具准备云服务器Xshell 7二、使用 Xshell 7 连接数据库三、新建会话属性后&#xff0c;没有自动弹出 SSH 用户名要求输入四、SSH 用户身份验证不能输入 Password五、Xshell 连接 centos 7 服务器 报错提示 “ssh服务拒绝了密码&#xff0c;请再试一次“&#xff0c;但是密…

信息安全工程

信息安全工程信息安全工程信息安全工程概述信息安全工程理论基础支撑信息安全工程的理论基础质量管理基本概念信息安全工程原理ISSE活动中支持认证和认可的活动信息安全工程监理模型信息安全工程能力评估SSE-CMM&#xff08;系统安全工程能力成熟度模型&#xff09;SSE-CMM 的安…

已解决SyntaxError: EOL while scanning string literal

已解决SyntaxError: EOL while scanning string literal 文章目录报错问题报错翻译报错原因解决方法联系博主免费帮忙解决报错报错问题 粉丝群里面的一个小伙伴遇到问题跑来私信我&#xff0c;想用eval函数转换字符串类型的字典&#xff0c;但是发生了报错&#xff08;当时他心…

全网最牛最全面的自动化平台从0到1地一步步搭建

来到新的公司有半年多了&#xff0c;由于业务和人员的极速扩张&#xff0c;整个局面处于百废待兴阶段&#xff0c;有太多方方面面的事情要做&#xff0c;前五个月基本上都是在给各式各样的需求进行支援&#xff0c;最近的两个月多月才比较固定做技术域的事情。所在组主要是做一…

前端学习第一阶段:第六章 HTML和CSS3

6-1 HTML5 01-HTML5CSS3提高导读 02-HTML5提高-新增语义化标签 03-HTML5-新增视频标签 04-HTML5新增音频标签 05-HTML5新增input表单 06-HTML5新增表单属性 6-2 CSS3 07-CSS3新增属性选择器&#xff08;上&#xff09; 08-CSS3新增属性选择器&#xff08;下&#xff09; 09-CSS…

C语言(指针,数组和函数)

目录 一.指针和数组 1.指向数组 2.数组下标和指针解引用的相等性 3.*和优先级问题 二..函数&#xff0c;数组和指针 1.声明数组形参 2.使用const保护地址数据 3.指针和多维数组 4.指向多维数组的指针 5.函数和多维数组 一.指针和数组 在前面的章节里面我们已经说过了…

Mr. Cappuccino的第39杯咖啡——Kubernetes之深入理解Pod

Kubernetes之深入理解PodPod相关概念Pod详细配置清单Pod核心配置Pod基本配置1. 创建yaml文件2. 创建namespace并根据yaml文件创建资源3. 查看namespace下的pod列表以及pod的详细信息Pod中多个容器的名称和端口号不能相同Pod镜像拉取策略Pod环境变量Pod端口相关设置Pod资源相关配…

echarts修改饼图,环形图的圆环宽度,大小

echarts修改环形图的圆环宽度&#xff0c;大小 环形图圆环的大小需要通过series-pie. radius属性来修改 radius 饼图的半径。 Array.<number|string>&#xff1a;数组的第一项是内半径&#xff0c;第二项是外半径。每一项遵从上述 number string 的描述。 把数组的第…

前端高频面试题—JavaScript篇(四)

&#x1f4bb; 前端高频面试题—JavaScript篇&#xff08;四&#xff09;&#x1f3e0;专栏&#xff1a;前端面试题 &#x1f440;个人主页&#xff1a;繁星学编程&#x1f341; &#x1f9d1;个人简介&#xff1a;一个不断提高自我的平凡人&#x1f680; &#x1f50a;分享方向…

150家半导体企业IPO最新进展(附企业名录)

前言 根据Omdia的数据显示&#xff0c;2022年全球在第一季度、第二季度、第三季度实现的半导体收入分别为1593亿美元、1581亿美元、1470亿美元&#xff0c;分别环比下降0.03%、1.9%、7.0%。 目前&#xff0c;半导体产业链经历了自2022上半年的欣欣向荣&#xff0c;到2022年下半…

万字长文掌握Python高并发

文章目录0 前言1 并发、并行、同步、异步、阻塞、非阻塞1.1 并发1.2 并行1.3 同步1.4 异步1.5 阻塞1.6 非阻塞2 多线程2.1 Python线程的创建方式2.1.1 方式一2.1.2 方式二 继承Thread2.1.3 通过线程池创建多线程2.2 聊聊GIL2.2.1 Python线程与操作系统线程的关系2.3 线程同步2.…

【CICD】Jenkins 部署 Docker 容器形态的后端服务

在实现 Jenkins 构建部署前端项目之后&#xff0c;逐渐对使用 Jenkins 部署后端服务有了一定兴趣&#xff1b;总体流程没有什么很大的变化&#xff0c;不过是后端服务需要以 Docker 的形式进行启动&#xff0c;在此记录一下具体过程&#xff08;部分过程与构建部署前端相同不做…

windows下载安装jdk1.8(jdk8)基础篇

一、前言 目前jdk最高升级到JDK19版本了&#xff0c;但是大部分应用系统都是用的1.8&#xff0c;对于初学者来说&#xff0c;也需要下载安装这个版本的jdk。 二、下载安装步骤 一、我已经下载下来&#xff0c;大家到【我的下载目录】下载&#xff0c;密码3360&#xff0c;分…

使用Benchto框架对Trino进行SQL性能对比测试

有时需要对魔改源码前后的不同版本Trino引擎进行性能对比测试&#xff0c;提前发现改造前后是否有性能变差或变好的现象&#xff0c;避免影响数据业务的日常查询任务性能。而Trino社区正好提供了一个性能测试对比框架&#xff1a;GitHub - trinodb/benchto: Framework for runn…

金额大写转换

金额大写转换&#xff08;C语言 &#xff09; 本人喜欢探索各种算法。见站内好多此类文章&#xff0c;有些很好&#xff0c;有些不完整。姑且也来凑下热闹。 金额大写应用在很多方面&#xff0c;如支票、发票、各种单据&#xff0c;各种财务凭证&#xff0c;合同文本金额部分。…

【逐步剖C】-第七章-数据的存储

一、数据类型介绍 1. C语言基本内置类型&#xff1a; char //字符数据类型 short //短整型 int //整形 long //长整型 long long //更长的整形 float //单精度浮点数 double //双精度浮点数2. 类型的基本归类 &#xff08;1&#xff09;整型&#xff1a; charunsign…

c语言指针

指针 指针是存放地址的变量&#xff0c;也可以说指针地址。 对于定义p&#xff08;这里的话&#xff0c;只是定义&#xff0c;说明p是指针&#xff09;&#xff0c;p作为一个指针去指向存放数据的位置&#xff0c;而p意思是取&#xff08;p指向的内存位置的数据&#xff09;&…