Java 能否自定义一个类叫 java.lang.ArrayList

关于这个问题,网上众说纷纭,类似的问题还有:java.lang.System,java.lang.String,java.lang.Math是否自己写一个jdk同名的类。

大概的答案主要分成以下2派:

  1. 可以,自定义classloader破坏双亲委派机制(由于系统自带的3个类加载器都加载特定目录下的类,如果我们自己的类加载器放在1个特殊的目录,那末系统的加载器就没法加载,也就是终究还是由我们自己的加载器加载。)
    点评:这个明显是没有实践过的一派人的发言,实践一下就会发现自定义classloader是行不通的。

  2. 不可以,即使自定义classloader,重写loadClass或者findClass方法,这样可以加载到自己指定目录下的class文件,但是由于自定义的classloader必须继承 ClassLoader,loadClass 方法会调用父类的 defineClass 方法。
    而父类的这个 defineClass 是一个 final 方法,无法被重写。



    protected final Class<?> defineClass(String name, byte[] b, int off, int len,
                                         ProtectionDomain protectionDomain)
        throws ClassFormatError
    {
        protectionDomain = preDefineClass(name, protectionDomain);
        String source = defineClassSourceLocation(protectionDomain);
        Class<?> c = defineClass1(name, b, off, len, protectionDomain, source);
        postDefineClass(c, protectionDomain);
        return c;
    }
    
    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;
    }

解密

可以自己写一个java.lang.System,java.lang.String,java.lang.Math,也能够得到编译,然而常规的方式会发现自己写的这个类没有机会被加载到JVM。
JVM中类加载采用的是双亲委派机制,BootStrapClassLoader是顶层父类,ExtClassLoader是BootStrapClassLoader类的子类,AppClassLoader又是ExtClassLoader的子类。

这里以java.lang.String为例,当我是使用到这个类时,Java虚拟机会将java.lang.String类的字节码加载到内存中。
这个时候jdk中自带一个java.lang.String,然而我们自己又写了一个java.lang.String,JVM会加载哪一个呢?

  • 因加载某个类时,优先使用父类加载器加载需要使用的类。如果我们自定义了java.lang.String这个类,
  • 加载该自定义的String类,该自定义String类使用的加载器是AppClassLoader,根据优先使用父类加载器原理,
  • AppClassLoader加载器的父类为ExtClassLoader,所以这时加载String使用的类加载器是ExtClassLoader,但是类加载器ExtClassLoader在jre/lib/ext目录下没有找到String.class类。然后使用ExtClassLoader父类的加载器BootStrapClassLoader,
  • 父类加载器BootStrap在JRE/lib目录的rt.jar找到了String.class,将其加载到内存中。这就是类加载器的委托机制。

参考文章
https://www.cnblogs.com/guweiwei/p/6641785.html
https://www.cnblogs.com/alexlo/p/5664543.html

终极结论

上面,只说对了一半(“可以自己写一个java.lang.System,java.lang.String,java.lang.Math,也能够得到编译,然而常规的方式会发现自己写的这个类没有机会被加载到JVM。”)
虽然jdk classloader与自定义classloader搞不定,但是不表示没有其它办法。
既然bootstrapclassloader可以加载java.lang.ArrayList ,那么bootstrapclasscloader扫描的包路径是如何指定的呢

解决方案
Java 命令行提供了如何扩展bootStrap 级别class的简单方法.

  • -Xbootclasspath: 完全取代基本核心的Java class 搜索路径. 不常用,否则要重新写所有Java 核心class
  • -Xbootclasspath/a: 后缀在核心class搜索路径后面.常用!!
  • -Xbootclasspath/p: 前缀在核心class搜索路径前面.不常用,避免引起不必要的冲突.

先来创建一个java.lang.JavaAlioo的java文件,编译并打包成jar javademo-javapackage-1.0.0-SNAPSHOT.jar

% more java/lang/JavaAlioo.java 
package java.lang;

public class JavaAlioo {
    static {
        System.out.println("JavaAlioo加载成功了,哈哈哈哈");
    }

    public void sayhi() {
        System.out.println("HELLO WORLD");
    }

    public static void main(String[] args) {
        new JavaAlioo().sayhi();
    }
}

其次编写测试用例JavaTest.java


% more JavaTest.java 
package com.alioo;

public class JavaTest {

    public static void main(String[] args) throws Exception {

        Class a = Class.forName("java.lang.JavaAlioo");

    }
}

常规运行会报如下错误:

Exception in thread "main" java.lang.SecurityException: Prohibited package name: java.lang
	at java.lang.ClassLoader.preDefineClass(ClassLoader.java:662)
	at java.lang.ClassLoader.defineClass(ClassLoader.java:761)
	at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142)
	at java.net.URLClassLoader.defineClass(URLClassLoader.java:468)
	at java.net.URLClassLoader.access$100(URLClassLoader.java:74)
	at java.net.URLClassLoader$1.run(URLClassLoader.java:369)
	at java.net.URLClassLoader$1.run(URLClassLoader.java:363)
	at java.security.AccessController.doPrivileged(Native Method)
	at java.net.URLClassLoader.findClass(URLClassLoader.java:362)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
	at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:349)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
	at java.lang.Class.forName0(Native Method)
	at java.lang.Class.forName(Class.java:264)
	at com.alioo.JavaTest.main(JavaTest.java:7)

将javademo-javapackage-1.0.0-SNAPSHOT.jar指定到bootstrappath中再运行一把发现就可以了

启动命令中增加如下参数:
-Xbootclasspath/p:/Users/mac/work/gitstudy/javademo/javademo-javapackage/target/javademo-javapackage-1.0.0-SNAPSHOT.jar

说明这里使用-Xbootclasspath/p 与 -Xbootclasspath/a都是可以的


JavaAlioo加载成功了,哈哈哈哈

进一步验证下jdk中的已经有的类java.lang.ArrayList来看看效果

package java.lang;

public class ArrayList {
    static {
        System.out.println("ArrayList加载成功了,哈哈哈哈");
    }

    public void sayhi() {
        System.out.println("ArrayListArrayListArrayListArrayList");
    }

}

启动命令中增加如下参数:
-Xbootclasspath/p:/Users/mac/work/gitstudy/javademo/javademo-javapackage/target/javademo-javapackage-1.0.0-SNAPSHOT.jar

说明这里只能使用-Xbootclasspath/p , 如果使用-Xbootclasspath/a 那么加载的就是jdk自带的java.lang.ArrayList了。

测试结果

ArrayList加载成功了,哈哈哈哈

至此,我们已经成功搞定了自己写的java.lang.JavaAlioo,java.lang.ArrayList的运行效果了,惊不惊喜,意不意外。
再提供另外一种方式也可以搞定,思路也是通过增加bootclasspath,api如下:

instrumentation.appendToBootstrapClassLoaderSearch(new JarFile(spyJarFile));

开源的arthas中用的就是这种方式,有兴趣的可以读读。

参考文章
https://www.cnblogs.com/duanxz/p/3482311.html

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值