结合 Java Swing 实现 Java 文件和 JAR 包的拖拽执行

news2024/11/18 19:57:01

相关文章:

  • 自己动手写分布式任务调度框架

之前开发了一个简易的分布式任务调度框架,任务的调度源头是通过各个业务服务的主动注册来实现的。当时有朋友提出可以添加一种任务来源,让用户能够手动将代码上传到调度平台并执行。所以在当时就“立项”了,然而,时光荏苒,转眼到了24年,这个“项”却依然未能完成。时间过得真是匆匆忙忙。

在这里插入图片描述

要实现这个功能,主要有以下几步:

  1. 解析上传的文件
    • 这涉及到类加载的问题
  2. 执行上传的文件
    • 这需要使用到反射技术

首先,会实现一个基础功能:拖拽一个 Java 文件并自动执行其 main 函数。由于编写前端界面比较麻烦,这里选择使用 Java Swing,这是我个人非常喜欢的一个工具。

拖拽一个 Java 文件,执行 main 函数

这段实现其实比较简单:

package blog.dongguabai.others.drag_run;

import javax.swing.*;

/**
 * @author dongguabai
 * @date 2024-01-03 12:38
 */
import javax.tools.*;
import java.awt.*;
import java.awt.datatransfer.DataFlavor;
import java.awt.dnd.*;
import java.io.*;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.List;

public class DragRunDemo1 extends JFrame {

    public DragRunDemo1() {
        JTextArea textArea = new JTextArea();
        textArea.setDropTarget(new DropTarget() {
            @Override
            public synchronized void drop(DropTargetDropEvent evt) {
                evt.acceptDrop(DnDConstants.ACTION_COPY);
                try {
                    List<File> droppedFiles = (List<File>) evt.getTransferable().getTransferData(DataFlavor.javaFileListFlavor);
                    droppedFiles.forEach(file -> compileAndRun(file, textArea));
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
        });
        JScrollPane scrollPane = new JScrollPane(textArea);
        JPanel panel = new JPanel(new BorderLayout());
        panel.setBorder(BorderFactory.createTitledBorder("Dongguabai-代码拖拽执行"));
        panel.add(scrollPane, BorderLayout.CENTER);
        this.add(panel);
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.setSize(600, 900);
        this.setVisible(true);
    }

    private void compileAndRun(File file, JTextArea textArea) {
        try {
            JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
            compiler.run(null, null, null, file.getPath());
            URLClassLoader classLoader = URLClassLoader.newInstance(new URL[]{file.getParentFile().toURI().toURL()});
            String className = file.getName().replace(".java", "");
            Class<?> cls = Class.forName(className, true, classLoader);
            Method method = cls.getDeclaredMethod("main", String[].class);
            PrintStream originalOut = System.out;
            PrintStream out = new PrintStream(new OutputStream() {
                @Override
                public void write(int b) {
                    textArea.append(String.valueOf((char) b));
                }
            });
            //重定向
            System.setOut(out);
            textArea.append("->" + file.getName() + ":\n");
            method.invoke(null, (Object) new String[]{});
            textArea.append("\n");
            System.setOut(originalOut);
        } catch (Throwable e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(DragRunDemo1::new);
    }
}

测试代码:

import java.util.Date;

/**
 * @author dongguabai
 * @date 2024-01-03 17:02
 */
public class SimpleTest {

    public static void main(String[] args) {
        System.out.println(new Date().toLocaleString());
    }
}

支持同时拖拽多个文件,运行效果如下:

在这里插入图片描述

然而,需要注意一个问题。在实际的开发场景中,逻辑通常会非常复杂,我们通常会将这些逻辑打包成 JAR 文件来执行。因此,这里的工具也需要能够执行 JAR 包中指定包下所有类的 main 函数。

拖拽一个 JAR 文件,执行指定 package 下所有类的 main 函数

实现如下:

package blog.dongguabai.others.drag.run;

import javax.swing.*;
import javax.tools.JavaCompiler;
import javax.tools.ToolProvider;
import java.awt.*;
import java.awt.datatransfer.DataFlavor;
import java.awt.dnd.DnDConstants;
import java.awt.dnd.DropTarget;
import java.awt.dnd.DropTargetDropEvent;
import java.io.File;
import java.io.OutputStream;
import java.io.PrintStream;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
 * @author dongguabai
 * @date 2024-01-03 19:24
 */
public class DragRunDemo2 extends JFrame {

    public DragRunDemo2() {
        JTextArea textArea = new JTextArea();
        textArea.setDropTarget(new DropTarget() {
            @Override
            public synchronized void drop(DropTargetDropEvent evt) {
                evt.acceptDrop(DnDConstants.ACTION_COPY);
                try {
                    List<File> droppedFiles = (List<File>) evt.getTransferable().getTransferData(DataFlavor.javaFileListFlavor);
                    for (File file : droppedFiles) {
                        if (file.getName().endsWith(".java")) {
                            compileAndRunJava(file, textArea);
                        } else if (file.getName().endsWith(".jar")) {
                            String[] parts = file.getName().split("#");
                            if (parts.length > 1) {
                                String packageName = parts[1].replace("_", ".").replace(".jar", "");
                                runJar(file, textArea, packageName);
                            }
                        }
                    }
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
        });
        JScrollPane scrollPane = new JScrollPane(textArea);
        JPanel panel = new JPanel(new BorderLayout());
        panel.setBorder(BorderFactory.createTitledBorder("Dongguabai-代码拖拽执行"));
        panel.add(scrollPane, BorderLayout.CENTER);
        this.add(panel);
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.setSize(600, 900);
        this.setVisible(true);
    }

    private void compileAndRunJava(File file, JTextArea textArea) {
        try {
            JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
            compiler.run(null, null, null, file.getPath());
            URLClassLoader classLoader = URLClassLoader.newInstance(new URL[]{file.getParentFile().toURI().toURL()});
            String className = file.getName().replace(".java", "");
            Class<?> cls = Class.forName(className, true, classLoader);
            Method method = cls.getDeclaredMethod("main", String[].class);
            PrintStream originalOut = System.out;
            PrintStream out = new PrintStream(new OutputStream() {
                @Override
                public void write(int b) {
                    textArea.append(String.valueOf((char) b));
                }
            });
            //重定向
            System.setOut(out);
            textArea.append("->" + file.getName() + ":\n");
            method.invoke(null, (Object) new String[]{});
            textArea.append("\n");
            System.setOut(originalOut);
        } catch (Throwable e) {
            e.printStackTrace();
        }
    }

    private void runJar(File file, JTextArea textArea, String packageName) {
        try {
            URLClassLoader classLoader = URLClassLoader.newInstance(new URL[]{file.toURI().toURL()});
            JarFile jarFile = new JarFile(file);
            Enumeration<JarEntry> entries = jarFile.entries();
            while (entries.hasMoreElements()) {
                JarEntry entry = entries.nextElement();
                if (entry.getName().endsWith(".class")) {
                    String className = entry.getName().replace("/", ".").replace(".class", "");
                    if (className.startsWith(packageName)) {
                        Class<?> cls = Class.forName(className, true, classLoader);
                        for (Method method : cls.getDeclaredMethods()) {
                            if (method.getName().equals("main") && Modifier.isStatic(method.getModifiers())) {
                                PrintStream originalOut = System.out;
                                PrintStream out = new PrintStream(new OutputStream() {
                                    @Override
                                    public void write(int b) {
                                        textArea.append(String.valueOf((char) b));
                                    }
                                });
                                System.setOut(out);
                                textArea.append("->" + file.getName() + ", class " + className + ":\n");
                                method.invoke(null, (Object) new String[]{});
                                textArea.append("\n");
                                System.setOut(originalOut);
                            }
                        }
                    }
                }
            }

            jarFile.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(DragRunDemo2::new);
    }
}

每个拖拽的 JAR 文件都是通过一个新的 URLClassLoader 实例来加载的。每个 URLClassLoader 实例都有自己的类加载空间,它们之间是相互隔离的。

构建一个简单的 Jar 文件:

package blog.dongguabai.drag.run;

import java.util.Date;

/**
 * Hello world!
 *
 */
public class App 
{
    public static void main( String[] args )
    {
        System.out.println(new Date().toLocaleString());
    }
}

打包:

➜  dongguabai-others-drag-run mvn clean install
...
[INFO] Building jar: /Users/dongguabai/IdeaProjects/gitee/dongguabai-others-drag-run/target/testJa#blog_dongguabai_drag_run.jar
...

生成的 Jar 名称是:testJa#blog_dongguabai_drag_run.jar,所以会执行 blog.dongguabai.drag.run 包下所有类的 main 函数。

拖拽执行:

在这里插入图片描述

依赖类加载问题

这里其实存在一个依赖类的加载问题,比如我在 JAR 中新增 Guava 依赖:

 <dependency>
      <groupId>com.google.guava</groupId>
      <artifactId>guava</artifactId>
      <version>31.1-jre</version>
    </dependency>

在要执行的 App 中使用 Guava 的 Lists

package blog.dongguabai.drag.run;

import com.google.common.collect.Lists;

import java.util.Date;

/**
 * Hello world!
 */
public class App {
    public static void main(String[] args) {
        System.out.println(Lists.newArrayList(new Date().toLocaleString()));
    }
}

重新打包后拖拽执行,会出现异常:

Caused by: java.lang.NoClassDefFoundError: com/google/common/collect/Lists
	at blog.dongguabai.drag.run.App.main(App.java:12)
	... 39 more
Caused by: java.lang.ClassNotFoundException: com.google.common.collect.Lists
	at java.net.URLClassLoader.findClass(URLClassLoader.java:382)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
	at java.net.FactoryURLClassLoader.loadClass(URLClassLoader.java:817)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
	... 40 more

这是因为我这里使用 URLClassLoader 直接加载了 JAR 文件,它只会加载这个 JAR 文件中的所有类,但是它不会加载 JAR 文件的依赖项。

胖 JAR

可以基于 Maven 创建一个包含所有依赖项的 “胖” JAR 文件,增加如下配置:

<plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <version>3.2.4</version>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>shade</goal>
                        </goals>
                        <configuration>
                            <!--只包含实际被使用的依赖项(不一定准,比如反射等特殊调用)-->
                            <minimizeJar>true</minimizeJar>
                        </configuration>
                    </execution>
                </executions>
            </plugin>

这里把胖 JAR 和瘦 JAR 做一个对比,可以看到胖 JAR 大很多(增加 minimizeJar 参数后,胖 JAR 实际只有 1.2M 了 ),而且将依赖类也加载了:

在这里插入图片描述

重新拖拽执行,效果不错:

在这里插入图片描述

类冲突问题

通过上文可以看出,支撑拖拽执行的核心类是 blog.dongguabai.others.drag.run.DragRunDemo2

但如果上传的 JAR 中也有一个同名类:

package blog.dongguabai.others.drag.run;

import com.google.common.collect.Lists;

import java.util.Date;

/**
 * @author dongguabai
 * @date 2024-01-04 10:23
 */
public class DragRunDemo2 {

    public static void main(String[] args) {
        System.out.println(Lists.newArrayList(new Date().toLocaleString()));
    }
}

JAR 名称:

<finalName>testJa#blog_dongguabai_others_drag_run</finalName>

拖拽执行:

在这里插入图片描述

这是因为使用了 URLClassLoader 来加载 JAR 文件中的类。URLClassLoader 遵循双亲委派模型。当尝试加载一个类时,URLClassLoader 会首先尝试在其父类加载器中查找这个类,如果找到了就直接返回,否则才会尝试自己加载这个类。

因此如果上传的 JAR 文件中包含一个与当前运行环境中的类完全相同的类,那么 URLClassLoader 就会直接返回当前运行环境中的类,而不会加载 JAR 文件中的类。

打破双亲委派机制

自定义类加载器:

package blog.dongguabai.others.drag.run;

import java.net.URL;
import java.net.URLClassLoader;

/**
 * @author dongguabai
 * @date 2024-01-04 11:56
 */
public class CustomizedClassLoader extends URLClassLoader {

    public CustomizedClassLoader(URL[] urls, ClassLoader parent) {
        super(urls, parent);
    }

    @Override
    public Class<?> loadClass(String name) throws ClassNotFoundException {
        try {
            return findClass(name);
        } catch (ClassNotFoundException e) {
            return super.loadClass(name);
        }
    }
}

修改加载类逻辑:

//            URLClassLoader classLoader = URLClassLoader.newInstance(new URL[]{file.toURI().toURL()});
            CustomizedClassLoader classLoader = new CustomizedClassLoader(new URL[]{file.toURI().toURL()}, ClassLoader.getSystemClassLoader().getParent());

再次拖拽运行:

在这里插入图片描述

效果不错。

在这里加上前后的类加载机制对比图(网上发现一张图画的不错,这里直接在其基础上进行修改,原文链接在文末):

之前:

在这里插入图片描述

打破双亲委派后:

在这里插入图片描述

总结

本文实现了一个简单的工具,用户可以通过拖拽的方式执行 Java 文件和 JAR 包。同时,通过使用胖 JAR 来解决依赖类的加载问题;通过自定义类加载器打破双亲委派机制,从而解决了类冲突问题。

源码地址:https://gitee.com/dongguabai/blog/tree/master/others

References

  • https://blog.csdn.net/qq_33591903/article/details/120088757

欢迎关注公众号:
在这里插入图片描述

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

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

相关文章

从《数据库索引设计与优化》看mysql索引设计

很久之前写的一篇文章&#xff0c;主要是结合mysql45讲和《数据库索引设计与优化》讨论索引设计的&#xff0c;拿出来分享下。 选用什么引擎 对于INSERT_SELECT型数据库&#xff0c;如果没有事务的要求&#xff0c;更倾向于选择MyISAM。 因为InnoDB会维护更多的数据&#xff…

Git如何将多个commit合并一个commit

问题场景&#xff1a;我在fork的仓库提交多个commit后&#xff0c;准备向原仓库提交pr&#xff0c;但是原仓库要求一个pr一个commit&#xff0c;因此需要先将这些commit合并为一个。 1.先拿到要合并的commit中最早的一个的commit id&#xff0c;然后进入仓库&#xff0c;使用如…

2024年山西省安全员C证证模拟考试题库及山西省安全员C证理论考试试题

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 2024年山西省安全员C证证模拟考试题库及山西省安全员C证理论考试试题是由安全生产模拟考试一点通提供&#xff0c;山西省安全员C证证模拟考试题库是根据山西省安全员C证最新版教材&#xff0c;山西省安全员C证大纲整理…

NX二次开发中如何从对象选择控件中获得选中面的TAG值

一、概述 在NX二次开发中所有的对象操作都是通过对对象的TAG值进行操作控制&#xff0c;如何结合BlockUI控件&#xff0c;得到对象的TAG值是十分重要的一步。今天就遇到了这个问题&#xff0c;其实不是不会&#xff0c;而是思维习惯&#xff0c;直接利用对象选择器->Tag()&a…

杨中科 ASP.NETCore Rest

什么是Rest RPC 1、Web API两种风格: 面向过程(RPC) 、面向REST (REST) 2、RPC:“控制器/操作方法“的形式把服务器端的代码当成方法去调用。把HTTP当成传输数据的通道&#xff0c;不关心HTTP谓词。通过QueryString请求报文体给服务器传递数据。状态码。比如/Persons/GetAll…

[Vulnhub靶机] DriftingBlues: 3

[Vulnhub靶机] DriftingBlues: 3靶机渗透思路及方法&#xff08;个人分享&#xff09; 靶机下载地址&#xff1a; https://download.vulnhub.com/driftingblues/driftingblues3.ova 靶机地址&#xff1a;192.168.67.19 攻击机地址&#xff1a;192.168.67.3 一、信息收集 1.…

离线Vscode 安装完成后 添加到右键菜单

复制下面代码&#xff0c;修改文件后缀名为&#xff1a;reg Windows Registry Editor Version 5.00[HKEY_CLASSES_ROOT\*\shell\VSCode] "Open with Code" "Icon""D:\\_Porgram_IT\\VsCode\\Code.exe"[HKEY_CLASSES_ROOT\*\shell\VSCode\comman…

GB∕T 33171-2016 城市交通运行状况评价规范

免登陆免积分下载地址 标准号&#xff1a;GB/T 33171-2016 中文标准名称&#xff1a;城市交通运行状况评价规范 英文标准名称&#xff1a;Specification for urban traffic performance evaluation 中国标准分类号&#xff08;CCS&#xff09;R85 国际标准分类号&#xff08;…

告别复杂排版:Markdown语法指南

导语&#xff1a;Markdown作为一种轻量级的标记语言&#xff0c;以其简洁、易学的语法和强大的兼容性赢得了广泛的应用。本文将为您详细介绍Markdown的起源、基本语法及其在写作、博客、项目管理等场景的应用&#xff0c;带您领略这一简洁高效的文本编写工具的无穷魅力。 Mark…

打工人的2.0时代,只需要一副AR眼镜!

在数字化时代&#xff0c;工业行业中的生产效率如何得到提升&#xff1f;工业AR眼镜或许是一个不错的选择。不过工业AR眼镜真的可以协助员工处理工作中所遇到的各种问题吗&#xff1f;我们以制造业、医疗行业、船舶业的不同从业者为例&#xff1a; 假如你是一名制造业从业者&am…

Linux操作系统——进程控制(一) 进程创建和进程终止

进程创建 fork函数 在linux中fork函数时非常重要的函数&#xff0c;它从已存在进程中创建一个新进程。新进程为子进程&#xff0c;而原进程为父进程。 #include <unistd.h> pid_t fork(void); 返回值&#xff1a;自进程中返回0&#xff0c;父进程返回子进程id&#xff…

数据分析-25-电商用户行为可视化分析

文章目录 0. 数据代码获取1. 项目介绍1.1 分析背景1.2 分析目的1.3 分析思路 2. 数据清洗2.1 加载必要的库2.2 读取数据2.3 统计缺失值2.4 处理数据a. 删除重复值b. 转换时间格式c. 提取日期和时间d. 转换数据类型 3. 分析内容3.1 用户活跃规律a. 日均pv与uvb. 日新增pv、uv趋势…

使用echarts制作柱状图并且下方带表格

实现效果: 调试地址: Examples - Apache ECharts 源码: option { title: { left: center, top: 0, text: 2022-05月 制造产量 达成情况(单位: 吨) (图1)\n\n集团目标产量: 106,675吨 集团实际产量: 2,636吨, textStyle:{ fontSize:20, colo…

React 入门 - 01

本章内容 目录 1. 简介1.1 初始 React1.2 React 相关技术点1.3 React.js vs Vue.js 2. React 开发环境准备2.1 关于脚手架工具2.2 create-react-app 构建一个 React 项目工程 1. 简介 1.1 初始 React React JS 是 Facebook 在 2013年5月开源的一款前端框架&#xff0c;其带来…

【UML建模】部署图(Deployment Diagram)

1.概述 部署图是一种结构图&#xff0c;用于描述软件系统在不同计算机硬件或设备上的部署和配置情况&#xff0c;以图形化的方式展示系统中组件、节点和连接之间的物理部署关系。 通过部署图&#xff0c;可以清晰地了解系统的物理结构和部署方式&#xff0c;包括系统组件和节…

小程序购物商城搭建开发分析

小程序商城作为现代商业模式的重要组成部分&#xff0c;具有巨大的发展潜力和商业价值。通过搭建一个功能完善、用户友好的小程序商城&#xff0c;您将能够提供便捷的购物体验&#xff0c;吸引更多的用户并实现商业增长。在进行小程序商城开发搭建之前&#xff0c;我们需要对项…

期末成绩这样发 老师省心 家长舒心

从很久以前开始就不能公布学生的分数了&#xff0c;很多学校都是只公布等级或者是大概的范围&#xff0c;那么如何合理告知家长成绩呢&#xff1f; 在家长群内发布公告&#xff0c;告知考试成绩已经出炉&#xff0c;如果家长需要了解具体分数&#xff0c;可以私信联系你。这样既…

网络割接为什么经常是半夜进行?

你们好&#xff0c;我的网工朋友。 假设你最近遇到了一个客户&#xff0c;客户有个新的园区刚刚建成&#xff0c;园区内包括建筑物若干&#xff0c;地理覆盖面也较广&#xff0c;园区建成后&#xff0c;肯定是需要一个专用网络的&#xff0c;用于承载公司的业务流量。 这时候&…

C语言编译器(C语言编程软件)完全攻略(第五部分:VS2015使用教程(使用VS2015编写C语言程序))

介绍常用C语言编译器的安装、配置和使用。 五、VS2015使用教程&#xff08;使用VS2015编写C语言程序&#xff09; 前面我们给出了一段完整的C语言代码&#xff0c;就是在显示器上输出“C语言中文网”&#xff0c;如下所示&#xff1a; #include <stdio.h> int main() {…

网页爬虫在数据分析中的作用,代理IP知识科普

在当今信息爆炸的时代&#xff0c;数据分析成为洞察信息和制定决策的不可或缺的工具。而网页爬虫&#xff0c;作为数据收集的得力助手&#xff0c;在数据分析中扮演着举足轻重的角色。今天&#xff0c;我们将一同探讨网页爬虫在数据分析中的作用。 1. 数据收集的先锋 网页爬虫…