前言

Servlet是Web开发中的核心技术,作为一名合格的开发人员,就必须清楚Servlet的工作原理。本章没有对Servlet技术本身进行详细的说明,只是针对开发过程中一次Servlet的请求的处理过程进行分析的。Servlet实际上就是一个java类,只不过可以和浏览器进行一些数据的交换。有Servlet类就有管理Servlet的容器,种类有很多,这里主要针对Tomcat对Servlet的工作原理进行说明。为了说清楚Servlet工作原理,需要知道Servlet的工作过程大致可以分为以下几个阶段:

1. 启动Tomcat容器
2. Web应用初始化
3. 创建Servlet实例
4. 初始化Servlet
5. 执行Servlet的service方法

Tomcat的启动过程

这部分可以用下图进行简化:

Tomcat容器启动序列图

Web应用初始化

下面分析Web应用的初始化,初始化工作是由ContextConfig类的configureStart方法完成的,该方法的主要任务是完成web.xml配置文件的解析。下面解析的关键代码:

代码清单5-1:

    Set<WebXml> defaults = new HashSet<WebXml>();
        defaults.add(getDefaultWebXmlFragment());
        WebXml webXml = createWebXml();
        // Parse context level web.xml
        InputSource contextWebXml = getContextWebXmlSource();
        parseWebXml(contextWebXml, webXml, false);
        ServletContext sContext = context.getServletContext();
        // Ordering is important here
        // Step 1. Identify all the JARs packaged with the application
        // If the JARs have a web-fragment.xml it will be parsed at this
        // point.
        Map<String,WebXml> fragments = processJarsForWebFragments(webXml);
        // Step 2. Order the fragments.
        Set<WebXml> orderedFragments = null;
        orderedFragments =
                WebXml.orderWebFragments(webXml, fragments, sContext);
        // Step 3. Look for ServletContainerInitializer implementations
        if (ok) {
            processServletContainerInitializers(context.getServletContext());
        }
        if  (!webXml.isMetadataComplete() || typeInitializerMap.size() > 0) {
            // Step 4. Process /WEB-INF/classes for annotations
            if (ok) {
                // Hack required by Eclipse's "serve modules without
                // publishing" feature since this backs WEB-INF/classes by
                // multiple locations rather than one.
                NamingEnumeration<Binding> listBindings = null;
                try {
                    try {
                        listBindings = context.getResources().listBindings(
                                "/WEB-INF/classes");
                    } catch (NameNotFoundException ignore) {
                        // Safe to ignore
                    }
                    while (listBindings != null &&
                            listBindings.hasMoreElements()) {
                        Binding binding = listBindings.nextElement();
                        if (binding.getObject() instanceof FileDirContext) {
                            File webInfClassDir = new File(
                                    ((FileDirContext) binding.getObject()).getDocBase());
                            processAnnotationsFile(webInfClassDir, webXml,
                                    webXml.isMetadataComplete());
                        } else {
                            String resource =
                                    "/WEB-INF/classes/" + binding.getName();
                            try {
                                URL url = sContext.getResource(resource);
                                processAnnotationsUrl(url, webXml,
                                        webXml.isMetadataComplete());
                            } catch (MalformedURLException e) {
                                log.error(sm.getString(
                                        "contextConfig.webinfClassesUrl",
                                        resource), e);
                            }
                        }
                    }
                } catch (NamingException e) {
                    log.error(sm.getString(
                            "contextConfig.webinfClassesUrl",
                            "/WEB-INF/classes"), e);
                }
            }
            // Step 5. Process JARs for annotations - only need to process
            // those fragments we are going to use
            if (ok) {
                processAnnotations(
                        orderedFragments, webXml.isMetadataComplete());
            }
            // Cache, if used, is no longer required so clear it
            javaClassCache.clear();
        }
        if (!webXml.isMetadataComplete()) {
            // Step 6. Merge web-fragment.xml files into the main web.xml
            // file.
            if (ok) {
                ok = webXml.merge(orderedFragments);
            }
            // Step 7. Apply global defaults
            // Have to merge defaults before JSP conversion since defaults
            // provide JSP servlet definition.
            webXml.merge(defaults);
            // Step 8. Convert explicitly mentioned jsps to servlets
            if (ok) {
                convertJsps(webXml);
            }
            // Step 9. Apply merged web.xml to Context
            if (ok) {
                webXml.configureContext(context);
            }
        } else {
            webXml.merge(defaults);
            convertJsps(webXml);
            webXml.configureContext(context);
        }
        // Step 9a. Make the merged web.xml available to other
        // components, specifically Jasper, to save those components
        // from having to re-generate it.
        // TODO Use a ServletContainerInitializer for Jasper
        String mergedWebXml = webXml.toXml();
        sContext.setAttribute(
               org.apache.tomcat.util.scan.Constants.MERGED_WEB_XML,
               mergedWebXml);
        if (context.getLogEffectiveWebXml()) {
            log.info("web.xml:\n" + mergedWebXml);
        }
        // Always need to look for static resources
        // Step 10. Look for static resources packaged in JARs
        if (ok) {
            // Spec does not define an order.
            // Use ordered JARs followed by remaining JARs
            Set<WebXml> resourceJars = new LinkedHashSet<WebXml>();
            if (orderedFragments != null) {
                for (WebXml fragment : orderedFragments) {
                    resourceJars.add(fragment);
                }
            }
            for (WebXml fragment : fragments.values()) {
                if (!resourceJars.contains(fragment)) {
                    resourceJars.add(fragment);
                }
            }
            processResourceJARs(resourceJars);
            // See also StandardContext.resourcesStart() for
            // WEB-INF/classes/META-INF/resources configuration
        }
        // Step 11. Apply the ServletContainerInitializer config to the
        // context
        if (ok) {
            for (Map.Entry<ServletContainerInitializer,
                    Set<Class<?>>> entry :
                        initializerClassMap.entrySet()) {
                if (entry.getValue().isEmpty()) {
                    context.addServletContainerInitializer(
                            entry.getKey(), null);
                } else {
                    context.addServletContainerInitializer(
                            entry.getKey(), entry.getValue());
                }
            }
        }

在代码已经清晰地说明了Web应用的初始化过程,由于在Tomcat7中增加了对注解(annotation)的支持,所以会对Servlet中的注解进行解析。首先查找jar包中的web-fragment.xml,并对其进行解析,接下来将对/WEB-INF/classes目录下的class进行注解的解析。之后,把web-fragment.xml文件合并到web.xml中,被解析后的web.xml文件的配置项将保存到WebXml对象中,然后把WebXml对象中的属性设置到Context容器中,这个过程是由configureContext方法来完成的。下面是其部分源码——完成Servlet的解析:

代码清单5-2:

    for (ServletDef servlet : servlets.values()) {
            Wrapper wrapper = context.createWrapper();
            // Description is ignored
            // Display name is ignored
            // Icons are ignored
            // jsp-file gets passed to the JSP Servlet as an init-param
            if (servlet.getLoadOnStartup() != null) {
                wrapper.setLoadOnStartup(servlet.getLoadOnStartup().intValue());
            }
            if (servlet.getEnabled() != null) {
                wrapper.setEnabled(servlet.getEnabled().booleanValue());
            }
            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());
            }
            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());
            context.addChild(wrapper);
        }

这段代码说明了把Servlet包装成StandardWrapper的过程,并把这个Wrapper实例设置到Context容器中。之所以要把Servlet封装成一个Wrapper,主要为了解耦,因为Wrapper是一个容器而Servlet是一个具体的类。

到目前为止,已经完成了web应用的初始化,其中将web.xml进行了解析并完成了注解的解析,还把配置的Servlet类封装成了一个Wrapper容器,接下来就是根据这个Wrapper创建Servlet实例了。

创建Servlet实例
创建Servlet实例要回到我们分析Wrapper容器中提到的loadServlet方法了,这个方法是由Wrapper容器的标准实现类StandardWrapper完成的,通过loadServlet方法获取servletClass,然后把这个servletClass交给实例管理器(InstanceManager)完成servletClass.class的对象创建。

初始化Servlet
初始化的工作是由StandardWrapper的initServlet方法完成的,这个方法主要就是调用Servlet的init方法,然后把包装了StandardWrapper的StandardWrapperFacade交给Servlet。下面是这个方法的源码:

代码清单5-3:

    private synchronized void initServlet(Servlet servlet)
            throws ServletException {

        if (instanceInitialized && !singleThreadModel) return;
        // Call the initialization method of this servlet
        try {
            instanceSupport.fireInstanceEvent(InstanceEvent.BEFORE_INIT_EVENT,
                                              servlet);
            if( Globals.IS_SECURITY_ENABLED) {
                boolean success = false;
                try {
                    Object[] args = new Object[] { facade };
                    SecurityUtil.doAsPrivilege("init",
                                               servlet,
                                               classType,
                                               args);
                    success = true;
                } finally {
                    if (!success) {
                        // destroy() will not be called, thus clear the reference now
                        SecurityUtil.remove(servlet);
                    }
                }
            } else {
                servlet.init(facade);
            }
            instanceInitialized = true;
            instanceSupport.fireInstanceEvent(InstanceEvent.AFTER_INIT_EVENT,
                                              servlet);
        } catch (UnavailableException f) {
            instanceSupport.fireInstanceEvent(InstanceEvent.AFTER_INIT_EVENT,
                                              servlet, f);
            unavailable(f);
            throw f;
        } catch (ServletException f) {
            instanceSupport.fireInstanceEvent(InstanceEvent.AFTER_INIT_EVENT,
                                              servlet, f);
            // If the servlet wanted to be unavailable it would have
            // said so, so do not call unavailable(null).
            throw f;
        } catch (Throwable f) {
            ExceptionUtils.handleThrowable(f);
            getServletContext().log("StandardWrapper.Throwable", f );
            instanceSupport.fireInstanceEvent(InstanceEvent.AFTER_INIT_EVENT,
                                              servlet, f);
            // If the servlet wanted to be unavailable it would have
            // said so, so do not call unavailable(null).
            throw new ServletException
                (sm.getString("standardWrapper.initException", getName()), f);
        }
    }

这样就完成了Servlet的初始化,下面就是执行Servlet的service方法了。

执行service方法

在分析Wrapper方法中提到,StandardWrapper会调用allocate方法从实例池栈中弹出一个Servlet处理请求,这个Servlet实例在完成初始化后,并经过一系列过滤器的过滤后就到达Servlet实例的service方法,这个过程就是过滤器执行的过程。这样Servlet实例就顺利调用到了service方法,之后发生的过程就是我们熟悉的request获取参数并用response进行响应的过程了。

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐