【spring】spring mvc与spring的整合源码分析

spring mvc与spring的整合简单介绍

  • 本文源码基于spring-framework-5.3.10。
  • mvc是spring源码中的一个子模块!
  • 本文重点是无xml方式的整合springmvc。
  • Spring整合SpringMVC唯一的体现就是父子容器。

spring mvc的父子容器

spring mvc的父子容器.png

  • 父容器(Spring)管理Service、Dao层的Bean, 子容器(SpringMVC)管理Controller的Bean。
  • 子容器可以访问父容器的Bean, 父容器无法访问子容器的Bean。

spring整合spring mvc的思路(xml方式)

  • 一般会在web.xml中配置下面的代码。核心是一个ContextLoaderListener(加载spring容器)、一个DispatcherServlet(加载spring mvc容器)。
<!--spring 基于web应用的启动-->
<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!--全局参数:spring配置文件-->
<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:spring-core.xml</param-value>
</context-param>
<!--前端调度器servlet-->
<servlet>
    <servlet-name>dispatcherServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <!--设置配置文件的路径-->
    <init-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:spring-mvc.xml</param-value>
</init-param>
    <!--设置启动即加载-->
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>dispatcherServlet</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>

零配置SpringMVC实现方式的基础:SPI

  • SPI:我们叫他服务接口扩展,(Service Provider Interface) 直译服务提供商接口。
  • 其实就是根据Servlet厂商(服务提供商)提供要求的一个接口,在固定的目录(META-INF/services)放上以接口全类名为命名的文件, 文件中放入接口的实现的全类名,该类由我们自己实现,按照这种约定的方式(即SPI规范),服务提供商会调用文件中实现类的方法,从而完成扩展。
  • 在往简单说:定义一个借口,把他的实现的全限定类名配置到一个配置文件,然后通过JDK8提供的方法进行调用。
public class Test {
    public static void main(String[] args) {
	// 得到你配置的所有实现了IUserDao的接口
        ServiceLoader<IUserDao> daos = ServiceLoader.load(IUserDao.class);
	// 循环调用每个接口
        for (IUserDao dao : daos) {
            dao.save();
        }
    }
}
  • spring mvc与spi的关系:在Serlvet3-1的规范手册中:就提供了一种更加易于扩展可用于共享库可插拔的一种方式,参见8.2.4:
    Serlvet3-1的规范 8.2.4.png

  • 简单说就是放一个SPI规范:你在应用META-INF/services 路径下放一个 javax.servlet.ServletContainerInitailizer,里面配置具体的实现的全限定类名!容器(Tomcat)就会去调用

零配置SpringMVC实现方式的基础:HandlesTypes

  • 通过HandlesTypes可以将感兴趣的一些类注入到ServletContainerInitializerde的onStartup方法作为参数传入。
  • 在SpringServletContainerInitializer的类上配置了@HandlesTypes(WebApplicationInitializer.class)
  • tomcat会自动找到程序中所有的实现了WebApplicationInitializer接口的实现类。
  • 然后把找到的所有的WebApplicationInitializer实现类传入onStartUp方法的webAppInitializerClasses参数上!

SPI的方式SpringMVC启动原理

  • 外置Tomcat启动的时候通过SPI的方式找到我们应用中的/META-INF/service/javax.servlet.ServletContainerInitializer。里面会配置org.springframework.web.SpringServletContainerInitializer。
  • 所以会调用SpringServletContainerInitializer.onStartUp()方法。
  • 调用onStartUp()前会先找到@HandlesTypes(WebApplicationInitializer.class) 所有实现了WebApplicationInitializer的类,传入到OnStartup的webAppInitializerClasses参数中,并传入Servlet上下文对象。
public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
		throws ServletException {

	// 得到一个空的数组,里面存放实例化好的WebApplicationInitializer对象
	List<WebApplicationInitializer> initializers = Collections.emptyList();

	// 找到有WebApplicationInitializer的实现类
	if (webAppInitializerClasses != null) {
		// 创建一个有长度的数组,长度和找到的WebApplicationInitializer实现类数量一致
		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);
				}
			}
		}
	}

	// 没有实例化的WebApplicationInitializer对象,直接返回。。
	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);
	}
}

spring整合spring mvc的思路(无xml方式)

  • 直接看默认是没有一个类(不是接口,不是抽象类的)去实现了WebApplicationInitializer接口,所以需要我们自己去实现
/**
 * ZhangweiStarterInitializer需要在META-INF/services/javax.servlet.ServletContainerInitailizer进行配置!
 * AbstractAnnotationConfigDispatcherServletInitializer 他的父类是WebApplicationInitializer
 */
public class ZhangweiStarterInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

	/**
	 * 方法实现说明:IOC 父容器的启动类
	 */
	@Override
	protected Class<?>[] getRootConfigClasses() {
		return new Class[]{RootConfig.class};
	}

	/**
	 * 方法实现说明 IOC子容器配置 web容器配置
	 */
	@Override
	protected Class<?>[] getServletConfigClasses() {
		return new Class[]{WebAppConfig.class};
	}

	/**
	 * 方法实现说明
	 * @return: 我们前端控制器DispatcherServlet的拦截路径
	 */
	@Override
	protected String[] getServletMappings() {
		return new String[]{"/"};
	}
}

/**
* IOC根容器,不扫描Controller的注解!
*/
@Configuration
@ComponentScan(basePackages = "com.zhangwei",excludeFilters = {
		@ComponentScan.Filter(type = FilterType.ANNOTATION,value={Controller.class}),
		@ComponentScan.Filter(type = ASSIGNABLE_TYPE,value =WebAppConfig.class ),
})
public class RootConfig {}


/**
* 子容器
*/
@Configuration
@ComponentScan(basePackages = {"com.zhangwei"},includeFilters = {
      @ComponentScan.Filter(type = FilterType.ANNOTATION,value = {RestController.class, Controller.class})
},useDefaultFilters =false)
@EnableWebMvc   // ≈<mvc:annotation-driven/>
public class WebAppConfig implements WebMvcConfigurer{

}

WebApplicationInitializer接口的实现类执行原理:onStartup源码调用逻辑

/**
 * 调用起始位置。
 * 源码位置:org.springframework.web.servlet.support.AbstractDispatcherServletInitializer.onStartup(ServletContext)
 */
public void onStartup(ServletContext servletContext) throws ServletException {
	//注册registerContextLoaderListener
	super.onStartup(servletContext);
	//注册registerDispatcherServlet
	registerDispatcherServlet(servletContext);
}

/**
 * 父类的onStartup
 * 源码位置:org.springframework.web.context.AbstractContextLoaderInitializer.onStartup(ServletContext)
 */
public void onStartup(ServletContext servletContext) throws ServletException {
	// 父容器的注册
	registerContextLoaderListener(servletContext);
}

/**
 * 注册ApplicationContext
 * 源码位置:org.springframework.web.context.AbstractContextLoaderInitializer.onStartup(ServletContext)
 */
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");
	}
}

/**
 * 创建父容器
 * 源码位置:org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer.createRootApplicationContext()
 */
protected WebApplicationContext createRootApplicationContext() {
	// 这里得到的是我们自己的配置类
	Class<?>[] configClasses = getRootConfigClasses();
	if (!ObjectUtils.isEmpty(configClasses)) {
		// 创建spring容器!
		AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
		// 注册当前的配置类
		context.register(configClasses);
		return context;
	}
	else {
		return null;
	}
}

/**
 * 注册DispatcherServlet
 * 源码位置:org.springframework.web.servlet.support.AbstractDispatcherServletInitializer.registerDispatcherServlet(ServletContext)
 */
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);
}

/**
 * 创建子容器
 * 源码位置:org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer.createServletApplicationContext()
 */
protected WebApplicationContext createServletApplicationContext() {
	// 创建一个spring容器
	AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
	// 调用我们重写的配置类:WebAppConfig
	Class<?>[] configClasses = getServletConfigClasses();
	if (!ObjectUtils.isEmpty(configClasses)) {
		// 给容器注册配置类
		context.register(configClasses);
	}
	return context;
}

监听器的初始化源码分析:核心是调用父容器的refresh方法

/**
 * 监听器的初始化源码开始位置。tomcat会调用到这里
 * 源码位置:org.springframework.web.context.ContextLoaderListener.contextInitialized(ServletContextEvent)
 */
public void contextInitialized(ServletContextEvent event) {
	initWebApplicationContext(event.getServletContext());
}

/**
 * 初始化ApplicationContext
 * 源码位置:org.springframework.web.context.ContextLoader.initWebApplicationContext(ServletContext)
 */
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
	if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
		throw new IllegalStateException(
				"Cannot initialize context because there is already a root application context present - " +
				"check whether you have multiple ContextLoader* definitions in your web.xml!");
	}

	servletContext.log("Initializing Spring root WebApplicationContext");
	Log logger = LogFactory.getLog(ContextLoader.class);
	if (logger.isInfoEnabled()) {
		logger.info("Root WebApplicationContext: initialization started");
	}
	long startTime = System.currentTimeMillis();

	try {
		// xml会在这里创建
		if (this.context == null) {
			this.context = createWebApplicationContext(servletContext);
		}
		// 我们配置的注解会生成AnnotationConfigWebApplicationContext对象,属于ConfigurableWebApplicationContext
		if (this.context instanceof ConfigurableWebApplicationContext) {
			// 得到AnnotationConfigWebApplicationContext对象
			ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
			if (!cwac.isActive()) {
				// The context has not yet been refreshed -> provide services such as
				// setting the parent context, setting the application context id, etc
				// 这里在spring5以上,都是null。可以自己扩展!
				if (cwac.getParent() == null) {
					// The context instance was injected without an explicit parent ->
					// determine parent for root web application context, if any.
					// 默认返回null
					ApplicationContext parent = loadParentContext(servletContext);
					// 设置父容器为null
					cwac.setParent(parent);
				}
				// 配置刷新容器
				configureAndRefreshWebApplicationContext(cwac, servletContext);
			}
		}
		// 绑定父子容器!在servlet域中设置根容器(在子容器就可以直接拿到了)
		servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);



		// 获取线程上下文类加载器,默认为WebAppClassLoader
		ClassLoader ccl = Thread.currentThread().getContextClassLoader();
		// 如果spring的jar包放在每个webapp自己的目录中
		// 此时线程上下文类加载器会与本类的类加载器(加载spring的)相同,都是
		if (ccl == ContextLoader.class.getClassLoader()) {
			currentContext = this.context;
		}
		// 如果不同,也就是上面说的那个问题的情况,那么用一个map把刚才创建的
		else if (ccl != null) {
			// 一个webapp对应一个记录,后续调用时直接根据WebAppClassLoader来取出
			currentContextPerThread.put(ccl, this.context);
		}

		if (logger.isInfoEnabled()) {
			long elapsedTime = System.currentTimeMillis() - startTime;
			logger.info("Root WebApplicationContext initialized in " + elapsedTime + " ms");
		}

		return this.context;
	}
	catch (RuntimeException | Error ex) {
		logger.error("Context initialization failed", ex);
		servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
		throw ex;
	}
}

/**
 * 配置和刷新容器
 * 源码位置:org.springframework.web.context.ContextLoader.configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext, ServletContext)
 */
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();
}

初始化DispatcherServlet:核心是调用父容器的refresh方法

/**
 * 外置的tomcat会调用这个方法
 * 源码位置:org.springframework.web.servlet.HttpServletBean.init()
 */
public final void init() throws ServletException {

	// 解析 init-param 并封装只 pvs 中(xml)
	PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
	if (!pvs.isEmpty()) {
		try {
			// 将当前的这个 Servlet 类转化为一个 BeanWrapper,从而能够以 Spring 的方法来对 init-param 的值进行注入
			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) {
			if (logger.isErrorEnabled()) {
				logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
			}
			throw ex;
		}
	}

	// Let subclasses do whatever initialization they like.
	// 初始化Servlet的Bean
	initServletBean();
}

/**
 * 初始化Servlet的Bean
 * 源码位置:org.springframework.web.servlet.FrameworkServlet.initServletBean()
 */
protected final void initServletBean() throws ServletException {
	getServletContext().log("Initializing Spring " + getClass().getSimpleName() + " '" + getServletName() + "'");
	if (logger.isInfoEnabled()) {
		logger.info("Initializing Servlet '" + getServletName() + "'");
	}
	long startTime = System.currentTimeMillis();

	try {
		// 初始化容器!
		this.webApplicationContext = initWebApplicationContext();
		// 无实现
		initFrameworkServlet();
	}
	catch (ServletException | RuntimeException ex) {
		logger.error("Context initialization failed", ex);
		throw ex;
	}

	if (logger.isDebugEnabled()) {
		String value = this.enableLoggingRequestDetails ?
				"shown which may lead to unsafe logging of potentially sensitive data" :
				"masked to prevent unsafe logging of potentially sensitive data";
		logger.debug("enableLoggingRequestDetails='" + this.enableLoggingRequestDetails +
				"': request parameters and headers will be " + value);
	}

	if (logger.isInfoEnabled()) {
		logger.info("Completed initialization in " + (System.currentTimeMillis() - startTime) + " ms");
	}
}

/**
 * 初始化容器
 * 源码位置:org.springframework.web.servlet.FrameworkServlet.initWebApplicationContext()
 */
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;
}

/**
 * 配置并且加载子容器
 * 源码位置:org.springframework.web.servlet.FrameworkServlet.configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext)
 */
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
	if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
		// 设置id
		if (this.contextId != null) {
			wac.setId(this.contextId);
		}
		else {
			// Generate default id...
			wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
					ObjectUtils.getDisplayString(getServletContext().getContextPath()) + '/' + getServletName());
		}
	}
	// 设置servlet上下文
	wac.setServletContext(getServletContext());
	wac.setServletConfig(getServletConfig());
	wac.setNamespace(getNamespace());
	// 监听器(这里注册了很多的springmvc的组件)  委托设计模式
	wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));

	// 将init-param设置到Environment中
	ConfigurableEnvironment env = wac.getEnvironment();
	if (env instanceof ConfigurableWebEnvironment) {
		((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
	}
	// 空方法可扩展
	postProcessWebApplicationContext(wac);
	// 容器启动前初始化
	applyInitializers(wac);
	wac.refresh();
}

结束语

  • 获取更多本文的前置知识文章,以及新的有价值的文章,让我们一起成为架构师!
  • 目前已经完成了并发编程、MySQL、spring源码、Mybatis的源码。可以在公众号下方菜单点击查看之前的文章!
  • 接下来的目标是深入分析JVM、tomcat、redis
  • 这个公众号,无广告!!!每日更新!!!
    作者公众号.jpg
posted @ 2022-03-28 21:51  程序java圈  阅读(58)  评论(0编辑  收藏  举报