类加载器器

类加载器器,第1张

加载器器

反射中,Class.forName和classloader的区别 
java中class.forName()和classLoader都可用来对类进行加载。 class.forName()前者除了将类的.class文件加载到jvm中之外,还会对类进行解释,执行类中的static 块。 而classLoader只干一件事情,就是将.class文件加载到jvm中,不会执行static中的内容,只有在 newInstance才会去执行static块。 Class.forName(name, initialize, loader)带参函数也可控制是否加载static块。并且只有调用了 newInstance()方法采用调用构造函数,创建类的对对象。

根据运行结果,可以看到,classloader并没有执行静态代码块,如开头的理论所说。
而下面的Class.forName则是夹在完之后,就里面执行了静态代码块,可以看到,2个类,line和point的 静态代码块执行结果是一起的,然后才是各自的打印结果。

。类加载器负责读取 Java 字节代码,并转换成 java.lang.Class 类的一个实例。每个这样的实例用 来表示一个 Java 类。通过此实例的 newInstance()方法就可以创建出该类的一个对象。实际的情况可能 更加复杂,比如 Java 字节代码可能是通过工具动态生成的,也可能是通过网络下载的ClassLoader 还负责加载 Java 应用所需的资源,如图像文件和配置文件等

 类加载器的树状组织结构

引导类加载器(bootstrap class loader):它用来加载 Java 的核心库,是用原生代码来实现 的,并不继承自 
java.lang.ClassLoader。

扩展类加载器(extensions class loader):它用来加载 Java 的扩展库。Java 虚拟机的实现会提 供一个扩展库目录。该类加载器在此目录里面查找并加载 Java 类。

系统类加载器(system class loader/App classloader):它根据 Java 应用的类路径 (CLASSPATH)来加载 Java 类。一般来说,Java 应用的类都是由它来完成加载的。可以通过 ClassLoader.getSystemClassLoader() 来获取它。

 

自定义类加载器

首先介绍自定义类的应用场景:
(1)加密:Java代码可以轻易的被反编译,如果你需要把自己的代码进行加密以防止反编译,可以先将 编译后的代码用某种加密算法加密,类加密后就不能再用Java的ClassLoader去加载类了,这时就需要自 定义ClassLoader在加载类的时候先解密类,然后再加载。

(2)从非标准的来源加载代码:如果你的字节码是放在数据库、甚至是在云端,就可以自定义类加载 器,从指定的来源加载类。
(3)以上两种情况在实际中的综合运用:比如你的应用需要通过网络来传输 Java 类的字节码,为了安 全性,这些字节码经过了加密处理。这个时候你就需要自定义类加载器来从某个网络地址上读取加密后 的字节代码,接着进行解密和验证,后定义出在Java虚拟机中运行的类。

双亲委派模型(  类加载器的代理模式 )

1、节约系统资源。只要,这个类已经被加载过了,就不会在次加载。
2、保证 Java 核心库的类型安全。

。*Java* *虚拟机不仅要看类的全名是否相同,还要看加载此类的类加载器 是否一样。只有两者都相同的情况,才认为两个类是相同的。即便是同样的字节代码,被不同的类加载 器加载之后所得到的类,也是不同的。*

 双亲委派模型的工作过程如下:
(1)当前类加载器从自己已经加载的类中查询是否此类已经加载,如果已经加载则直接返回原来已经加 载的类。
(2)如果没有找到,就去委托父类加载器去加载(如代码c = parent.loadClass(name, false)所示)。 父类加载器也会采用同样的策略,查看自己已经加载过的类中是否包含这个类,有就返回,没有就委托 父类的父类去加载,一直到启动类加载器。因为如果父加载器为空了,就代表使用启动类加载器作为父 加载器去加载。
(3)如果启动类加载器加载失败(例如在$JAVA_HOME/jre/lib里未查找到该class),则会抛出一个异 常ClassNotFoundException,然后再调用当前加载器的findClass()方法进行加载。 
 
双亲委派模型的好处:
(1)主要是为了安全性,避免用户自己编写的类动态替换 Java的一些核心类,比如 String。
(2)同时也避免了类的重复加载,因为 JVM中区分不同类,不仅仅是根据类名,相同的 class文件被不 同的 ClassLoader加载就是不同的两个类。


 2. 自定义类加载器 (1)从上面源码看出,调用loadClass时会先根据委派模型在父加载器中加载,如果加载失败,则会调 用当前加载器的findClass来完成加载。 (2)因此我们自定义的类加载器只需要继承ClassLoader,并覆盖findClass方法,下面是一个实际例 子,在该例中我们用自定义的类加载器去加载我们事先准备好的class文件。
 

public class MyClassLoader extends ClassLoader {

    MyClassLoader(){

    }
    MyClassLoader(ClassLoader classLoader){
        super(classLoader);
    }

    @Override
    protected Class findClass(String name) throws ClassNotFoundException {
        File file=new File("D:/People.class");
        try {
            byte[] bytes = getFile(file);
            //defineClass方法可以把二进制流字节组成的文件转换为一个java.lang.Class
            Class c = this.defineClass(name, bytes, 0, bytes.length);
            return c;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return super.findClass(name);
    }

    public byte[] getFile(File file) throws Exception {
        // 这里要读入.class的字节,因此要使用字节流
        FileInputStream fis = new FileInputStream(file);
        FileChannel fc = fis.getChannel();
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        WritableByteChannel wbc = Channels.newChannel(baos);
        ByteBuffer by = ByteBuffer.allocate(1024);
        while (true){
            int i = fc.read(by);
            if (i == 0 || i == -1)
                break;
            by.flip();
            wbc.write(by);
            by.clear();
        }
        fis.close();
        return baos.toByteArray();
    }

    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
        MyClassLoader myClassLoader = new MyClassLoader();
        Class aClass = Class.forName("People", true, myClassLoader);
        Object o = aClass.newInstance();
        System.out.println(o);
        System.out.println(o.getClass().getClassLoader());

    }

线程上下文类加载器 

Java 应用运行的初始线程的上下文类加载器是系统类加载器。在线程中运行的 代码可以通过此类加载器来加载类和资源。 

如果类库提供了 SPI 接口,并且利用线程上下文类加载器来加载 SPI 实现的 Java 类,有可能会找不 到 Java 类。如果出现了  NoClassDefFoundError 异常,首先检查当前线程的上下文类加载器是否 正确。通过 Thread.currentThread().getContextClassLoader() 就可以得到该类加载器。该类加载 器应该是该模块对应的类加载器。如果不是的话,可以首先通过 class.getClassLoader() 来得到模块对应的类加载器,再通过 Thread.currentThread().setContextClassLoader()  来设置当 前线程的上下文类加载器。 

对于运行在 Java EE? 容器中的 Web 应用来说,类加载器的实现方式与一般的 Java 应用有所不同。 不同的 Web 容器的实现方式也会有所不同。以 Apache Tomcat 来说,每个 Web 应用都有一个对应的类加载器实例。该类加载器也使用代理模式,所不同的是它是首先尝试去加载某个 类,如果找不到再代理给父类加载器。这与一般类加载器的顺序是相反的。这是 *Java Servlet 规范中 的推荐做法,其目的是使得 Web 应用自己的类的优先级高于 Web 容器提供的类。这种代理模式的一个 例外是:Java* *核心库的类是不在查找范围之内的。这也是为了保证 Java 核心库的类型安全。* 绝大 多数情况下,Web 应用的开发人员不需要考虑与类加载器相关的细节。下面给出几条简单的原则

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/zaji/5695503.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-17
下一篇 2022-12-17

发表评论

登录后才能评论

评论列表(0条)

保存