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


    一.JDK版本

    在这里插入图片描述

    二.工程介绍

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

    在这里插入图片描述

    1.依赖

    
    <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.0modelVersion>
    
        <groupId>org.examplegroupId>
        <artifactId>spring-dynamicartifactId>
        <version>1.0-SNAPSHOTversion>
    
        <properties>
            <maven.compiler.source>8maven.compiler.source>
            <maven.compiler.target>8maven.compiler.target>
            <project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
            <spring.version>2.7.4spring.version>
        properties>
    
        <dependencies>
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-webartifactId>
                <version>${spring.version}version>
                <exclusions>
                    <exclusion>
                        <groupId>org.springframework.bootgroupId>
                        <artifactId>spring-boot-starter-loggingartifactId>
                    exclusion>
                exclusions>
            dependency>
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-log4j2artifactId>
                <version>${spring.version}version>
            dependency>
    
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-aopartifactId>
                <version>${spring.version}version>
            dependency>
    
            <dependency>
                <groupId>org.projectlombokgroupId>
                <artifactId>lombokartifactId>
                <version>1.18.26version>
            dependency>
    
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-loaderartifactId>
                <version>${spring.version}version>
            dependency>
    
            <dependency>
                <groupId>com.sungroupId>
                <artifactId>toolsartifactId>
                <version>1.8.0_341version>
                <scope>systemscope>
                <systemPath>${JAVA_HOME}\lib\tools.jarsystemPath>
            dependency>
        dependencies>
        <build>
            <finalName>dynamic-demofinalName>
            <plugins>
                <plugin>
                    <groupId>org.springframework.bootgroupId>
                    <artifactId>spring-boot-maven-pluginartifactId>
                    <version>2.7.4version>
                    <executions>
                        <execution>
                            <goals>
                                <goal>repackagegoal>
                            goals>
                        execution>
                    executions>
                    
    
    
    
                plugin>
            plugins>
        build>
    project>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83

    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);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    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");
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    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 "";
        }
    
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52

    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 
         * @throws BeansException
         */
        public static <T> T getBean(String name) throws BeansException{
            return (T) applicationContext.getBean(name);
        }
    
        /**
         * 通过类型获取 Bean
         * @param clazz
         * @return
         * @param 
         * @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));
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137

    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);
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    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);
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    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);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    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);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    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);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    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();
    
        }
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54

    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 {
    
    	/**
         * 是否忽略告警信息
         */
        boolean ignoreWarnings = true;
    
        /**
         * 缓存字节码
         */
        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;
        }
    
    	/**
         * 编译源码
         * @param className
         * @param javaStr
         * @throws Exception
         */
        private Map<String, byte[]> compileByNames(String className,String javaStr) {
    
            if (EasyTools.isStrAnyEmpty(className,javaStr)) {
                throw new RuntimeException("No source code to compile");
            }
    
            //定义警告和错误信息输出集合
            DiagnosticCollector<JavaFileObject> collector = new DiagnosticCollector<>();
            //初始化编译器
            JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
            MemoryJavaFileManager manager = new MemoryJavaFileManager(StandardCharsets.UTF_8);
            JavaFileObject fileObject = manager.makeStringSource(className, javaStr);
            JavaCompiler.CompilationTask task = compiler.getTask(null, manager, collector, Arrays.asList("-Xlint:unchecked"), null, Arrays.asList(fileObject));
            //编译
            boolean result = task.call();
            //编译结果
            if (!result || collector.getDiagnostics().size() > 0) {
                StringBuffer exceptionMsg = new StringBuffer();
                exceptionMsg.append("Unable to compile the source");
                boolean hasWarnings = false;
                boolean hasErrors = false;
                for (Diagnostic<? extends JavaFileObject> d : collector.getDiagnostics()) {
                    switch (d.getKind()) {
                        case NOTE:
                        case MANDATORY_WARNING:
                        case WARNING:
                            hasWarnings = true;
                            break;
                        case OTHER:
                        case ERROR:
                            hasErrors = true;
                            break;
                        default:
                            break;
                    }
                    exceptionMsg.append("\n").append("[kind=").append(d.getKind());
                    exceptionMsg.append(", ").append("line=").append(d.getLineNumber());
                    exceptionMsg.append(", ").append("message=").append(d.getMessage(Locale.US)).append("]");
                }
                //是否忽略警告
                if ((hasWarnings && !ignoreWarnings ) || hasErrors) {
                    throw new RuntimeException(exceptionMsg.toString());
                }
                //
            }
            return manager.getClassBytes();
        }
    
        /**
         * 获取 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");
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167

    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);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237
    • 238
    • 239
    • 240
    • 241
    • 242
    • 243
    • 244
    • 245
    • 246
    • 247
    • 248
    • 249
    • 250
    • 251
    • 252
    • 253
    • 254
    • 255
    • 256
    • 257
    • 258
    • 259
    • 260
    • 261
    • 262
    • 263
    • 264
    • 265
    • 266
    • 267
    • 268
    • 269
    • 270
    • 271
    • 272
    • 273
    • 274
    • 275
    • 276
    • 277
    • 278
    • 279
    • 280
    • 281
    • 282
    • 283
    • 284
    • 285
    • 286
    • 287
    • 288
    • 289
    • 290
    • 291
    • 292
    • 293
    • 294
    • 295
    • 296
    • 297
    • 298
    • 299
    • 300
    • 301
    • 302
    • 303
    • 304
    • 305
    • 306
    • 307
    • 308
    • 309
    • 310
    • 311
    • 312
    • 313
    • 314
    • 315
    • 316
    • 317
    • 318
    • 319
    • 320
    • 321
    • 322
    • 323
    • 324
    • 325
    • 326
    • 327
    • 328
    • 329
    • 330
    • 331
    • 332
    • 333
    • 334
    • 335
    • 336
    • 337
    • 338
    • 339
    • 340
    • 341
    • 342
    • 343
    • 344
    • 345
    • 346
    • 347
    • 348

    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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114

    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);
        }
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79

    8.配置文件

    server:
      port: 8082
    my:
      ip: 123.456.789.1
    
    • 1
    • 2
    • 3
    • 4

    三.测试

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

    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);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    2.测试

    1.原类直接打印

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

    在这里插入图片描述

    2.原类修改

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

    在这里插入图片描述
    调用测试
    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 目录
    反编译命令
    
    • 1
    • 2
    %JAVA_HOME_19%\java -cp java-decompiler.jar org.jetbrains.java.decompiler.main.decompiler.ConsoleDecompiler -dgs=true tools.jar src
    
    • 1

    在这里插入图片描述

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

    在这里插入图片描述

  • 相关阅读:
    《Effective C++中文版,第三版》读书笔记7
    正则表达式replaceFirst()方法具有什么功能呢?
    要有遥不可及的梦想,也要有脚踏实地的本事
    Redis 主从搭建
    Nginx 安装配置
    Charles抓包工具常用操作
    (っ•̀ω•́)っ 如何在PPT中为文本框添加滚动条
    CSS Position(定位)
    深度学习——深度学习计算二
    解决Python requests库中的重定向问题
  • 原文地址:https://blog.csdn.net/weixin_42176639/article/details/132660810