面试题:能否自定义一个java.lang.Object类

创建自定义的java.lang.Object

首先尝试在工程中创建自己的java.lang.Object类:

package java.lang;

/**
 * 自己创建的java.lang.Object
 *
 * @author hujy
 * @version 1.0
 * @date 2020-01-30 01:12
 */
public class Object {
    static {
        System.out.println("hello");
    }
    public static void main(String[] args) {
        Object o = new Object();
    }
}

运行main方法,发现打印报错:

我们知道,类加载的过程会遵循双亲委派原则,当一个类首次被加载时,会依次向上级类加载器委托, 直到最顶层的BootstrapClassLoader。java.lang.Object属于系统类,会由BootstrapClassLoader优先加载,最终加载的还是系统原生的java.lang.Object类,因此会报找不到main方法的错误。


创建自定义类加载器

有了这一前提,我们需要通过自定义类加载器绕过双亲委派机制,实现自定义类的加载。

MyClassLoader为自定义类加载器,它继承于ClassLoader,覆写了loadClass方法,实现自定义加载。

package com.hujy.classloader;

import java.io.IOException;
import java.io.InputStream;

/**
 * 自定义类加载器
 *
 * @author hujy
 * @date 2020-01-30 00:56
 */
public class MyClassLoader extends ClassLoader {

    @Override
    public Class<?> loadClass(String name) throws ClassNotFoundException {
        if (name == null || "".equals(name)) {
            throw new ClassNotFoundException();
        }
        InputStream is = null;
        try {
            String className = "/" + name.replace('.', '/') + ".class";
            System.out.println(className);
            // 在classpath路径下加载java/lang/Object.class文件
            is = getClass().getResourceAsStream(className);
            System.out.println(is);
            if (is == null) {
                throw new ClassNotFoundException();
            }
            byte[] bytes = new byte[is.available()];
            is.read(bytes);
            // 调用父类classLoader的defineClass方法
            // 将字节数组转换为Class实例
            return defineClass(name, bytes, 0, bytes.length);
        } catch (Exception e) {
            e.printStackTrace();
            throw new ClassNotFoundException();
        } finally {
            if (is != null) {
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static void main(String[] args) {
        MyClassLoader myClassLoader = new MyClassLoader();
        try {
            Class<?> clazz = Class.forName("java.lang.Object", true, myClassLoader);
            System.out.println("自定义类加载器:" + clazz.newInstance().getClass().getClassLoader());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

运行main函数:

提示:禁止使用包名:“java.lang”。

跟进defineClass的源码:

    protected final Class<?> defineClass(String name, byte[] b, int off, int len)
        throws ClassFormatError
    {
        return defineClass(name, b, off, len, null);
    }

    /* Determine protection domain, and check that:
        - not define java.* class,
        - signer of this class matches signers for the rest of the classes in
          package.
    */
    private ProtectionDomain preDefineClass(String name,
                                            ProtectionDomain pd)
    {
        if (!checkName(name))
            throw new NoClassDefFoundError("IllegalName: " + name);

        // Note:  Checking logic in java.lang.invoke.MemberName.checkForTypeAlias
        // relies on the fact that spoofing is impossible if a class has a name
        // of the form "java.*"
        if ((name != null) && name.startsWith("java.")) {
            throw new SecurityException
                ("Prohibited package name: " +
                 name.substring(0, name.lastIndexOf('.')));
        }
        if (pd == null) {
            pd = defaultDomain;
        }

        if (name != null) checkCerts(name, pd.getCodeSource());

        return pd;
    }

在defineClass的逻辑中是禁止包以“java.”开头命名的,并且该方法为final方法,禁止覆写,说明了我们无法通过自定义类加载器加载以“java.”开头的类。

我们将自定义java.lang.Object修改成myjava.lang.Object,再对自定义类加载器中loadClass方法做一下修改:以“java.”开头的类交给父加载器加载。

public class MyClassLoader extends ClassLoader {

    @Override
    public Class<?> loadClass(String name) throws ClassNotFoundException {
        if (name == null || "".equals(name)) {
            throw new ClassNotFoundException();
        } else if (name.startsWith("java.")) {
            // 由父类加载java.开头的类
            return super.loadClass(name);
        }
        InputStream is = null;
        try {
            String className = "/" + name.replace('.', '/') + ".class";
            System.out.println(className);
            // 在classpath路径下加载java/lang/Object.class文件
            is = getClass().getResourceAsStream(className);
            System.out.println(is);
            if (is == null) {
                throw new ClassNotFoundException();
            }
            byte[] bytes = new byte[is.available()];
            is.read(bytes);
            // 调用父类classLoader的defineClass方法
            // 将字节数组转换为Class实例
            return defineClass(name, bytes, 0, bytes.length);
        } catch (Exception e) {
            e.printStackTrace();
            throw new ClassNotFoundException();
        } finally {
            if (is != null) {
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static void main(String[] args) {
        MyClassLoader myClassLoader = new MyClassLoader();
        try {
            Class<?> clazz = Class.forName("myjava.lang.Object", true, myClassLoader);
            System.out.println("自定义类加载器:" + clazz.newInstance().getClass().getClassLoader());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

再次运行main方法:


总结

  • 正常情况下类加载过程会遵循双亲委派机制,依次向上级类加载器委托加载,上级都加载不了,才会自行加载。
  • 如果想绕过双亲委派机制,需要覆写ClassLoader类的loadClass方法,一般不推荐这么做。
  • 由于final方法defineClass的限制,正常情况下我们无法加载以“java.”开头的系统类。
  • 一般自定义类加载器只需实现ClassLoader的findClass方法来加载自定义路径下的类,而不是覆写loadClass破坏双亲委派,避免带来系统安全隐患。

补充:endorsed 技术

当 Java 的原生 API 不能满足需求时,比如我们要修改 HashMap 类,就必须要使用到 Java 的 endorsed 技术。我们需要将自己的 HashMap 类,打包成一个 jar 包,然后放到 -Djava.endorsed.dirs 指定的目录中。注意类名和包名,应该和 JDK 自带的是一样的。但是,java.lang 包下面的类除外,因为这些都是特殊保护的。

因为我们上面提到的双亲委派机制,是无法直接在应用中替换 JDK 的原生类的。但是,有时候又不得不进行一下增强、替换,比如你想要调试一段代码,或者比 Java 团队早发现了一个 Bug。所以,Java 提供了 endorsed 技术,用于替换这些类。这个目录下的 jar 包,会比 rt.jar 中的文件,优先级更高,可以被最先加载到。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

@从入门到入土

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值