zoukankan      html  css  js  c++  java
  • Servlet在启动时加载的tomcat源码(原创)

    tomcat 8.0.36

    知识点:

    • 通过配置loadOnStartup可以设置Servlet是否在Tomcat启动时加载,以及按值大小进行有序加载,其最小有效值为0,最大有效值为Integer.MAX_VALUE。
    • Jsp Servlet的类是org.apache.jasper.servlet.JspServlet。
    • Jsp Servlet是强制性启动时加载,其loadOnStartup的默认值,或其值是失效值时,将使用最大有效值。
    • 通过配置Context或Host的failCtxIfServletStartFails属性值,优先使用Context的,设置tomcat启动时加载servlet时,是否忽略抛出的ServletException异常,如果不忽略,则tomcat启动失败,默认不忽略。
    • servlet有多线程模式,和单线程模式,默认是多线程模式,单线程模式需要实现SingleThreadModel接口。单线程模式下,开启一个实例池,同一时间,一个实例只分配给一个线程占用。

    解说源码:

    Tomcat在启动时,就会调用loadOnStartup,传入的参数是一个Container[]的数组,这个数组代表的是从我们的web.xml,web-fragment.xml,注解,以及动态添加等方式配置的关于Servlet等信息的数组。

    ok设置为false,表示Tomcat启动失败,将无法运作。即loadOnStartup的返回值将影响Tomcat是否运作。

    if (!loadOnStartup(findChildren())) {
        ok = false;
    }

     

    进入loadOnStartup方法后,这里首先定义了一个TreeMap,使用TreeMap的主要目的就是可以排序。

    然后遍历Servlet信息集,将遍历中的Servlet信息转化为实际类型Wrapper。

    通过getLoadOnStartup方法得到Servlet的启动序级,过滤掉启动序级小于0的Servlet。

    接下来,就把大于或等于0的启动序级加入到TreeMap,映射的值是一个Wrapper表,即把Servlet分箱装入到所属启动序级的箱子里面。

    TreeMap<Integer, ArrayList<Wrapper>> map = new TreeMap<>();
    for (int i = 0; i < children.length; i++) {
        Wrapper wrapper = (Wrapper) children[i];
        int loadOnStartup = wrapper.getLoadOnStartup();
        if (loadOnStartup < 0)
            continue;
        Integer key = Integer.valueOf(loadOnStartup);
        ArrayList<Wrapper> list = map.get(key);
        if (list == null) {
            list = new ArrayList<>();
            map.put(key, list);
        }
        list.add(wrapper);
    }

     

    实际上Wrapper只是一个接口,真正实现Wrapper方法的是StandardWrapper,而StandardWrapper还实现了servlet规范中的ServletConfig接口。

    public class StandardWrapper extends ContainerBase 
    implements ServletConfig, Wrapper

    但是,最后传递给应用使用的ServletConfig并非StandardWrapper,而是StandardWrapperFacade,当然也是实现ServletConfig接口。

    public final class StandardWrapperFacade
    implements ServletConfig

    正如名字上的意思,使用外观模式再一次包装StandardWrapper,然后调用其的同名方法。

    protected final StandardWrapperFacade facade = new StandardWrapperFacade(this);

    所以servlet的所有配置信息都存放在StandardWrapper,如成员变量loadOnStartup就是配置servlet中的一项,其默认值为-1。

    protected int loadOnStartup = -1;

    getLoadOnStartup方法主要获取成员变量loadOnStartup值,但有特殊情况,成员变量isJspServlet是记录配置的Servlet类是不是org.apache.jasper.servlet.JspServlet,即一个Servlet是不是Jsp Servlet就看它的类是不是这个类。

    简单的说,普通Servlet的启动序级的默认值为-1,最小有效值为0,最大有效值为Integer.MAX_VALUE。Jsp Servlet的启动序级的默认值为最大值,有效值与普通Servlet一致,并且其值失效时将使用最大值。

    public int getLoadOnStartup() {
        if (isJspServlet && loadOnStartup < 0) {
            return Integer.MAX_VALUE;
        } else {
            return (this.loadOnStartup);
        }
    }

     

    回到原来的getLoadOnStartup方法里,map已经把需要启动时加载的Servlet分装到按启动序级排列好的箱子里面,接下来就是先按启动序级顺序,再按加入顺序遍历servlet,并且执行其load方法。

    load方法如果抛出ServletException异常,将通过getComputedFailCtxIfServletStartFails获取的值来决定是否让Tomcat启动失败。

    而获取的值是Context配置的failCtxIfServletStartFails属性,如果该属性未配置,则使用Host配置failCtxIfServletStartFails的属性,其默认值为false。

    for (ArrayList<Wrapper> list : map.values()) {
        for (Wrapper wrapper : list) {
            try {
                wrapper.load();
            } catch (ServletException e) {
                if (getComputedFailCtxIfServletStartFails()) {
                    return false;
                }
            }
        }
    }
    return true;

    进入load方法后,load方法是一个线程安全的方法,为什么需要线程安全?因为,servlet大多数情况下是不进行预加载,什么时候用到,就什么时候加载。换句话说,为了节省从来没用到的servlet资源空间,所以等到有请求访问到servlet时才加载。

    所以这种后加载的方式,就有可能出现两个请求,在同一时间访问到同一个servlet时,这时候就需要线程安全了。那什么时候需要预加载,什么时候不需要预加载,这个问题可以思考到,为什么定义Jsp Servlet时,tomcat是强制性要预加载,这里不再往深叙述了。

    成员变量instance就是配置的servlet类的实例,实例是需要加载与初始化,才能对请求做处理。

    public synchronized void load() throws ServletException {
        instance = loadServlet();
    
        if (!instanceInitialized) {
            initServlet(instance);
        }
    }

    来到loadServlet方法,在加载实例之前,这里要验证servlet实例是一个单例还是多例。如果是单例,并且实例已经存在,那就返回该实例。如果是多例,不管实例存与不存在,都将再创建实例。

    判断是使用单例还是多例,由成员变量singleThreadModel的值来判断,其含意就是如果同一个servlet实例,只允许在同一时间让一个请求(线程)访问,所以叫单线程模式。反之,同一个servlet实例,允许同一时间,让多个请求(线程)访问,就叫多线程模式。

    显然,单线程与多线程的区别在于,servlet的成员变量共享与不共享,设计的重点就得往这里思考。那变量singleThreadModel默认值为false,即servlet模认是多线程模式,其如何改变为单线程模式在后面。

    if (!singleThreadModel && (instance != null))
        return instance;

    接下来获取实例管理器,然后通过servlet类加载并创建servlet,这个过实例化的过程,内容也是挺丰富多彩的,其中包括JNDI注入,POST构造方法调用等,这里就不往里面叙述了。

    InstanceManager instanceManager = ((StandardContext)getParent()).getInstanceManager();
    Servlet servlet = (Servlet) instanceManager.newInstance(servletClass);

    判断servlet是否单线程模式,如果serlvet实现了SingleThreadModel接口,那么成员变量singleThreadModel标记为true,同时创建出实例池。

    if (servlet instanceof SingleThreadModel) {
        if (instancePool == null) {
            instancePool = new Stack<>();
        }
        singleThreadModel = true;
    }

    如果servlet需要得到Wrapper对象实例,那么servlet实现ContainerServlet接口,并且在Context的配置里设置属性privileged为true,默认为false。

    if ((servlet instanceof ContainerServlet) 
        &&(Context) getParent()).getPrivileged()) {
        ((ContainerServlet) servlet).setWrapper(this);
    }

    来到initServlet方法,如果servlet是多线程模式,则实例只执行一次初始化。反之,如果servlet是单线程模式,每次创建的实例,都会执行一次初始化。

    private synchronized void initServlet(Servlet servlet) throws ServletException {
        if (instanceInitialized && !singleThreadModel)
            return;
        servlet.init(facade);
        instanceInitialized = true;
    }
  • 相关阅读:
    深入浅出列生成算法
    小游戏云开发入门
    代码生成器插件与Creator预制体文件解析
    使用四叉树优化碰撞检测
    游戏开发中的人工智能
    一个可屏蔽长短链接的网络模块
    游戏开发中的新手引导与事件管理系统
    Creator填色游戏的一种实现方案
    CocosCreator之AssetBundle使用方案分享
    跨引擎游戏框架说明文档
  • 原文地址:https://www.cnblogs.com/hvicen/p/6010688.html
Copyright © 2011-2022 走看看