spring启动流程 (6完结) springmvc启动流程

SpringMVC的启动入口在SpringServletContainerInitializer类,它是ServletContainerInitializer实现类(Servlet3.0新特性)。在实现方法中使用WebApplicationInitializer创建ApplicationContext、创建注册DispatcherServlet、初始化ApplicationContext等。

SpringMVC已经将大部分的启动逻辑封装在了几个抽象WebApplicationInitializer中,开发者只要继承这些抽象类实现抽象方法即可。

本文将详细分析ServletContainerInitializer、SpringServletContainerInitializer和WebApplicationInitializer的工作流程。

Servlet3.0的ServletContainerInitializer

ServletContainerInitializer接口

ServletContainerInitializer是Servlet3.0的接口。

该接口在web应用程序启动阶段接收通知,注册servlet、filter、listener等。

该接口的实现类可以用HandlesTypes进行标注,并指定一个Class值,后续会将实现、扩展了这个Class的类集合作为参数传递给onStartup方法。

以tomcat为例,在容器配置初始化阶段,将使用SPI查找实现类,在ServletContext启动阶段,初始化并调用onStartup方法来进行ServletContext的初始化。

SpringServletContainerInitializer实现类

在onStartup方法创建所有的WebApplicationInitializer对并调用onStartup方法。

以下为SPI配置,在spring-web/src/main/resources/META-INF/services/javax.servlet.ServletContainerInitializer文件:

WebApplicationInitializer接口

WebApplicationInitializer接口概述

Interface to be implemented in Servlet 3.0+ environments in order to configure the ServletContext programmatically -- as opposed to (or possibly in conjunction with) the traditional web.xml-based approach.

Implementations of this SPI will be detected automatically by SpringServletContainerInitializer, which itself is bootstrapped automatically by any Servlet 3.0 container. See its Javadoc for details on this bootstrapping mechanism.

示例:

java 复制代码
public class MyWebAppInitializer implements WebApplicationInitializer {

   @Override
   public void onStartup(ServletContext container) {
     // Create the 'root' Spring application context
     AnnotationConfigWebApplicationContext rootContext =
       new AnnotationConfigWebApplicationContext();
     rootContext.register(AppConfig.class);

     // Manage the lifecycle of the root application context
     container.addListener(new ContextLoaderListener(rootContext));

     // Create the dispatcher servlet's Spring application context
     AnnotationConfigWebApplicationContext dispatcherContext =
       new AnnotationConfigWebApplicationContext();
     dispatcherContext.register(DispatcherConfig.class);

     // Register and map the dispatcher servlet
     ServletRegistration.Dynamic dispatcher =
       container.addServlet("dispatcher", new DispatcherServlet(dispatcherContext));
     dispatcher.setLoadOnStartup(1);
     dispatcher.addMapping("/");
   }
}

开发者可以编写类继承AbstractAnnotationConfigDispatcherServletInitializer抽象类,这个抽象类已经将大部分的初始化逻辑进行了封装。

WebApplicationInitializer实现和继承关系

AbstractContextLoaderInitializer类:

Convenient base class for WebApplicationInitializer implementations that register a ContextLoaderListener in the servlet context. The only method required to be implemented by subclasses is createRootApplicationContext(), which gets invoked from registerContextLoaderListener(ServletContext).

AbstractDispatcherServletInitializer类:

Base class for WebApplicationInitializer implementations that register a DispatcherServlet in the servlet context. Most applications should consider extending the Spring Java config subclass AbstractAnnotationConfigDispatcherServletInitializer.

AbstractAnnotationConfigDispatcherServletInitializer类:

WebApplicationInitializer to register a DispatcherServlet and use Java-based Spring configuration.

Implementations are required to implement:

  • getRootConfigClasses() -- for "root" application context (non-web infrastructure) configuration.
  • getServletConfigClasses() -- for DispatcherServlet application context (Spring MVC infrastructure) configuration.

If an application context hierarchy is not required, applications may return all configuration via getRootConfigClasses() and return null from getServletConfigClasses().

开发者SpringMvcInitializer示例

开发者需要编写类继承AbstractAnnotationConfigDispatcherServletInitializer类,实现几个抽象方法:

java 复制代码
public class SpringMvcInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

  /**
   * 指定创建root application context需要的@Configuration/@Component类
   */
  @Override
  protected Class<?>[] getRootConfigClasses() {
    return new Class[]{AppConfig.class};
  }

  /**
   * 指定创建Servlet application context需要的@Configuration/@Component类
   * 如果所有的配置类都使用root config classes就返回null
   */
  @Override
  protected Class<?>[] getServletConfigClasses() {
    return null;
  }

  /**
   * Specify the servlet mapping(s) for the DispatcherServlet --- for example "/", "/app", etc.
   */
  @Override
  protected String[] getServletMappings() {
    return new String[]{"/"};
  }
}

SpringMVC启动流程

入口AbstractDispatcherServletInitializer.onStartup方法

java 复制代码
public void onStartup(ServletContext servletContext) throws ServletException {
	super.onStartup(servletContext);
	registerDispatcherServlet(servletContext);
}

父类的onStartup方法创建RootApplicationContext、注册ContextLoaderListener监听器:

java 复制代码
public void onStartup(ServletContext servletContext) throws ServletException {
	registerContextLoaderListener(servletContext);
}

protected void registerContextLoaderListener(ServletContext servletContext) {
	WebApplicationContext rootAppContext = createRootApplicationContext();
	if (rootAppContext != null) {
		// ContextLoaderListener是ServletContextListener
		// 会在contextInitialized阶段初始化RootApplicationContext
		ContextLoaderListener listener = new ContextLoaderListener(rootAppContext);
		listener.setContextInitializers(getRootApplicationContextInitializers());
		servletContext.addListener(listener);
	}
}

注册DispatcherServlet

registerDispatcherServlet方法用于创建ServletApplicationContext、注册DispatcherServlet:

java 复制代码
protected void registerDispatcherServlet(ServletContext servletContext) {
	String servletName = getServletName();

	// 创建ServletApplicationContext
	WebApplicationContext servletAppContext = createServletApplicationContext();

	// 创建DispatcherServlet
	FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);
	// 添加ApplicationContextInitializer集,会在初始化时调用
	dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());

	// 添加到ServletContext
	ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet);

	registration.setLoadOnStartup(1);
	registration.addMapping(getServletMappings());
	registration.setAsyncSupported(isAsyncSupported());

	Filter[] filters = getServletFilters();
	if (!ObjectUtils.isEmpty(filters)) {
		for (Filter filter : filters) {
			registerServletFilter(servletContext, filter);
		}
	}

	customizeRegistration(registration);
}

触发ContextLoaderListener监听器contextInitialized事件

这个是Servlet的ServletContextListener机制,在ServletContext创建之后触发contextInitialized事件:

java 复制代码
public void contextInitialized(ServletContextEvent event) {
	initWebApplicationContext(event.getServletContext());
}

public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
	// 判断是否已经在当前ServletContext绑定了WebApplicationContext
	// 如果已经绑定抛错
	if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
		throw new IllegalStateException("已经初始化过了");
	}

	try {
		if (this.context == null) {
			this.context = createWebApplicationContext(servletContext);
		}
		if (this.context instanceof ConfigurableWebApplicationContext) {
			ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
			if (!cwac.isActive()) {
				if (cwac.getParent() == null) {
					ApplicationContext parent = loadParentContext(servletContext);
					cwac.setParent(parent);
				}
				// refresh
				configureAndRefreshWebApplicationContext(cwac, servletContext);
			}
		}
		servletContext
            .setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);

		ClassLoader ccl = Thread.currentThread().getContextClassLoader();
		if (ccl == ContextLoader.class.getClassLoader()) {
			currentContext = this.context;
		} else if (ccl != null) {
			currentContextPerThread.put(ccl, this.context);
		}

		return this.context;
	} catch (RuntimeException | Error ex) {
		servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
		throw ex;
	}
}

protected void configureAndRefreshWebApplicationContext(
		ConfigurableWebApplicationContext wac, ServletContext sc) {
	// 给wac设置id

	wac.setServletContext(sc);
	// 设置spring主配置文件路径
	String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
	if (configLocationParam != null) {
		wac.setConfigLocation(configLocationParam);
	}

	ConfigurableEnvironment env = wac.getEnvironment();
	if (env instanceof ConfigurableWebEnvironment) {
		((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
	}

	customizeContext(sc, wac);
	// 刷新ApplicationContext
	wac.refresh();
}

触发DispatcherServlet的init事件

Servlet在接收请求之前会调用其init方法进行初始化,这个是Servlet的规范。

init方法在其父类HttpServletBean中:

java 复制代码
public final void init() throws ServletException {

	// 从ServletConfig获取配置设置到Servlet
	PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
	if (!pvs.isEmpty()) {
		try {
			BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
			ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
			bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
			initBeanWrapper(bw);
			bw.setPropertyValues(pvs, true);
		} catch (BeansException ex) {
			throw ex;
		}
	}

	// 初始化
	initServletBean();
}

// FrameworkServlet
protected final void initServletBean() throws ServletException {
	try {
		// 初始化ServletApplicationContext
		this.webApplicationContext = initWebApplicationContext();
		initFrameworkServlet();
	} catch (ServletException | RuntimeException ex) {
		throw ex;
	}
}

protected WebApplicationContext initWebApplicationContext() {
	// 获取rootApplicationContext
	// 之前的ServletContext初始化阶段已经绑定
	WebApplicationContext rootContext =
			WebApplicationContextUtils.getWebApplicationContext(getServletContext());
	WebApplicationContext wac = null;

	if (this.webApplicationContext != null) {
		wac = this.webApplicationContext;
		if (wac instanceof ConfigurableWebApplicationContext) {
			ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
			if (!cwac.isActive()) {
				// 将rootApplicationContext设置为Parent
				if (cwac.getParent() == null) {
					cwac.setParent(rootContext);
				}
				// 刷新ApplicationContext
				configureAndRefreshWebApplicationContext(cwac);
			}
		}
	}
	// 如果没有需要查找或创建
	if (wac == null) {
		wac = findWebApplicationContext();
	}
	if (wac == null) {
		wac = createWebApplicationContext(rootContext);
	}

	if (!this.refreshEventReceived) {
		synchronized (this.onRefreshMonitor) {
			// 子类实现
			onRefresh(wac);
		}
	}

	if (this.publishContext) {
		String attrName = getServletContextAttributeName();
		getServletContext().setAttribute(attrName, wac);
	}

	return wac;
}

// DispatcherServlet
protected void onRefresh(ApplicationContext context) {
	initStrategies(context);
}

// 初始化SpringMVC相关组件
protected void initStrategies(ApplicationContext context) {
	initMultipartResolver(context);
	initLocaleResolver(context);
	initThemeResolver(context);
	initHandlerMappings(context);
	initHandlerAdapters(context);
	initHandlerExceptionResolvers(context);
	initRequestToViewNameTranslator(context);
	initViewResolvers(context);
	initFlashMapManager(context);
}

SpringMVC启动流程总结

  • 创建RootApplicationContext、注册ContextLoaderListener监听器
  • 创建ServletApplicationContext、注册DispatcherServlet
  • 触发ContextLoaderListener监听器contextInitialized事件,初始化RootApplicationContext
  • 触发DispatcherServlet的init事件,初始化ServletApplicationContext
相关推荐
不得了zz26 分钟前
04-Spring AI Alibaba ReactAgent 生产级实践:工具调用、结构化输出、HITL 与上下文管理
java·ai编程·springaialibaba
猿究院_夏燊37 分钟前
SpringMVC 到底是怎么干活的?
java·网络·职场和发展
鶴哥只手遮天39 分钟前
Spring Framework 7.0.9 深度剖析-05-配置元数据解析:XML、注解与 JavaConfig
java·后端·开源
NGINX开源社区1 小时前
与我们交流:NGINX Gateway Fabric 与 NGINX Ingress Controller 社区会议
java·服务器·前端
鶴哥只手遮天1 小时前
Spring Framework 7.0.9 深度剖析-16-异步与调度
java·后端·开源
十年Java程序媛1 小时前
Java 多线程基础与锁机制|进程线程、synchronized、volatile、Lock 面试全梳理
java·spring boot·面试
弈栈录1 小时前
大模型服务抽象:兼容 OpenAI、通义千问与本地模型
java·后端·架构
鶴哥只手遮天1 小时前
Spring Framework 7.0.9 深度剖析-06-Bean 生命周期与依赖注入原理
java·后端·开源
唐钰小宝B1 小时前
深度解读 JDK 8、JDK 11、JDK 17 和 JDK 21 的区别
java·开发语言·windows
golang学习记1 小时前
最新版VS Code开发Spring Boot项目保姆级教程
java·spring boot·vscode·后端