java自定义异常_Java到底能不能加载一个自己写的java.lang.System类

本文章读者们.默认你们已经对Java的类加载机制有一定了解,一些基本的概念我不会再一一解释,本文将彻底的阐述清楚这个我疑惑了很久的问题.

1.其实网上很多有类似的文章都在讲这个问题,但是我看了他们写的都感觉写的比较乱,而且看到最后也不知道他们说的对不对,决定自己试一下.

首先看下网上的其他人的说法,我总结了一下网上比较流行的说法,并按照理解深度依次描述一下这些观点.

首先是理解最浅显的一种说法: Java类加载机制遵循双亲委派机制,当你自定义了一个java.lang.System类时候并尝试加载时,会将加载请求最终委派给bootstrap 类加载器,而bootstrap类加载器会最终成功的加载jdk中自带的java.lang.System类并返回结果.也就是说这种说法认为是不可以加载自定义System的.

然后是第二种说法: Java类加载机制虽然有双亲委派机制,但是可以通过重写ClassLoader 的loadClass方法打破双亲委派机制,然后在尝试加载自定义的System类的时候,直接使用自己写的方法去加载这个类(跳过双亲委派),也就是说这种观点的人认为可以加载自定义System.

然后是第三种说法: 虽然可以打破双亲委派机制,但是最终还是要调用ClassLoader中defineClass方法,这个方法才是最终将class文件的byte[]数组转换成内存中的class对象,而这个方法会在执行是检查安全,发现包名是java.开头会抛出异常从而阻止你加载自定义的System类,也就是说这种观点认为不可以加载自定义System.

首先,如果你看到以上三种说法的时候,认为第一种说法是对的,那我建议你不要继续往下看了,先去了解一下java的双亲委派机制后再过来看我这篇文章,否则你会看的云里雾里,完全看不懂我在说什么.先看一下ClassLoader的loadClass方法,以下是我简化后的代码,

c908a0f9578eca999127a44d9b356e33.png

也就是说只要我们自定义的类加载器重写loadClass和findClass方法,不使用父加载器和根加载器,就可以轻松的反驳掉第一种说法.

这么看来好像第二种说法是对的喽,就是只要打破了双亲委派就可以了,但是答案是否定的,虽然你跳过了双亲委派,但是findClass方法才是你真正能加载类的方法,只有真正的让我们自己写的findClass方法跑起来,可惜findClass方法不会让我们这么顺利重写自己的实现的,其实也就是上说的第三种说法中的观点.咱们先来看下findClass方法具体是做了什么吧,看一下代码

1b642a792b60e7ec65a1c5762a2a6fc4.png

实际上findClass方法什么都没做直接抛异常,就是为了留给自定义classLoader来实现的,包括上面的javadoc也明确说了,这个方法需要被子类重写,然后会被loadClass方法调用.对于这一点ClassLoader的javadoc也提到这一点并且给了一个例子:

95d993f33ef5964775849a0c5fef24ff.png

从例子中可以看到官方推荐的就是重写findClass方法,在findClass方法中将class文件转换成byte[],之后调用defineClass来完成最终的class文件转内存中的class对象.接下来就我们就来看看你这个defineClass吧,看图

2c0742ea37fe5cb14ba2e7933d4a38e9.png

javadoc中明确说明了.如果你的类名以java.开头,不好意思我要抛异常了.这也就是刚刚上面的第三种说法,你虽然打破了双亲委派机制,但是还是逃不过defineClass中的安全检查.仔细看一下这个方法的实现,核心的代码及是Class<?> c = defineClass1(this, name, b, off, len, protectionDomain, source);就是这句,其他的都是一些前置后置处理,我们再看一下这个defineClass1方法具体的实现.

9d0684fe534f0e89872aeaa91bd26280.png

哦噢,这个是一个native方法,作用域是private,综合这些信息总结一下就是,真正转化class文件到内存中的class对象,是调用的这个native方法,这个方法只能本类内调用.看到这里,你是不是觉得第三种说法是对的,毕竟这个defineClass1是没办法外部调用的,而且调用这个native方法的defineClass还是一个finnal修饰的,也是不能重写的.

真的没办法了吗? NO,NO,NO,用反射啊,管你是private还是public,用一个setAccessible(true)统统搞定.怎么样,是不是又看到一丝希望了.下面直接上代码验证.

b8275c86cc1c78cd5fd1cb78092c2e2f.png

b581014a4ec4118297a9562dbb939a9e.png

02b7e8b94587daca3d58d36587bc4ddf.png

仔细看一下下面的调用报错的堆栈,可以看出来在调用ClassLoader中的defineClass1方法中抛出了一个SecurityException,异常的message也说明了是一个禁止的包名:java.lang.

看到这里其实基本已经有答案了,ClassLoader最终将class文件转换成内存中的class对象是一个native方法,并在这个native方法中会抛出java.lang.SecurityException: Prohibited package name: java.lang这个异常,因此自定义的System是没办法加载的.

以上所有说明都是基于java1.8.0_151-b12版本.

反转:如果你觉得这个问题已经结束了,那我这篇文章跟网上那些流传的说法123基本就是一个级别的,一点突出的点都没有.所以在这里我明确的告诉你,不是这样的,请继续往下看.如果你说你没有兴趣继续往下看了,那我先把结论告诉你,结论就是:虽然很难.但是事实上是可以加载一个自定义的java.lang.System类的.

之前我一直在用java8这个版本来试验的,但是java11这个LTS已经出来了,所以是不是应该尝试一下呢.接下来就试一下吧,我就直接上结果了:

1080632be85af37d07b1bfa38ba067b0.png

第一步就是失败, 在java8里起码自己写的java.lang.System还能编译,到java11里直接编译的试就报错了,说java.lang这个package已经存在java.base这个模块里了(不理解的可以看一下java9里引入的模块系统).

那我们就曲线救国吧,java8能编译不能加载,那我们就用java8编译好之后用java11来加载试试看,这里编译过程略过,直接用java11加载的编译后的class文件.

运行代码后发现,直接报错类,原因很快就找到类,通过反射调用的defineClass1方法在java11中变成了静态方法,看下图

25ef58bd0f110aa3e7dea87f0777189d.png
defineClass1方法变成类一个static native方法,所有之前的代码反射出错了

所以代码需要修改一下,下面直接上修改后的代码和运行结果

369a5f2c961161838a442599d6272230.png

运行结果:

445b7a81a4464348f9b974f908744e18.png
运行结果中已经成功打印出了我自定义的System的test方法的执行结果

总结:如果你是一直看到这里的,我想你应该有了一个明确的答案了,但是我还是简单的总结一下吧.

1.java8中可以正常编译包名为java.lang开头的类,但是编译后还是没法跳过defineClass1中的安全检查,所以是没法加载自定义System类的.

2.java11中在编译的过程中会检查包名是否与当前已存在的包名冲突,所以想编译java.lang.包开头的类是不行的.

3.但是种种限制实际上并不是不可打破的,只需要是用java8来编译的,使用java11中的platfromClassLoader来加载就可以成功的把自定义的java.lang.System加载到内存当中并实例化对象.

课后思考:上面代码中的main方法实际上已经成功的加载了自定义的java.lang.System,然后代码中还使用Java自带的System类的out.println方法,那么问题来了:

1.那我们在main方法这个System为什么不是自定义System类的,而是jdk自带的?

2.代码是使用的是反射来实例化自定义的System类的对象,能不能直接通过new 的方式来实例化一个自定义的System对象呢?

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值