java动态加载jar lib

1 篇文章 0 订阅
1 篇文章 0 订阅

    前几天遇到一个动态加载jar的问题, 有点绕, 还好解决了, 这里记录一下.
    我遇到的问题是, 一个Swing工具中, 在外部类Outer中动态加载一个组件类Inner, 这个组件类Inner需要一个几百兆的jar包, 而外部类Outer其实是不需要的, 所以如果在启动时直接加载jar包会导致外部类启动速度很慢, 过了5秒左右界面才出来. 所以就在研究组件类采用延迟加载的方式, 在Outer执行完以后等Inner动态加载完成然后更新到Outer里去. 动态加载需要通过URLClassLoader实现.
    遇到的两个问题是:
    ①在外部类启动时, 内部类已经一起加载到启动时的ClassLoader里去了, 而启动时ClassLoader是不认识我指定的子级URLClassLoader的, 所以对于Inner需要的类库还是会通过原ClassLoader去查找, 自然是找不到的.
    ②Inner组件类使用了一个外部类的JFrame对象, 所以不能采用父子分离的加载方式, 即Outer使用原ClassLoader, Inner使用一个新的URLClassLoader, 且URLClassLoader和原ClassLoader没有继承关系. 这样会导致出现两个JFrame class, 分属于两个ClassLoader, 在Outer中用反射去调用Inner中的方法时会出现找不到方法的异常, 因为参数类型的class不完全一样.
    后来的解决方法是, 重写URLClassLoader的loadClass方法, 指定了 只在加载Inner类时不使用父ClassLoader中已有的类, 而是通过子URLClassLoader重新加载一个新的Inner类, 这样对于Inner类需要的各种类库, 都能在子URLClassLoader中找到. 而对于JFrame等共通类, 依然使用父ClassLoader加载的方式, 这样可以保持两个ClassLoader参数的一致性.
由于手上没有原始代码, 以下代码只是模拟我遇到的情况.
代码文件如下:
在这里插入图片描述
具体java代码如下:
LoadJars.java

package work.drqf;

import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.FileVisitResult;
import java.nio.file.FileVisitor;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.List;

public class LoadJars {

	public static URLClassLoader loadJars(String path) throws Exception {

		Path p = Paths.get(path);
		List<URL> urls = new ArrayList<>();
		Files.walkFileTree(p, new FileVisitor<Path>() {
			@Override
			public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
				return FileVisitResult.CONTINUE;
			}

			@Override
			public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
				if (file.toFile().getName().toLowerCase().endsWith(".jar")) {
					urls.add(file.toUri().toURL());
				}
				return FileVisitResult.CONTINUE;
			}

			@Override
			public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
				return FileVisitResult.CONTINUE;
			}

			@Override
			public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
				return FileVisitResult.CONTINUE;
			}
		});
		// urls.forEach(System.out::println);
		URLClassLoader loader = new URLClassLoader(urls.toArray(new URL[urls.size()]),
				Thread.currentThread().getContextClassLoader()) {
			@Override
			public Class<?> loadClass(String name) throws ClassNotFoundException {
				// 对于DynamicClass$InnerClass类, 通过子classLoader加载
				if ("work.drqf.DynamicClass$InnerClass".equals(name)) {
					synchronized (getClassLoadingLock(name)) {
						Class<?> c = findLoadedClass(name);
						if (c == null) {
							c = findClass(name);
						}
						return c;
					}
				} else {
					// 其他类, 通过原classLoader加载
					return super.loadClass(name);
				}
			}
		};
		return loader;
	}

}

DynamicClass.java

package work.drqf;

import java.lang.reflect.Method;
import java.net.URLClassLoader;
import java.util.HashMap;
import java.util.Map;

import com.google.gson.Gson;

public class DynamicClass {

	public static void main(String[] args) throws Exception {
		Map<String, String> map = new HashMap<>();
		map.put("aaa", "bbb");

		URLClassLoader loader = LoadJars.loadJars("D:\\tmp\\Test\\loadJars");
		System.out.println(ClassLoader.getSystemClassLoader());
		System.out.println(loader);

		Class<?> gsonClazz = loader.loadClass("com.google.gson.Gson");
		System.out.println(gsonClazz);
		Class<?> clazz = loader.loadClass("work.drqf.DynamicClass$InnerClass");
		System.out.println(clazz.getClassLoader());
		Method m = clazz.getDeclaredMethod("callGson", Map.class);
		m.setAccessible(true);

		m.invoke(null, map);
	}

	private static class InnerClass {

		private static void callGson(Map<String, String> map) {
			Gson gson = new Gson();
			System.out.println(gson.toJson(map));
		}
	}

}

其中Inner类用到了Gson库.
导出项目为Test.jar
测试时文件如图:
在这里插入图片描述
在这里插入图片描述
run.bat代码如下:

java -cp .\Test.jar work.drqf.DynamicClass
pause

run.bat用于执行Test.jar, loadJars中是待加载的Gson库.
注意的是待加载lib中也要有一份Test.jar代码. 因为待加载的Inner类在其中. 外部的Test.jar中删掉Inner类也是可以的.
执行run.bat
在这里插入图片描述
成功在Inner中调用Gson, 即实现了动态加载lib.

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值