打破双亲代理机制是否可以自己定义一个String 类

本文探讨了是否能够自定义一个与标准Java库中相同的String类,并通过实验验证了在不同包名下自定义String类的可能性。文章指出,自定义类的包名不能以java.*开头,否则会因安全限制而无法被类加载器加载。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

打破双亲代理机制是否可以自己定义一个 String 类

面试的时候被问了一个问题:能否打破双亲代理机制是否可以自己定义一个String 类,如果包名和java.lang.String 一致呢?
当时一脸懵逼,这方面的知识还是缺乏啊,下来之后写了代码之后试了一下,答案是:可以
详细一点回答是:使用非 “java.* ” 开头的包名是可以的,但包名不能以 “java.* ” 开头。

类加载器

package myClassLoader;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class MyClassLoader extends ClassLoader {
    // 类加载器名称
    private String loaderName;

    public MyClassLoader(String loaderName) {
        // 让系统类加载器成为该 类加载器的父加载器
        super();
        this.loaderName = loaderName;
    }

    public MyClassLoader(ClassLoader parent, String loaderName) {
        // 显示指定该类加载器的父加载器
        super(parent);
        this.loaderName = loaderName;
    }

    @Override
    public String toString() {
        return this.loaderName;
    }

    /**
     * 获取.class文件的字节数组
     * 
     * @param name
     * @return
     * @throws FileNotFoundException 
     */
    private byte[] loaderClassData(String name) throws IOException {
        FileInputStream in = new FileInputStream(name);
        byte[] b = new byte[1024];
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        int len = 0;
        while((len = in.read(b)) != -1){
            out.write(b, 0, len);
        }
        out.close();
        b = out.toByteArray();
        return b;
    }

    /**
     * 获取Class对象
     */
    @Override
    public Class<?> findClass(String name) throws ClassNotFoundException {
        byte[] b = null;
        try {
            b = loaderClassData(name);
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return defineClass(null, b, 0, b.length);
    }

    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, SecurityException, NoSuchMethodException, IllegalArgumentException, InvocationTargetException {

            MyClassLoader loader1 = new MyClassLoader("MyClassLoader");

           //两个自定String类,一个的包名为java.lang.String,第二个为Custom.String
            //Class<?> clazz = loader1.loadClass("D:/customclass/java/lang/String.class");
            Class<?> clazz = loader1.loadClass("D:/customclass/Custom/String.class");
            Object obj = clazz.newInstance();
            System.out.println(obj.getClass().getName());
            Method m = clazz.getMethod("hello", null);
            m.invoke(obj, null);
            System.out.println(clazz.getName());

    }
}

两个自定义string类

package java.lang;

public class String {
    public void hello(){
        System.out.println("I'm String class");
        }
}
package Custom;

public class String {
    public void hello(){
        System.out.println("I'm String class");
        }
}

运行结果

包名为 java.lang.String 的类

Exception in thread "main" java.lang.SecurityException: Prohibited package name: java.lang
    at java.lang.ClassLoader.defineClass1(Native Method)
    at java.lang.ClassLoader.defineClass(Unknown Source)
    at java.lang.ClassLoader.defineClass(Unknown Source)
    at myClassLoader.MyClassLoader.findClass(MyClassLoader.java:65)
    at java.lang.ClassLoader.loadClass(Unknown Source)
    at java.lang.ClassLoader.loadClass(Unknown Source)
    at myClassLoader.MyClassLoader.main(MyClassLoader.java:72)

包名为Custom.String的类

Custom.String
I'm String class
Custom.String

看到这里大家应该明白了吧,JVM基于安全考虑,自己写的类的package名称不可以”java. * ”开头,否则不会被类加载器加载。对于自己写java.lang.String,肯定也不会被加载成功了,如果是其它名字开头的,如Custom.String,就能正确加载


参考

Java自定义类加载器实现

### Java定义加载器打破双亲委派机制的实现方式 #### 背景介绍 Java 加载器采用的是双亲委派模型,即当一个加载器收到加载请求时,它首先不会自己去尝试加载这个,而是把这个请求委派给父加载器完成。只有当父加载器无法找到或加载该时,子加载器才会尝试自行加载。 要打破这种双亲委派机制,可以通过自定义加载器并重写 `loadClass` 方法来实现[^1]。 --- #### 核心方法解析 在 Java 中,`java.lang.ClassLoader` 是所有加载器的基。其核心方法如下: 1. **`protected Class<?> loadClass(String name, boolean resolve)`** - 这是实现双亲委派的核心方法,在默认情况下会调用父加载器进行加载操作。 2. **`protected Class<?> findClass(String name)`** - 默认是一个空方法,用于由开发者提供具体的加载逻辑。如果希望完全绕过双亲委派,则可以不依赖于 `loadClass` 的默认行为而直接通过此方法加载[^1]。 为了打破双亲委派机制,可以选择以下两种主要途径之一: --- #### 方式一:重写 `loadClass` 方法 通过覆盖 `loadClass` 方法改变原有的双亲委派流程。以下是具体代码示例: ```java public class CustomClassLoader extends ClassLoader { @Override protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { // 不调用父的loadClass方法,从而跳过双亲委派 try { String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class"; InputStream inputStream = getClass().getResourceAsStream(fileName); if (inputStream == null) { throw new ClassNotFoundException(); } byte[] bytes = inputStream.readAllBytes(); // 将字节码读取到内存中 return defineClass(name, bytes, 0, bytes.length); // 定义 } catch (IOException e) { throw new ClassNotFoundException(e.getMessage()); } } } ``` 在这个例子中,我们手动处理了 `.class` 文件的加载过程,并未调用父的 `loadClass` 方法,因此打破双亲委派机制[^2]。 --- #### 方式二:仅重写 `findClass` 方法 另一种更常见的做法是让 `loadClass` 继续遵循双亲委派原则,但在必要时通过重写的 `findClass` 来接管特定的加载工作。这种方式更加灵活且安全。 下面展示了一个简单的案例: ```java public class MyCustomClassLoader extends ClassLoader { private final String path; public MyCustomClassLoader(String path) { this.path = path; } @Override protected Class<?> findClass(String name) throws ClassNotFoundException { try { String filePath = path + "/" + name.replace('.', '/') + ".class"; // 构造文件路径 FileInputStream fis = new FileInputStream(filePath); ByteArrayOutputStream baos = new ByteArrayOutputStream(); int data; while ((data = fis.read()) != -1) { // 将 .class 文件内容读入流中 baos.write(data); } byte[] classData = baos.toByteArray(); // 获取字节数组形式的数据 return defineClass(name, classData, 0, classData.length); // 使用defineClass定义 } catch (Exception ex) { throw new ClassNotFoundException(ex.getMessage(), ex); } } } ``` 在此种情况之下,虽然仍然保留了部分双亲委派特性(因为没有修改 `loadClass`),但对于某些特殊需求下的加载则交给了我们的定制化逻辑执行[^3]。 需要注意的一点是,如果你试图加载属于受保护包空间内的(如`java.*`),将会抛出异常: ```plaintext java.lang.SecurityException: Prohibited package name: java.lang ``` 这是由于 JVM 对这些敏感区域进行了严格的安全控制所致[^3]。 --- #### 注意事项 - 如果多个自定义加载器分别加载同一个全限定名的实例,它们会被视为不同的型对象,彼此之间不可互换使用[^2]。 - 在实际开发过程中应谨慎考虑是否真的有必要破坏双亲委派模式,因为它可能会引发意想不到的问题或者兼容性隐患。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值