引用

classloader机制

  • 如下图所示,java的classloader是双亲委派机制。会首先从父classloader加载指定的class,如果加载不到才会从子classloader中加载。
  • java自定义classloader引发的思考-LMLPHP
  • 主要这里的图片主要用于体现classloader的父子关系,实际上实现时并不一定存在继承关系。比如AppClassLoader的父classLoader是ExtClassLoader,但是实际实现时两者都是继承自URLClassLoader的。

自定义classloader

  • java自定义classloader引发的思考-LMLPHP

Animal类&包结构

  • java自定义classloader引发的思考-LMLPHP
  • 包结构如图:
  • java自定义classloader引发的思考-LMLPHP

测试入口

  • java自定义classloader引发的思考-LMLPHP
  • 输出结果如图所示:java自定义classloader引发的思考-LMLPHP

思考1

  • 我们定义的classloader真的被运行了吗?
  • 实际上如果打一些日志或者是debug进去看下,会发现我们重写的方法并没有被执行,这是java classloader的委派机制搞的鬼,我们可以发现定制的TinglangClassLoader的父classloader为URLClassLoader,在URLClassLoader中就已经找到了Animal类了。
  • 所以这里可以重写loadClass方法来保证我们的classLoader被执行。

思考2

  • 我们将代码做一些变种,如下图所示:
  • java自定义classloader引发的思考-LMLPHP
  • 执行结果直接抛异常了java自定义classloader引发的思考-LMLPHP
  • 这是由于Animal类来源于两个不同的类加载器。如果只是需要执行Animal中的say方法的话,直接反射调用即可,如下图所示:
  • java自定义classloader引发的思考-LMLPHP

思考3

  • 我们再将代码做一下变化,如下图所示,Animal作为一个普通内部类来实现,如下图所示:
  • 可以看到代码已经提示无法实例化内部类了,这个是由于要实例化非静态的内部类对象,必须要先实例化外部类的对象,可以采用下面的方法来解决:
  • java自定义classloader引发的思考-LMLPHP
  • ps:这里内部类有public修饰符~

思考4

  • tomcat也是定义了自己的classloader,那么为啥不用jvm提供的classloader呢?
  • 结合相关的资料主要是三个方面的目的:
    • webapp隔离:由于各个webapp中的class和lib文件需要相互隔离,不能出现一个应用中加载的类库会影响到另一个应用的情况。
    • 安全性:与jvm相同,tomcat也期望使用单独的classloader去装载tomcat自身的类库,以免其他恶意或者无意的破坏。
    • 热部署:tomcat修改文件可以不用重启自动装载类库,这个点后面我们会单独抽取示例。

思考5

  • 既然ExtClassLoader是读取特定目录下的class文件,那么如果我将自定义的class文件移到$JAVA_HOME/jre/lib/ext/目录下是不是就能够达到指定classloader加载类的目标呢?
  • 这个点是可以的,但是必须要求是jar。

思考6

  • 类加载器常见的用途有类的隔离和热替换,类的隔离非常好理解,那么热替换呢?
  • 仍然以我们之前的代码为例,如下图所示:
  • java自定义classloader引发的思考-LMLPHP
  • 在程序运行过程中更新并编译Animal类中say方法
  • java自定义classloader引发的思考-LMLPHP
  • 可以看到运行结果如图所示:
  • java自定义classloader引发的思考-LMLPHP
  • ps:注意第一张图中的红色框框部分,如果修改为Thread.currentThread().getContextClassLoader()会发现实际上不会起作用,这是由于要想实现同一个类的不同版本的共存,这些不同的版本必须由不同的类加载器进行加载,因此就不能把这些类的加载工作委托给类加载器来完成,因为它们只有一份。
05-11 17:22