类加载器之URLClassLoader
在前面关于类加载子系统的文章中,我们提到启动类加载器是其他类加载器的parent,其本身parent为null、扩展类加载器的parent是启动类加载器、应用类加载器的parent是扩展类加载器,但是在那篇文章中并没有强调parent代表的含义是一个“委派”体系,而并不是继承关系。
在类加载器的体系中,真实的继承关系如下:
通过源码我们可以发现AppClassLoader和ExtClassLoader都是Launcher的静态内部类,继承自URLClassLoader。
那么URLClassLoader和SecureClassLoader具体是做什么的呢?
- SecureClassLoader:扩展了ClassLoader,并为定义具有相关代码源和权限的类提供了额外支持,这些代码源和权限默认情况下由系统策略检索。
- URLClassLoader:继承自SecureClassLoader,支持从jar文件和文件夹中获取class,继承于classload,加载时首先去classload里判断是否由启动类加载器加载过。
今天这篇文章我们重点要说的就是URLClassLoader,在上面类加载器的真实继承关系图中,我们知道URLClassLoader扩展了ClassLoader,所以它在ClassLoader的基础上扩展了一些功能,这些扩展的功能中,最主要的一点就是URLClassLoader却可以加载任意路径下的类(ClassLoader只能加载classpath下面的类)。
如果你未接触URLClassLoader,那么要实现动态加载类都是使用用Class.forName()这个方法,但是这个方法只能创建程序中已经引用的类,如果我们需要动态加载程序外的类,Class.forName()是不够的,这个时候就是需要使用URLClassLoader的时候。
在我的个人项目中,对于URLClassLoader是有实际使用过的,这里以我的项目作为案例,来看一下URLClassLoader的使用。
背景: 在我的个人网站和小程序接口开发的最开始阶段,是使用Spring Boot来开发的,这种方式搭建接口十分便捷,但是在后面不断学习的过程中,我萌发了自己实现一个类似Tomcat的小应用,然后模拟Spring实现自己的一套开发规范。这套个人项目首先是一个Maven项目,以Netty为基础实现Http协议,然后参考Spring实现DI和AOP,以及一系列的注解实现接口的开发,下面大概展示一些目录结构:
那么这个应用和URLClassLoader有什么关系呢?
首先一个普通Maven项目在打包成jar的时候,jar包内部本身是不包含依赖的,我们可以通过Maven-assembly-plugin插件将程序和它本身所依赖的jar包一起打到一个包里,但是这种方式每次打包后,jar都会比较大,所以最终采用依赖和项目代码进行分离的方案,将依赖jar放入lib文件下。
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>copy-dependencies</id>
<phase>package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<!--target/lib是依赖jar包的输出目录,根据自己喜好配置-->
<outputDirectory>target/lib</outputDirectory>
<excludeTransitive>false</excludeTransitive>
<stripVersion>false</stripVersion>
<includeScope>runtime</includeScope>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
当程序代码和依赖代码分离之后,如何部署到服务器成为了一个问题,最开始我想的是将其打包为一个可执行的jar,以类似SpringBoot的方式去部署,但是思考之后我更倾向于能有一个统一的部署方案,对于后续新的应用也可以部署,于是就想到了URLClassLoader:将项目jar和lib下的依赖jar放入URLClassLoader类的urls(存储加载的classes和resources)中,然后通过JarFile对项目jar进行遍历,找到程序的入口Main,然后使用URLClassLoader对类进行加载,最终启动它。这里的jar和lib路径是使用参数进行传递(这里为了直观所以写死),然后将程序编译为可执行的命令(参考模拟javac命令文章),后续就可以进行统一部署发布了
public static void main(String[] args) throws Exception {
String jar = "/jerry/target/jerry-1.0-SNAPSHOT.jar";
String lib = "/jerry/target/lib";
JarFile jarFile = new JarFile(new File(jar));
Enumeration<JarEntry> entry = jarFile.entries();
//Lib文件+项目Jar
URL url = new URL("file:" + jar);
File file_directory = new File(lib);
URL[] urls = new URL[file_directory.listFiles().length + 1];
int i = 0;
urls[i++] = url;
for (File file : file_directory.listFiles()) {
urls[i++] = new URL("file:" + file.getAbsolutePath());
}
ClassLoader loader = new URLClassLoader(urls);
Class<?> c = null;
Object o = null;
while (entry.hasMoreElements()) {
JarEntry jarEntry = entry.nextElement();
String name = jarEntry.getName();
System.out.println(name);
if (name != null && name.endsWith(".class")) {
if (name.equals("Main.class")) {
c = loader.loadClass(name.substring(0, name.length() - 6));
o = c.newInstance();
}
}
}
if (o != null) {
System.out.println(o);
System.out.println(c);
Method method = c.getDeclaredMethod("main", String[].class);
method.invoke(o, (Object) new String[]{});
}
}
- Docker容器学习梳理--手动制作系统镜像
- 怎样裁剪图片的局部
- vb中实现最佳按钮效果
- silverlight:wcf双工通讯学习笔记
- Docker容器学习梳理--web管理工具DockerUI部署记录
- Docker容器学习梳理-容器硬盘热扩容
- 检测到Loaderlock的问题
- 权威报告预测比特币在2018年“王位”不保
- Linux下FTP环境部署梳理(vsftpd和proftpd)
- Silverlight如何与JS相互调用
- Docker容器学习梳理--私有仓库Registry使用
- 从插件重构看如何提升测试质量与效率
- 巧用WinRAR+Javascript解决activeX的自动安装问题
- 在网页中实现QQ的屏幕截图功能
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- Docker六脉神剑 (三) 编写Dockerfile构建nginx镜像并推送到远程仓库给其他人使用
- 快速学习UML类图查看
- 设计模式 | 抽象工厂模式
- 设计模式 | 单例模式
- macOS 安装软件已损坏无法打开解决办法 (真好用!)
- nginx 配置反向代理
- ES6新特性速查表
- React-Native Android打包
- React-Native iOS打包
- Webpack+Babel手把手带你搭建开发环境(内附配置文件)
- Redux 异步解决方案2. Redux-Saga中间件
- Redux异步解决方案 1. Redux-Thunk中间件
- 深度学习Pytorch检测实战 - Notes - 第1&2章 基础知识
- Java多线程编程在JMeter中应用
- Kubernetes 升级填坑指南(一)