java通过URLClassLoader类加载器加载外部jar

这篇具有很好参考价值的文章主要介绍了java通过URLClassLoader类加载器加载外部jar。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

    相信在实际工作中,大家可能会遇到这种需求,这个jar是外部的,并没有添加到项目依赖中,只能通过类加载器加载并调用相关方法。

    这种jar加载,其实也简单,我们通过普通的URLClassLoader就可以加载。代码如下所示:

public static URLClassLoader getClassLoader(String url) {
    URLClassLoader classLoader = new URLClassLoader(new URL[]{}, ClassLoader.getSystemClassLoader());
    try {
        Method method = URLClassLoader.class.getDeclaredMethod("addURL", new Class[]{URL.class});
        if (!method.isAccessible()) {
            method.setAccessible(true);
        }
        method.invoke(classLoader, new URL("file:" + url));
        return classLoader;
    } catch (NoSuchMethodException | MalformedURLException | InvocationTargetException | IllegalAccessException e) {
        throw new RuntimeException(e);
    }
}

    这里,只是把jar加载到了虚拟机中,要调用,我们需要根据类名与类加载器来 获取类对象。

public static List<Class<?>> getClassListByInterface(String url, ClassLoader classLoader, Class<?> clazz) {
	List<Class<?>> classList = new ArrayList<>();
	if (!clazz.isInterface()) {
		return classList;
	}
	List<String> classNames = new ArrayList<>();
	try (JarFile jarFile = new JarFile(url)) {
		Enumeration<JarEntry> entries = jarFile.entries();
		while (entries.hasMoreElements()) {
			JarEntry jarEntry = entries.nextElement();
			String entryName = jarEntry.getName();
			if (entryName != null && entryName.endsWith(".class")) {
				entryName = entryName.replace("/", ".").substring(0, entryName.lastIndexOf("."));
				classNames.add(entryName);
			}
		}
	} catch (IOException e) {
		throw new RuntimeException(e);
	}

	if (classNames.size() > 0) {
		for (String className : classNames) {
			try {
				Class<?> theClass = classLoader.loadClass(className);
				if (clazz.isAssignableFrom(theClass) && !theClass.equals(clazz)) {
					classList.add(theClass);
				}
			} catch (ClassNotFoundException e) {
				throw new RuntimeException(e);
			}
		}
	}
	return classList;
}

    还需要做一些准备:

    1,定义一个接口,外部去实现:

import java.util.Map;

public interface IService {
    String name();

    Object process(Map<String, Object> params);
}

    2、三方实现:


import com.example.service.IService;
import com.example.util.FileUtil;

import java.util.HashMap;
import java.util.Map;


public class BusinessService implements IService {
    @Override
    public String name() {
        return "Test";
    }

    @Override
    public Object process(Map<String, Object> params) {
        Map<String, Object> result = new HashMap<>();
        result.put("name", name());
        result.put("path", FileUtil.getPath("tmp"));
        return result;
    }
}

    我们在这个实现里面,故意调用了另一个三方的依赖FileUtil:

import java.io.File;


public class FileUtil {

    public static String getPath(String name) {
        return String.join(File.separator, "D:", name);
    }
}

    打包插件配置:

<build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.10.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>${project.build.sourceEncoding}</encoding>
                </configuration>

            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <version>3.2.2</version>
                <configuration>
                    <archive>
                        <manifest>
                            <!--告知maven-jar-plugin添加一个classpath到manifest.mf文件,以及在class-path元素中包含所有依赖项-->
                            <addClasspath>true</addClasspath>
                            <!--所有依赖应该位于lib包 -->
                            <classpathPrefix>lib/</classpathPrefix>
                        </manifest>
                    </archive>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-dependency-plugin</artifactId>
                <version>3.2.0</version>
                <executions>
                    <execution>
                        <id>copy-dependencies</id>
                        <phase>prepare-package</phase>
                        <goals>
                            <goal>copy-dependencies</goal>
                        </goals>
                        <configuration>
                            <outputDirectory>${project.build.directory}/classes/lib</outputDirectory>
                            <!--是否不包含间接依赖包-->
                            <excludeTransitive>false</excludeTransitive>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

 

   我们会把三方实现打jar包,jar包含了所需的依赖:

urlclassloader加载jar,java,java,jar,URLClassLoader,URL,classloader

    实际中使用,我们需要先获取类加载器,然后获取类对象,再实例化,最后调用对象的方法。

public class BusinessTest {
    public static void main(String[] args) {
        String url = "E:\\workspace\\java\\classloaderexample\\common-test\\target\\common-test-1.0-SNAPSHOT.jar";
        URLClassLoader classLoader = ClassLoaderUtil.getClassLoader(url);
        // URLClassLoader classLoader = ClassLoaderUtil.loadAllJar(url);
        List<Class<?>> classList = ClassLoaderUtil.getClassListByInterface(url, classLoader, IService.class);
        System.out.println(classList);
        for (Class clazz : classList) {
            try {
                IService service = (IService) clazz.newInstance();
                System.out.println(service.process(null));
            } catch (InstantiationException e) {
                throw new RuntimeException(e);
            } catch (IllegalAccessException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

    运行报错:

urlclassloader加载jar,java,java,jar,URLClassLoader,URL,classloader

    这里报错信息,提示是NoClassDefFoundError:com/example/util/FileUtil。

    但是我们进行打包的时候,明明把FileUtil的jar打到了common-test-1.0-SNAPSHOT.jar中了,为什么找不到呢?

    这里其实就是今天要说的重点,我们通过URLClassLoader加载jar,确实能加载到相关的类对象,而且实例化也不会有问题,但是我们在调用实例方法的时候,因为这里面使用了其他三方的jar,但是这个三方jar是包含在common-test本身jar内部的,它不能再像文件系统那样读取自己内部的资源。 

    解决办法就是把common-test里面lib下的jar资源读出来,写入本地。

public static URLClassLoader loadAllJar(String url) {
        URLClassLoader classLoader = new URLClassLoader(new URL[]{}, ClassLoader.getSystemClassLoader());
        Method method;
        try {
            method = URLClassLoader.class.getDeclaredMethod("addURL", new Class[]{URL.class});
            if (!method.isAccessible()) {
                method.setAccessible(true);
            }
            method.invoke(classLoader, new URL("file:" + url));
        } catch (NoSuchMethodException | MalformedURLException | InvocationTargetException | IllegalAccessException e) {
            throw new RuntimeException(e);
        }
        // write to local lib
        try {
            String jarResourceStr = "jar:file:" + url + "!/";
            URL libUrl = new URL(jarResourceStr);
            URLConnection urlConnection = libUrl.openConnection();
            if (urlConnection instanceof JarURLConnection) {
                JarURLConnection jarURLConnection = (JarURLConnection) urlConnection;
                JarFile jarFile = jarURLConnection.getJarFile();
                Enumeration<JarEntry> entries = jarFile.entries();
                String parentPath = Paths.get(url).getParent().toString();
                File libDir = Paths.get(parentPath, "lib").toFile();
                libDir.deleteOnExit();
                boolean isSuccess = libDir.mkdirs();
                if (!isSuccess) {
                    System.out.println("create lib dir fail");
                }
                while (entries.hasMoreElements()) {
                    JarEntry jarEntry = entries.nextElement();
                    String entryName = jarEntry.getName();
                    if (entryName.endsWith(".jar")) {
                        try (InputStream inputStream = classLoader.getResourceAsStream(entryName)) {
                            File target = new File(parentPath, entryName);
                            FileUtils.copyInputStreamToFile(inputStream, target);
                            method.invoke(classLoader, new URL("file:" + target.getPath()));
                        } catch (IOException e) {
                            throw new RuntimeException(e);
                        } catch (InvocationTargetException e) {
                            throw new RuntimeException(e);
                        } catch (IllegalAccessException e) {
                            throw new RuntimeException(e);
                        }
                    }
                }
            }

        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        return classLoader;
    }

    这里重新定义了一个类加载器方法,加载jar,获取类加载器之后,把jar包中lib下的jar也写入本地,同样的,也需要把这些jar加载起来。

    最后运行调用示例,正确打印:

urlclassloader加载jar,java,java,jar,URLClassLoader,URL,classloader

    这里成功调用之后,我们可以看看本地的jar结构:

urlclassloader加载jar,java,java,jar,URLClassLoader,URL,classloader

 在common-test-1.0-SNAPSHOT.jar同级目录下,还有一个lib文件夹,这里面就是common-test-1.0-SNAPSHOT.jar依赖的两个jar。

urlclassloader加载jar,java,java,jar,URLClassLoader,URL,classloader 

    其实本地文件系统有了lib这个目录之后,我们再回过去用getClassLoader()那种方式调用,也是没有问题的。

urlclassloader加载jar,java,java,jar,URLClassLoader,URL,classloader 

    这是为什么呢?我们可以看看 common-test jar包中的manifest.mf信息:

Manifest-Version: 1.0
Class-Path: lib/common-api-1.0-SNAPSHOT.jar lib/third-party-1.0-SNAPSH
 OT.jar
Build-Jdk-Spec: 1.8
Created-By: Maven JAR Plugin 3.2.2

    这里指定的Class-Path是lib/comon-api-1.0-SNAPSHOT.jar lib/third-party-1.0-SNAPSHOT.jar,我们通过类加载器加载了common-test jar,但是它内部的lib路径是common-test-1.0-SNAPSHOT.jar!/lib/xxx.jar,通过普通的文件路径lib/xxx.jar是无法加载的。

    上面的代码,如果使用loadAllJar()方法,虽然不会有问题,但是,如果我们获取类加载器,得到类对象信息,在实例化之前,我们不小心调用了classloader.close()把类加载器关闭了,同样会执行报错,如下所示:

urlclassloader加载jar,java,java,jar,URLClassLoader,URL,classloader

    本文主要讲如何动态调用外部jar,以及调用外部jar过程中可能遇到的问题。我们需要注意,外部jar内部依赖的jar,虽然在jar中,但是因为路径中包含了!/,并不能被访问到,所以需要将内部jar读出来,并写入本地。  

    本文其实也隐含了一个知识点,就是如何提取jar内部的资源。我们需要借助流读取的方式来将他们写出来。

    完整代码:https://gitee.com/buejee/classloaderexample/文章来源地址https://www.toymoban.com/news/detail-568099.html

到了这里,关于java通过URLClassLoader类加载器加载外部jar的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Java-springboot动态加载jar包,动态配置

    1、背景 ​ 目前数据治理服务中有众多治理任务,当其中任一治理任务有改动需要升级或新增一个治理任务时,都需要将数据治理服务重启,会影响其他治理任务的正常运行。 2、目标 能够动态启动、停止任一治理任务 能够动态升级、添加治理任务 启动、停止治理任务或升

    2024年02月13日
    浏览(44)
  • springboot打包的jar,使用java -cp报错:找不到或无法加载主类

    问题 springboot打包的jar,使用java -cp报错:找不到或无法加载主类 解决办法 springboot打包的文件与一般java启动方式有一些差别 需要修改为使用以下命令,后面增加类:org.springframework.boot.loader.PropertiesLauncher,如下: java -cp .:test-0.0.1-SNAPSHOT.jar -Dloader.main=com.aa.common org.springfram

    2024年02月16日
    浏览(54)
  • JAVA项目通过IDEA如何构建可执行JAR

    开发JAVA工程代码(main方法启动)如何构建出可以通过java -jar命令直接执行的jar?如果工程中依赖其他jar又如何处理?本文即针对以上两点通过IDEA开发工具来做一个简单的分析和尝试,测试如下四种方法,如有瑕疵请轻喷。 如下所示通过IDEA创建一个本地JAVA工程,此处工程名J

    2024年02月04日
    浏览(62)
  • node.js通过node-java库调用java接口(jar包)

    最近因项目需求,需要调用第三方java的打的jar包,但项目后端是用node.js写的,因此需要用node.js调用第三方jar,网上搜集资料,发现有一个库,node-java,可以通过js的方式调用java语言的包,以及包中的方法,因此整理了一下,以防时间长忘记,具体如下: jdk 1.8 并配置java_home、

    2024年02月12日
    浏览(41)
  • 通过IDEA将java程序或者项目打包为jar包(详细步骤)

    通过IDEA将java程序或者项目打包为jar包 文章目录 目录 文章目录 一、使用java语言,编写一个字典(Dict)Demo 二、将Dict程序打包为jar包 代码如下: 1.点击file,点击project structure. 2.点击Artifacts,点击“+”,点击JAR,点击from......dependencies.  3.选择项目位置Module,和程序类Main Class(要

    2024年02月11日
    浏览(62)
  • Linux 下通过 java 命令启动 jar 包的几种常见方式

    Linux 下通过 java 命令启动 jar 包的几种常见方式 一、后台启动jar包命令 方法一:直接启动 jar 包服务 方法二:后台启动 jar 包服务 方法三:后台不挂断启动 方式四:指定日志输出的启动 方式五:指定配置文件启动 方式六:指定配置文件,使用系统默认的log配置,不另行指定

    2024年02月13日
    浏览(41)
  • JAVA开发与运维(怎么通过docker部署微服务jar包)

    目标: 通过docker的方式部署微服务。 我们通过java开发的微服务可以打成jar包,我们可以直接通过裸机部署,也可以通过docker来部署,本文介绍通过docker来部署微服务。 Docker 是一个用于开发,交付和运行应用程序的开放平台。 Docker 将应用程序与基础架构分开,从而可以快速

    2024年02月09日
    浏览(41)
  • SpringBoot项目如何引入外部jar及将外部jar打包到项目发布jar包

    1、创建一个SpringBoot项目 下载项目之后将项目导入IDEA 2、如何添加外部jar包 准备一个外部的jar包, 我这里使用的是 guava-31.1-jre.jar 作为演示 下载地址:https://repo1.maven.org/maven2/com/google/guava/guava/31.1-jre/guava-31.1-jre.jar 在项目根路径下创建一个文件夹libs,将guava-31.1-jre.jar放到li

    2024年02月15日
    浏览(52)
  • 通过exe4j将 java 项目(jar)打包成exe可执行文件(详细步骤)

    通过exe4j将 java 项目(jar)打包成exe可执行文件 文章目录 文章目录 一、下载安装 二、使用步骤 下载链接: ej-technologies - Java APM, Java Profiler, Java Installer Builder  安装只需一直下一步即可。 1.输入注册码“L-g782dn2d-1f1yqxx1rv1sqd”。 2.如下图: 3.如下图: 4.如下图: 5.如下图:

    2024年02月16日
    浏览(43)
  • SpringBoot日常:引入外部jar包

    我们开发中经常会遇到这样的场景,对接外部的应用接口,对方给了一个sdk,也就是一个jar包文件,此时需要引入即可调用封装好的方法。 最常解决的一个办法就是将这个jar包打包放入我们的仓库,不管是本地还是私服,这样就可以直接在pom文件引用。 1、进入jar包当前目录

    2024年03月24日
    浏览(51)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包