Tomcat源码剖析(五)Tomcat如何管理Servlet生命周期

【问题】

Servlet规范中规定Servlet生命周期只调用一次init()distory()方法,每次请求来只调用service()方法,那么Tomcat是如何管理Servlet生命周期的?

  1. 高并发请求中,如果保证只初始化一个Servlet
  2. 如和实现loadOnStartup参数的功能?

【猜想】

按照Servlet的规范,一定需要缓存Servlet对象,如果是我来做的话,应该会使用Map缓存,每次请求判断一下Servlet是否在Map中,若不存在,就load() servlet然后存进Map

在高并发的情况下,肯定得用线程安全的ConcurrentHashMap,

对于loadOnStartup参数,可以使用TreeSet排序初始化

【Tomcat】

Tomcat的实现并不是如此,在Tomcat中,每个Servlet都有一个对应的StandardWrapper,而在前面我们分析过在Tomcat启动的时候,会调用Server,Connector,以及各个容器的strat()方法,其中在Contextstart()方法中,就会首先加载所有的StandardWrapper,这个StandardWrapper中包含了Servlet对象,只是当在调用对应的Servlet方法后,StandardWrapper会检查Servlet是否被加载过,如果没有加载,则加载,如果加载过了,则直接使用。

前一篇关于分析[Tomcat如何解析Url找到对应的Servlet](<https://dengchengchao.com/?p=1065>)的文章中说过,Serlvet的匹配其实算是一个树形结构,每个Host会缓存多个Context,每个Context会缓存多个StandardWrapper,因此,StandardWrapper在系统启动的时候就已经被加载了,只是Servlet是通过LoadOnStartup`参数配置的。

对于Servlet只初始化一个的问题,Tomcat使用的是典型的单例类中的双重检验完成的。

对于loadOnStartupTomcat则使用的是TreeMap实现,这一点我确实没相当,刚开始我说TreeSet,是因为我直接将Servelt重写Comparable接口,使用loadOnStartup属性作为对比的对象,但是这样实现远没有TreeMap中将loadOnStartup作为key简洁。

整体说下流程:

  • 加载

Context在启动的时候,并不会自己去寻找Child,也就是StandardWrapper,而是在初始化之前,有一个ContextConfig会监听StandardContext的所有时间,当Context触发了Start事件的时候,ContextConfig便会遍历CATLINA_HOME下的所有目录,去读取所有的war以及各个文件夹,对于war包,会先解压War包,然后将信息生成StandardWrapper加入到StandardContext中。

那么loadOnStartup这个参数是怎么实现的呢?

当加入到Child之后,StardandContextstart()方法会遍历所有的Child,当发现loadOnStartup大于0之后,会获取loadOnStartUp配置文件的值,然后将它的值作为key加入到TreeMap中,后续再遍历这个map依次loadServlet即可.

  • 使用

Servlet使用就非常简单了,当一个请求到来了,ServletMapper会通过规则查找到对应的SerlvetWarpper,而StandardWrapper便会在真正调用Servlet的时候判断Serlvet是否已经加载过,如果没有,则先加载。

  • 卸载

Serlvet卸载就比较简单了,在系统关闭的时候,会触发StandardWrapper,StandardWrapper会调用Stop()方法,从而调用SerlvetWarpperunload()方法,而unload()方法中就会调用Servletdestory()方法

【代码】

这里代码比较散,而且还包含其他业务,因此便不全部贴出来.

  • 加载1
ContextConfig##configureContext()
//读取webxml中servlet节点配置 
for (ServletDef servlet : webxml.getServlets().values()) {
            Wrapper wrapper = context.createWrapper();
            //设置LoadOnStartup 属性
            if (servlet.getLoadOnStartup() != null) {
                wrapper.setLoadOnStartup(servlet.getLoadOnStartup().intValue());
            }
            //设置Servlet是否有效
            if (servlet.getEnabled() != null) {
                wrapper.setEnabled(servlet.getEnabled().booleanValue());
            }
            // 设置name
            wrapper.setName(servlet.getServletName());
            // 设置初始化参数
            Map<String,String> params = servlet.getParameterMap();
            for (Entry<String, String> entry : params.entrySet()) {
                wrapper.addInitParameter(entry.getKey(), entry.getValue());
            }
            wrapper.setRunAs(servlet.getRunAs());
            Set<SecurityRoleRef> roleRefs = servlet.getSecurityRoleRefs();
            for (SecurityRoleRef roleRef : roleRefs) {
                wrapper.addSecurityReference(
                        roleRef.getName(), roleRef.getLink());
            }
            //设置servlet映射的类
            wrapper.setServletClass(servlet.getServletClass());
            MultipartDef multipartdef = servlet.getMultipartDef();
            if (multipartdef != null) {
                if (multipartdef.getMaxFileSize() != null &&
                        multipartdef.getMaxRequestSize()!= null &&
                        multipartdef.getFileSizeThreshold() != null) {
                    wrapper.setMultipartConfigElement(new MultipartConfigElement(
                            multipartdef.getLocation(),
                            Long.parseLong(multipartdef.getMaxFileSize()),
                            Long.parseLong(multipartdef.getMaxRequestSize()),
                            Integer.parseInt(
                                    multipartdef.getFileSizeThreshold())));
                } else {
                    wrapper.setMultipartConfigElement(new MultipartConfigElement(
                            multipartdef.getLocation()));
                }
            }
            if (servlet.getAsyncSupported() != null) {
                wrapper.setAsyncSupported(
                        servlet.getAsyncSupported().booleanValue());
            }
            wrapper.setOverridable(servlet.isOverridable());
            //将配置好的servlet对象作为Child加入到context中
            context.addChild(wrapper);
        }
  • 加载2
StandardContext##startInternal()
// Load and initialize all "load on startup" servlets
if (ok) {
    if (!loadOnStartup(findChildren())){
        log.error(sm.getString("standardContext.servletFail"));
        ok = false;
    }
}
 */
public boolean loadOnStartup(Container children[]) {

    // Collect "load on startup" servlets that need to be initialized
    TreeMap<Integer, ArrayList<Wrapper>> map = new TreeMap<>();
     //遍历所有子对象
    for (int i = 0; i < children.length; i++) {
        Wrapper wrapper = (Wrapper) children[i];
        int loadOnStartup = wrapper.getLoadOnStartup();
        //如果loadOnStartup值小于0,则跳过
        if (loadOnStartup < 0)
            continue;
        //否则将它对应的值作为key加入到TreeMap中,这样值越小,就越会先遍历到
        //也就是loadOnStartup值越小,就越先加载
        Integer key = Integer.valueOf(loadOnStartup);
        ArrayList<Wrapper> list = map.get(key);
        if (list == null) {
            list = new ArrayList<>();
            map.put(key, list);
        }

        list.add(wrapper);
    }

    //加载servlet
    for (ArrayList<Wrapper> list : map.values()) {
        for (Wrapper wrapper : list) {
            try {
                wrapper.load();
            } catch (ServletException e) {
                getLogger().error(sm.getString("standardContext.loadOnStartup.loadException",
                      getName(), wrapper.getName()), StandardWrapper.getRootCause(e));
                // NOTE: load errors (including a servlet that throws
                // UnavailableException from the init() method) are NOT
                // fatal to application startup
                // unless failCtxIfServletStartFails="true" is specified
                if(getComputedFailCtxIfServletStartFails()) {
                    return false;
                }
            }
        }
    }
    return true;

}
  • 使用

这里使用就是典型的双重检验实现单例了

 if (instance == null || !instanceInitialized) {
                synchronized (this) {
                    if (instance == null) {
                        try {
                            instance = loadServlet();
                            newInstance = true;
                    }
                    if (!instanceInitialized) {
                        initServlet(instance);
                    }
                }
            }

这里再复习一下双重校验单例:

  1. instance必须为volatile,为了防止JVM优化而改变代码顺序

    因为new不是一个原子操作,new一个对象分为以下三步:

  • 创建对象,分配内存
  • 执行构造方法初始化内存
  • instance指向分配的内存地址,(此时Instance变不为空了)

    而上面的执行顺序由于JVM的指令重排的优化,可能改为了1->3->2,在单线程看来是没有毛病的,但是多线程中很可能第二个线程在判断是否为null的时候,instance仅仅被分配了内存,却没有执行构造方法,这个时候第二个线程拿到的可能就是一个不完整的instance

  • 销毁

销毁就没什么好说了了

StandardWrapper##stop()
@Override
protected synchronized void stopInternal() throws LifecycleException {
      unload();
}

@Override
public synchronized void unload() throws ServletException {
      if (instanceInitialized) {
        instance.destroy();
      }
}