Jdk8 dynamically compiles Java source code into Class files (3)

1.JDK version

Insert image description here

2. Project introduction

Dynamic source code compilation requires a custom class loader. The JVM will determine whether it is the same class based on the class loader and the full class name. Therefore, during dynamic compilation and loading, the same class cannot be loaded twice with the same class loader unless Remove old classes from JVM level.
When the same class is loaded by different class loaders, the JVM will judge it as non-similar, so it cannot be directly instantiated and then forced to an instance of the same type.Need to implement dynamic replacement based on interfaces and abstract classes

Insert image description here

1. Dependence

<?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>spring-dynamic</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <spring.version>2.7.4</spring.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>${spring.version}</version>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-logging</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-log4j2</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.26</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-loader</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <dependency>
            <groupId>com.sun</groupId>
            <artifactId>tools</artifactId>
            <version>1.8.0_341</version>
            <scope>system</scope>
            <systemPath>${JAVA_HOME}\lib\tools.jar</systemPath>
        </dependency>
    </dependencies>
    <build>
        <finalName>dynamic-demo</finalName>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>2.7.4</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
                <!-- for tools.jar -->
<!--                <configuration>-->
<!--                    <includeSystemScope>true</includeSystemScope>-->
<!--                </configuration>-->
            </plugin>
        </plugins>
    </build>
</project>

2. Startup class

package com.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * @author
 * @date 2023-08-10 9:51
 * @since 1.8
 */
@SpringBootApplication
public class DynamicApp {
    
    
    public static void main(String[] args) {
    
    
        SpringApplication.run(DynamicApp.class,args);
    }
}

3. Configuration class (for testing dependency injection)

package com.example.config;

import org.springframework.stereotype.Component;

/**
 * @author moon
 * @date 2023-08-30 14:58
 * @since 1.8
 */
@Component
public class KafkaConfig {
    
    

    public void getConfig(){
    
    
        System.out.println("kafka config");
    }
}

4.Tools

1.Java source file reading class

package com.example.util;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;

/**
 * @author moon
 * @date 2023-08-31 9:25
 * @since 1.8
 */
public class FileUtil {
    
    

    public static String readJson(String filePath){
    
    

        if (org.springframework.util.StringUtils.hasLength(filePath)){
    
    
            InputStream inputStream = null;
            StringBuilder builder = new StringBuilder();
            try {
    
    
                int batchSize = 2048;
                inputStream = new FileInputStream(filePath);
                byte[] temp = new byte[batchSize];
                int read;
                while ((read = inputStream.read(temp)) != -1){
    
    
                    if (read < batchSize){
    
    
                        byte[] tail = Arrays.copyOf(temp,read);
                        builder.append(new String(tail));
                    } else {
    
    
                        builder.append(new String(temp));
                    }
                }
                return builder.toString();
            } catch (IOException e) {
    
    
                return "";
            } finally {
    
    
                if (null != inputStream){
    
    
                    try {
    
    
                        inputStream.close();
                    } catch (IOException e) {
    
    
                        throw new RuntimeException(e);
                    }
                }
            }

        }
        return "";
    }


}

2.SpringBoot container instance management class

package com.example.util;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * @author moon
 * @date 2023-08-31 11:18
 * @since 1.8
 */
@Component
public class SpringBeanUtil implements ApplicationContextAware {
    
    

    private static ApplicationContext applicationContext;

    private static ConfigurableApplicationContext context ;

    /**
     * 获取 Bean 工厂
     */
    private static DefaultListableBeanFactory beanFactory ;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
    
    
        SpringBeanUtil.applicationContext = applicationContext;
        SpringBeanUtil.context= (ConfigurableApplicationContext) applicationContext;
        SpringBeanUtil.beanFactory= (DefaultListableBeanFactory) context.getBeanFactory();
    }

    /**
     * 替换 bean 并获取新的
     * @param beanName
     * @param clazz
     * @return
     */
    public static Object replace(String beanName,Class clazz){
    
    
        //卸载
        unregister(beanName);
        //注册
        register(beanName,clazz);
        //获取
        return getBean(beanName);
    }

    /**
     * 注册 Bean
     * @param clazz
     */
    public static void register(String beanName,Class clazz){
    
    

        BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(clazz);

        BeanDefinition definition = builder.getBeanDefinition();

        //为 definition 设置额外属性
        definition.setScope("singleton");

        //注册
        beanFactory.registerBeanDefinition(beanName,definition);
    }

    /**
     * 卸载 Bean
     * @param beanName
     */
    public static void unregister(String beanName){
    
    
        if (beanFactory.containsBean(beanName)){
    
    
            beanFactory.removeBeanDefinition(beanName);
        }
    }

    /**
     * 获取所有 Bean
     * @return
     */
    public static List<String> getBeans(){
    
    
        String[] names = applicationContext.getBeanDefinitionNames();
        List<String> beans = new ArrayList<>(names.length);
        for (String name:names){
    
    
            beans.add(applicationContext.getBean(name).getClass().getName());
        }
        return beans;
    }

    /**
     * bean 是否存在
     * @param name
     * @return
     */
    public static boolean isBeanExist(String name){
    
    
        return applicationContext.containsBean(name);
    }

    /**
     * 通过名称获取 Bean
     * @param name
     * @return
     * @param <T>
     * @throws BeansException
     */
    public static <T> T getBean(String name) throws BeansException{
    
    
        return (T) applicationContext.getBean(name);
    }

    /**
     * 通过类型获取 Bean
     * @param clazz
     * @return
     * @param <T>
     * @throws BeansException
     */
    public static <T> T getBean(Class<?> clazz) throws BeansException{
    
    
        return (T) applicationContext.getBean(clazz);
    }

    /**
     * 获取指定类型的 Bean 的名称
     * @param className
     * @return
     * @throws BeansException
     */
    public static List<String> getBeanName(String className) throws BeansException, ClassNotFoundException {
    
    
        Class<?> clazz = Class.forName(className);
        return Arrays.asList(applicationContext.getBeanNamesForType(clazz));
    }
}

5.Test category

1.Abstract class

package com.example.service;

/**
 * @author moon
 * @date 2023-08-30 14:15
 * @since 1.8
 */
public abstract class TestAbstract {
    
    

    /**
     * 抽象方法
     * @param str
     */
    public abstract void hand(String str);
}

2.Interface class

package com.example.service;

/**
 * @author moon
 * @date 2023-08-31 10:58
 * @since 1.8
 */
public interface TestService {
    
    

    /**
     * 处理
     * @param str
     */
    void hand(String str);

}

3. Default abstract implementation

package com.example.service.impl;

import com.example.config.KafkaConfig;
import com.example.service.TestAbstract;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

/**
 * @author moon
 * @date 2023-08-31 11:01
 * @since 1.8
 */
@Component
public class TestAbstractImpl extends TestAbstract {
    
    

    @Autowired
    KafkaConfig config;

    @Value("${my.ip}")
    String ip;

    @Override
    public void hand(String str) {
    
    
        config.getConfig();
        System.out.println(str);
        System.out.println(ip);
    }
}

4.Default interface implementation

package com.example.service.impl;

import com.example.config.KafkaConfig;
import com.example.service.TestService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * @author moon
 * @date 2023-08-31 10:59
 * @since 1.8
 */
@Service
public class TestServiceImpl implements TestService {
    
    

    @Autowired
    KafkaConfig config;

    @Override
    public void hand(String str) {
    
    
        config.getConfig();
        System.out.println("hand: " + this);
    }
}

6.Interface class

1. Test interface

package com.example.controller;

import com.example.service.TestAbstract;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

/**
 * @author moon
 * @date 2023-08-30 17:27
 * @since 1.8
 */
@RestController
@RequestMapping("/test")
public class TestController {
    
    

    /**
     * 引入抽象类依赖
     */
    @Resource(name = "testAbstractImpl")
    TestAbstract testAbstract;

    @GetMapping("/print")
    public void print(String content){
    
    
        testAbstract.hand("Hello : " + content);
    }
}

2. Class overloading control interface

package com.example.controller;


import com.example.dynamic.MemoryClassLoader;
import com.example.service.TestAbstract;
import com.example.util.FileUtil;
import com.example.util.SpringBeanUtil;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.lang.reflect.InvocationTargetException;
import java.net.MalformedURLException;

/**
 * @author moon
 * @date 2023-08-30 14:10
 * @since 1.8
 */
@RestController
@RequestMapping("/reload")
public class Reload extends ClassLoader{
    
    


    @GetMapping("/re")
    public void re(String name,String beanName) throws InstantiationException, IllegalAccessException, InvocationTargetException, MalformedURLException, NoSuchMethodException, ClassNotFoundException {
    
    

        String className = "com.example.service.impl." + name;
        String classPath = "C:\\Users\\administrator\\Desktop\\jar\\"+name+".java";
        String javaStr = FileUtil.readJson(classPath);

        /**
         * 定义新的 MemoryClassLoader 同一个 MemoryClassLoader 不能两次加载同一个类
         */
        MemoryClassLoader loader = new MemoryClassLoader();
        loader.registerJava(className,javaStr);
        Class clazz = loader.findClass(className);

        TestAbstract handler = (TestAbstract) clazz.getDeclaredConstructor().newInstance();

        // 将外部Jar包中的Bean注入到Spring容器中

        Object obj = SpringBeanUtil.replace(beanName,clazz);

        TestAbstract test = (TestAbstract) obj;

        test.hand("sss");

        System.out.println();

    }

}

7. Dynamically compiled classes

1. Class loader

package com.example.dynamic;

import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.system.ApplicationHome;
import org.springframework.stereotype.Component;

import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
import javax.tools.ToolProvider;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Arrays;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author moon
 */
@Slf4j
@Component
public class MemoryClassLoader extends URLClassLoader {
    
    

    /**
     * 缓存字节码
     */
    private Map<String, byte[]> classBytesMap = new ConcurrentHashMap<>();

    /**
     * 构造
     */
    public MemoryClassLoader() {
    
    
        super(new URL[0], MemoryClassLoader.class.getClassLoader());
    }

    /**
     * 注册 Java 字符串到内存类加载器中
     *
     * @param className 类名字
     * @param javaStr   Java字符串
     */
    public void registerJava(String className, String javaStr) {
    
    
        try {
    
    
            this.classBytesMap.putAll(compile(className, javaStr));
        } catch (Exception e) {
    
    
            log.error("register java class exception:");
        }
    }

    /**
     * 编译 Java 源码
     *
     * @param className 类名字
     * @param javaStr   Java代码
     * @return class 二进制
     */
    private Map<String, byte[]> compile(String className, String javaStr) {
    
    
        //初始化编译器
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        //获取Java文件管理器
        try (MemoryJavaFileManager manager = new MemoryJavaFileManager()) {
    
    
            JavaFileObject javaFileObject = manager.makeStringSource(className, javaStr);
            JavaCompiler.CompilationTask task = compiler.getTask(null, manager, null, null, null, Arrays.asList(javaFileObject));
            if (task.call()) {
    
    
                return manager.getClassBytes();
            }
        } catch (Exception e) {
    
    
            log.error("compile java str exception:",e);
        }
        return null;
    }

    /**
     * 获取 Class
     * @param name the name of the class
     * @return
     * @throws ClassNotFoundException
     */
    @Override
    public Class<?> findClass(String name) throws ClassNotFoundException {
    
    
        byte[] buf = classBytesMap.get(name);
        if (buf == null) {
    
    
            return super.findClass(name);
        }
        classBytesMap.remove(name);
        return defineClass(name, buf, 0, buf.length);
    }

    /**
     * 获取jar包所在路径
     *
     * @return jar包所在路径
     */
    public static String getPath() {
    
    
        ApplicationHome home = new ApplicationHome(MemoryJavaFileManager.class);
        String path = home.getSource().getPath();
        return path;
    }

    /**
     * 判断是否jar模式运行
     *
     * @return
     */
    public static boolean isJar() {
    
    
        return getPath().endsWith(".jar");
    }

}

2. Class Manager

package com.example.dynamic;

import com.sun.tools.javac.file.JavacFileManager;
import com.sun.tools.javac.util.Context;
import com.sun.tools.javac.util.Log;
import org.springframework.boot.loader.jar.JarFile;
import javax.tools.*;
import java.io.*;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.jar.JarEntry;
import java.util.stream.Collectors;

/**
 * Java 文件管理器
 * 用于加载 SpringBoot 下面的依赖资源
 *
 * @author moon
 * @date 2023-08-10 9:58
 * @since 1.8
 */
public class MemoryJavaFileManager extends ForwardingJavaFileManager<JavaFileManager> {
    
    

    /**
     * 缓存字节码
     */
    final Map<String, byte[]> classBytesMap = new ConcurrentHashMap<>();

    /**
     * 缓存文件对象
     */
    final Map<String, List<JavaFileObject>> classObjectPackageMap = new ConcurrentHashMap<>();

    /**
     * 文件管理器
     */
    private JavacFileManager javaFileManager;

    /**
     * 包名 / JavaFile(.java)
     */
    public final static Map<String, List<JavaFileObject>> CLASS_OBJECT_PACKAGE_MAP = new ConcurrentHashMap<>();

    /**
     * 锁对象
     */
    private static final Object lock = new Object();

    /**
     * 初始化标识
     */
    private static boolean isInit = false;

    /**
     * 初始化
     */
    public void init() {
    
    
        try {
    
    
            JarFile tempJarFile;
            List<JavaFileObject> javaFiles;
            String packageName,className;
            //获取当前 Jar 包
            String jarBaseFile = MemoryClassLoader.getPath();
            //加载 Jar 包
            JarFile jarFile = new JarFile(new File(jarBaseFile));
            //取包自身文件
            for (JarEntry entry:jarFile){
    
    
                //SpringBoot repackage 打包 class 文件带一个 BOOT-INF/classes/ 之后才是包名
                String name = entry.getName().replace("BOOT-INF/classes/","");
                String classPath = name.replace("/", ".");
                //如果不是 class 文件跳过
                if (name.endsWith(".class")){
    
    
                    //取出包名
                    packageName = classPath.substring(0, name.lastIndexOf("/"));
                    //取类名
                    className = classPath.replace(".class", "");
                    //创建集合
                    javaFiles = Optional.ofNullable(CLASS_OBJECT_PACKAGE_MAP.get(packageName)).orElse(new ArrayList<>()) ;
                    //取 JavaFile
                    filterClass(packageName,className,jarFile.getUrl(),entry.getName(),javaFiles);
                }
            }
            //遍历取内部 Jar 包
            List<JarEntry> entries = jarFile.stream().filter(jarEntry -> {
    
    
                return jarEntry.getName().endsWith(".jar");
            }).collect(Collectors.toList());
            // Jar File
            for (JarEntry entry : entries) {
    
    
                //取内部文件
                tempJarFile = jarFile.getNestedJarFile(jarFile.getEntry(entry.getName()));
                //跳过工具包 Jar
                if (tempJarFile.getName().contains("tools.jar")) {
    
    
                    continue;
                }
                //遍历 Jar 文件
                Enumeration<JarEntry> tempEntriesEnum = tempJarFile.entries();
                while (tempEntriesEnum.hasMoreElements()) {
    
    
                    JarEntry jarEntry = tempEntriesEnum.nextElement();
                    String classPath = jarEntry.getName().replace("/", ".");
                    //如果不是 class 文件跳过
                    if (!classPath.endsWith(".class") || jarEntry.getName().lastIndexOf("/") == -1) {
    
    
                        continue;
                    } else {
    
    
                        //取出包名
                        packageName = classPath.substring(0, jarEntry.getName().lastIndexOf("/"));
                        //取类名
                        className = jarEntry.getName().replace("/", ".").replace(".class", "");
                        //创建集合
                        javaFiles = Optional.ofNullable(CLASS_OBJECT_PACKAGE_MAP.get(packageName)).orElse(new ArrayList<>()) ;
                        //取 JavaFile
                        filterClass(packageName,className,tempJarFile.getUrl(),jarEntry.getName(),javaFiles);
                    }
                }
            }
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
        isInit = true;
    }

    /**
     * 取 class
     * @param packageName
     * @param className
     * @param url
     * @param entryName
     * @param javaFiles
     */
    private void filterClass(String packageName,String className,URL url,String entryName,List<JavaFileObject> javaFiles) throws MalformedURLException {
    
    
        //取 JavaFile
        javaFiles.add(new MemorySpringBootInfoJavaClassObject(className, new URL(url, entryName), javaFileManager));
        //缓存 Package / JavaFile
        CLASS_OBJECT_PACKAGE_MAP.put(packageName, javaFiles);
    }

    /**
     * 构造
     */
    MemoryJavaFileManager() {
    
    
        super(getStandardFileManager(null, null, null));
        this.javaFileManager = (JavacFileManager) fileManager;
    }

    /**
     * 获取文件对象集合
     * @param packageName
     * @return
     */
    public List<JavaFileObject> getLibJarsOptions(String packageName) {
    
    
        synchronized (lock) {
    
    
            if (!isInit) {
    
    
                init();
            }
        }
        return CLASS_OBJECT_PACKAGE_MAP.get(packageName);
    }

    @Override
    public Iterable<JavaFileObject> list(Location location,
                                         String packageName,
                                         Set<JavaFileObject.Kind> kinds,
                                         boolean recurse)
            throws IOException {
    
    


        if ("CLASS_PATH".equals(location.getName()) && MemoryClassLoader.isJar()) {
    
    
            List<JavaFileObject> result = getLibJarsOptions(packageName);
            if (result != null) {
    
    
                return result;
            }
        }

        Iterable<JavaFileObject> it = super.list(location, packageName, kinds, recurse);

        if (kinds.contains(JavaFileObject.Kind.CLASS)) {
    
    
            final List<JavaFileObject> javaFileObjectList = classObjectPackageMap.get(packageName);
            if (javaFileObjectList != null) {
    
    
                if (it != null) {
    
    
                    for (JavaFileObject javaFileObject : it) {
    
    
                        javaFileObjectList.add(javaFileObject);
                    }
                }
                return javaFileObjectList;
            } else {
    
    
                return it;
            }
        } else {
    
    
            return it;
        }
    }

    @Override
    public String inferBinaryName(Location location, JavaFileObject file) {
    
    
        if (file instanceof MemoryInputJavaClassObject) {
    
    
            return ((MemoryInputJavaClassObject) file).inferBinaryName();
        }
        return super.inferBinaryName(location, file);
    }

    @Override
    public JavaFileObject getJavaFileForOutput(Location location, String className, JavaFileObject.Kind kind,
                                               FileObject sibling) throws IOException {
    
    
        if (kind == JavaFileObject.Kind.CLASS) {
    
    
            return new MemoryOutputJavaClassObject(className);
        } else {
    
    
            return super.getJavaFileForOutput(location, className, kind, sibling);
        }
    }

    /**
     * 设置源码
     * @param className
     * @param code
     * @return
     */
    JavaFileObject makeStringSource(String className, final String code) {
    
    
        String classPath = className.replace('.', '/') + JavaFileObject.Kind.SOURCE.extension;

        return new SimpleJavaFileObject(URI.create("string:///" + classPath), JavaFileObject.Kind.SOURCE) {
    
    
            @Override
            public CharBuffer getCharContent(boolean ignoreEncodingErrors) {
    
    
                return CharBuffer.wrap(code);
            }
        };
    }

    /**
     * 设置字节码
     * @param className
     * @param bs
     */
    void makeBinaryClass(String className, final byte[] bs) {
    
    
        JavaFileObject javaFileObject = new MemoryInputJavaClassObject(className, bs);
        String packageName = "";
        int pos = className.lastIndexOf('.');
        if (pos > 0) {
    
    
            packageName = className.substring(0, pos);
        }
        List<JavaFileObject> javaFileObjectList = classObjectPackageMap.get(packageName);
        if (javaFileObjectList == null) {
    
    
            javaFileObjectList = new LinkedList<>();
            javaFileObjectList.add(javaFileObject);

            classObjectPackageMap.put(packageName, javaFileObjectList);
        } else {
    
    
            javaFileObjectList.add(javaFileObject);
        }
    }

    /**
     * 内部输入类
     */
    class MemoryInputJavaClassObject extends SimpleJavaFileObject {
    
    
        final String className;
        final byte[] bs;

        MemoryInputJavaClassObject(String className, byte[] bs) {
    
    
            super(URI.create("string:///" + className.replace('.', '/') + Kind.CLASS.extension), Kind.CLASS);
            this.className = className;
            this.bs = bs;
        }

        @Override
        public InputStream openInputStream() {
    
    
            return new ByteArrayInputStream(bs);
        }

        public String inferBinaryName() {
    
    
            return className;
        }
    }

    /**
     * 内部输出类
     */
    class MemoryOutputJavaClassObject extends SimpleJavaFileObject {
    
    
        final String className;

        MemoryOutputJavaClassObject(String className) {
    
    
            super(URI.create("string:///" + className.replace('.', '/') + Kind.CLASS.extension), Kind.CLASS);
            this.className = className;
        }

        @Override
        public OutputStream openOutputStream() {
    
    
            return new FilterOutputStream(new ByteArrayOutputStream()) {
    
    
                @Override
                public void close() throws IOException {
    
    
                    out.close();
                    ByteArrayOutputStream bos = (ByteArrayOutputStream) out;
                    byte[] bs = bos.toByteArray();
                    classBytesMap.put(className, bs);
                    makeBinaryClass(className, bs);
                }
            };
        }
    }

    /**
     * 获取编译结果
     * @return
     */
    public Map<String, byte[]> getClassBytes() {
    
    
        return new HashMap<>(this.classBytesMap);
    }

    /**
     * 刷新
     * @throws IOException
     */
    @Override
    public void flush() throws IOException {
    
    
    }

    /**
     * 关闭
     * @throws IOException
     */
    @Override
    public void close() throws IOException {
    
    
        classBytesMap.clear();
    }

    /**
     * 自定义 Java 文件管理器
     *
     * @param var1
     * @param var2
     * @param var3
     * @return
     */
    public static SpringJavaFileManager getStandardFileManager(DiagnosticListener<? super JavaFileObject> var1, Locale var2, Charset var3) {
    
    
        Context var4 = new Context();
        var4.put(Locale.class, var2);
        if (var1 != null) {
    
    
            var4.put(DiagnosticListener.class, var1);
        }
        PrintWriter var5 = var3 == null ? new PrintWriter(System.err, true) : new PrintWriter(new OutputStreamWriter(System.err, var3), true);
        var4.put(Log.outKey, var5);
        return new SpringJavaFileManager(var4, true, var3);
    }
}

3. Class objects

package com.example.dynamic;

import com.sun.tools.javac.file.BaseFileObject;
import com.sun.tools.javac.file.JavacFileManager;

import javax.tools.JavaFileObject;
import java.io.*;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;

/**
 * 用来读取 spring boot 的 class
 *
 * @author moon
 * @date 2023-08-10 9:57
 * @since 1.8
 */
public class MemorySpringBootInfoJavaClassObject extends BaseFileObject {
    
    
    private final String className;
    private URL url;

    public MemorySpringBootInfoJavaClassObject(String className, URL url, JavacFileManager javacFileManager) {
    
    
        super(javacFileManager);
        this.className = className;
        this.url = url;
    }

    @Override
    public JavaFileObject.Kind getKind() {
    
    
        return JavaFileObject.Kind.valueOf("CLASS");
    }

    @Override
    public URI toUri() {
    
    
        try {
    
    
            return url.toURI();
        } catch (URISyntaxException e) {
    
    
            e.printStackTrace();
        }
        return null;
    }

    @Override
    public String getName() {
    
    
        return className;
    }

    @Override
    public InputStream openInputStream() {
    
    
        try {
    
    
            return url.openStream();
        } catch (IOException e) {
    
    
            e.printStackTrace();
        }
        return null;
    }

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

    @Override
    public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
    
    
        return null;
    }

    @Override
    public Writer openWriter() throws IOException {
    
    
        return null;
    }

    @Override
    public long getLastModified() {
    
    
        return 0;
    }

    @Override
    public boolean delete() {
    
    
        return false;
    }

    public String inferBinaryName() {
    
    
        return className;
    }

    @Override
    public String getShortName() {
    
    
        return className.substring(className.lastIndexOf("."));
    }

    @Override
    protected String inferBinaryName(Iterable<? extends File> iterable) {
    
    
        return className;
    }


    @Override
    public boolean equals(Object o) {
    
    
        return false;
    }

    @Override
    public int hashCode() {
    
    
        return 0;
    }


    @Override
    public boolean isNameCompatible(String simpleName, JavaFileObject.Kind kind) {
    
    
        return false;
    }
}

4.Java file class

package com.example.dynamic;

import com.sun.tools.javac.file.JavacFileManager;
import com.sun.tools.javac.util.Context;
import com.sun.tools.javac.util.ListBuffer;
import java.io.File;
import java.lang.reflect.Constructor;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.charset.Charset;
import java.util.Iterator;

/**
 * Java 文件管理器
 *
 * @author moon
 * @date 2023-08-10 9:53
 * @since 1.8
 */
public class SpringJavaFileManager extends JavacFileManager {
    
    

    /**
     *
     * @param context
     * @param b
     * @param charset
     */
    public SpringJavaFileManager(Context context, boolean b, Charset charset) {
    
    
        super(context, b, charset);
    }

    /**
     * 重写类加载器
     * @param location a location
     * @return
     */
    @Override
    public ClassLoader getClassLoader(Location location) {
    
    
        nullCheck(location);
        Iterable var2 = this.getLocation(location);
        if (var2 == null) {
    
    
            return null;
        } else {
    
    
            ListBuffer var3 = new ListBuffer();
            Iterator var4 = var2.iterator();
            while (var4.hasNext()) {
    
    
                File var5 = (File) var4.next();
                try {
    
    
                    var3.append(var5.toURI().toURL());
                } catch (MalformedURLException var7) {
    
    
                    throw new AssertionError(var7);
                }
            }
            return this.getClassLoader((URL[]) var3.toArray(new URL[var3.size()]));
        }
    }

    /**
     * 获取 LaunchedURLClassLoader 加载器
     *
     * @param var1
     * @return
     */
    @Override
    protected ClassLoader getClassLoader(URL[] var1) {
    
    
        ClassLoader var2 = this.getClass().getClassLoader();
        try {
    
    
            Class loaderClass = Class.forName("org.springframework.boot.loader.LaunchedURLClassLoader");
            Class[] var4 = new Class[]{
    
    URL[].class, ClassLoader.class};
            Constructor var5 = loaderClass.getConstructor(var4);
            return (ClassLoader) var5.newInstance(var1, var2);
        } catch (Throwable var6) {
    
    
        }
        return new URLClassLoader(var1, var2);
    }

}

8.Configuration file

server:
  port: 8082
my:
  ip: 123.456.789.1

3. Test

Start the Java service (Xbootclasspath introduces tools.jar)

java -Xbootclasspath/a:C:\Progra~1\Java\jdk1.8.0_341\jre\lib\tools.jar -jar dynamic-demo.jar

Insert image description here

1. Test class

1. Modification of the original test class

package com.example.service.impl;

import com.example.config.KafkaConfig;
import com.example.service.TestAbstract;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

/**
 * @author moon
 * @date 2023-08-31 11:01
 * @since 1.8
 */
@Component
public class TestAbstractImpl extends TestAbstract {
    
    

    @Autowired
    KafkaConfig config;

    @Value("${my.ip}")
    String ip;

    @Override
    public void hand(String str) {
    
    
        config.getConfig();
        System.out.println("How are you" + str);
        System.out.println(ip);
    }
}

2. Test

1. Direct printing of the original type

http://127.0.0.1:8082/test/print?content=lisi

Insert image description here

2. Modification of original class

重载
http://127.0.0.1:8082/reload/re?name=TestAbstractImpl&beanName=testAbstractImpl

Insert image description here
Call the test
http://127.0.0.1:8082/test/print?content=zhangsan

Insert image description here

4.Jar decompilation record

1. IDEA installs the plug-in Java Decompiler

Insert image description here

2. Find the plug-in package (the Jar package can be taken to other locations for use): C:\Program Files\JetBrains\IntelliJ IDEA Community Edition 2022.2\plugins\java-decompiler\lib

创建一个 SRC 目录
反编译命令
%JAVA_HOME_19%\java -cp java-decompiler.jar org.jetbrains.java.decompiler.main.decompiler.ConsoleDecompiler -dgs=true tools.jar src

Insert image description here

The result is a tools.jar file. Change its extension to .zip and decompress it to see that it is actually a java file.

Insert image description here

Guess you like

Origin blog.csdn.net/weixin_42176639/article/details/132660810