java通過URLClassLoader類加載器加載外部jar代碼示例
相信在實(shí)際工作中,大家可能會(huì)遇到這種需求,這個(gè)jar是外部的,并沒有添加到項(xiàng)目依賴中,只能通過類加載器加載并調(diào)用相關(guān)方法。
這種jar加載,其實(shí)也簡單,我們通過普通的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加載到了虛擬機(jī)中,要調(diào)用,我們需要根據(jù)類名與類加載器來 獲取類對象。
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;
}還需要做一些準(zhǔn)備:
1,定義一個(gè)接口,外部去實(shí)現(xiàn):
import java.util.Map;
public interface IService {
String name();
Object process(Map<String, Object> params);
}2、三方實(shí)現(xiàn):
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;
}
}我們在這個(gè)實(shí)現(xiàn)里面,故意調(diào)用了另一個(gè)三方的依賴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添加一個(gè)classpath到manifest.mf文件,以及在class-path元素中包含所有依賴項(xiàng)-->
<addClasspath>true</addClasspath>
<!--所有依賴應(yīng)該位于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>我們會(huì)把三方實(shí)現(xiàn)打jar包,jar包含了所需的依賴:

實(shí)際中使用,我們需要先獲取類加載器,然后獲取類對象,再實(shí)例化,最后調(diào)用對象的方法。
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);
}
}
}
}運(yùn)行報(bào)錯(cuò):

這里報(bào)錯(cuò)信息,提示是NoClassDefFoundError:com/example/util/FileUtil。
但是我們進(jìn)行打包的時(shí)候,明明把FileUtil的jar打到了common-test-1.0-SNAPSHOT.jar中了,為什么找不到呢?
這里其實(shí)就是今天要說的重點(diǎn),我們通過URLClassLoader加載jar,確實(shí)能加載到相關(guān)的類對象,而且實(shí)例化也不會(huì)有問題,但是我們在調(diào)用實(shí)例方法的時(shí)候,因?yàn)檫@里面使用了其他三方的jar,但是這個(gè)三方j(luò)ar是包含在common-test本身jar內(nèi)部的,它不能再像文件系統(tǒng)那樣讀取自己內(nèi)部的資源。
解決辦法就是把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;
}這里重新定義了一個(gè)類加載器方法,加載jar,獲取類加載器之后,把jar包中l(wèi)ib下的jar也寫入本地,同樣的,也需要把這些jar加載起來。
最后運(yùn)行調(diào)用示例,正確打?。?/p>

這里成功調(diào)用之后,我們可以看看本地的jar結(jié)構(gòu):

在common-test-1.0-SNAPSHOT.jar同級目錄下,還有一個(gè)lib文件夾,這里面就是common-test-1.0-SNAPSHOT.jar依賴的兩個(gè)jar。

其實(shí)本地文件系統(tǒng)有了lib這個(gè)目錄之后,我們再回過去用getClassLoader()那種方式調(diào)用,也是沒有問題的。

這是為什么呢?我們可以看看 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,但是它內(nèi)部的lib路徑是common-test-1.0-SNAPSHOT.jar!/lib/xxx.jar,通過普通的文件路徑lib/xxx.jar是無法加載的。
上面的代碼,如果使用loadAllJar()方法,雖然不會(huì)有問題,但是,如果我們獲取類加載器,得到類對象信息,在實(shí)例化之前,我們不小心調(diào)用了classloader.close()把類加載器關(guān)閉了,同樣會(huì)執(zhí)行報(bào)錯(cuò),如下所示:

本文主要講如何動(dòng)態(tài)調(diào)用外部jar,以及調(diào)用外部jar過程中可能遇到的問題。我們需要注意,外部jar內(nèi)部依賴的jar,雖然在jar中,但是因?yàn)槁窂街邪?/,并不能被訪問到,所以需要將內(nèi)部jar讀出來,并寫入本地。
本文其實(shí)也隱含了一個(gè)知識(shí)點(diǎn),就是如何提取jar內(nèi)部的資源。我們需要借助流讀取的方式來將他們寫出來。
完整代碼:https://gitee.com/buejee/classloaderexample/
總結(jié)
到此這篇關(guān)于java通過URLClassLoader類加載器加載外部jar的文章就介紹到這了,更多相關(guān)java URLClassLoader加載外部jar內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java中parallelStream().forEach()的踩坑日記
本文主要介紹了Java中parallelStream().forEach()的踩坑日記,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-06-06
SpringBoot集成Nacos的項(xiàng)目實(shí)踐
本文主要介紹了SpringBoot集成Nacos的項(xiàng)目實(shí)踐,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-07-07
以Java代碼的方式總結(jié)幾個(gè)典型的內(nèi)存溢出案例
作為程序員,多多少少都會(huì)遇到一些內(nèi)存溢出的場景,如果你還沒遇到,說明你工作的年限可能比較短,或者你根本就是個(gè)假程序員!哈哈,開個(gè)玩笑.今天分享給大家Java內(nèi)存溢出的相關(guān)案例,希望大家在日常工作中,盡量避免寫這些low水平的代碼,需要的朋友可以參考下2021-06-06
Java使用Knife4j優(yōu)化Swagger接口文檔的操作步驟
在現(xiàn)代微服務(wù)開發(fā)中,接口文檔的質(zhì)量直接影響了前后端協(xié)作效率,Swagger 作為一個(gè)主流的接口文檔工具,雖然功能強(qiáng)大,但其默認(rèn)界面和部分功能在實(shí)際使用中略顯不足,而 Knife4j 的出現(xiàn)為我們提供了一種增強(qiáng)的選擇,本篇文章將詳細(xì)介紹如何在項(xiàng)目中集成和使用 Knife4j2024-12-12
spring boot創(chuàng)建項(xiàng)目包依賴問題的解決
本篇文章主要介紹了spring boot創(chuàng)建項(xiàng)目包依賴問題的解決,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-11-11
Hystrix?Dashboard斷路監(jiān)控儀表盤的實(shí)現(xiàn)詳細(xì)介紹
這篇文章主要介紹了Hystrix?Dashboard斷路監(jiān)控儀表盤的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-09-09

