Spring 소스코드 분석(11) SpringMVC 통합

1. SPI 기반의 외부 Tomcat 시작 프로세스

Tomcat은 /META-INF/service/javax.servlet.ServletContainerInitializer 아래의 애플리케이션에서 기본 sping-web 패키지를 찾았습니다.

SpringServletContainerInitializer.onStartUp()을 호출하기 전에 WebApplicationInitializer를 구현하는 모든 클래스를 찾아 OnStartup의 webAppInitializerClasses 매개변수에 전달하고 Servlet 컨텍스트 객체에 전달합니다.

org.springframework.web.SpringServletContainerInitializer#onStartup

/**
	 * Delegate the {@code ServletContext} to any {@link WebApplicationInitializer}
	 * implementations present on the application classpath.
	 * <p>Because this class declares @{@code HandlesTypes(WebApplicationInitializer.class)},
	 * Servlet 3.0+ containers will automatically scan the classpath for implementations
	 * of Spring's {@code WebApplicationInitializer} interface and provide the set of all
	 * such types to the {@code webAppInitializerClasses} parameter of this method.
	 * <p>If no {@code WebApplicationInitializer} implementations are found on the classpath,
	 * this method is effectively a no-op. An INFO-level log message will be issued notifying
	 * the user that the {@code ServletContainerInitializer} has indeed been invoked but that
	 * no {@code WebApplicationInitializer} implementations were found.
	 * <p>Assuming that one or more {@code WebApplicationInitializer} types are detected,
	 * they will be instantiated (and <em>sorted</em> if the @{@link
	 * org.springframework.core.annotation.Order @Order} annotation is present or
	 * the {@link org.springframework.core.Ordered Ordered} interface has been
	 * implemented). Then the {@link WebApplicationInitializer#onStartup(ServletContext)}
	 * method will be invoked on each instance, delegating the {@code ServletContext} such
	 * that each instance may register and configure servlets such as Spring's
	 * {@code DispatcherServlet}, listeners such as Spring's {@code ContextLoaderListener},
	 * or any other Servlet API componentry such as filters.
	 * @param webAppInitializerClasses all implementations of
	 * {@link WebApplicationInitializer} found on the application classpath
	 * @param servletContext the servlet context to be initialized
	 * @see WebApplicationInitializer#onStartup(ServletContext)
	 * @see AnnotationAwareOrderComparator
	 */
	@Override
	public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
			throws ServletException {

		List<WebApplicationInitializer> initializers = Collections.emptyList();

		if (webAppInitializerClasses != null) {
			initializers = new ArrayList<>(webAppInitializerClasses.size());
			for (Class<?> waiClass : webAppInitializerClasses) {
				// 接口和抽象类servlet容器也会给我们,但是我们不要
				// 排除接口和容器
				if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
						WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
					try {
						// 实例化,然后添加到集合中
						initializers.add((WebApplicationInitializer)
								ReflectionUtils.accessibleConstructor(waiClass).newInstance());
					}
					catch (Throwable ex) {
						throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);
					}
				}
			}
		}

		if (initializers.isEmpty()) {
			servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
			return;
		}

		servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
		AnnotationAwareOrderComparator.sort(initializers);
		// 调用initializer.onStartup  进行扩展
		for (WebApplicationInitializer initializer : initializers) {
			initializer.onStartup(servletContext);
		}
	}

2. 상위 컨테이너 ContextLoaderListener 생성

org.springframework.web.servlet.support.AbstractDispatcherServletInitializer#onStartup

@Override
	public void onStartup(ServletContext servletContext) throws ServletException {
		//registerContextLoaderListener  ok
		super.onStartup(servletContext);
		// registerDispatcherServlet
		registerDispatcherServlet(servletContext);
	}
	/**
	 * Register a {@link ContextLoaderListener} against the given servlet context. The
	 * {@code ContextLoaderListener} is initialized with the application context returned
	 * from the {@link #createRootApplicationContext()} template method.
	 * @param servletContext the servlet context to register the listener against
	 */
	protected void registerContextLoaderListener(ServletContext servletContext) {
		// 创建父容器 ,
		WebApplicationContext rootAppContext = createRootApplicationContext();
		if (rootAppContext != null) {
			ContextLoaderListener listener = new ContextLoaderListener(rootAppContext);
			// 设置初始化器
			listener.setContextInitializers(getRootApplicationContextInitializers());
			servletContext.addListener(listener);
		}
		else {
			logger.debug("No ContextLoaderListener registered, as " +
					"createRootApplicationContext() did not return an application context");
		}
	}

3. 하위 컨테이너 DispatcherServlet을 생성합니다.

org.springframework.web.servlet.support.AbstractDispatcherServletInitializer#registerDispatcherServlet

protected void registerDispatcherServlet(ServletContext servletContext) {
		String servletName = getServletName();
		Assert.hasLength(servletName, "getServletName() must not return null or empty");
		// 创建子容器
		WebApplicationContext servletAppContext = createServletApplicationContext();
		Assert.notNull(servletAppContext, "createServletApplicationContext() must not return null");
		// 创建DispatcherServlet
		FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);
		Assert.notNull(dispatcherServlet, "createDispatcherServlet(WebApplicationContext) must not return null");
		// 初始化器
		dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());

		ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet);
		if (registration == null) {
			throw new IllegalStateException("Failed to register servlet with name '" + servletName + "'. " +
					"Check if there is another servlet registered under the same name.");
		}
		// 启动时加载
		registration.setLoadOnStartup(1);
		// 映射
		registration.addMapping(getServletMappings());
		// 是否异步支持
		registration.setAsyncSupported(isAsyncSupported());
		// 设置DispatcherServlet的过滤器
		Filter[] filters = getServletFilters();
		if (!ObjectUtils.isEmpty(filters)) {
			for (Filter filter : filters) {
				registerServletFilter(servletContext, filter);
			}
		}
		// 空方法, 可以再对DispatcherServlet进行定制
		customizeRegistration(registration);
	}

4.ContextLoaderListener 초기화

외부 Tomcat은 초기화를 위해 ContextLoaderListener#contextInitialized를 호출하는 데 도움이 됩니다.

이전 단계는 Spring 컨테이너를 생성하는 것이었고, 여기서는configureAndRefreshWebApplicationContext() 메소드가 호출되어 Refresh() 메소드를 호출한다.

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
		if (ObjectUtils.identityToString(wac).equals(wac.getId())) {

			// 设置id
			String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
			if (idParam != null) {
				wac.setId(idParam);
			}
			else {
				// Generate default id...
				wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
						ObjectUtils.getDisplayString(sc.getContextPath()));
			}
		}
		// 设置ServletContext到spring上下文
		wac.setServletContext(sc);
		// 获得servlet容器中的全局参数contextConfigLocation  (xml)
		String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
		if (configLocationParam != null) {
			wac.setConfigLocation(configLocationParam);
		}

		// The wac environment's #initPropertySources will be called in any case when the context
		// is refreshed; do it eagerly here to ensure servlet property sources are in place for
		// use in any post-processing or initialization that occurs below prior to #refresh
		ConfigurableEnvironment env = wac.getEnvironment();
		if (env instanceof ConfigurableWebEnvironment) {
			((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
		}
		// 在容器加载前 可以通过设置初始化参数contextInitializerClasses、globalInitializerClasses 进行扩展
		customizeContext(sc, wac);
		// 刷新容器
		wac.refresh();
	}

5. DispatcherServlet 초기화

외부 Tomcat은 DispatcherServlet#init()를 호출하는 데 도움이 됩니다.

마지막으로 org.springframework.web.servlet.FrameworkServlet#initWebApplicationContext를 호출합니다.

/**
	 * Initialize and publish the WebApplicationContext for this servlet.
	 * <p>Delegates to {@link #createWebApplicationContext} for actual creation
	 * of the context. Can be overridden in subclasses.
	 * @return the WebApplicationContext instance
	 * @see #FrameworkServlet(WebApplicationContext)
	 * @see #setContextClass
	 * @see #setContextConfigLocation
	 */
	protected WebApplicationContext initWebApplicationContext() {
		// 获得ContextLoaderListener存的父容器
		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()) {
					// 如果没有设置父容器   spring  doGetBean
					if (cwac.getParent() == null) {
						cwac.setParent(rootContext);
					}
					// 配置并且加载子容器
					configureAndRefreshWebApplicationContext(cwac);
				}
			}
		}
		if (wac == null) {
			// 从servlet上下文根据<contextAttribute>名字从域里面获取
			wac = findWebApplicationContext();
		}
		if (wac == null) {
			// xml会在这里创建
			wac = createWebApplicationContext(rootContext);
		}

		//refreshEventReceived 它会在容器加载完设置为true (通过事件onApplicationEvent)
		// springboot在这初始化组件
		if (!this.refreshEventReceived) {
			synchronized (this.onRefreshMonitor) {
				onRefresh(wac);
			}
		}

		if (this.publishContext) {
			// 将当前容器放到servlet域中, 可以再创建子容器
			String attrName = getServletContextAttributeName();
			getServletContext().setAttribute(attrName, wac);
		}

		return wac;
	}

그리고 하위 컨테이너의 ContextRefreshListener를 수신하여 SpringMvc 구성 요소를 초기화합니다.

org.springframework.web.servlet.DispatcherServlet#initStrategies

/**初始化策略,加了s都是多个
	 * Initialize the strategy objects that this servlet uses.
	 * <p>May be overridden in subclasses in order to initialize further strategy objects.
	 */
	protected void initStrategies(ApplicationContext context) {
		initMultipartResolver(context);
		initLocaleResolver(context);
		initThemeResolver(context);
		initHandlerMappings(context);
		initHandlerAdapters(context);
		initHandlerExceptionResolvers(context);
		initRequestToViewNameTranslator(context);
		initViewResolvers(context);
		initFlashMapManager(context);
	}

Tomcat 프로세스는 추후 추가될 예정입니다.

게시물에 오신 것을 환영합니다: caicongyang_CSDN Java 기본, SSHM, Hadoop/spark 분야의 블로그 블로거

Supongo que te gusta

Origin blog.csdn.net/caicongyang/article/details/123171457
Recomendado
Clasificación