聊聊spring.mvc.servlet.load-on-startup

本文主要研究一下

WebMvcProperties$Servlet

org/springframework/boot/autoconfigure/web/servlet/WebMvcProperties.java

    public static class Servlet {

        /**
         * Path of the dispatcher servlet.
         */
        private String path = "/";

        /**
         * Load on startup priority of the dispatcher servlet.
         */
        private int loadOnStartup = -1;

        public String getPath() {
            return this.path;
        }

        public void setPath(String path) {
            Assert.notNull(path, "Path must not be null");
            Assert.isTrue(!path.contains("*"), "Path must not contain wildcards");
            this.path = path;
        }

        public int getLoadOnStartup() {
            return this.loadOnStartup;
        }

        public void setLoadOnStartup(int loadOnStartup) {
            this.loadOnStartup = loadOnStartup;
        }

        public String getServletMapping() {
            if (this.path.equals("") || this.path.equals("/")) {
                return "/";
            }
            if (this.path.endsWith("/")) {
                return this.path + "*";
            }
            return this.path + "/*";
        }

        public String getPath(String path) {
            String prefix = getServletPrefix();
            if (!path.startsWith("/")) {
                path = "/" + path;
            }
            return prefix + path;
        }

        public String getServletPrefix() {
            String result = this.path;
            int index = result.indexOf('*');
            if (index != -1) {
                result = result.substring(0, index);
            }
            if (result.endsWith("/")) {
                result = result.substring(0, result.length() - 1);
            }
            return result;
        }

    }

loadOnStartup默认为-1

DispatcherServletAutoConfiguration

org/springframework/boot/autoconfigure/web/servlet/DispatcherServletAutoConfiguration.java

    @Configuration(proxyBeanMethods = false)
    @Conditional(DispatcherServletRegistrationCondition.class)
    @ConditionalOnClass(ServletRegistration.class)
    @EnableConfigurationProperties(WebMvcProperties.class)
    @Import(DispatcherServletConfiguration.class)
    protected static class DispatcherServletRegistrationConfiguration {

        @Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
        @ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
        public DispatcherServletRegistrationBean dispatcherServletRegistration(DispatcherServlet dispatcherServlet,
                WebMvcProperties webMvcProperties, ObjectProvider<MultipartConfigElement> multipartConfig) {
            DispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean(dispatcherServlet,
                    webMvcProperties.getServlet().getPath());
            registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
            registration.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup());
            multipartConfig.ifAvailable(registration::setMultipartConfig);
            return registration;
        }

    }

这里注册了dispatcherServlet,并设置loadOnStartup

ServletRegistrationBean

org/springframework/boot/web/servlet/ServletRegistrationBean.java

    protected void configure(ServletRegistration.Dynamic registration) {
        super.configure(registration);
        String[] urlMapping = StringUtils.toStringArray(this.urlMappings);
        if (urlMapping.length == 0 && this.alwaysMapUrl) {
            urlMapping = DEFAULT_MAPPINGS;
        }
        if (!ObjectUtils.isEmpty(urlMapping)) {
            registration.addMapping(urlMapping);
        }
        registration.setLoadOnStartup(this.loadOnStartup);
        if (this.multipartConfig != null) {
            registration.setMultipartConfig(this.multipartConfig);
        }
    }

configure最后设置到ServletRegistration.Dynamic

ApplicationServletRegistration

org/apache/catalina/core/ApplicationServletRegistration.java

    public void setLoadOnStartup(int loadOnStartup) {
        wrapper.setLoadOnStartup(loadOnStartup);
    }

最后是设置到tomcat的Wrapper.loadOnStartup

TomcatWebServer

org/springframework/boot/web/embedded/tomcat/TomcatWebServer.java

    public void start() throws WebServerException {
        synchronized (this.monitor) {
            if (this.started) {
                return;
            }
            try {
                addPreviouslyRemovedConnectors();
                Connector connector = this.tomcat.getConnector();
                if (connector != null && this.autoStart) {
                    performDeferredLoadOnStartup();
                }
                checkThatConnectorsHaveStarted();
                this.started = true;
                logger.info("Tomcat started on port(s): " + getPortsDescription(true) + " with context path '"
                        + getContextPath() + "'");
            }
            catch (ConnectorStartFailedException ex) {
                stopSilently();
                throw ex;
            }
            catch (Exception ex) {
                PortInUseException.throwIfPortBindingException(ex, () -> this.tomcat.getConnector().getPort());
                throw new WebServerException("Unable to start embedded Tomcat server", ex);
            }
            finally {
                Context context = findContext();
                ContextBindings.unbindClassLoader(context, context.getNamingToken(), getClass().getClassLoader());
            }
        }
    }

TomcatWebServer的start方法对于autoStart为true(默认)的会执行performDeferredLoadOnStartup

performDeferredLoadOnStartup

org/springframework/boot/web/embedded/tomcat/TomcatWebServer.java

    private void performDeferredLoadOnStartup() {
        try {
            for (Container child : this.tomcat.getHost().findChildren()) {
                if (child instanceof TomcatEmbeddedContext) {
                    ((TomcatEmbeddedContext) child).deferredLoadOnStartup();
                }
            }
        }
        catch (Exception ex) {
            if (ex instanceof WebServerException) {
                throw (WebServerException) ex;
            }
            throw new WebServerException("Unable to start embedded Tomcat connectors", ex);
        }
    }

performDeferredLoadOnStartup遍历child挨个执行deferredLoadOnStartup

deferredLoadOnStartup

org/springframework/boot/web/embedded/tomcat/TomcatEmbeddedContext.java

    void deferredLoadOnStartup() throws LifecycleException {
        doWithThreadContextClassLoader(getLoader().getClassLoader(),
                () -> getLoadOnStartupWrappers(findChildren()).forEach(this::load));
    }

    private Stream<Wrapper> getLoadOnStartupWrappers(Container[] children) {
        Map<Integer, List<Wrapper>> grouped = new TreeMap<>();
        for (Container child : children) {
            Wrapper wrapper = (Wrapper) child;
            int order = wrapper.getLoadOnStartup();
            if (order >= 0) {
                grouped.computeIfAbsent(order, (o) -> new ArrayList<>()).add(wrapper);
            }
        }
        return grouped.values().stream().flatMap(List::stream);
    }   

它通过getLoadOnStartupWrappers获取Wrapper,然后挨个执行其load方法

load

org/springframework/boot/web/embedded/tomcat/TomcatEmbeddedContext.java

    private void load(Wrapper wrapper) {
        try {
            wrapper.load();
        }
        catch (ServletException ex) {
            String message = sm.getString("standardContext.loadOnStartup.loadException", getName(), wrapper.getName());
            if (getComputedFailCtxIfServletStartFails()) {
                throw new WebServerException(message, ex);
            }
            getLogger().error(message, StandardWrapper.getRootCause(ex));
        }
    }

load方法即执行wrapper.load()

load

org/apache/catalina/core/StandardWrapper.java

    public synchronized void load() throws ServletException {
        instance = loadServlet();

        if (!instanceInitialized) {
            initServlet(instance);
        }

        if (isJspServlet) {
            StringBuilder oname = new StringBuilder(getDomain());

            oname.append(":type=JspMonitor");

            oname.append(getWebModuleKeyProperties());

            oname.append(",name=");
            oname.append(getName());

            oname.append(getJ2EEKeyProperties());

            try {
                jspMonitorON = new ObjectName(oname.toString());
                Registry.getRegistry(null, null).registerComponent(instance, jspMonitorON, null);
            } catch (Exception ex) {
                log.warn(sm.getString("standardWrapper.jspMonitorError", instance));
            }
        }
    }

tomcat的StandardWrapper的load是个同步方法,它加载和初始化servlet,如果已经加载或初始化不会重复加载

小结

springboot提供了spring.mvc.servlet.load-on-startup配置,可以设置未非负,这样子springboot启动时可以对servlet进行加载与初始化。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容