Jdk8 动态编译 Java 源码为 Class 文件(三)

一.JDK版本

在这里插入图片描述

二.工程介绍

动态源码编译需要自定义类加载器,JVM会根据所属类加载器和全类名判断是否为同一个类,所以动态编译和加载时,同一个类无法用同一个类加载器加载两次,除非从 JVM 层面移除旧的类。
同一个类由不同类加载器加载时,JVM 会判断为非同类,所以无法直接实例化后强转为同一类型的实例,需要基于接口、抽象类来实现动态替换

在这里插入图片描述

1.依赖

<?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.启动类

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.配置类(用于测试依赖注入)

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.工具类

1.Java 源码文件读取类

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 容器实例管理类

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.测试类

1.抽象类

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.接口类

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.默认抽象实现

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.默认接口实现

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.接口类

1.测试接口

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.类重载控制接口

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.动态编译类

1.类加载器

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.类管理器

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.类对象

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 文件类

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.配置文件

server:
  port: 8082
my:
  ip: 123.456.789.1

三.测试

启动 Java 服务(Xbootclasspath 引入 tools.jar)

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

在这里插入图片描述

1.测试用类

1.测试类原类修改

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.测试

1.原类直接打印

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

在这里插入图片描述

2.原类修改

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

在这里插入图片描述
调用测试
http://127.0.0.1:8082/test/print?content=zhangsan

在这里插入图片描述

四.Jar 反编译记录

1.IDEA 安装插件 Java Decompiler

在这里插入图片描述

2.找到插件包(可以将该Jar包取到其他位置使用):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

在这里插入图片描述

结果是一个 tools.jar 文件,将其扩展名改为 .zip 并解压就可以看到实际已经是 java 文件了

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_42176639/article/details/132660810
今日推荐