从零开始手写Tomcat的教程8节----加载器

2022-05-10 15:48:52 浏览数 (1)

从零开始手写Tomcat的教程8节----加载器

  • Java的类加载器
    • 解答一: servlet只能访问指定目录下的类,类加载器如何实现这种隔离访问机制的呢?
    • 解决二: 类加载器如何实现在项目不重启的情况下,对某个发生变化的类进行热更新呢?
  • Loader接口
  • WebAppLoader类
    • 创建类加载器
    • 设置仓库
    • 设置类路径
    • 设置访问权限
    • 开启新线程执行类的重新载入
    • WebappClassLoader类
    • 类缓存
    • 载入类
  • 应用程序
  • 总结

我首先提出几个问题,大家先思考一下,如果都可以想出来,说明对类加载器的掌握程度还算不错:

  • servlet只能访问指定目录下的类,类加载器如何实现这种隔离访问机制的呢?
  • 类加载器如何实现在项目不重启的情况下,对某个发生变化的类进行热更新呢?

Java的类加载器

对classpath不了解的可以看一下这篇文章----Java中令人困惑的classpath和jar到底是什么鬼?

看到这里,提问: 为什么要执行这样一个循环呢?

当然,不是直接委托给父类加载器,而是先判断该类型是否已经被当前类加载器所加载,如果没有再委托给父类加载器,即一个类只能被同一个类加载器加载一次,但是可以被多个不同的类加载器加载多次,这也引出一个没说的知识点: 类加载器 class对象才能唯一确定一个类

这里类加载器指的是两种情况: 两个类加载器本身class类型都不同; 同一个类加载器的两个实例对象加载出来的类,也被视为不同的类


更多类加载器知识点,可以看一下我之前写过关于类加载器的文章:

JVM第六卷—类加载机制

JVM第八卷—类加载与执行子系统的案例与实战

类加载器如何实现类隔离


解答一: servlet只能访问指定目录下的类,类加载器如何实现这种隔离访问机制的呢?

这里先给出一个解决思路,然后下面我们在来看一下tomcat是如何进行处理的

这里只是给我一个思路方向,这不等于tomcat选择的解决办法


解决二: 类加载器如何实现在项目不重启的情况下,对某个发生变化的类进行热更新呢?

这里类重载技术没那么简单,本节先不讲,感兴趣的,可以等几天,等到12节左右,才会进行类自动重载技术的讲解


Loader接口


WebAppLoader类


创建类加载器


设置仓库

tomcat自定义类加载器会去这些仓库下面寻找需要的servelt类


设置类路径


设置访问权限

这里说的安全管理器指的是java中的SecurityManager,感兴趣的小伙伴可以去了解一下,主要是处理权限管理的,比如对某个文件,资源是否有读写权限等


开启新线程执行类的重新载入

因为检查每个资源是否改动,以及重新加载的过程耗时可能会比较长,并且该过程与当前线程能否继续运行也没有之间关联,因此单开一个线程处理这种事情,是明智的选择

因为这样做对性能会有损耗,因此默认自动重载功能是关闭的。



WebappClassLoader类


类缓存

这样说明了在JVM中提到的方法区进行垃圾回收时执行类卸载动作时,一定要求当前类的class和实例对象不被引用,并且当前类的加载器也不被引用的前提下,该类型才会被卸载。


载入类

delegate的中文是委托的意思,放在这里很合适,表示是否将加载当前类的职责委托给父类加载器完成


应用程序

对StandardContext的分析放在12节进行。

这里我们只需要了解StandardContext类是如何与监听他触发事件的监听器协同工作的,监听器必须实现LifecycleListener接口,并调用StandardContext类的setConfigured()方法。

对于本章应用程序来说,监听器是SimpleContextConfig实例。

代码语言:javascript复制
public class SimpleContextConfig implements LifecycleListener{
   public void lifecycleEvent(LifecycleEvent event)
   {
      if(Lifecycle.START_EVENT.equals(event.getType()))
      {
        Context context=(Context)event.getLifecycle();
        context.setConfigured(true);
      }
   }
}

这里还需要做的就是实例化StandardContext和SimpleContextConfig类,然后调用Lifecycle的addLifecycleListener()方法,通过StandardContext注册SimpleContextConfig.


这里使用PrimitiveServlet类和ModernServlet类进行测试,但是使用了StandardContext类的实例作为Servlet容器,所以servlet类只能放在应用程序目录的WEB-INF/classes目录下。

应用程序目录名称为myApp,其次还需要通知StandardContext实例到哪里查找应用程序目录,需要设置一个"catalina.base"的系统属性,其值为"user.dir"属性的值,如下所示:

代码语言:javascript复制
        System.setProperty("catalina.base",System.getProperty("user.dir"));

事实上,上面代码就是BootStrap类的main方法的第一行代码,然后实例化默认连接器:

代码语言:javascript复制
Connector connector=new Connector();

然后,与之前章节中应用程序一样,它为两个servlet类创建两个Wrapper实例。

代码语言:javascript复制
Wrapper wrapper1=new Wrapper();
wrapper1.setName("Primitive");
wrapper1.setServletClass("PrimitiveServlet");
Wrapper wrapper2=new Wrapper();
wrapper2.setName("Modern");
wrapper2.setServletClass("ModernServlet");

上面docBase指定的是相对路径,因此会根据应用程序的目录而发生改变,如果指定绝对路径,则不会改变,但是一般都是相对路径


总结

本小节到此基本就结束了,现在让我们再来回顾一下开头提出的问题,这里只回答第一个问题,第二个问题会在12小节进行回答:

servlet只能访问指定目录下的类,类加载器如何实现这种隔离访问机制的呢?

0 人点赞