Java中如何自定义一个类加载器

目录
  • 如何自定义加载器?
    • 示例:读取某文件的下的某class文件
  • 类加载器的使用及自定义类加载器

如何自定义加载器?

1.创建一个自定义加载器类 继承 ClassLoader 类

2.重写 findClass 方法。 主要是实现从那个路径读取 jar包或者.class文件,将读取到的文件用字节数组来存储,然后可以使用父类的 defineClass 来转换成字节码。

如果想破坏双亲委派的话,就重写 loadClass 方法, 否则不用重写

注意:

1.ClassLoader提供的 protected final Class<?> defineClass(String name, byte[] b, int off, int len) 是用来将字节数组转换成字节码文件的,传入参数 是 (类名,字节数组数据,字节数组读取的开始下标,字节数组的长度)

示例:读取某文件的下的某class文件

创建一个名为MyClassLoader的类加载器:

import java.io.*;

public class MyClassLoader extends ClassLoader{
    String path ;
    MyClassLoader(String dir){
        this.path = dir ;
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        try {
            this.path = this.path + name +".class";
            File f = new File(path);
            InputStream in = new FileInputStream(f);
            byte [] bys = new byte[  (int)f.length() ];
            int len = 0;
            while( (len = in.read(bys) )!= -1  ){

            }
            // byte[] -> .class
            return defineClass(name,bys,0,bys.length);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return super.findClass(name);
    }
}

测试:

在如下目录,生成一个Hello.class字节码文件

Hello.java:

public class Hello {
    public void sayHello(){
        System.out.println("Hello World!");
    }
}

测试类:

import java.lang.reflect.Method;

public class Test {
    public static void main(String[] args) throws Exception {
        MyClassLoader my = new MyClassLoader("D:\\test\\jvmtest\\");
        Class<?> c1 = my.loadClass("Hello");
        Object o = c1.newInstance();
        Method d = c1.getMethod("sayHello",null);
        d.invoke(o);
    }
}

运行测试类:

类加载器的使用及自定义类加载器

package com.tech.load.def;

/**
 * @author lw
 * @since 2021/12/3
 */
public class UserImpl {
    static {
        System.out.println("UserImpl init ...");
    }
}
package com.tech.load.def;

/**
 * @author lw
 * @since 2021/12/3
 */
public class DefLoader {
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        //上下文类加载器,默认使用的是 应用程序类加载器
//        ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
//        Class<?> c1 = contextClassLoader.loadClass("com.tech.load.def.UserImpl");
//        c1.newInstance(); //classloader.loadClass 不会触发初始化,当创建对象时执行初始化,执行静态程序块内容 输出 "UserImpl init ..."
//        ClassLoader contextClassLoader1 = Thread.currentThread().getContextClassLoader();
//        Class<?> c2 = contextClassLoader1.loadClass("com.tech.load.def.UserImpl");
//        c2.newInstance(); //使用相同的类加载器 加载相同的类名 则加载的是同一个类,c1 c2是同一个类,由于已经初始化过 创建对象不再初始化 不再打印  "UserImpl init ..."
//        System.out.println(contextClassLoader==contextClassLoader1); //true 获取的上下文类加载器是同一个类加载器
//        System.out.println(c1==c2); // true 同一个类加载器器,加载同名的类,第一次加载时加载的类会缓存到类加载器的缓存,再次加载直接在缓存读取,两次加载的是同一个类

        //直接获取类的类加载器 应用程序类加载器
        ClassLoader classLoader = UserImpl.class.getClassLoader();
        ClassLoader classLoader1 = UserImpl.class.getClassLoader();
        System.out.println(classLoader==classLoader1); //true 获取的是同一个应用程序类加载器

        ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
        System.out.println(classLoader==contextClassLoader); //true 线程上下文类加载器默认采用的也是应用程序类加载器 与通过类型对象getClassLoader获取方式相同

        Class<?> c1 = Class.forName("com.tech.load.def.UserImpl"); //会触发类的初始化
        ClassLoader classLoader2 = c1.getClassLoader();
        System.out.println(classLoader==classLoader2); //true 获取的是同一个应用程序类加载器

    }
}

在应用程序中,默认我们获取上下文类加载器、类型对象getClassLoader都是采用的同一个应用程序类加载器,类在第一次被加载后会缓存到类加载器的缓存中,由于是同一个类加载器此时同名的类不能被多次加载,且应用程序类加载器只能加载classpath下的类。

如果我们想加载自定义路径下的类,需要用到自定义类加载器,可以去指定路径下加载类,且通过创建多个类加载器对象,加载的同名类相互隔离,也就是说同名类可以被多个自定义类加载器对象加载。

编写自定义类加载器:

  • 继承ClassLoader;
  • 重写findClass方法在指定路径下进行类的加载,得到字节数组,然后使用defineClass根据字节数组生成字节码文件 也就是class文件;

编写一个测试类Goods放在D盘下,javac得到class文件

/**
 * @author lw
 * @since 2021/12/3
 */
public class Goods {
    static {
        System.out.println("Goods init ...");
    }
}

javac Goods.java

package com.tech.load.def;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;

/**
 * 自定义类加载器 加载类
 * @author lw
 * @since 2021/12/3
 */
public class DefLoad7 {
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        MyClassLoader classLoader1 = new MyClassLoader();
        Class<?> c1 = classLoader1.loadClass("Goods");
        Class<?> c2 = classLoader1.loadClass("Goods");
        System.out.println(c1==c2);//true 使用同一个类加载器加载同名类两次,实际只加载了一次,第二次是在类加载器的缓存加载的 结果两次加载的是同一个
        c1.newInstance(); //会初始化
        c2.newInstance(); //不会初始化 

        MyClassLoader classLoader2 = new MyClassLoader();
        Class<?> c3 = classLoader2.loadClass("Goods");
        System.out.println(c1==c3); //false 使用不同的类加载器对同一个类进行加载,会得到不同的类型对象
        c3.newInstance(); //会初始化
    }
}

//自定义类加载器 加载D盘下的类
class MyClassLoader extends ClassLoader{

    //去指定的路径下加载类
    //name是类名称
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        String path="D:\\"+name+".class";
        try {
            ByteArrayOutputStream os = new ByteArrayOutputStream();
            //将指定路径下的文件 拷贝到输出流
            Files.copy(Paths.get(path),os);
            byte[] bytes = os.toByteArray();
            //调用父类的方法 根据字节数组生成字节码文件 也就是class文件
            //bytes -> *.class

            return defineClass(name,bytes,0,bytes.length);
        } catch (IOException e) {
            e.printStackTrace();
            throw new ClassNotFoundException("类文件未找到",e);
        }
    }
}

使用自定义加载器,创建多个类加载器对象去加载同一个类,会得到多个类型对象。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • Java中线程上下文类加载器超详细讲解使用

    目录 一.什么是线程上下文类加载器 1.1.重要性 1.2.使用场景 二.ServiceLoader简单介绍 三.案例 3.1.使用ServiceLoader加载mysql驱动 3.2.Class.forName加载Mysql驱动 3.2.1.com.mysql.jdbc.Driver 3.2.2.java.sql.DriverManager初始化 3.2.3.调用DriverManager的registerDriver方法 3.2.4.执行DriverManager.getConnection

  • Java类加载器ClassLoader源码层面分析讲解

    目录 Launcher 源码 AppClassLoader 源码 ExtClassLoader 源码 ClassLoader 源码 总结 最终总结一下 Launcher 源码 sun.misc.Launcher类是java 虚拟机的入口,在启动 java应用 的时候会首先创建Launcher.在初始化Launcher对象的时候会创建一个ExtClassLoader拓展程序加载器 和 AppClassLoader应用程序类加载器(这俩鬼东西好像只是加载类的路径不一样而已),然后由这俩类加载器去加载

  • Java类加载器与双亲委派机制和线程上下文类加载器专项解读分析

    目录 一.类加载器 1.启动类加载器 2.拓展类加载器 3.应用类加载器 4.类的命名空间 二.双亲委派机制 1.类加载机制流程 2.类加载器加载顺序 3.双亲委派机制流程 4.源码分析 5.双亲委派机制优缺点 三.线程上下文类加载器 1.线程上下文类加载器(Context Classloader) 2.ServiceLoader 四.自定义类加载器 一.类加载器 类加载器就是根据类的二进制名(binary name)读取java编译器编译好的字节码文件(.class文件),并且转化生成一个ja

  • java自定义类加载器如何实现类隔离

    目录 自定义类加载器 准备 通过URLClassLoader来实现[推荐] 通过继承ClassLoader实现 网上java自定义类加载器很多容易找到,但是都是加载的单个类,如果被加载的类,有引用了其他类怎么办呢?接下来看一下如何来处理这种情况 有时候一个项目中可能会引用不同版本的第三方依赖,比如笔者在升级hbase系统时,代理层就同时用到了1.X和2.X版本的hbase-client的jar包. 当时是使用的阿里的SOFAArk来实现的. 它的本质就是是哟个类加载来实现的,接下来就通过一个小例

  • Java类加载器ClassLoader的使用详解

    目录 BootstrapClassLoader ExtClassLoader AppClassLoader 类加载器的具体实现在哪里 类加载器的初始化时机 如何进行的类加载 Loader.getResource(resourceName) 调用获取资源方法的一些常见问题 BootstrapClassLoader 加载范围(根据系统参数): System.getProperty("sun.boot.class.path"); 负责加载核心类库,以我的本地的环境来展示获取的内容: D:\d

  • Java通过自定义类加载器实现类隔离

    目录 前言 类隔离是什么 使用场景 解决方案 重写findClass 重写loadClass 总结 前言 由于微服务的快速迭代.持续集成等特性,越来越多的团队更倾向于它.但是也体现出了一些问题,比如在基础设施建设过程中,需要把通用功能下沉,把现有大而全的基础设施按领域拆分,考虑需要兼容现有生产服务,会产生不同的依赖版本,有时不注意就可以引发问题.比如本文遇到的依赖包版本冲突问题,以及如何利用类隔离技术解决的分析. 类隔离是什么 类隔离是一种通过类加载器实现加载所需类的实现方式,使得不同版本类间隔

  • 深入解析Java中的Class Loader类加载器

    类加载的过程 类加载器的主要工作就是把类文件加载到JVM中.如下图所示,其过程分为三步: 1.加载:定位要加载的类文件,并将其字节流装载到JVM中: 2.链接:给要加载的类分配最基本的内存结构保存其信息,比如属性,方法以及引用的类.在该阶段,该类还处于不可用状态: (1)验证:对加载的字节流进行验证,比如格式上的,安全方面的: (2)内存分配:为该类准备内存空间来表示其属性,方法以及引用的类: (3)解析:加载该类所引用的其它类,比如父类,实现的接口等. 3.初始化:对类变量进行赋值. 类加载器

  • JAVA提高第七篇 类加载器解析

    今天我们学习类加载器,关于类加载器其实和JVM有很大关系,在这里这篇文章只是简单的介绍下类加载器,后面学习到JVM的时候还会详细讲到类加载器,本文分为下面几个小节讲解: 一.认识类加载器 1.什么是类加载器? 所谓的类加载器可以从其作用来理解,其功能就是将classpath目录下.class文件,加载到内存中来进行一些处理,处理完的结果就是一些字节码.那是谁把这些class类加载到内存中来的呢?就是类加载器. 2.JVM中默认的类加载器有哪些? java虚拟机中可以安装多个类加载器,系统默认三个

  • Java中过滤器 (Filter) 和 拦截器 (Interceptor)的使用

    1.过滤器 (Filter) 过滤器的配置比较简单,直接实现Filter 接口即可,也可以通过@WebFilter注解实现对特定URL拦截,看到Filter 接口中定义了三个方法. init() :该方法在容器启动初始化过滤器时被调用,它在 Filter 的整个生命周期只会被调用一次.注意:这个方法必须执行成功,否则过滤器会不起作用. doFilter() :容器中的每一次请求都会调用该方法, FilterChain 用来调用下一个过滤器 Filter. destroy(): 当容器销毁 过滤器

  • Java中Socket下载一个文本文件

    废话不多说了,直接给大家贴代码了,具体代码如下所示: package com.lanqiao.demo2; import java.io.BufferedInputStream; import java.io.FileInputStream; import java.io.IOException; import java.io.OutputStream; import java.net.ServerSocket; import java.net.Socket; /** * @author * @

  • 详谈Java中BigDecimal的一个除法异常

    如下所示: java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result. 说明遇到除不尽的情况了,需要指定商的小数精度和舍入模式. 比如: a=b.divide(c,2,RoundingMode.HALF_UP); 以上这篇详谈Java中BigDecimal的一个除法异常就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持我们.

  • java基础学习笔记之类加载器

    类加载器 java类加载器就是在运行时在JVM中动态地加载所需的类,java类加载器基于三个机制:委托,可见,单一. 把classpath下的那些.class文件加载进内存,处理后成为字节码,这些工作是类加载器做的. 委托机制指的是将加载类的请求传递给父加载器,如果父加载器找不到或者不能加载这个类,那么再加载他. 可见性机制指的是父加载器加载的类都能被子加载器看见,但是子加载器加载的类父加载器是看不见的. 单一性机制指的是一个类只能被同一种加载器加载一次. 默认类加载器 系统默认三个类加载器:

  • JAVA中通过自定义注解进行数据验证的方法

    前言 最近为了工作也为了更加深入了解掌握java注解的使用,决定自定义注解来实现数据验证. API开发中经常会遇到一些对请求数据进行验证的情况,这时候如果使用注解就有两个好处,一是验证逻辑和业务逻辑分离,代码清晰,二是验证逻辑可以轻松复用,只需要在要验证的地方加上注解就可以. Java提供了一些基本的验证注解,比如@NotNull.@Size,但是更多情况下需要自定义验证逻辑,这时候就可以自己实现一个验证注解,方法很简单,仅需要两个东西: 一个自定义的注解,并且指定验证器 一个验证器的实现 自定

  • 在java中如何定义一个抽象属性示例详解

    前言 本文主要给大家介绍的是在java中定义一个抽象属性的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍: Abstract关键字通常被用于类和方法,用来把某些行为的实现委托给子类.由于Java不支持抽象属性,如果你试图将类属性标记为抽象,将会得到一个编译时错误. 在本教程中,我们将介绍两种定义抽象属性的方法,这些抽象属性可以由子类进行设置,而且不使用Abstract 关键字. 实用案例 假设我们想要实现一个记录事务的日志模块,用来记录特定事务的信息.我们希望这个模块是抽

  • Java中反射的一个简单使用

    简介 首先介绍一些不太实用的解释:JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法:对于任意一个对象,都能够调用它的任意方法和属性:这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制. 简单使用 反射,在java中是非常常见和好用的一种方式,(但是大家需要知道,他的效率是比较低的,所以要慎用)当然在基于java语言而产生的Android中也是可以使用的,我们可以使用反射来获取一些系统并不开放,但是存在的类,从而调用他的一些方法,下面就简单的写一下

  • java类加载机制、类加载器、自定义类加载器的案例

    类加载机制 java类从被加载到JVM到卸载出JVM,整个生命周期包括:加载(Loading).验证(Verification).准备(Preparation).解析(Resolution).初始化(Initialization).使用(using).和卸载(Unloading)七个阶段. 其中验证.准备和解析三个部分统称为连接(Linking). 1.加载 加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Clas

随机推荐