Tomcat war包加载过程

简介:

开篇

 这篇文章试图对war包的解析过程进行更加深刻的分析,不过似乎这篇文章分析的也不是特别的清楚,不过这篇文章至少说清楚了add和start两个过程,也算欣慰。

 用一句话来概括war包的解析过程,就是父容器添加子容器并在子容器的启动过程中触发子容器递归加载子子容器。

 如StandardHost加载StandardContext,在启动StandardContext的过程中加载StandardWrapper,因为StandardWrapper是最后一个容器直接执行启动。


图解war包加载过程

war包解析过程

war包解析过程

说明:上图说明war包的启动过程,Engine->Host->Context->Wrapper的流程时刻谨记。

  • 1、StandardHost的addChild添加StandardContext对象。
  • 2、StandardHost的start启动StandardContext对象。
  • 3、StandardContext的addChild添加StandardWrapper对象。
  • 4、StandardContext的start启动StandardWrapper对象。



解析过程

解析过程

说明:上图是来自idea的调用栈

  • 1、StandardHost的addChild方法添加StandardContext。
  • 2、StandardContext的addChild方法添加StandardWrapper。


源码解析

1、DeployWar启动部署

  • config.deployWAR(cn, war);
public class HostConfig implements LifecycleListener {

    private static class DeployWar implements Runnable {

        private HostConfig config;
        private ContextName cn;
        private File war;

        public DeployWar(HostConfig config, ContextName cn, File war) {
            this.config = config;
            this.cn = cn;
            this.war = war;
        }

        @Override
        public void run() {
            // 开始部署war包
            config.deployWAR(cn, war);
        }
    }
}

2、StandardHost添加StandardContext

  • host.addChild(context);
public class HostConfig implements LifecycleListener {

    protected String contextClass = "org.apache.catalina.core.StandardContext";

    protected void deployWAR(ContextName cn, File war) {

        context = (Context) Class.forName(contextClass).getConstructor().newInstance();

        try {
            Class<?> clazz = Class.forName(host.getConfigClass());
            LifecycleListener listener = (LifecycleListener) clazz.getConstructor().newInstance();
            context.addLifecycleListener(listener);

            context.setName(cn.getName());
            context.setPath(cn.getPath());
            context.setWebappVersion(cn.getVersion());
            context.setDocBase(cn.getBaseName() + ".war");

            // 添加StandardContext对象
            host.addChild(context);
        } catch (Throwable t) {
        } 
    }
}

3、StandardHost添加StandardContext对象

  • super.addChild(child);
public class StandardHost extends ContainerBase implements Host {

    public void addChild(Container child) {
        child.addLifecycleListener(new MemoryLeakTrackingListener());

        // 调用父类进行添加StandardContext对象
        super.addChild(child);
    }
}

4、StandardHost启动StandardContext对象

  • addChildInternal(child);
  • child.start();
public abstract class ContainerBase extends LifecycleMBeanBase
        implements Container {

    public void addChild(Container child) {
        if (Globals.IS_SECURITY_ENABLED) {
            PrivilegedAction<Void> dp =
                new PrivilegedAddChild(child);
            AccessController.doPrivileged(dp);
        } else {

            // 添加StandardContext对象
            addChildInternal(child);
        }
    }


    private void addChildInternal(Container child) {

        synchronized(children) {
            child.setParent(this);
            children.put(child.getName(), child);
        }

        try {
            if ((getState().isAvailable() ||
                    LifecycleState.STARTING_PREP.equals(getState())) &&
                    startChildren) {
                // StandardContext启动
                child.start();
            }
        } catch (LifecycleException e) {
            // 省略代码
        } finally {
            fireContainerEvent(ADD_CHILD_EVENT, child);
        }
    }
}

5、StandardContext基类LifecycleBase启动

  • startInternal();
public abstract class LifecycleBase implements Lifecycle {

    public final synchronized void start() throws LifecycleException {

        if (LifecycleState.STARTING_PREP.equals(state) ||
                 LifecycleState.STARTING.equals(state) ||
                LifecycleState.STARTED.equals(state)) {
            return;
        }

        if (state.equals(LifecycleState.NEW)) {
            init();
        } else if (state.equals(LifecycleState.FAILED)) {
            stop();
        } else if (!state.equals(LifecycleState.INITIALIZED) &&
                !state.equals(LifecycleState.STOPPED)) {
            invalidTransition(Lifecycle.BEFORE_START_EVENT);
        }

        try {
            setStateInternal(LifecycleState.STARTING_PREP, null, false);
            // 执行StandardContext启动
            startInternal();
            if (state.equals(LifecycleState.FAILED)) {
                stop();
            } else if (!state.equals(LifecycleState.STARTING)) {
                invalidTransition(Lifecycle.AFTER_START_EVENT);
            } else {
                setStateInternal(LifecycleState.STARTED, null, false);
            }
        } catch (Throwable t) {
        }
    }
}

6、StandardContext启动

  • fireLifecycleEvent(Lifecycle.CONFIGURE_START_EVENT, null)
public class StandardContext extends ContainerBase
        implements Context, NotificationEmitter {

    protected synchronized void startInternal() throws LifecycleException {

        boolean ok = true;

        if (namingResources != null) {
            namingResources.start();
        }

        postWorkDirectory();

        if (getResources() == null) {   // (1) Required by Loader
            try {
                setResources(new StandardRoot(this));
            } catch (IllegalArgumentException e) {
            }
        }
        if (ok) {
            resourcesStart();
        }

        if (getLoader() == null) {
            WebappLoader webappLoader = new WebappLoader(getParentClassLoader());
            webappLoader.setDelegate(getDelegate());
            setLoader(webappLoader);
        }

        if (cookieProcessor == null) {
            cookieProcessor = new Rfc6265CookieProcessor();
        }

        getCharsetMapper();

        try {
            if (ok) {
                // 通过HostConfig启动war包
                fireLifecycleEvent(Lifecycle.CONFIGURE_START_EVENT, null);
               
               // 启动StandardWrapper
                for (Container child : findChildren()) {
                    if (!child.getState().isAvailable()) {
                        child.start();
                    }
                }

                // Start the Valves in our pipeline (including the basic),
                if (pipeline instanceof Lifecycle) {
                    ((Lifecycle) pipeline).start();
                }
           }

        if (!ok) {
            setState(LifecycleState.FAILED);
        } else {
            setState(LifecycleState.STARTING);
        }
    }
}

7、StandardContext执行事件

  • listener.lifecycleEvent(event);
public abstract class LifecycleBase implements Lifecycle {

    protected void fireLifecycleEvent(String type, Object data) {
        LifecycleEvent event = new LifecycleEvent(this, type, data);
        for (LifecycleListener listener : lifecycleListeners) {

            // ContextConfig对象
            listener.lifecycleEvent(event);
        }
    }
}

8、ContextConfig执行事件

  • war包解压缩: beforeStart();
  • 解析web.xml:configureStart();
public class ContextConfig implements LifecycleListener {

    public void lifecycleEvent(LifecycleEvent event) {
        try {
            context = (Context) event.getLifecycle();
        } catch (ClassCastException e) {
            return;
        }

        if (event.getType().equals(Lifecycle.CONFIGURE_START_EVENT)) {
            configureStart();

        } else if (event.getType().equals(Lifecycle.BEFORE_START_EVENT)) {
            beforeStart();

        } else if (event.getType().equals(Lifecycle.AFTER_START_EVENT)) {
            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)) {
            init();

        } else if (event.getType().equals(Lifecycle.AFTER_DESTROY_EVENT)) {
            destroy();
        }
    }
}

9、解析war包

  • fixDocBase();
public class ContextConfig implements LifecycleListener {

    protected synchronized void beforeStart() {
        try {
            fixDocBase();
        } catch (IOException e) {
        }
    }


    protected void fixDocBase() throws IOException {

        Host host = (Host) context.getParent();
        File appBase = host.getAppBaseFile();

        String docBase = context.getDocBase();
        if (docBase == null) {
            // Trying to guess the docBase according to the path
            String path = context.getPath();
            if (path == null) {
                return;
            }
            ContextName cn = new ContextName(path, context.getWebappVersion());
            docBase = cn.getBaseName();
        }

        File file = new File(docBase);
        if (!file.isAbsolute()) {
            docBase = (new File(appBase, docBase)).getPath();
        } else {
            docBase = file.getCanonicalPath();
        }
        file = new File(docBase);
        String origDocBase = docBase;

        ContextName cn = new ContextName(context.getPath(), context.getWebappVersion());
        String pathName = cn.getBaseName();

        boolean unpackWARs = true;
        if (host instanceof StandardHost) {
            unpackWARs = ((StandardHost) host).isUnpackWARs();
            if (unpackWARs && context instanceof StandardContext) {
                unpackWARs =  ((StandardContext) context).getUnpackWAR();
            }
        }

        boolean docBaseInAppBase = docBase.startsWith(appBase.getPath() + File.separatorChar);

        if (docBase.toLowerCase(Locale.ENGLISH).endsWith(".war") && !file.isDirectory()) {
            URL war = UriUtil.buildJarUrl(new File(docBase));
            if (unpackWARs) {
                docBase = ExpandWar.expand(host, war, pathName);
                file = new File(docBase);
                docBase = file.getCanonicalPath();
                if (context instanceof StandardContext) {
                    ((StandardContext) context).setOriginalDocBase(origDocBase);
                }
            } else {
                ExpandWar.validate(host, war, pathName);
            }
        } else {
            File docDir = new File(docBase);
            File warFile = new File(docBase + ".war");
            URL war = null;
            if (warFile.exists() && docBaseInAppBase) {
                war = UriUtil.buildJarUrl(warFile);
            }
            if (docDir.exists()) {
                if (war != null && unpackWARs) {
                    ExpandWar.expand(host, war, pathName);
                }
            } else {
                if (war != null) {
                    if (unpackWARs) {
                        docBase = ExpandWar.expand(host, war, pathName);
                        file = new File(docBase);
                        docBase = file.getCanonicalPath();
                    } else {
                        docBase = warFile.getCanonicalPath();
                        ExpandWar.validate(host, war, pathName);
                    }
                }
                if (context instanceof StandardContext) {
                    ((StandardContext) context).setOriginalDocBase(origDocBase);
                }
            }
        }

        context.setDocBase(docBase);
    }
}

10、解析web.xml生成路由

  • configureContext(webXml);
  • context.addServletMappingDecoded(urlPattern, jspServletName, true);
public class ContextConfig implements LifecycleListener {

    protected synchronized void configureStart() {
        // 启动webConfig配置
        webConfig();
    }

    protected void webConfig() {
        WebXmlParser webXmlParser = new WebXmlParser(context.getXmlNamespaceAware(),
                context.getXmlValidation(), context.getXmlBlockExternal());

        Set<WebXml> defaults = new HashSet<>();
        defaults.add(getDefaultWebXmlFragment(webXmlParser));

        WebXml webXml = createWebXml();

        InputSource contextWebXml = getContextWebXmlSource();
        if (!webXmlParser.parseWebXml(contextWebXml, webXml, false)) {
            ok = false;
        }

        ServletContext sContext = context.getServletContext();

        Map<String,WebXml> fragments = 
                      processJarsForWebFragments(webXml, webXmlParser);

        Set<WebXml> orderedFragments = null;
        orderedFragments =
                WebXml.orderWebFragments(webXml, fragments, sContext);


        if (!webXml.isMetadataComplete()) {
            if (ok) {
                ok = webXml.merge(orderedFragments);
            }

            webXml.merge(defaults);
            if (ok) {
                convertJsps(webXml);
            }

            if (ok) {
                configureContext(webXml);
            }
        } else {
            webXml.merge(defaults);
            convertJsps(webXml);
            // 配置
            configureContext(webXml);
        }
    }
}

11、解析web.xml

  • Wrapper wrapper = context.createWrapper();
public class ContextConfig implements LifecycleListener {

    private void configureContext(WebXml webxml) {

        context.setPublicId(webxml.getPublicId());

        context.setEffectiveMajorVersion(webxml.getMajorVersion());
        context.setEffectiveMinorVersion(webxml.getMinorVersion());

        for (Entry<String, String> entry : webxml.getContextParams().entrySet()) {
            context.addParameter(entry.getKey(), entry.getValue());
        }
        context.setDenyUncoveredHttpMethods(
                webxml.getDenyUncoveredHttpMethods());
        context.setDisplayName(webxml.getDisplayName());
        context.setDistributable(webxml.isDistributable());
        for (ContextLocalEjb ejbLocalRef : webxml.getEjbLocalRefs().values()) {
            context.getNamingResources().addLocalEjb(ejbLocalRef);
        }
        for (ContextEjb ejbRef : webxml.getEjbRefs().values()) {
            context.getNamingResources().addEjb(ejbRef);
        }
        for (ContextEnvironment environment : webxml.getEnvEntries().values()) {
            context.getNamingResources().addEnvironment(environment);
        }
        for (ErrorPage errorPage : webxml.getErrorPages().values()) {
            context.addErrorPage(errorPage);
        }
        for (FilterDef filter : webxml.getFilters().values()) {
            if (filter.getAsyncSupported() == null) {
                filter.setAsyncSupported("false");
            }
            context.addFilterDef(filter);
        }
        for (FilterMap filterMap : webxml.getFilterMappings()) {
            context.addFilterMap(filterMap);
        }
        context.setJspConfigDescriptor(webxml.getJspConfigDescriptor());
        for (String listener : webxml.getListeners()) {
            context.addApplicationListener(listener);
        }
        for (Entry<String, String> entry :
                webxml.getLocaleEncodingMappings().entrySet()) {
            context.addLocaleEncodingMappingParameter(entry.getKey(),
                    entry.getValue());
        }

        if (webxml.getLoginConfig() != null) {
            context.setLoginConfig(webxml.getLoginConfig());
        }
        for (MessageDestinationRef mdr :
                webxml.getMessageDestinationRefs().values()) {
            context.getNamingResources().addMessageDestinationRef(mdr);
        }

        context.setIgnoreAnnotations(webxml.isMetadataComplete());
        for (Entry<String, String> entry :
                webxml.getMimeMappings().entrySet()) {
            context.addMimeMapping(entry.getKey(), entry.getValue());
        }
        // Name is just used for ordering
        for (ContextResourceEnvRef resource :
                webxml.getResourceEnvRefs().values()) {
            context.getNamingResources().addResourceEnvRef(resource);
        }
        for (ContextResource resource : webxml.getResourceRefs().values()) {
            context.getNamingResources().addResource(resource);
        }
        boolean allAuthenticatedUsersIsAppRole =
                webxml.getSecurityRoles().contains(
                        SecurityConstraint.ROLE_ALL_AUTHENTICATED_USERS);
        for (SecurityConstraint constraint : webxml.getSecurityConstraints()) {
            if (allAuthenticatedUsersIsAppRole) {
                constraint.treatAllAuthenticatedUsersAsApplicationRole();
            }
            context.addConstraint(constraint);
        }
        for (String role : webxml.getSecurityRoles()) {
            context.addSecurityRole(role);
        }
        for (ContextService service : webxml.getServiceRefs().values()) {
            context.getNamingResources().addService(service);
        }
        for (ServletDef servlet : webxml.getServlets().values()) {
            Wrapper wrapper = context.createWrapper();

            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);
        }
        for (Entry<String, String> entry :
                webxml.getServletMappings().entrySet()) {
            context.addServletMappingDecoded(entry.getKey(), entry.getValue());
        }
        SessionConfig sessionConfig = webxml.getSessionConfig();
        if (sessionConfig != null) {
            if (sessionConfig.getSessionTimeout() != null) {
                context.setSessionTimeout(
                        sessionConfig.getSessionTimeout().intValue());
            }
            SessionCookieConfig scc =
                context.getServletContext().getSessionCookieConfig();
            scc.setName(sessionConfig.getCookieName());
            scc.setDomain(sessionConfig.getCookieDomain());
            scc.setPath(sessionConfig.getCookiePath());
            scc.setComment(sessionConfig.getCookieComment());
            if (sessionConfig.getCookieHttpOnly() != null) {
                scc.setHttpOnly(sessionConfig.getCookieHttpOnly().booleanValue());
            }
            if (sessionConfig.getCookieSecure() != null) {
                scc.setSecure(sessionConfig.getCookieSecure().booleanValue());
            }
            if (sessionConfig.getCookieMaxAge() != null) {
                scc.setMaxAge(sessionConfig.getCookieMaxAge().intValue());
            }
            if (sessionConfig.getSessionTrackingModes().size() > 0) {
                context.getServletContext().setSessionTrackingModes(
                        sessionConfig.getSessionTrackingModes());
            }
        }

        for (String welcomeFile : webxml.getWelcomeFiles()) {
            if (welcomeFile != null && welcomeFile.length() > 0) {
                context.addWelcomeFile(welcomeFile);
            }
        }

        for (JspPropertyGroup jspPropertyGroup :
                webxml.getJspPropertyGroups()) {
            String jspServletName = context.findServletMapping("*.jsp");
            if (jspServletName == null) {
                jspServletName = "jsp";
            }
            if (context.findChild(jspServletName) != null) {
                for (String urlPattern : jspPropertyGroup.getUrlPatterns()) {
                    context.addServletMappingDecoded(urlPattern, jspServletName, true);
                }
            } else {
            }
        }

        for (Entry<String, String> entry :
                webxml.getPostConstructMethods().entrySet()) {
            context.addPostConstructMethod(entry.getKey(), entry.getValue());
        }

        for (Entry<String, String> entry :
            webxml.getPreDestroyMethods().entrySet()) {
            context.addPreDestroyMethod(entry.getKey(), entry.getValue());
        }
    }



    public void addServletMappingDecoded(String pattern, String name) {
        addServletMappingDecoded(pattern, name, false);
    }


    public void addServletMappingDecoded(String pattern, String name,
                                  boolean jspWildCard) {

        String adjustedPattern = adjustURLPattern(pattern);

        synchronized (servletMappingsLock) {
            String name2 = servletMappings.get(adjustedPattern);

                Wrapper wrapper = (Wrapper) findChild(name2);
                wrapper.removeMapping(adjustedPattern);
            }
            servletMappings.put(adjustedPattern, name);
        }
        Wrapper wrapper = (Wrapper) findChild(name);
        wrapper.addMapping(adjustedPattern);

        fireContainerEvent("addServletMapping", adjustedPattern);
    }
}

12、默认解析来自Catalina目录下的web.xml

    <servlet>
        <servlet-name>default</servlet-name>
        <servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
        <init-param>
            <param-name>debug</param-name>
            <param-value>0</param-value>
        </init-param>
        <init-param>
            <param-name>listings</param-name>
            <param-value>false</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet>
        <servlet-name>jsp</servlet-name>
        <servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class>
        <init-param>
            <param-name>fork</param-name>
            <param-value>false</param-value>
        </init-param>
        <init-param>
            <param-name>xpoweredBy</param-name>
            <param-value>false</param-value>
        </init-param>
        <load-on-startup>3</load-on-startup>
    </servlet>
目录
相关文章
|
1天前
|
应用服务中间件 容器
从零手写实现 tomcat-07-war 如何解析处理三方的 war 包?
这是一个关于构建Web应用和理解类加载器的文章摘要。作者探讨了如何解析和处理WAR包,使用Netty权威指南系列(BIO, NIO, AIO)作为背景阅读。文章通过一个简单的Web项目实例,展示了项目的目录结构,包括`pom.xml`, `web.xml`和`IndexServlet`。作者还介绍了自定义的`WebAppClassLoader`,它扩展了`URLClassLoader`,用于根据类路径加载非当前项目类。最后提到了一个名为mini-cat的开源项目,它是简易版Tomcat实现,可在GitHub上找到。
|
6月前
|
Java 应用服务中间件
idea tomcat 404 无法自动打开本地项目war包路径
idea tomcat 404 无法自动打开本地项目war包路径
73 0
|
1天前
|
Java 应用服务中间件 API
SpringBoot项目 Tomcat部署war程序时启动成功但是访问404异常处理
SpringBoot项目 Tomcat部署war程序时启动成功但是访问404异常处理
90 0
|
6月前
|
缓存 JavaScript 应用服务中间件
Nginx+Tomcat代理环境下JS无法完全加载问题
Nginx+Tomcat代理环境下JS无法完全加载问题
|
1天前
|
Java 应用服务中间件 容器
SpringBoot配置外部Tomcat并打war包
SpringBoot配置外部Tomcat并打war包
76 0
|
1天前
|
存储 Java 应用服务中间件
Springboot项目打war包部署到外置tomcat容器【详解版】
该文介绍了将Spring Boot应用改为war包并在外部Tomcat中部署的步骤:1) 修改pom.xml打包方式为war;2) 排除内置Tomcat依赖;3) 创建`ServletInitializer`类继承`SpringBootServletInitializer`;4) build部分需指定`finalName`;5) 使用`mvn clean package`打包,将war包放入外部Tomcat的webapps目录,通过startup脚本启动Tomcat并访问应用。注意,应用访问路径和静态资源引用需包含war包名。
|
1天前
|
Java 应用服务中间件
SpringBoot 项目war包部署 配置外置tomcat方法
SpringBoot 项目war包部署 配置外置tomcat方法
76 0
|
1天前
|
Java 应用服务中间件 Maven
Tomcat部署SpringBoot war包
Tomcat部署SpringBoot war包
30 0
|
1天前
|
jenkins Java 应用服务中间件
Jenkins【部署 01】两种方式+两种环境部署最新版本 Jenkins v2.303.2 WAR包(直接使用 java -jar+使用Tomcat的Web端部署)
Jenkins【部署 01】两种方式+两种环境部署最新版本 Jenkins v2.303.2 WAR包(直接使用 java -jar+使用Tomcat的Web端部署)
82 0
|
1天前
1分钟入门angular动画效果animations,敲简单滴哟~~
1分钟入门angular动画效果animations,敲简单滴哟~~
1分钟入门angular动画效果animations,敲简单滴哟~~