Java类加载器(二)——自定义类加载器

简介:   用户定制自己的ClassLoader可以实现以下的一些应用:自定义路径下查找自定义的class类文件,也许我们需要的class文件并不总是在已经设置好的Classpath下面,那么我们必须想办法来找到这个类,在这种清理下我们需要自己实现一个ClassLoader。

  用户定制自己的ClassLoader可以实现以下的一些应用:

  1. 自定义路径下查找自定义的class类文件,也许我们需要的class文件并不总是在已经设置好的Classpath下面,那么我们必须想办法来找到这个类,在这种清理下我们需要自己实现一个ClassLoader。
  2. 确保安全性:Java字节码很容易被反编译,对我们自己的要加载的类做特殊处理,如保证通过网络传输的类的安全性,可以将类经过加密后再传输,在加密到JVM之前需要对类的字节码在解密,这个过程就可以在自定义的ClassLoader中实现。
  3. 实现类的热部署:可以定义类的实现机制,如果我们可以检查已经加载的class文件是否被修改,如果修改了,可以重新加载这个类。

  findClass()的功能是找到class文件并把字节码加载到内存中。自定义的ClassLoader一般覆盖改方法,以便使用不同的加载路径,然后调用defineClass()解析字节码。
  defineClass()方法用来将byte字节流解析成JVM能够识别的Class对象。有了这个方法意味着我们不仅仅可以通过class文件实例化对象,还可以通过其他方式实例化对象,如我们通过网络接收到一个类的字节码,拿这个字节码流直接创建类的Class对象形式实例化对象。
  自定义的加载器可以覆盖方法loadClass()以便定义不同的加载机制。
  如果自定义的加载器仅覆盖了findClass(),而未覆盖loadClass(即加载规则一样,但加载路径不同);则调用getClass().getClassLoader()返回的仍然是AppClassLoader!因为真正的load类,还是AppClassLoader.


加载自定义路径下的class文件

  下面演示一个方法加载指定路径下(”D:/workspace_jee/JavaTest/src/”)的类文件。

package classloader;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

public class PathClassLoader extends ClassLoader
{
    private String classPath;

    public PathClassLoader(String classPath)
    {
        this.classPath = classPath;
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException
    {
        byte[] classData = getData(name);
        if (classData == null)
        {
            throw new ClassNotFoundException();
        }
        else
        {
            return defineClass(name, classData, 0, classData.length);
        }
    }

    private byte[] getData(String className)
    {
        String path = classPath + File.separatorChar+className.replace('.', File.separatorChar)+".class";
        try
        {
            InputStream is = new FileInputStream(path);
            ByteArrayOutputStream stream = new ByteArrayOutputStream();
            byte[] buffer = new byte[2048];
            int num = 0;
            while((num = is.read(buffer))!=-1)
            {
                stream.write(buffer,0,num);
            }
            return stream.toByteArray();
        }
        catch(IOException e)
        {
            e.printStackTrace();
        }

        return null;
    }

    public static void main(String args[]) throws ClassNotFoundException, InstantiationException, IllegalAccessException
    {
        ClassLoader pcl = new PathClassLoader("D:/workspace_jee/JavaTest/src/");
        Class c = pcl.loadClass("classloader.SingleClass");
        System.out.println(c.newInstance());
    }
}

  输出结果:classloader.SingleClass@22a7fdef


加载自定义格式的class文件

  如果我们从网路上下载一个class文件的字节码,但是为了安全性在传输之前对这个字节码进行了简单的加密处理,然后再通过网络传输。当客户端接收到这个类的字节码后需要经过解密才能还原成原始的类格式,然后再通过ClassLoader的defineClass()方法创建这个类的实例,最后完成类的加载工作。
  比如上面的代码中,在获取到字节码(byte[] classData = getData(name);)之后再通过一个类似以下的代码:

    private byte[] deCode(byte[] src){
        byte[] decode = null;
        //do something niubility! 精密解码过程
        return decode; 
    }

  将字节码解码成所需要的字节码即可。


实现类的热部署

  JVM默认不能热部署类,因为加载类时会去调用findLoadedClass(),如果类已被加载,就不会再次加载。
  JVM判断类是否被加载有两个条件:完整类名是否一样,ClasssLoader是否是同一个
  所以要实现热部署的话,只需要使用ClassLoader的不同实例来加载。
  如果用同一个ClassLoader实例来加载同一个类,则会抛出LinkageError.
  Jsp就是一个热部署的例子。
  如下所示:

package classloader;

import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

public class ClassReloader extends ClassLoader
{
    private String classPath;
    String classname = "classloader.SingleClass";

    public ClassReloader(String classpath)
    {
        this.classPath = classpath;
    }

    protected Class<?> findClass(String name) throws ClassNotFoundException{
        byte [] classData = getData(name);
        if(classData == null)
        {
            throw new ClassNotFoundException();
        }
        else
        {
            return defineClass(classname,classData,0,classData.length);
        }
    }

    private byte[] getData(String className)
    {
        String path = classPath+className;
        try
        {
            InputStream is = new FileInputStream(path);
            ByteArrayOutputStream stream = new ByteArrayOutputStream();
            byte[] buffer = new byte[2048];
            int num = 0;
            while((num = is.read(buffer))!=-1)
            {
                stream.write(buffer,0,num);
            }
            return stream.toByteArray();
        }
        catch(IOException e)
        {
            e.printStackTrace();
        }
        return null;
    }

    public static void main(String[] args)
    {
        try
        {
            String path = "D:/workspace_jee/JavaTest/src/classloader/";
            ClassReloader reloader = new ClassReloader(path);
            Class r = reloader.findClass("SingleClass.class");
            System.out.println(r.newInstance());
//            ClassReloader reloader2 = new ClassReloader(path);
            Class r2 = reloader.findClass("SingleClass.class");
            System.out.println(r2.newInstance());
        }
        catch (ClassNotFoundException | InstantiationException | IllegalAccessException e)
        {
            e.printStackTrace();
        }
    }
}

  这段代码的运行结果为:

 java.lang.LinkageError: loader (instance of  classloader/ClassReloader): attempted  duplicate class definition for name: "classloader/SingleClass"
    at java.lang.ClassLoader.defineClass1(Native Method)
    at java.lang.ClassLoader.defineClass(Unknown Source)
    at java.lang.ClassLoader.defineClass(Unknown Source)
    at classloader.ClassReloader.findClass(ClassReloader.java:26)
    at classloader.ClassReloader.main(ClassReloader.java:62)

  比较两个类是否“相等”,只有在这两个类是由同一个类加载器加载的前提下才有意义,否则,即使这两个类来源于同一个Class文件,被同一个虚拟机加载,只要加载他们的类加载器不同,那这两个类就必定不相等。

目录
相关文章
|
8天前
|
Java
在 Java 中捕获和处理自定义异常的代码示例
本文提供了一个 Java 代码示例,展示了如何捕获和处理自定义异常。通过创建自定义异常类并使用 try-catch 语句,可以更灵活地处理程序中的错误情况。
|
8天前
|
Java
在 Java 中,如何自定义`NumberFormatException`异常
在Java中,自定义`NumberFormatException`异常可以通过继承`IllegalArgumentException`类并重写其构造方法来实现。自定义异常类可以添加额外的错误信息或行为,以便更精确地处理特定的数字格式转换错误。
|
1月前
|
Java
让星星⭐月亮告诉你,自定义定时器和Java自带原生定时器
定时器是一种可以设置多个具有不同执行时间和间隔的任务的工具。本文介绍了定时器的基本概念、如何自定义实现一个定时器,以及Java原生定时器的使用方法,包括定义定时任务接口、实现任务、定义任务处理线程和使用Java的`Timer`与`TimerTask`类来管理和执行定时任务。
47 3
|
23天前
|
Java 开发者 Spring
[Java]自定义注解
本文介绍了Java中的四个元注解(@Target、@Retention、@Documented、@Inherited)及其使用方法,并详细讲解了自定义注解的定义和使用细节。文章还提到了Spring框架中的@AliasFor注解,通过示例帮助读者更好地理解和应用这些注解。文中强调了注解的生命周期、继承性和文档化特性,适合初学者和进阶开发者参考。
44 14
|
30天前
|
安全 Java
如何在 Java 中创建自定义安全管理器
在Java中创建自定义安全管理器需要继承SecurityManager类并重写其方法,以实现特定的安全策略。通过设置系统安全属性来启用自定义安全管理器,从而控制应用程序的访问权限和安全行为。
|
1月前
|
Java
java实现从HDFS上下载文件及文件夹的功能,以流形式输出,便于用户自定义保存任何路径下
java实现从HDFS上下载文件及文件夹的功能,以流形式输出,便于用户自定义保存任何路径下
58 2
java实现从HDFS上下载文件及文件夹的功能,以流形式输出,便于用户自定义保存任何路径下
|
1月前
|
消息中间件 存储 Java
大数据-58 Kafka 高级特性 消息发送02-自定义序列化器、自定义分区器 Java代码实现
大数据-58 Kafka 高级特性 消息发送02-自定义序列化器、自定义分区器 Java代码实现
44 3
|
2月前
|
IDE Java 开发工具
java自定义异常20
java自定义异常20
29 3
|
2月前
|
Java 编译器 程序员
Java注解,元注解,自定义注解的使用
本文讲解了Java中注解的概念和作用,包括基本注解的用法(@Override, @Deprecated, @SuppressWarnings, @SafeVarargs, @FunctionalInterface),Java提供的元注解(@Retention, @Target, @Documented, @Inherited),以及如何自定义注解并通过反射获取注解信息。
Java注解,元注解,自定义注解的使用
|
1月前
|
算法 Java 测试技术
数据结构 —— Java自定义代码实现顺序表,包含测试用例以及ArrayList的使用以及相关算法题
文章详细介绍了如何用Java自定义实现一个顺序表类,包括插入、删除、获取数据元素、求数据个数等功能,并对顺序表进行了测试,最后还提及了Java中自带的顺序表实现类ArrayList。
21 0