ClassLoader初探:loadclass之后调用成员方法的正确方式

背景

最近比较闲,加之本人转java时间不久,基础不牢靠,或者说大部分的时间都在搞业务代码,底层机制了解的较少,于是就想着趁这段时间好好的了解一些java的机制,但是如果不针对某个问题广撒网可能效果并不好,想着就看看tomcat源码吧,谁知道刚开始看就有点懵逼,只能感叹如果继续沉醉于业务代码中,永远会是大厂眼中的渣渣。

 

问题

Tomcat源码中有这么一段(位于Bootstrap.java的init函数中)

 

这段代码我做了三个标记

1 initClassLoaders这个函数内部初始化了Catalina的classLoader,其实是用了URLClassLoader。

2 这个地方直接load了class,然后创建实例,看着没啥问题,但后来我就有一个疑问,如果看过代码结构就会发现Catalina这个类和Bootstrap这个类是在同一个目录下的,为什么不直接new,而是通过这种方式?

3 假设我们就通过loadClass之后newInstance的方式初始化对象,为什么又要很费劲的去通过反射的方式去调用setParentClassLoader这个方法?不能通过强转的方式转换成目标类型直接去调用吗?

 

大神们怎么说?

看tomcat源码如果直接去看太痛苦了,里面很多机制决定了代码的书写方式,所以如果不懂设计者的意图,直接去看,那肯定一脸懵逼,所以我决定先去网上看一下大神们对tomcat的框架机制的讲解,其中有一篇我觉得写得很好,但是有些地方当时还有有些疑惑,下面是链接和其中部分内容的截图:

https://blog.csdn.net/liweisnake/article/details/8470285

 

这个其实就是在说我的标记3,我大概明白作者的意思是想说一个class加载后决定其唯一性的是其绝对路径和使用的classLoader,如果用反射可以保证使用的是我们loadClass的classLoader,但如果直接类型强转后调用方法则是当前默认的classLoader,当前的classLoader又有可能加载不到Catalina.class,这样会导致类型转换失败。

 

那么如何理解呢?

当然如果看完这段文字直接就明白是咋回事了,那说明你是java高手对classLoader的设计初衷完全理解,如果你和我一样还处于小白状态,那么可能需要来段辅助代码理解下。

 

思路

按照tomcat源码和前文链接作者的说法,应该是如果我自定义了一个classLoader,比如叫做ALoader,那么对于class A来说,load之后得到的object强转为A失败的原因是A对应的class文件对于默认的classLoader来说找不到,只有ALoader能找到。我们需要构造这种场景进行验证。

 

设计

由于双亲委派,如果想让我们自定义的classLoader来完成这个load过程,而不是通过parent classLoader完成,必须保证我们的类的class文件只有我们自定义的classLoader才能找到。现在java并不鼓励我们重写loadClass,如果我们想打破双亲委派,应该重写一下findClass,按照我们自定义的规则来寻找被load的class文件,一定不能在classpath中(更不用说ext了),然后通过两种方式去调用被加载类的同一个方法,输出当前的classLoader。

其中待加载类设计如下:

 

doTest方法用于被外部调用,测试输出当前类的classLoader

自定义classLoader设计如下:

 

重写findClass方法,从指定路径加载class文件。

 

外部调用设计如下:

 

创建LoadClassA的对象之后对比通过反射和强转调用方法的结果。

 

执行

测试结果如下:

 

注意:LoadClassA的class文件一定不能和RunTest的class文件在同一个目录,否则就会被parent classLoader找到,因为java命令运行时,会把当前运行目录放在classpath下。

 

结论:

1 如果我们通过classLoader的方式创建对象实例,那么调用方法最好时通过反射的方式。不管是Bootstrap Classloader还是Extension ClassLoader或者Application ClassLoader,他们的加载规则已经确定,而我们使用自定义的classLoader一定是因为现有的classLoader无法满足我们的需求,所以不要冒险去通过强转的方式调用方法。

 

2 使用自定义classLoader的场景需要明确。如果现有的classLoader就能满足我们的需求,尽量是重复利用双亲委派这个规则,否则代码很繁琐且不易懂,对于一个团队来说,代码不是一个人的。

 

3 阅读源码需要先明确设计意图。Tomcat通过自定义classLoader的方式实现加载,首先tomcat是一个java程序,其次tomcat要加载其他java程序,在java系统、tomcat、用户应用上需要保证隔离和共享,如果我们不能很好的理解这一点,在阅读tomcat源码时会像我一样吃力。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值