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

序

本文主要研究一下spring.mvc.servlet.load-on-startup

spring.mvc.servlet.load-on-startup

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

复制代码
@ConfigurationProperties(prefix = "spring.mvc")
public class WebMvcProperties {
	//......

	private final Servlet servlet = new Servlet();

	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;
		}

	}	
}

WebMvcProperties.Servlet定义了loadOnStartup属性,默认为-1

DispatcherServletAutoConfiguration

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

复制代码
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass(DispatcherServlet.class)
@AutoConfigureAfter(ServletWebServerFactoryAutoConfiguration.class)
public class DispatcherServletAutoConfiguration {

	//......

	@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;
		}

	}

	//......
}	

DispatcherServletRegistrationConfiguration注册了DispatcherServletRegistrationBean,它会读取webMvcProperties.getServlet().getLoadOnStartup()然后设置其loadOnStartup属性

ServletRegistrationBean

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

复制代码
public class ServletRegistrationBean<T extends Servlet> extends DynamicRegistrationBean<ServletRegistration.Dynamic> {

	private static final String[] DEFAULT_MAPPINGS = { "/*" };

	private T servlet;

	private Set<String> urlMappings = new LinkedHashSet<>();

	private boolean alwaysMapUrl = true;

	private int loadOnStartup = -1;

	private MultipartConfigElement multipartConfig;

	//......

	@Override
	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);
		}
	}

}	

ServletRegistrationBean定义了loadOnStartup属性,默认为-1,其configure方法会设置loadOnStartup到ServletRegistration.Dynamic

StandardWrapper

org/apache/catalina/core/StandardWrapper.java

复制代码
    /**
     * Set the load-on-startup order value (negative value means
     * load on first call).
     *
     * @param value New load-on-startup value
     */
    @Override
    public void setLoadOnStartup(int value) {

        int oldLoadOnStartup = this.loadOnStartup;
        this.loadOnStartup = value;
        support.firePropertyChange("loadOnStartup",
                                   Integer.valueOf(oldLoadOnStartup),
                                   Integer.valueOf(this.loadOnStartup));

    }

    /**
     * @return the load-on-startup order value (negative value means
     * load on first call).
     */
    @Override
    public int getLoadOnStartup() {

        if (isJspServlet && loadOnStartup < 0) {
            /*
             * JspServlet must always be preloaded, because its instance is
             * used during registerJMX (when registering the JSP
             * monitoring mbean)
             */
             return Integer.MAX_VALUE;
        } else {
            return this.loadOnStartup;
        }
    }    

loadOnStartup属性最后设置到了tomcat的StandardWrapper

StandardContext

org/apache/catalina/core/StandardContext.java

复制代码
    /**
     * Load and initialize all servlets marked "load on startup" in the
     * web application deployment descriptor.
     *
     * @param children Array of wrappers for all currently defined
     *  servlets (including those not declared load on startup)
     * @return <code>true</code> if load on startup was considered successful
     */
    public boolean loadOnStartup(Container children[]) {

        // Collect "load on startup" servlets that need to be initialized
        TreeMap<Integer, ArrayList<Wrapper>> map = new TreeMap<>();
        for (Container child : children) {
            Wrapper wrapper = (Wrapper) child;
            int loadOnStartup = wrapper.getLoadOnStartup();
            if (loadOnStartup < 0) {
                continue;
            }
            Integer key = Integer.valueOf(loadOnStartup);
            ArrayList<Wrapper> list = map.get(key);
            if (list == null) {
                list = new ArrayList<>();
                map.put(key, list);
            }
            list.add(wrapper);
        }

        // Load the collected "load on startup" servlets
        for (ArrayList<Wrapper> list : map.values()) {
            for (Wrapper wrapper : list) {
                try {
                    wrapper.load();
                } catch (ServletException e) {
                    getLogger().error(sm.getString("standardContext.loadOnStartup.loadException",
                          getName(), wrapper.getName()), StandardWrapper.getRootCause(e));
                    // NOTE: load errors (including a servlet that throws
                    // UnavailableException from the init() method) are NOT
                    // fatal to application startup
                    // unless failCtxIfServletStartFails="true" is specified
                    if(getComputedFailCtxIfServletStartFails()) {
                        return false;
                    }
                }
            }
        }
        return true;

    }

StandardContext的loadOnStartup方法会取出所有loadOnStartup大于等于0的wrapper,按loadOnStartup值放入到TreeMap<Integer, ArrayList<Wrapper>>,然后遍历该TreeMap挨个执行wrapper.load()进行加载

小结

springboot的spring.mvc.servlet.load-on-startup属性,最后设置到tomcat的StandardWrapper;而tomcat的StandardContext的loadOnStartup方法会取出所有loadOnStartup大于等于0的wrapper,按loadOnStartup值放入到TreeMap<Integer, ArrayList<Wrapper>>,然后遍历该TreeMap挨个执行wrapper.load()进行加载。

相关推荐
夕除27 分钟前
redis--主从还是集群,内存
java·spring·mybatis
霸道流氓气质1 小时前
AI 简历制作平台:结构化输出解析、多模板渲染与 PDF 导出的 Vue3 + Spring AI 全栈实战
人工智能·spring·pdf
打工仔折腾 AI1 小时前
Spring AI + Qwen 周报生成实战:从原始日志到人工确认卡
人工智能·后端·python·spring·elasticsearch·ai agent 实战
开开心心就好1 小时前
手机电池健康怎么查?看容量温度循环次数
spring·智能手机·ffmpeg·计算机外设·电脑·maven·音视频
云和数据.ChenGuang2 小时前
Spring AI 对接 LangGraph:构建企业级智能体工作流实战
java·人工智能·spring boot·后端·spring
云和数据.ChenGuang2 小时前
Spring AI 入门实战:构建你的第一个 AI 应用
java·人工智能·深度学习·spring·机器学习·语言模型
不老刘2 小时前
Maven 多医院分支场景下 groupId 的设计与 Jenkins 落地实践
servlet·jenkins·maven
从零到一未来可期2 小时前
第8集:MCP协议:一个协议让AI接入所有企业系统
spring·spring cloud
木棉软糖3 小时前
JDBC 读 ResultSet 读到一半,报 stream closed
java·mysql·spring
需要82614 小时前
Spring Bean 生命周期:从实例化到销毁的完整钩子
java·后端·spring