1. 程式人生 > >深入理解Tomcat系列之六 Servlet工作原理

深入理解Tomcat系列之六 Servlet工作原理

                     

前言

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());                }            }        }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140

在程式碼已經清晰地說明了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);        }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47

這段程式碼說明了把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);        }    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51

這樣就完成了Servlet的初始化,下面就是執行Servlet的service方法了。

執行service方法

在分析Wrapper方法中提到,StandardWrapper會呼叫allocate方法從例項池棧中彈出一個Servlet處理請求,這個Servlet例項在完成初始化後,並經過一系列過濾器的過濾後就到達Servlet例項的service方法,這個過程就是過濾器執行的過程。這樣Servlet例項就順利呼叫到了service方法,之後發生的過程就是我們熟悉的request獲取引數並用response進行響應的過程了。