[2]菜鸡写Tomcat之Context

上学期买了本How Tomcat Works然后一直丢在书柜里没看,之前有一天闲,翻出来看了几页,觉得挺有趣的,所以就跟着书上的思路和一些tomcat源码,自己写了一个简单的应用服务器—Tiny Server

感觉在这个过程中学到了不少东西,所以想把自己的思路和想法写出来分享一下

Ps:因为是自己的理解,所以如有不对请各位大佬指出,感激不尽

我写的Tiny Server的源码在这里:https://github.com/Lehr130/TinyServer

整个项目并没有完全按照tomcat的写法来,只是按照自己的理解去实现的,而且有些功能并没有完全实现,还有待各位指教

相关内容

[7]菜鸡写Tomcat之Container
[6]菜鸡写Tomcat之Cookie与Session
[5]菜鸡写Tomcat之WebClassloader
[4]菜鸡写Tomcat之生命周期控制
[3]菜鸡写Tomcat之Filter
[2]菜鸡写Tomcat之Context
[1]菜鸡写Tomcat之Wrapper

Context能干些什么

在正式讲Context之前,我们先来回顾一下一个Web应用在Tomcat中的三种部署方式

Web应用部署的三种方式

目录下部署

在Tomcat对应的目录下(通常为webapps,不过也可以自己修改,这和Host节点有关,在server.xml里配置),直接放入你的war包,Tomcat就会自动解压,然后完成应用的部署

其实也可以直接把解压后的文件夹放入(就比如idea你选择打包方式为exploded),然后Tomcat就会开始部署了

不过该文件夹必须满足一定的要求,比如,有WEB-INF子目录,在WEB-INF子目录下有web.xml配置文件等等等等

War包和包的区别

其实就是和你的打包的war项目的文件结构要一样,这是Tomcat的规范,而war文件和jar文件其实没什么区别,主要就是war文件满足了这种目录结构,然后我们把后缀名写死成war以便tomcat好去识别,不然如果是jar包的话,tomcat就需要花很多精力去识别这个jar包倒是是个web项目还是单纯的就是一个包

外目录

在Server.xml文件里,可以去Engine–>Host节点下面手动配置一个应用:

<Context path="/Lehr" docBase="/home/lehr/LehrsProject" reloadable="true" />

这是时候,你就可以自己去配置docBase的路径,从而实现在服务器目录外面部署了

不过这样做有一个缺点,因为server.xml只会在服务器启动的时候被读取,所以如果你在这里再添加了一个新的应用,那么只能重启服务器才能加载进来了

文件描述符

在Tomcat的Catalina目录的localhost下添加一个xml文件,写法也是<Context docBase = "xxx">,但是可以不需要指定path,path会被默认当成“/”加上这个文件的名字,不过如果一旦指定path,就必须和你的xml文件名一致

这样做本质上和在Server.xml里配置节点是类似的,因为Catalina就是Tomcat里唯一的Engine,localhost也就是一个Host【关于容器的概念具体可以看这里(还没写好到时补上链接)】

Context的职能

上面无论是哪一种项目部署方法,在配置的时候,都会扯上Context,所以说,Context就是整个Web项目的容器

Context容器是Tomcat的4大容器中比Wrapper大比Host小的容器,直接翻译过来,就是上下文的意思,一个Context实例表示一个具体的Web应用,说简单点,Context管理的东西大概就是你的一个JavaWeb项目,他维护了这个项目里的所有Servlet小程序,并把他们统一组织起来,使得他们能够成为一个整体

Context的组件概述

Context是代表一个Web应用,他的子容器就是若干个用于管理本Web应用中的Servlet的Wrapper容器

Context容器会统一对Wrapper容器的初始化,销毁进行管理,在遇到请求的时候选择匹配的Wrapper容器找到Servlet并执行

不过Context容器里不光只有Wrapper容器,为了把所有Servlet小程序弄成一个整体来工作,并实现Servlet中各种资源共享,生命管理,加载机制等等,Context里面还包含了很多其他的组件,他们的作用和功能大概如下:

Wrapper容器管理

给张图:

image-20200226111249636

在Context创建并启动之后,他就会开始为这个应用中所有的servlet准备好一个wrapper,配置好初始化的数据并统一进行生命周期管理和请求分发。所以现在应该考虑Context如何准备好每个Wrapper的初始数据了的问题了

解析web.xml

Wrapper的基本数据有初始化参数,名字,匹配路径等等,要能够获取这些参数,则说明Context在启动之后,能读取Web应用WEB-INF目录下的web.xml文件,从中获取到一系列Wrapper的配置,而且还能够根据load-on-startup标签,把那些需要提前实例化的Servlet在Wrapper容器中提前初始化。同理,还有Filter的信息和监听器的信息

除此之外,在web.xml里还可以有这样一段配置:


    <context-param>
        <param-name>myBlog</param-name>
        <param-value>blog.imlehr.com</param-value>
    </context-param>

    <context-param>
        <param-name>myWeb</param-name>
        <param-value>imlehr.com</param-value>
    </context-param>

这是本Web应用预先设置的上下文参数,在Servlet规范中,他的具体体现就是ServletContext这个类

域对象

ServletContext又被称为域对象,里面存放的信息是本应用中所有的Servlet都可以共享的

在Servlet中调用这个对象,他有以下这些方法:

image-20200226112554618

设置属性,获取当前项目的路径,获取Context的初始化参数…全都是和整个Web应用全局相关的内容

这里的话我想单独说说两个方法:

get/setAttribute

Attribute就是属性的意思,本质就是一个Hashmap,通过String–>Object的方法可以让用户在Servlet中添加和删除某条属性,由于ServletContext是一个域对象,所以在一个Servlet中修改的Attribute是对其他所有Servlet都是可见的

新手入门的时候这个方法最经典的例子就是:实现网站浏览量统计,每次有人访问,然后就给域对象中的那条对应的attribute加1

get/setInitParameter方法

initParameter,就是你在web.xml里配置好的那几个参数,可以通过get方法在Servlet中获取到,这个倒是没啥

不过他的set方法,emmm这个真的是个巨坑,因为,在Tomcat中,如果你调用setInitParameter方法,服务器就会直接给抛一个异常出来不让你调用,所以我真不知道那为啥Servlet规范中还把这个方法设置成public的,有大佬知道的话求讲解…

所以说,如果想要设置一个其他Servlet可见的参数,那就只能使用setAttribute

匹配Wrapper

完成了Wrapper的初始化工作和自己内部参数的加载之后,当每次遇到新的请求的时候,Context就需要去根据本次请求中的uri和协议来寻找到一个正确的Wrapper然后执行了

匹配uri可以通过一个叫Mapper的组件来实现映射,然后返回一个Wrapper容器,下文会讲到

Filter管理

Servlet中三大类的加载顺序是:Listener>Filter>Servlet

相比Servlet按需加载,当整个项目启动的时候,所有的Filter就会被依次实例化和初始化,然后被Context统一管理起来,如果某个Servlet接收到请求,调用invoke方法的时候需要生成一条FilterChain,则我们可以直接从Context中获取所需要的Filter对象并引用

当Context执行stop方法的时候(往往是服务器停止或者发生热加载热部署的时候),Context会统一把所有Filter实例调用他们的destory方法销毁掉,从而实现对Filter的管理

类加载

接下来就是个很重要的问题了:我们在web.xml里面只知道servlet的类名,我们怎么样把他们给实例化呢?

可能有人会想,不就用个反射就好了吗

但是问题来了:

在某个类中调用反射所加载的类,他的默认的类加载器就是当前类对象的

感觉这句话有点绕,这样解释:

A类是由1号类加载器加载的,在A类中通过反射去加载B类,那么B类的类加载器则也是1号类加载器

这会导致什么问题呢?

看这样一个场景叭:

Lehr写了一个辣鸡小服务器,然后水松同学部署了一个项目,其中有一个类叫com.mss.user;然后过了几天,小廖同学也来部署了另外一个项目,其中也有一个类叫com.mss.user,但是这个类和之前那个不一样的

结果,小廖的项目跑起来就爆炸了,因为虚拟机以为他在几天之前已经从水松同学那里加载过这个类了(因为虚拟机判定他们的类加载器相同,类名也一样,所以就认为是一个类了),所以就直接拿着之前的那个类在小廖的项目上跑,不爆炸才怪…

所以说,对于服务器里面部署的每一个项目,他们都需要有自己独特的类加载器,从而使得不会导致上面的问题发生,这也就是Tomcat源码中为何类加载器违背了双亲委派模型的原因,关于我对自定义类加载器的实现【具体可以看这里(还没写好到时补上链接)】

会话管理

Servlet规范中通过一个叫做HttpSession的类,并结合Cookie来实现了服务器的会话管理机制,他的大概工作过程如下:image-20200226142424352

在Context容器中,我们需要做的事情是,准备一个会话管理器,每次接受到请求,先通过会话管理器来获取session,然后再执行后续操作,由于这个会话管理器组件功能相对独立,我打算分开来讲,【具体可以看这里(还没写好到时补上链接)】

热加载

热加载就是,当你的这个Web项目里面的某个文件发生变化之后,tomcat会更新一次Context容器,使得你新添加进来的功能能够马上工作

在原版tomcat中,热加载是默认关闭,如果想要开启的话,则需要到server.xml里的对应的<context>去配置,把reloadable设置为true即可

在Context中,实现热加载的组件其实还是容器自己的类加载器,在Context容器启动的同时,会产生一条后台线程,不断对项目的目录文件夹进行扫描,一旦监听到文件有变化,就会发起热加载动作,【具体可以看这里(还没写好到时补上链接)】

Context每个阶段要干什么

start---->创建和初始化

上面说到,Context容器里除了Wrapper,还有很多组件,比如:

  • SessionManager—会话管理器(源码中就叫Manager)

  • Mapper—映射管理器

  • loader—类加载器

  • Filters—某个管理Filter的地方

  • ServletContext—域对象

在刚刚创建出本对象的时候,Context容器会从构造函数中获得到自己的路径和路径映射信息(在源码中,似乎是一个叫做namingResource的组件来管理),然后把这些数据再交给自己的组件,例如Loader类加载器需要获得文件路径才能进行后续加载。

不一定会成功

相比Wrapper容器但单纯的new一些pipline和valve,Context容器还需要创建和初始化各个组件,而且还会有初始化失败的风险

例如当用户乱写web.xml后,Context解析结果就会失败,会导致后续所有步骤无法继续进行

所以说这时候需要一些标志位用来表示容器的状态,如果这些标志位为false,则不能进行后续的步骤,容器将仍然停留在未启动的状态

启动顺序

由于我这块自己在实现过程中写得比较混乱,所以引用书上的

在How Tomcat Works这本书中,作者在第12章给出的StandardContext的start()方法的启动顺序是:

1.触发BEFORE_START事件

2.将availability属性设置为false

3.将configured属性设置为false

4.配置资源

5.设置载入器

6.设置Session管理器

7.初始化字符集映射器

8启动与该Context容器相关联的组件

9.启动子容器

10.启动管道对象

11.启动Session管理器

12.触发START事件,在这里监听器(ContextConfig)会执行一些配置操作,若设置成功,ContextConfig实例将会将StandardContext实例的configured变量设置为true

13.检查confgured属性的值,若为true,则调用postWelcomePages()方法,载入那些需要在启动时就载入的子容器,即Wrapper实例,将availability属性设置为true,若为false,则调用stop()方法

14.触发AFTER_START事件

实际上在tomcat源码的StandardContext中,他的启动方法的逻辑是在

protected synchronized void startInternal()

这个方法中进行的,而且他最后还会开启一条后台线程

backgroundProcess---->后台线程

在这个方法的最后

protected synchronized void startInternal()

image-20200226151508572

在Tomcat4的时候,他的实现方法是给每一个需要进行后台任务的组件分配一个线程,但是后来为了节省资源就没有这样做了

在Tomcat5以后的版本里tomcat只会开启一条后台线程,来执行一些清理和检测任务,这条线程在Context容器执行stop之前,会一直进行一些任务,比如清理过期的session,比如扫描一遍文件目录查看是否需要触发热加载(这条线程其实是ContainerBase类里的一个实现了Runnable接口的内部类ContainerBackgroundProcessor)

Ps:我特意去网上查了一下这个线程执行任务的间隔时间,默认是10秒一次

image-20200226152517130

【关于热部署的具体可以看这里(还没写好到时补上链接)】

invoke---->被请求

invoke的核心的逻辑代码在ContextValve里的invoke方法

检查状态

由于我们说道的热加载的问题,所以,当Context收到请求后需要做的第一件事情就是检查当前容器的状态,如果当前容器正在进行重新热加载,则我们要访问的Wrapper容器可能已经被拔走了,所以,这一步要做的事情就是:检查容器状态标志位,如果标志位是false,则等待一段时间,再次查看Context是否完成重载,如果完成了,才能继续访问,进行路径映射,找到对应的Wrapper容器

关于路径映射

其实这里的逻辑很好理解,但是关于实现方面,我想多写两句:

在Tomcat5之前,Context容器中是存在一个叫做Mapper的组件的,专门用来做路径映射

不过Tomcat5之后,这个路径映射功能被放到了Request对象里去了

stop---->销毁

何时调用?—>对于Context而言,stop方法除了服务器关闭的时候,还有可能在发生热加载和热部署的时候

要干什么?—>和start方法恰好相反而已:

  • 设置各种标志位为false
  • 把后台线程停掉
  • 开始关闭各个组件和子容器
  • done

Context中的我的实现方法浅谈

关于start方法和stop方法以及reload方法,我想放在热加载这篇博客里再讲,所以下面会说得比较简略,【关于热部署的具体可以看这里(还没写好到时补上链接)】

构造函数

真没啥,因为我的版本只支持往指定目录下放的部署方式,所以代码就这么简陋

public TommyContext(String name, String appPath) throws Exception {
		//获取项目名
        this.appName = name;
        //获取路径
        this.appPath = appPath;
    }

start方法

    @Override
    protected void doStart() throws Exception {

        //FIXME:这里还有个设计:如果报错了的话,有一个叫做available的布尔值会为false然后本容器是无法调用的

        isPaused = true;

        //准备好session管理器
        sessionManager = new TommySessionManager(this);
        sessionManager.start();

        //通过路径准备好类加载器,热加载默认关闭
        loader = new TommyWebAppLoader(appPath, true, this);
        loader.start();

        //解析web.xml内容
        context = new TommyContextConfig(appPath);

        //获取域对象
        servletContext = new TommyServletContext(context.getContextInitParameters(), appPath);

        //加载所有的filter
        initAllFilter(context.getFilterConfigMap());

        //初始化子容器
        loadWrappers();

        //一个一个启动子容器
        //我这里没有去做类型判断,因为我觉得这样好像没有毛病
        wrappers.values().forEach(TommyWrapper::start);


        //现在就是可以访问的了
        isPaused = false;

        //开始后台线程
        threadStart();

    }

也就和我上面说的基本一样,至于每个组件的具体工作,我以后也会写博客的

这里的话我并没有去实现servlet的load-on-startup功能,见谅…

另外还有个小细节:

在Tomcat源码中,容器实例化每个组件的方式,是通过传入组件的类名,然后调用反射完成的

invoke方法

@Override
protected void basicValveInvoke(MyRequest req, MyResponse res) {


    //检查载入情况,以防重载的时候被调用
    while (isPaused) {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {

        }
    }

    //源代码基本也是这样做的,只不过他是放在wrapper的基础阀里面
    //但,其实我觉得嘛,放这里也没啥
    req.setContext(this);

    //找到正确的wrapper
    TommyWrapper wrapper = getWrapper(req);

    //启动wrapper
    try {
        wrapper.invoke(req, res);
    } catch (Exception e) {
        e.printStackTrace();
    }


}

stop方法

没啥,停了就好了

    @Override
    protected void doStop() {

        isPaused = true;

        //先把后台线程停止了,这里用的是tomcat的源码的处理方法
        threadStop();

        //停止子容器
        wrappers.values().forEach(TommyWrapper::stop);
    }

关于解析web.xml

解析web.xml的话,我的逻辑是,写了一个类,然后传入路径,这个类就会自动找到web.xml文件,然后利用org.w3c的处理xml的包来解析和封装信息,我把这个类的代码附在这里:

package tiny.lehr.tomcat;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import tiny.lehr.tomcat.bean.TommyFilterConfig;
import tiny.lehr.tomcat.bean.TommyServletDef;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.File;
import java.util.HashMap;
import java.util.Map;

/**
 * @author Lehr
 * @create: 2020-01-22
 * 这个类专门用来对每个Webapp的web.xml进行解析
 */
public class TommyContextConfig {

    private Document xmlDoc;

    /**
     * 以名字为Key
     */
    private Map<String, TommyServletDef> servletDefMap;

    private Map<String, String> contextInitParameters;

    private Map<String, TommyFilterConfig> filterConfigMap;





    public TommyContextConfig(String path) throws Exception {
        //把web.xml解析为一棵dom树
        File xmlFile = new File(path + File.separator + "WEB-INF" + File.separator + "web.xml");
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        DocumentBuilder builder = factory.newDocumentBuilder();
        xmlDoc = builder.parse(xmlFile);
        servletDefMap = new HashMap<>();
        filterConfigMap = new HashMap<>();
        contextInitParameters = new HashMap<>();

        parseFilter();
        parseServlet();
        getContextParams();


    }

    public Map<String, TommyServletDef> getServletDefMap() {
        return servletDefMap;
    }

    public Map<String, String> getContextInitParameters() {
        return contextInitParameters;
    }

    public Map<String, TommyFilterConfig> getFilterConfigMap() {
        return filterConfigMap;
    }

    private void parseServlet() {

        //解析servlet标签
        NodeList servletNodeList = xmlDoc.getElementsByTagName("servlet");
        for (int i = 0; i < servletNodeList.getLength(); i++) {
            Element node = (Element) servletNodeList.item(i);

            //获取名字
            String servletName = getByTag(node, "servlet-name");

            //获取类
            String servletClass = getByTag(node, "servlet-class");

            //加入map
            servletDefMap.put(servletName, new TommyServletDef(servletName, servletClass, getInitParamsMap(node)));
        }

        //解析Servlet-mapping标签
        NodeList mappingNodeList = xmlDoc.getElementsByTagName("servlet-mapping");
        for (int i = 0; i < mappingNodeList.getLength(); i++) {
            Element node = (Element) mappingNodeList.item(i);

            //从集合里搜索出名字一样的servletConfig
            TommyServletDef config = servletDefMap.get(getByTag(node, "servlet-name"));

            //放入url
            config.setServletUrl(getByTag(node, "url-pattern"));

        }

    }

    private void parseFilter() {
        //解析Filter

        //解析servlet标签
        NodeList filterNodeList = xmlDoc.getElementsByTagName("filter");
        for (int i = 0; i < filterNodeList.getLength(); i++) {
            Element node = (Element) filterNodeList.item(i);

            //获取名字
            String filterName = getByTag(node, "filter-name");

            //获取类
            String filterClass = getByTag(node, "filter-class");

            //加入map
            filterConfigMap.put(filterName, new TommyFilterConfig(filterName, filterClass, getInitParamsMap(node)));
        }

        //解析Servlet-mapping标签
        NodeList mappingNodeList = xmlDoc.getElementsByTagName("filter-mapping");
        for (int i = 0; i < mappingNodeList.getLength(); i++) {
            Element node = (Element) mappingNodeList.item(i);

            //从集合里搜索出名字一样的servletConfig
            TommyFilterConfig config = filterConfigMap.get(getByTag(node, "filter-name"));

            //放入url
            config.setFilterUrl(getByTag(node, "url-pattern"));

        }
    }

    /**
     * 这也是封装的人家的api
     * 从一个标签里获得属性值
     *
     * @param node
     * @param tag
     * @return
     */
    private String getByTag(Element node, String tag) {
        return node.getElementsByTagName(tag).item(0).getFirstChild().getNodeValue();
    }



    private void getContextParams() {

        NodeList contextParamNodeList = xmlDoc.getElementsByTagName("context-param");
        for (int i = 0; i < contextParamNodeList.getLength(); i++) {
            Element node = (Element) contextParamNodeList.item(i);

            //获取名字
            String paramName = getByTag(node, "param-name");

            //获取类
            String paramValue = getByTag(node, "param-value");

            //加入map
            contextInitParameters.put(paramName,paramValue);
        }
    }

    private Map<String,String> getInitParamsMap(Element node) {
        //获取参数
        Map<String, String> parametersMap = new HashMap<>();

        NodeList servletParamNodeList = node.getElementsByTagName("init-param");
        for (int j = 0; j < servletParamNodeList.getLength(); j++) {
            Element paramNode = (Element) servletParamNodeList.item(j);
            String paramName = getByTag(paramNode, "param-name");
            String paramValue = getByTag(paramNode, "param-value");
            parametersMap.put(paramName,paramValue);
        }

        return parametersMap;
    }


}

不过我这里只实现了解析servlet或filter的一个映射,但实际上一个servlet或filter是可以有多个映射的,以后再改叭…

我的TinyServer里的代码

TinyServer是我个人在读tomcat源码的时候模仿着写的一个非常简单的玩具,所以很多设计结构上都比tomcat简陋很多,由于上文是用到了自己的代码来做的分享,所以这里附上我对于Wrapper级容器的实现的源码,完整项目见https://github.com/Lehr130/TinyServer

因为Context这个类的组件太多,然后要讲的东西也不少,所以以后会慢慢把各个组件各个部分的工作原理的我的实现慢慢补上的,现在先贴上Context的我的实现的代码:

package tiny.lehr.tomcat.container;

import tiny.lehr.bean.MyRequest;
import tiny.lehr.bean.MyResponse;
import tiny.lehr.tomcat.TommyContextConfig;
import tiny.lehr.tomcat.TommySessionManager;
import tiny.lehr.tomcat.bean.TommyFilterConfig;
import tiny.lehr.tomcat.bean.TommyServletContext;
import tiny.lehr.tomcat.loader.TommyWebAppLoader;

import javax.servlet.Filter;
import java.util.HashMap;
import java.util.Map;

/**
 * @author Lehr
 * @create 2020-01-16
 * Context级容器的实现,代表了一个项目的级别,即war的容器
 * 里面包含了多个Wrapper级子容器,用来管理
 * <p>
 * FIXME: Wrapper不是按需加载,而是里面的servlet是按需实例化
 */
public class TommyContext extends TommyContainer {

    private String appName;

    private TommyWebAppLoader loader;

    private String appPath;


    //用url来找wrapper
    //IMPROVE: 感觉源码好像是 先根据配置文件实例化所有,但是只不过servlet没有实例化而已,然后他就好实现loadupstart那个顺序了
    private Map<String, TommyWrapper> wrappers;

    private TommyServletContext servletContext;

    private TommyContextConfig context;

    private Map<String, TommyFilterConfig> filterPool;

    private TommySessionManager sessionManager;

    private Boolean isPaused = true;


    public TommyContext(String name, String appPath) throws Exception {

        this.appName = name;
        //获取路径
        this.appPath = appPath;

    }

    public String getAppName() {
        return appName;
    }

    public TommySessionManager getSessionManager() {
        return sessionManager;
    }

    private void initAllFilter(Map<String, TommyFilterConfig> filterConfigMap) {

        filterPool = new HashMap<>();

        filterConfigMap.forEach((filterName, filterConfig) -> {
            try {

                filterConfig.setServletContext(servletContext);

                Filter myFilter = (Filter) loader.loadClass(filterConfig.getFilterClassName()).getDeclaredConstructor().newInstance();

                myFilter.init(filterConfig);

                filterConfig.setFilter(myFilter);

                filterPool.put(filterName, filterConfig);

            } catch (Exception e) {
                e.printStackTrace();
            }


        });
    }

    /**
     * 通过servlet请求获取子容器
     * 请求化为servletUrl--->如果之前加载了,就直接返回实例,如果没有,就加载后放入池中然后返回
     *
     * @param req
     * @return
     */
    private TommyWrapper getWrapper(MyRequest req) {

        String servletUrl = req.getServletPath();

        //检查之前实例化过这个wrapper没有
        Boolean alreadyHave = wrappers.containsKey(servletUrl);

        //如果没有就加载,不然直接返回
        if (alreadyHave == false) {
            System.out.println("没有!!!");
        }

        //获取
        TommyWrapper wrapper = wrappers.get(servletUrl);

        return wrapper;

    }

    public Map<String, TommyFilterConfig> getFilterPool() {
        return filterPool;
    }

    public TommyServletContext getServletContext() {
        return servletContext;
    }


    private void loadWrappers() {
        wrappers = new HashMap<>();

        context.getServletDefMap().values().forEach(servletDef -> {
            TommyWrapper wrapper = new TommyWrapper(this, servletDef);
            //加入到wrappers池里去
            wrappers.put(servletDef.getServletUrl(), wrapper);
        });
    }


    public TommyWebAppLoader getLoader() {
        return loader;
    }

    /**
     *
     * 设置基础阀任务:选择正确的Wrapper并启动
     *
     * @param req
     * @param res
     */
    @Override
    protected void basicValveInvoke(MyRequest req, MyResponse res) {


        //检查载入情况,以防重载的时候被调用
        while (isPaused) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {

            }
        }

        //源代码基本也是这样做的,只不过他是放在wrapper的基础阀里面
        //但,其实我觉得嘛,放这里也没啥
        req.setContext(this);

        //找到正确的wrapper
        TommyWrapper wrapper = getWrapper(req);

        //启动wrapper
        try {
            wrapper.invoke(req, res);
        } catch (Exception e) {
            e.printStackTrace();
        }


    }


    @Override
    protected void doStart() throws Exception {

        //FIXME:这里还有个设计:如果报错了的话,有一个叫做available的布尔值会为false然后本容器是无法调用的


        isPaused = true;

        //准备好session管理器
        sessionManager = new TommySessionManager(this);
        sessionManager.start();

        //通过路径准备好类加载器,热加载默认关闭
        loader = new TommyWebAppLoader(appPath, true, this);
        loader.start();

        //解析web.xml内容
        context = new TommyContextConfig(appPath);

        //获取域对象
        servletContext = new TommyServletContext(context.getContextInitParameters(), appPath);

        //加载所有的filter
        initAllFilter(context.getFilterConfigMap());

        //初始化子容器
        loadWrappers();

        //一个一个启动子容器
        //我这里没有去做类型判断,因为我觉得这样好像没有毛病
        wrappers.values().forEach(TommyWrapper::start);


        //现在就是可以访问的了
        isPaused = false;

        //开始后台线程
        threadStart();

    }

    @Override
    protected void doStop() {

        isPaused = true;

        //先把后台线程停止了,这里用的是tomcat的源码的处理方法
        threadStop();

        //停止子容器
        wrappers.values().forEach(TommyWrapper::stop);
        //销毁Filter
        filterPool.values().forEach(TommyFilterConfig::destory);
    }


    private void threadStart()
    {
        System.out.println("后代线程开始运行");
        backgroundThread = new Thread(() -> {
        threadDone = false;

            while (!threadDone) {
                System.out.println("后台检查ing:");
                loader.backgroundProcess();
                sessionManager.backgroundProcess();
                try {
                    //原版默认设置的就是10秒
                    Thread.sleep(10*000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

        },"Lehr's Background Process");

        backgroundThread.setDaemon(true);
        backgroundThread.start();
    }

    /**
     * Stop the background thread that is periodically checking for
     * session timeouts.
     */
    private void threadStop() {

        System.out.println("后代线程停止运行");

        if (backgroundThread == null)
            return;

        threadDone = true;
        backgroundThread.interrupt();

        //FIXME: 我也不知道为什么 这里调用interrupt居然没用,所以只能暴力来了
        //backgroundThread.stop();
        //FIXME: 好吧不知道为什么 stop也被阻塞了??!!

        try {
            backgroundThread.join();

        } catch (InterruptedException e) {
            // Ignore
        }

        backgroundThread = null;

    }


    public void reload() {

        stop();

        start();

        System.out.println("重载完成!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");

    }


    /**
     * The background thread completion semaphore.
     */
    private volatile boolean threadDone = false;

    private Thread backgroundThread;



}

发布了47 篇原创文章 · 获赞 105 · 访问量 7万+

猜你喜欢

转载自blog.csdn.net/qq_43948583/article/details/104618410