美文网首页Tomcat原理解析
Servlet工作原理解析(一)Servlet容器

Servlet工作原理解析(一)Servlet容器

作者: 黄金矿工00七 | 来源:发表于2018-08-17 18:27 被阅读0次

    请结合这篇文章阅读Tomcat生命周期

    Servlet是Java Web技术的核心基础,简单来说,处理请求和发送响应的过程是由一种叫做Servlet的程序来完成的,并且Servlet是为了解决实现动态页面而衍生的东西。

    Servlet容器

    要介绍Servlet,必须要先了解Servlet容器,他们是 相互依存的,但是又是独立发展的,从技术来说是为了解耦合,通过标准的接口(Servlet-API)来相互协作,在javax.servlet包的文档中是这样描述的:

    The javax.servlet package contains a number of classes and interfaces that describe and define the contracts between a servlet class and the runtime environment provided for an instance of such a class by a conforming servlet container.

    我以Tomcat为例来介绍Servlet容器的,Tomcat的容器体系如下:

    Tomcat容器
    真正管理Servlet的容器是Context,一个Context对应一个Web工程,也就是webapps下的一个项目,Context中的Wrapper也就是包装后的servlet。
    Servlet容器的启动过程

    Tomcat 7开始支持嵌入式功能,增加了一个启动类,我们可以很容易的通过一个实例对象来日东Tomcat(Spring Boot),还可以通过这个对象来配置Tomcat的参数,我给出一段示例代码:

        private void startTomcat(int port, String contextPath, String baseDir)
                throws ServletException, LifecycleException {
            tomcat = new Tomcat();
            //  端口号
            tomcat.setPort(port);
            //项目物理路径
            tomcat.setBaseDir(".");
            StandardServer server = (StandardServer) tomcat.getServer();
            //容器生命周期监听
            AprLifecycleListener listener = new AprLifecycleListener();
            server.addLifecycleListener(listener);
            //添加web应用
            tomcat.addWebapp(contextPath, baseDir);
            tomcat.start();
        }
    

    前面说过一个web应用对应一个Context容器,当添加一个web应用的时候会创建一个StandardContext容器,并且做相关配置,其中最重要的是ContextConfig(实际上是一个监听器),这个类将负责整个web应用的解析工作,当context容器的状态被更改时,ContextConfig会被通知,做相关解析工作,最后将这个容器加到父容器Host中。
    我们来看一下addWebApp的代码:

     public Context addWebapp(Host host, String contextPath, String docBase,
                LifecycleListener config) {
            //设置日志级别
            silence(host, contextPath);
            //创建一个Context容器
            Context ctx = createContext(host, contextPath);
            //做相关配置
            ctx.setPath(contextPath);
            ctx.setDocBase(docBase);
            ctx.addLifecycleListener(getDefaultWebXmlListener());
            ctx.setConfigFile(getWebappConfigFile(docBase, contextPath));
            //添加生命周期监听
            ctx.addLifecycleListener(config);
    
            if (config instanceof ContextConfig) {
                // prevent it from looking ( if it finds one - it'll have dup error )
                ((ContextConfig) config).setDefaultWebXml(noDefaultWebXmlPath());
            }
            //将context容器添加到Host容器中
            if (host == null) {
                getHost().addChild(ctx);
            } else {
                host.addChild(ctx);
            }
    
            return ctx;
        }
    

    接着,我们看一下Context容器状态变化时,ContextConfig作出的相应工作。

    public void lifecycleEvent(LifecycleEvent event) {
    
        // Identify the context we are associated with
        try {
        //  从事件对象中获取context对象
        context = (Context) event.getLifecycle();
        } catch (ClassCastException e) {
        log.error(sm.getString("contextConfig.cce", event.getLifecycle()), e);
        return;
        }
        //  处理发生的事件
        // Process the event that has occurred
        if (event.getType().equals(Lifecycle.CONFIGURE_START_EVENT)) {
        //  Web应用的初始化工作
        configureStart();
        } else if (event.getType().equals(Lifecycle.BEFORE_START_EVENT)) {
        beforeStart();
        } else if (event.getType().equals(Lifecycle.AFTER_START_EVENT)) {
        // Restore docBase for management tools
        if (originalDocBase != null) {
        context.setDocBase(originalDocBase);
        }
        } else if (event.getType().equals(Lifecycle.CONFIGURE_STOP_EVENT)) {
        configureStop();
        } else if (event.getType().equals(Lifecycle.AFTER_INIT_EVENT)) {
        //  主要解析context.xml(例如springMVC的配置文件)、Host配置文件、Context自身配置文件、DocBase
        init();
        } else if (event.getType().equals(Lifecycle.AFTER_DESTROY_EVENT)) {
        destroy();
        }
    
        }
    
    Web应用的初始化工作

    Web应用的初始化工作主要是在上面代码中的 configureStart();中进行的,当Context容器的状态变为Lifecycle.CONFIGURE_START_EVENT),会调用ContextConfig的configureStart()进行Web应用的初始化工作(主要是对web.xml的解析工作,这里的web.xml包括全局、host、应用以及fragment,原本一个web应用的任何配置都需要在web.xml中进行,因此会使得web.xml变得很混乱,而且灵活性差,因此Servlet 3.0可以将每个Servlet、Filter、Listener打成jar包,然后放在WEB-INF\lib中;注意各自的模块都有各自的配置文件,这个配置文件的名称为 web-fragment.xml)。

        protected synchronized void configureStart() {
            // Called from StandardContext.start()
            //该方法会在Context启动时被调用
            if (log.isDebugEnabled()) {
                log.debug(sm.getString("contextConfig.start"));
            }
    
            if (log.isDebugEnabled()) {
                log.debug(sm.getString("contextConfig.xmlSettings",
                        context.getName(),
                        Boolean.valueOf(context.getXmlValidation()),
                        Boolean.valueOf(context.getXmlNamespaceAware())));
            }
            //解析XXweb.xml
            webConfig();
            //解析注解
            if (!context.getIgnoreAnnotations()) {
                applicationAnnotationsConfig();
            }
            //验证角色
            if (ok) {
                validateSecurityRoles();
            }
             //配置认证器
            // Configure an authenticator if we need one
            if (ok) {
                authenticatorConfig();
            }
    
            // Dump the contents of this pipeline if requested
            if (log.isDebugEnabled()) {
                log.debug("Pipeline Configuration:");
                Pipeline pipeline = context.getPipeline();
                Valve valves[] = null;
                if (pipeline != null) {
                    valves = pipeline.getValves();
                }
                if (valves != null) {
                    for (int i = 0; i < valves.length; i++) {
                        log.debug("  " + valves[i].getClass().getName());
                    }
                }
                log.debug("======================");
            }
    
            // Make our application available if no problems were encountered
            if (ok) {
                context.setConfigured(true);
            } else {
                log.error(sm.getString("contextConfig.unavailable"));
                context.setConfigured(false);
            }
    
        }
    

    在这里我们主要来看webConfig()中的代码:

     /**
         * Scan the web.xml files that apply to the web application and merge them
         * using the rules defined in the spec. For the global web.xml files,
         * where there is duplicate configuration, the most specific level wins. ie
         * an application's web.xml takes precedence over the host level or global
         * web.xml file.
         *扫描web.xml(包括globalWebXml,hostWebXml以及应用中配置的webXml,如果存在重复,则以具体级别的为准)
         */
    

    这里我不贴代码,给出一个具体步骤:
    Parse global level web.xml(解析默认全局配置web.xml)
    Parse context level web.xml(解析应用中配置的web.xml)
    Step 1. Identify all the JARs packaged with the application and those provided by the container. If any of the application JARs have a web-fragment.xml it will be parsed at this point. web-fragment.xml files are ignored for container provided JARs.(识别应用程序以及容器中所有的jar包,解析web-fragment.xml,提供该jar包的容器将忽略这个xml文件,对于容器来说应用级别的web.xml才是真正的配置文件)
    Step 2. Order the fragments.(对这些fragments排序,Servlet、Filter、Listener等,并且保存这些fragments
    Step 3. Look for ServletContainerInitializer implementations(查找servlet容器的初始化器)
    Step 4. Process /WEB-INF/classes for annotations and @HandlesTypes matches(Skip the META-INF directory)(处理/WEB-INF/classes下的@HandlesTypes 注解的匹配类,跳过了META-INF 目录)
    Step 5. Process JARs for annotations and @HandlesTypes matches - only need to process those fragments we are going to use (处理jar包中@HandlesTypes 注解的匹配类,只处理我们需要使用的fragment所在的jar包)
    Step 6. Merge web-fragment.xml files into the main web.xml file.(合并web-fragment.xml到应用中配置的web.xml中)
    Step 7. Apply global defaults Have to merge defaults before JSP conversion since defaults provide JSP servlet definition.(应用默认全局配置,因为默认全局配置中提供了JSP servlet的定义)
    Step 8. Convert explicitly mentioned jsps to servlets(转换显示声明的jsp到servlet)
    Step 9. Apply merged web.xml to Context(应用合并后的web.xml到容器)
    Step 10. Look for static resources packaged in JARs(寻找jar包中的静态资源)
    Step 11. Apply the ServletContainerInitializer config to the context(配置容器中的servlet初始化器)

    Step5,6,11主要是处理Servlet3.0所支持的动态Servlet,这里我们重点关注Step 9,Step 11.
    先说一下Step9 ,这一步的代码主要在configureContext(WebXml webxml)中,
    由于代码太长就不放了,简单说一下,主要是创建Servlet对象、Filter、Listener等,处理工作是按照字母顺序来做的,主要是为了防止遗漏

    • 设置context容器的PublicID、版本号、ContextParams等参数
    • 设置context的environment
    • 设置errorPage
    • 设置filter是否为异步
    • 设置filter Mapping
    • 设置jsp配置描述符
    • 设置lister
    • 设置LocaleEncodingMappings
    • 设置登录配置
    • 设置安全角色SecurityRoles
    • 设置service
    • 设置servlet
    • 设置servlet Mapping
    • 设置session(cookie)配置
    • 设置欢迎页
    • 设置jsp的属性组(放在最后是因为依赖servlets)
      好吧,这么多够烦人的,我们来看一下servlet的创建过程。这个过程呢,是将Servlet包装成StandardWrapper的一个过程,实际上Context容器中运行的是StandardWrapper对象而不是Servlet对象为啥这么做?
      开局我们说过,Servlet容器和Servlet是相互独立的,StandardWrapper是Tomcat的一部分,它具有容器的特征,而Servlet作为一个独立的web开发标准,不应该强耦合在Tomcat中。所以将Servlet包装成StandardWrapper,作为context容器的子容器添加到context容器中,由StandardWrapper负责Servlet的管理工作。有兴趣的伙伴可以看一下响应的源码。

    相关文章

      网友评论

        本文标题:Servlet工作原理解析(一)Servlet容器

        本文链接:https://www.haomeiwen.com/subject/vqftiftx.html