Java通过jar包方式加载指定对象类教程以及源码分析

简介: Java通过jar包方式加载指定对象类教程以及源码分析

一,类加载器基础巩固


类加载器是JVM的重要核心组件之一,也是字节码执行的发源地,只有准确加载了类,JVM才能够创建对象。

一般地有三种类加载器,其名称、对应的对象类以及作用分别是(以JDK8为例):


启动类加载器——加载JRE库文件

用于加载rt.jar等11份文件,如下图所示,

1666254860426.jpg


扩展类加载器——加载JRE扩展文件

用于加载dnsns.jar等12份文件,如下图所示,

1666254871488.jpg


系统类加载器——加载类路径(classPath)下的所有文件


后两种类加载器均是sun.misc.Launcherd的内部嵌套静态类且继承了java.net包中的URLClassLoader类,启动类使用的加载器为null。

1666254885450.jpg


二,使用场景


1.对类进行动态地加载、使用和卸载

常用在Web系统中对编解码器的开发,将编解码器按照一定规则编写好后打包成外部jar包,上传到系统平台中,通过类加载器对jar包的加载完成编解码对象的生成,进而使用其编解码方法。不需要重新编译整个系统程序,完成代码的局部更新;

2.自定义类加载器

通过自定义类加载器,


三,打包一个jar


3.1 功能


把字符串解码为新格式的字符串


3.2 解码接口


public interface DecodeToPOJO {
    Object decode(String hexContent);
}


3.3 解码接口实现类


public class Decoder implements DecodeToPOJO {
    @Override
    public Object decode(String hexContent) {
        return "say Hi";
    }
    public static void main(String[] args) {
        Decoder decoder = new Decoder();
        System.out.println(decoder.decode(""));
    }
}

3.4 目的


在HTTP GET接口中通过加载jar包的形式调用Decoder类的decode方法。


3.5 生成jar包

1666255061227.jpg


四,编写一个接口加载类


    @RequestMapping("/doClassLoad")
    public String doClassLoad() throws IOException {
        long start = System.currentTimeMillis();
        ClassLoader classLoader = JarClassLoader.loadJarToSystemClassLoader(new File("F:\\respository\\MessageTransformer\\out"));
        Class aClass = null;
        try {
            aClass = classLoader.loadClass("org.leobit.codec.Decoder");
            System.out.println(aClass.hashCode());
        } catch (Exception e) {
            e.printStackTrace();
        }
        long end = System.currentTimeMillis();
        return Long.toString(end - start);
    }

JarClassLoader类在cn.hutool.hutool-core包中,省的自己编写了。


<dependency>
  <groupId>cn.hutool</groupId>
  <artifactId>hutool-core</artifactId>
  <version>4.4.0</version>
</dependency>

五,源码分析


我们主要关注加载类方法。

aClass = classLoader.loadClass("org.leobit.codec.Decoder");

进入该方法,可以看到该方法是线程安全的,首选检查类是否被加载,未加载的选择合适的类加载器进行加载,这就是双亲委派机制,

    protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }
                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    long t1 = System.nanoTime();
                    c = findClass(name);
                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

重点看findClass方法,实现类是URLClassLoaderfindClass方法后的sun.misc.PerfCounter表示性能计数器,可以看到生成类对象后,对象类计数加1。


protected Class<?> findClass(final String name)
        throws ClassNotFoundException
    {
        final Class<?> result;
        try {
            result = AccessController.doPrivileged(
                new PrivilegedExceptionAction<Class<?>>() {
                    public Class<?> run() throws ClassNotFoundException {
                        String path = name.replace('.', '/').concat(".class");
                        Resource res = ucp.getResource(path, false);
                        if (res != null) {
                            try {
                                return defineClass(name, res);
                            } catch (IOException e) {
                                throw new ClassNotFoundException(name, e);
                            }
                        } else {
                            return null;
                        }
                    }
                }, acc);
        } catch (java.security.PrivilegedActionException pae) {
            throw (ClassNotFoundException) pae.getException();
        }
        if (result == null) {
            throw new ClassNotFoundException(name);
        }
        return result;
    }

在来看defineClass方法,就是最终的生成对象的方法。


  private Class<?> defineClass(String name, Resource res) throws IOException {
        long t0 = System.nanoTime();
        int i = name.lastIndexOf('.');
        URL url = res.getCodeSourceURL();
        if (i != -1) {
            String pkgname = name.substring(0, i);
            // Check if package already loaded.
            Manifest man = res.getManifest();
            definePackageInternal(pkgname, man, url);
        }
        // Now read the class bytes and define the class
        java.nio.ByteBuffer bb = res.getByteBuffer();
        if (bb != null) {
            // Use (direct) ByteBuffer:
            CodeSigner[] signers = res.getCodeSigners();
            CodeSource cs = new CodeSource(url, signers);
            sun.misc.PerfCounter.getReadClassBytesTime().addElapsedTimeFrom(t0);
            return defineClass(name, bb, cs);
        } else {
            byte[] b = res.getBytes();
            // must read certificates AFTER reading bytes.
            CodeSigner[] signers = res.getCodeSigners();
            CodeSource cs = new CodeSource(url, signers);
            sun.misc.PerfCounter.getReadClassBytesTime().addElapsedTimeFrom(t0);
            return defineClass(name, b, 0, b.length, cs);
        }
    }

一直重载定义类的方法,最后终于在defineClass1Native方法中创建出对象。


可以看到就是将文件流读入到虚拟机中进行对象类的创建,该过程又包含class文件的校验、类变量的准备、方法的解析和初始化的阶段。


六,加载jar包结果展示


我们请求第4节中编写的接口,打印每次加载类的哈希码。


可以看到通过同一类加载器加载的对象的哈希码是相同的。

1666255181520.jpg

相关文章
|
6月前
|
Java 关系型数据库 数据库
Java 项目实战教程从基础到进阶实战案例分析详解
本文介绍了多个Java项目实战案例,涵盖企业级管理系统、电商平台、在线书店及新手小项目,结合Spring Boot、Spring Cloud、MyBatis等主流技术,通过实际应用场景帮助开发者掌握Java项目开发的核心技能,适合从基础到进阶的学习与实践。
976 3
|
10月前
|
消息中间件 算法 安全
JUC并发—1.Java集合包底层源码剖析
本文主要对JDK中的集合包源码进行了剖析。
|
5月前
|
安全 Java
Java之泛型使用教程
Java之泛型使用教程
414 10
|
4月前
|
Oracle Java 关系型数据库
Java 简单教程
Java是跨平台、面向对象的编程语言,广泛用于企业开发、Android应用等。本教程涵盖环境搭建、基础语法、流程控制、面向对象、集合与异常处理,助你快速入门并编写简单程序,为进一步深入学习打下坚实基础。
411 0
|
JavaScript NoSQL Java
接替此文【下篇-服务端+后台管理】优雅草蜻蜓z系统JAVA版暗影版为例-【蜻蜓z系列通用】-2025年全新项目整合搭建方式-这是独立吃透代码以后首次改变-独立PC版本vue版搭建教程-优雅草卓伊凡
接替此文【下篇-服务端+后台管理】优雅草蜻蜓z系统JAVA版暗影版为例-【蜻蜓z系列通用】-2025年全新项目整合搭建方式-这是独立吃透代码以后首次改变-独立PC版本vue版搭建教程-优雅草卓伊凡
609 96
接替此文【下篇-服务端+后台管理】优雅草蜻蜓z系统JAVA版暗影版为例-【蜻蜓z系列通用】-2025年全新项目整合搭建方式-这是独立吃透代码以后首次改变-独立PC版本vue版搭建教程-优雅草卓伊凡
|
7月前
|
缓存 安全 Java
Java 并发新特性实战教程之核心特性详解与项目实战
本教程深入解析Java 8至Java 19并发编程新特性,涵盖CompletableFuture异步编程、StampedLock读写锁、Flow API响应式流、VarHandle内存访问及结构化并发等核心技术。结合电商订单处理、缓存系统、实时数据流、高性能计数器与用户资料聚合等实战案例,帮助开发者高效构建高并发、低延迟、易维护的Java应用。适合中高级Java开发者提升并发编程能力。
279 0
|
9月前
|
Java Linux
java的jar后台启动
java的jar后台启动
212 14
|
8月前
|
Oracle Java 关系型数据库
java 编程基础入门级超级完整版教程详解
这份文档是针对Java编程入门学习者的超级完整版教程,涵盖了从环境搭建到实际项目应用的全方位内容。首先介绍了Java的基本概念与开发环境配置方法,随后深入讲解了基础语法、控制流程、面向对象编程的核心思想,并配以具体代码示例。接着探讨了常用类库与API的应用,如字符串操作、集合框架及文件处理等。最后通过一个学生成绩管理系统的实例,帮助读者将理论知识应用于实践。此外,还提供了进阶学习建议,引导学员逐步掌握更复杂的Java技术。适合初学者系统性学习Java编程。资源地址:[点击访问](https://pan.quark.cn/s/14fcf913bae6)。
1046 2
|
11月前
|
前端开发 JavaScript Java
[Java计算机毕设]基于ssm的OA办公管理系统的设计与实现,附源码+数据库+论文+开题,包安装调试
OA办公管理系统是一款基于Java和SSM框架开发的B/S架构应用,适用于Windows系统。项目包含管理员、项目管理人员和普通用户三种角色,分别负责系统管理、请假审批、图书借阅等日常办公事务。系统使用Vue、HTML、JavaScript、CSS和LayUI构建前端,后端采用SSM框架,数据库为MySQL,共24张表。提供完整演示视频和详细文档截图,支持远程安装调试,确保顺利运行。
460 17
|
Java
[JarEditor]可直接修改jar包的IDEA插件
### 修改JAR包变得更简单:JarEditor插件简介 **背景:** 开发中常需修改JAR包中的class文件,传统方法耗时费力。JarEditor插件让你一键编辑JAR包内文件,无需解压。 **插件使用:** 1. **安装:** 在IDEA插件市场搜索JarEditor并安装。 2. **修改class:** 打开JAR文件中的class,直接编辑,保存后一键构建更新JAR。 3. **文件管理:** 右键菜单支持在JAR内新增/删除/重命名文件等操作。 4. **搜索:** 使用内置搜索功能快速定位JAR包内的字符串。
2174 2
[JarEditor]可直接修改jar包的IDEA插件