SpringMVC 框架的理解与分析(1)

SpringMVC 框架的理解与分析

1.介绍

Spring框架是IOC容器思想的一个具体实现 ,而SpringMVC则是在SpringIOC 的容器基础上来进一步扩展的用于Web 环境的框架。

Spring IOC 是Spring框架内部的一个模块,而如果IOC 容器用于Web 环境的话,则容器启动初始化 需要伴随着Web容器(Tomcat/Weblogic/jboss)的启动而启动的,进而把IOC容器加载到Web环境中去;

下面是以tomcat 作为web 容器的例子进行分析 ,在tomcat中web.xml是应用的部署描述文件

 <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:applicationContext.xml</param-value>
    </context-param>
    <!-- 配置Spring MVC Servlet -->
    <servlet>
        <servlet-name>yyqdata</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:springMvc.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>yyqdata</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

1.1 ContextLoaderListener 简述

首先我们看看ContextLoaderListener这里先简单的说一下ContextLoaderListener看传承结构

public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
package javax.servlet;

import java.util.EventListener;

	/** 
	 * Implementations of this interface receive notifications about
	 * changes to the servlet context of the web application they are
	 * part of.
	 * To receive notification events, the implementation class
	 * must be configured in the deployment descriptor for the web
	 * application.
	 * @see ServletContextEvent
	 * @since	v 2.3
	 */

public interface ServletContextListener extends EventListener {

ContextLoaderListener(spring实现的)其实是servlet 规范的一个实现(不懂的可以网上搜索查看servlet 规范 的监听器机制)。

它的主要功能是关联到web服务器(tomcat)的生命周期,何时启动?进而会调用它的方法contextInitialized 会对spring 的IOC 容器触发初始化工作 根据配置的applicationContext.xml

1.2 DispatcherServlet 简述

DispatcherServlet是Spring MVC 核心类用于请求的转发 在xml中的定义完全符合Servlet 规范的配置,用于拦截请求,后续会详细讲解

contextInitialized 初始化方法中 会传入一个对象就是 event.getServletContext()==ServletContext

@Override
	public void contextInitialized(ServletContextEvent event) {
		initWebApplicationContext(event.getServletContext());
	}

ServletContext(也是属于Servlet 规范的内容) 该对象就是web 容器和Spring 接口的一个耦合。实现请求转发到DispatcherServlet

备注:Web应用部署好以后,Servlet容器在启动时会加载Web应用,并为每个Web应用创建唯一的ServletContext对象。你可以把ServletContext看成是一个全局对象,一个Web应用可能有多个Servlet,这些Servlet可以通过全局的ServletContext来共享数据,这些数据包括Web应用的初始化参数,Web应用目录下的文件资源等。因为ServletContext持有所有Servlet实例,还可以通过它来实现Servlet请求的转发。

2.上下文在web环境中的启动

测试代码

package com.sy.controller;

import com.sy.service.DemoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
 

/**
 * @author SFF
 * @version 1.0.0
 * @description
 * @date 2020/10/26 - 23:11
 */
@Controller
@RequestMapping("/demo")
public class DemoController {

    @Autowired
    private DemoService demoService;

    public DemoController() {
        System.out.println("chushihua");
    }

    @RequestMapping("/index")
    public String index(){
        return "/demo/index";
    }
}

package com.sy.service;

import org.springframework.stereotype.Service;

/**
 * @author SFF
 * @version 1.0.0
 * @description
 * @date 2020/10/28 - 22:08
 */
@Service
public class DemoService {


    public DemoService() {
        System.out.println("DemoService");
    }

    public void fun() {
        System.out.println("DemoService:fun ");
    }
}

继承体系

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UCDss9Tp-1609773404640)(C:\Users\sff\AppData\Roaming\Typora\typora-user-images\1603894711835.png)]

2.1 IOC 容器的启动过程

扩一步讲解IOC 容器的启动过程,容器启动加载ContextLoaderListener然后调用方法 contextInitialized 在该方法中携带了一个ServletContext对象

直接贴代码

1.开始初始化根(父)容器 也就是是Spring 的IOC

@Override
	public void contextInitialized(ServletContextEvent event) {
		initWebApplicationContext(event.getServletContext());//携带ServletContext  对象
	}

2.ContextLoader的方法initWebApplicationContext 部分核心片段

public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
//   servletContext  的  ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE 在容器初始化之前是没有值得初始化之后才会赋值  后面会看到   会把上下文对象赋值到 里面去
		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!");
		}
.................................
		long startTime = System.currentTimeMillis();

		try {
			// Store context in local instance variable, to guarantee that
			// it is available on ServletContext shutdown.
			if (this.context == null) {
                // 创建上面类型的上下文环境   创建一个还在初始化状态的context
				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);
					}
                    //初始化容器
					configureAndRefreshWebApplicationContext(cwac, servletContext);
				}
			}
			servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);

			.............

			return this.context;
		}
	.........
		}
  1. configureAndRefreshWebApplicationContext
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
		.........
 
		ConfigurableEnvironment env = wac.getEnvironment();
		if (env instanceof ConfigurableWebEnvironment) {
			((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
		}
    //设置xml 文件资源位置
    String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
		if (configLocationParam != null) {
			wac.setConfigLocation(configLocationParam);
		}
    
    	wac.setServletContext(getServletContext());
		wac.setServletConfig(getServletConfig());
		wac.setNamespace(getNamespace());
   //!!!!!!直接添加了一个监听器  这个监听器会在 持有的ioc 容器结束时调用监听器内部类的一个方法
    //然后转到DispatacherServlet  的初始化策略
		wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));

		customizeContext(sc, wac);
    //核心方法ConfigurableWebApplicationContext 刷新容器
    //这里的wac  其实就是webApplicationContext
		wac.refresh();
	}

4.refresh 方法

public interface WebApplicationContext extends ApplicationContext

到这里想必都是知道了 refresh ()方法才是IOC 容器真正初始化的方法 这里就直接进入了业务层代码断点

public DemoService() {
    // 业务层对象被初始化  根据ApplicationConText.xml
        System.out.println("DemoService");
    }

到此 web 环境里面的ioc 容器初始化完成

2.2 web容器的上下文设计

在SpringIOC 容器启动的过程中 会默认使用webApplicationContext的实现作为IOC 容器 ,而这个实现就是XmlWebApplicationContext

注: 为什么是这个呢? 在框架初始化会加载一个文件 在ContextLoaderContextLoaderListener的父类)的静态代码块中 加载了文件

private static final String DEFAULT_STRATEGIES_PATH = "ContextLoader.properties";


	private static final Properties defaultStrategies;

	static {
		try {
			ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, ContextLoader.class);
			defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
		}
		catch (IOException ex) {
			throw new IllegalStateException("Could not load 'ContextLoader.properties': " + ex.getMessage());
		}
	}

文件内容

org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HnyjfP8v-1609773404642)(C:\Users\sff\AppData\Roaming\Typora\typora-user-images\1603897436380.png)]

现在再来看看这个 XmlWebApplicationContext 继承结构 还是重ApplicationContext实现来的 ,XmlWebApplicationContext 主要是做的是对于Web 环境的XML 定义的一个适配,比如默认的xml 路径

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VDewuuQf-1609773404643)(C:\Users\sff\AppData\Roaming\Typora\typora-user-images\1603897980557.png)]

public class XmlWebApplicationContext extends AbstractRefreshableWebApplicationContext {

	/** Default config location for the root context */
	public static final String DEFAULT_CONFIG_LOCATION = "/WEB-INF/applicationContext.xml";

	/** Default prefix for building a config location for a namespace */
	public static final String DEFAULT_CONFIG_LOCATION_PREFIX = "/WEB-INF/";

	/** Default suffix for building a config location for a namespace */
	public static final String DEFAULT_CONFIG_LOCATION_SUFFIX = ".xml";

每个ApplicationContext的具体实现,熟悉Spring 初始化的都知道 ,主要的区别还是loadBeanDefinitions 这个方法的区别 loadBeanDefinitions 方法会是在ApplicationContextrefresh ()中根据不同的实现来调用具体的loadBeanDefinitions

下面来看看XmlWebApplicationContext如何加装资源文件 先看2.1 .3 中路径的已经设置

@Override
	protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
		// Create a new XmlBeanDefinitionReader for the given BeanFactory.
		XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);

		// Configure the bean definition reader with this context's
		// resource loading environment.
		beanDefinitionReader.setEnvironment(getEnvironment());
		beanDefinitionReader.setResourceLoader(this);
		beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));

		// Allow a subclass to provide custom initialization of the reader,
		// then proceed with actually loading the bean definitions.
		initBeanDefinitionReader(beanDefinitionReader);
		loadBeanDefinitions(beanDefinitionReader);
	}
	protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws IOException {
		String[] configLocations = getConfigLocations();
		if (configLocations != null) {
			for (String configLocation : configLocations) {
				reader.loadBeanDefinitions(configLocation);
			}
		}
	}
	protected String[] getConfigLocations() {
		return (this.configLocations != null ? this.configLocations : getDefaultConfigLocations());
	}

这里就定位到了自己设置的配置文件地址

到了这里如果没有设置资源文件位置呢 不烦做个测试 会走到这个方法

@Override
	protected String[] getDefaultConfigLocations() {
		if (getNamespace() != null) {
			return new String[] {DEFAULT_CONFIG_LOCATION_PREFIX + getNamespace() + DEFAULT_CONFIG_LOCATION_SUFFIX};
		}
		else {
			return new String[] {DEFAULT_CONFIG_LOCATION};
		}
	}

DEFAULT_CONFIG_LOCATION就是前面默认的资源文件位置"/WEB-INF/applicationContext.xml";

然后继而加载beanDefinition 初始化bean

3.Spring MVC的设计和实现

3.1 介绍

我们都知道Spring MVC 是Spring框架对于MVC思想的一个具体实现,对于所有的基于servlet 规范的javaweb应用,在使用Spring MVC时还需要配置一个Dispatchservlet。 它是servlet 的一个具体实现,是实现Sun公司J2EE核心模式中的前端控制器模式,是对于过来的请求处理 转发 匹配 最后页面显示。也是Spring MVC 对于web 应用的一个核心类和核心入口方法。

3.2 Spring MVC 设计概括
  1. 对于前面web.xml 的分析可知,spring初始化的时候对contextLoadListening 和DispatchServlet 建立并初始化的过程。先是有web容器的生命周期方法调用contextLoadListening 里面方法初始化ioc 容器,然后在根据启动次序(可配置)
   <load-on-startup>1</load-on-startup>

来完成对DispatchServlet 的初始化。contextLoadListening 初始化后会把容器对象放在 见2.1.2 29行代码

servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);

会在这个对象里面获取到IOC 容器并作为DispatcherServlet持有的上下文的双亲上下文

  1. 下面先看看DispatcherServlet的一个类继承结构

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BxUS3eAP-1609773404646)(C:\Users\sff\AppData\Roaming\Typora\typora-user-images\1604329094395.png)]

通过servlet 的一个生命周期函数 init() services () doget() doPost() distory() 等

DispatcherServlet是一个标准的servlet 所以他的初始化也会在init()方法为入口

下面就来看一个DispatcherServlet 的 init() 方法 这个方法是继承自 HttpServletBean

@Override
	public final void init() throws ServletException {
		if (logger.isDebugEnabled()) {
			logger.debug("Initializing servlet '" + getServletName() + "'");
		}
		//设置一些配置的requiredProperties  
		// Set bean properties from init parameters.
		try {
			PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
			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) {
			logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
			throw ex;
		}

		// Let subclasses do whatever initialization they like.
        //====> 和兴方法  初始化 ServletBean
		initServletBean();

		if (logger.isDebugEnabled()) {
			logger.debug("Servlet '" + getServletName() + "' configured successfully");
		}
	}
  1. initServletBean() 也是继承于 FrameworkServlet
	protected WebApplicationContext initWebApplicationContext() {
		WebApplicationContext rootContext =
// a) 获取到跟上下文   就是监听器创建的那个上下文对象				WebApplicationContextUtils.getWebApplicationContext(getServletContext());
		WebApplicationContext wac = null;

		if (this.webApplicationContext != null) {
			// A context instance was injected at construction time -> use it
			wac = this.webApplicationContext;
			if (wac instanceof ConfigurableWebApplicationContext) {
				ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
				if (!cwac.isActive()) {
					// The context has not yet been refreshed -> provide services such as
					// setting the parent context, setting the application context id, etc
					if (cwac.getParent() == null) {
						// The context instance was injected without an explicit parent -> set
						// the root application context (if any; may be null) as the parent
						cwac.setParent(rootContext);
					}
					configureAndRefreshWebApplicationContext(cwac);
				}
			}
		}
		if (wac == null) {
			// No context instance was injected at construction time -> see if one
// has been registered in the servlet context. If one exists, it is assumed
			// that the parent context (if any) has already been set and that the
			// user has performed any initialization such as setting the context id
			wac = findWebApplicationContext();
		}
		if (wac == null) {
   //b) 第一次创建容器上下文 并且把获取到的根上下文作为父级上下文
			// No context instance is defined for this servlet -> create a local one
			wac = createWebApplicationContext(rootContext);
		}

		if (!this.refreshEventReceived) {
			// Either the context is not a ConfigurableApplicationContext with refresh
			// support or the context injected at construction time had already been
			// refreshed -> trigger initial onRefresh manually here.
			onRefresh(wac);
		}

		if (this.publishContext) {
			// Publish the context as a servlet context attribute.
			String attrName = getServletContextAttributeName();
			getServletContext().setAttribute(attrName, wac);
			if (this.logger.isDebugEnabled()) {
				this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +
						"' as ServletContext attribute with name [" + attrName + "]");
			}
		}

		return wac;
	}

a) 获取根(父)上下文

public static WebApplicationContext getWebApplicationContext(ServletContext sc) {
		return getWebApplicationContext(sc, WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
	}

b) 第一次创建容器上下文 并且把获取到的根上下文作为父级上下文

protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) {
Class<?> contextClass = getContextClass();//默认的是 XmlWebApplicationContext.class;
		if (this.logger.isDebugEnabled()) {
			this.logger.debug("Servlet with name '" + getServletName() +
					"' will try to create custom WebApplicationContext context of class '" +
					contextClass.getName() + "'" + ", using parent context [" + parent + "]");
		}
		if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
			throw new ApplicationContextException(
					"Fatal initialization error in servlet with name '" + getServletName() +
					"': custom WebApplicationContext class [" + contextClass.getName() +
					"] is not of type ConfigurableWebApplicationContext");
		}
		ConfigurableWebApplicationContext wac =
				(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
		//创建好后 赋值环境对象
		wac.setEnvironment(getEnvironment());
    //设置上级上下文
		wac.setParent(parent);
    //根据自己配置的资源路径地址来加载配置或者bean的资源
		wac.setConfigLocation(getContextConfigLocation());
	//刷新容器 在这里面  ===> ioc 的核心方法 刷新容器 AbstractApplicationContext的方法
		configureAndRefreshWebApplicationContext(wac);

		return wac;
	}
  1. DispatcherServlet 的容器 refresh() 刷新的是SpringMVC 里面的一些bean 对象和配置SpringMVC 的控制层对象 也就是在在SpringMVC.xml 里面的bean
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
	..........
		postProcessWebApplicationContext(wac);
		applyInitializers(wac);
    ===> 核心方法中的最后一步  发布事件
		wac.refresh();
	}
//后续会用到这个方法 初始化的对象 SimpleApplicationEventMulticaster
initApplicationEventMulticaster()

// Last step: publish corresponding event.
finishRefresh();

我们看看着DispatherServlet 持有的容器(webApplicationContext)创建的最后一步做了什么

protected void finishRefresh() {
		// Initialize lifecycle processor for this context.
		initLifecycleProcessor();

		// Propagate refresh to lifecycle processor first.
		getLifecycleProcessor().onRefresh();

		// Publish the final event.
    //====> 发布事件  this ==>就是webApplicationContext
		publishEvent(new ContextRefreshedEvent(this));

		// Participate in LiveBeansView MBean, if active.
		LiveBeansView.registerApplicationContext(this);
	}
  1. 简略的说一下事件的发布 publishEvent(new ContextRefreshedEvent(this)); 一个事件 容器里面所有持有ContextRefreshedEvent类型的bean 回去匹配到这个事件 然后触发后续的运行

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3txK3twr-1609773404648)(C:\Users\sff\AppData\Roaming\Typora\typora-user-images\1604502072476.png)]

a) 还是 AbstractApplicationContext 的方法

protected void publishEvent(Object event, ResolvableType eventType) {
		....
	.
		if (this.earlyApplicationEvents != null) {
			this.earlyApplicationEvents.add(applicationEvent);
		}
		else {
            //获取时间播发器  有默认实现ApplicationEventMulticaster  统一由该对象来发布
            //===>
			getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
		}

		// Publish event via parent context as well...
		if (this.parent != null) {
			if (this.parent instanceof AbstractApplicationContext) {
				((AbstractApplicationContext) this.parent).publishEvent(event, eventType);
			}
			else {
				this.parent.publishEvent(event);
			}
		}
	}
	@Override
	public void multicastEvent(final ApplicationEvent event, ResolvableType eventType) {
        
        //解析 时间的类型为 ContextRefreshedEvent
		ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
        
        // getApplicationListeners(event, type) 为根据监听器类型和持有的类型匹配到事件
		for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) {
			Executor executor = getTaskExecutor();
			if (executor != null) {
				executor.execute(new Runnable() {
					@Override
					public void run() {
						invokeListener(listener, event);
					}
				});
			}
			else {
				invokeListener(listener, event);
			}
		}
	}
///===>listener.onApplicationEvent(event);

b) SourceFilteringListener 使用装饰者模式 持有 真正干活的 见2.1.3

private GenericApplicationListener delegate;

还有一层修饰
其实还是使用 GenericApplicationListener 的子类GenericApplicationListenerAdapter 中
	@Override
	public void onApplicationEvent(ApplicationEvent event) {
	// delegate 对象是什么呢  看下面 所以  有 看  ==>c  ContextRefreshListener.onApplicationEvent()  的方法中
		this.delegate.onApplicationEvent(event);
	}

回顾2.1.3

wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));

====>
public SourceFilteringListener(Object source, ApplicationListener<?> delegate) {
		this.source = source;
		this.delegate = (delegate instanceof GenericApplicationListener ?
				(GenericApplicationListener) delegate : new GenericApplicationListenerAdapter(delegate));
	}
	===>GenericApplicationListenerAdapter 的 构造方法
public GenericApplicationListenerAdapter(ApplicationListener<?> delegate) {
		Assert.notNull(delegate, "Delegate listener must not be null");
		this.delegate = (ApplicationListener<ApplicationEvent>) delegate;
		this.declaredEventType = resolveDeclaredEventType(this.delegate);
	}
  // delegate 被出实话为     ContextRefreshListener()=

C) ContextRefreshListener 是 FrameworkServlet ((也是DispatherServlet 父类))的内部类

private class ContextRefreshListener implements ApplicationListener<ContextRefreshedEvent> {

		@Override
		public void onApplicationEvent(ContextRefreshedEvent event) {
            
            //继续调用的 外部类的onApplicationEvent 方法
			FrameworkServlet.this.onApplicationEvent(event);
		}
	}
//FrameworkServlet 
	public void onApplicationEvent(ContextRefreshedEvent event) {
		this.refreshEventReceived = true;
        //被子类  DispatherServlet   方法
		onRefresh(event.getApplicationContext());
	}

//DispatherServlet
@Override
	protected void onRefresh(ApplicationContext context) {
		initStrategies(context);
	}
  1. 到此终于到了DispatherServlet 的有一个核心方法initStrategies
3.3 initStrategies () 初始化
3.3.1 介绍

在这个方法里面 将会对SpringMVC的 DispatcherServlet 的一些核心组件 的一个初始化工作开始

在DispatcherServlet 定义了很多默认的bean的名字 ,也就是后面框架的一些核心组件的bean的名称

 	//文件解析器
	public static final String MULTIPART_RESOLVER_BEAN_NAME = "multipartResolver";
 	//区域解析器
	public static final String LOCALE_RESOLVER_BEAN_NAME = "localeResolver";
	//样式解析器
	public static final String THEME_RESOLVER_BEAN_NAME = "themeResolver";
	//映射器处理器
	public static final String HANDLER_MAPPING_BEAN_NAME = "handlerMapping";
	//映射器适配器
	public static final String HANDLER_ADAPTER_BEAN_NAME = "handlerAdapter";
	//异常解析器
	public static final String HANDLER_EXCEPTION_RESOLVER_BEAN_NAME = "handlerExceptionResolver";
	//试图名称转换器
	public static final String REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME = "viewNameTranslator";
	//试图解析器
	public static final String VIEW_RESOLVER_BEAN_NAME = "viewResolver";

	 // 。。。
	public static final String FLASH_MAP_MANAGER_BEAN_NAME = "flashMapManager";

其中还有一段代码 加载对应的默认类

private static final String DEFAULT_STRATEGIES_PATH = "DispatcherServlet.properties";
	private static final Properties defaultStrategies;

	static {
		try {
			ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);
			defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
		}
		catch (IOException ex) {
			throw new IllegalStateException("Could not load 'DispatcherServlet.properties': " + ex.getMessage());
		}
	}

看看文件内容

# Default implementation classes for DispatcherServlet's strategy interfaces.
# Used as fallback when no matching beans are found in the DispatcherServlet context.
# Not meant to be customized by application developers.

org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver

org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver

org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
	org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping

org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
	org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
	org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter

org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerExceptionResolver,\
	org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
	org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver

org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator

org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver

org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager
//	DispatherServlet
protected void initStrategies(ApplicationContext context) {
    
    // 文件上传解析器
		initMultipartResolver(context);
    //支持国际化,区域解析器。每DispatcherServlet只能注册一个区域解析器
		initLocaleResolver(context);
    // 动态更换样式的支持(主题)
		initThemeResolver(context);
//BeanNameUrlHandlerMapping: 查找spring容器中和请求的url同名的bean.
//BeanNameUrlHandlerMapping :通过对比url和bean的name找到对应的对象 
//SimpleUrlHandlerMapping :也是直接配置url和对应bean,比BeanNameUrlHandlerMapping功能更多 
//DefaultAnnotationHandlerMapping : 主要是针对注解配置@RequestMapping的,已过时 
//RequestMappingHandlerMapping :取代了上面一个 
		initHandlerMappings(context);
//SimpleControllerHandlerAdapter
// SimpleServletHandlerAdapter
//RequestMappingHandlerAdapter
// HttpRequestHandlerAdapter
// AnnotationMethodHandlerAdapter
		initHandlerAdapters(context);
    //异常解析处理
		initHandlerExceptionResolvers(context);
    //用于直接将请求转换为逻辑视图名。
		initRequestToViewNameTranslator(context);
    //视图解析器:定义了如何通过view 名称来解析对应View实例的行为
		initViewResolvers(context);
    //SessionFlashMapManager
		initFlashMapManager(context);
	}
3.3.2 初始化文件解析器 MultipartResolver

直接在容器里面查找 没有的就返回null 会在文件无法上传

MULTIPART_RESOLVER_BEAN_NAME = "multipartResolver";
private void initMultipartResolver(ApplicationContext context) {
		try {
            //直接在容器里面查找  没有的就返回null 会在文件无法上传
			this.multipartResolver = context.getBean(MULTIPART_RESOLVER_BEAN_NAME, MultipartResolver.class);
			if (logger.isDebugEnabled()) {
				logger.debug("Using MultipartResolver [" + this.multipartResolver + "]");
			}
		}
		catch (NoSuchBeanDefinitionException ex) {
			// Default is no multipart resolver.
			this.multipartResolver = null;
			if (logger.isDebugEnabled()) {
				logger.debug("Unable to locate MultipartResolver with name '" + MULTIPART_RESOLVER_BEAN_NAME +
						"': no multipart request handling provided");
			}
		}
	}

按需要配置 如果你的应用需要文件上传 着还需要添加依赖 并设置bean 交由spring IOC 管理

  • 添加依赖
   <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-core</artifactId>
            <version>${jackson.version}</version>
        </dependency>

  • 添加配置 在springMVC.xml(其实两个配置文件都可以 )
<bean id="multipartResolver"  class="org.springframework.web.multipart.commons.CommonsMultipartResolver" >
    <!--上传文件的最大大小 -->
        <property name="maxUploadSize" value="1024000"></property>
    <!--文件的字符集-->
    <property name="defaultEncoding" value="UTF-8"></property>
</bean>                          
3.3.3 初始化区域解析器 LocaleResolver

一般没有配置 看3.3.1 中的默认配置 AcceptHeaderLocaleResolver 毫无疑问又是反射创建这个对象,有兴趣的同学可以追一下这个代码,这里就先不说了;

private void initLocaleResolver(ApplicationContext context) {
		try {
			this.localeResolver = context.getBean(LOCALE_RESOLVER_BEAN_NAME, LocaleResolver.class);
			if (logger.isDebugEnabled()) {
				logger.debug("Using LocaleResolver [" + this.localeResolver + "]");
			}
		}
		catch (NoSuchBeanDefinitionException ex) {
			// We need to use the default. 使用默认配置
			this.localeResolver = getDefaultStrategy(context, LocaleResolver.class);
			if (logger.isDebugEnabled()) {
				logger.debug("Unable to locate LocaleResolver with name '" + LOCALE_RESOLVER_BEAN_NAME +
						"': using default [" + this.localeResolver + "]");
			}
		}
	}
3.3.4 初始化处理器映射器 HandlerMappings

RequestMappingInfoHandlerMapping 如何 和默认的两个一起被加载?

private void initHandlerMappings(ApplicationContext context) {
		this.handlerMappings = null;

		if (this.detectAllHandlerMappings) {
//在上下文中获取所有的 HandlerMapping 用于处理请求
			Map<String, HandlerMapping> matchingBeans =
					BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
			if (!matchingBeans.isEmpty()) {
				this.handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values());
				// 排序
				AnnotationAwareOrderComparator.sort(this.handlerMappings);
			}
		}
		else {
			try {
				HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
				this.handlerMappings = Collections.singletonList(hm);
			}
			catch (NoSuchBeanDefinitionException ex) {
				// Ignore, we'll add a default HandlerMapping later.
               //忽略错误  我们后续添加默认的处理器映射器
			}
		}

		// Ensure we have at least one HandlerMapping, by registering
		// a default HandlerMapping if no other mappings are found.
		if (this.handlerMappings == null) {
			this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
			if (logger.isDebugEnabled()) {
				logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default");
			}
		}
	}

下面我来深度解析这个方法 这个方法也是困扰了我很久的一个方法 首先看这个方法 在容器里面获取所有的HandlerMapping 类型的bean 对象 这个方法是获取到了三个对象(按照前面配置的web.xml),令我困惑的一点是为什么或者说是什么时候注册的这三个bean对象到SpringIOC 里面去的。

	Map<String, HandlerMapping> matchingBeans =
					BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);

最开始我的思路是找DispatcherServlet.properties 配置文件 然后查看配置文件里面配置的bean和这个方法获取到不一样。

于是我就换了一个思路就是在DefaultListableBeanFactory(至于为什么是这个方法 ,我就不多说了,简单的就是spring容器bean 工厂) 的内部维护beanDefinitionMap 的put 方法处打了一个断点 果然 看到了这三个bean是如何定义的 ,这里说明一下DefaultListableBeanFactory 是DispatcherServlet内部维护的子容器的工厂(需要跳过父容器的断点,也就是说第一次打算的是父容器bean定义的加载 是看不到HandlerMapping 加载的)

a) 看代码org.springframework.beans.factory.xml.BeanDefinitionParserDelegate 中 熟悉IOC 的都知道这个类是解析xml 文件获取bean的定义的

	public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
        //获取xml文件的命名空间定义
		String namespaceUri = getNamespaceURI(ele);
        //根据不同的 命名空间使用不同的解析器
		NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
		if (handler == null) {
		。。。。。。。
		}//[mvc:annotation-driven: null] 就是MvcNamespaceHandler
		return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
	}

1.RequestMappingHandlerMapping

@Override
	public BeanDefinition parse(Element element, ParserContext parserContext) {
        //findParserForElement(element, parserContext)  根据内容annotation-driven 
        //又获得了基于注解的解析器 AnnotationDrivenBeanDefinitionParser
       // org.springframework.web.servlet.config.AnnotationDrivenBeanDefinitionParser
		return findParserForElement(element, parserContext).parse(element, parserContext);
	}
//解析方法里面

@Override
	public BeanDefinition parse(Element element, ParserContext parserContext) {
        ···
 //HANDLER_MAPPING_BEAN_NAME  ==>org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping           readerContext.getRegistry().registerBeanDefinition(HANDLER_MAPPING_BEAN_NAME , handlerMappingDef);
            ···  注册其他bean
  1. 在上面代码同样的方法中
//解析方法里面

@Override
	public BeanDefinition parse(Element element, ParserContext parserContext) {
    ....
        // Ensure BeanNameUrlHandlerMapping (SPR-8289) and default HandlerAdapters are not "turned off"
        //确保BeanNameUrlHandlerMapping 和默认的适配器被初始化
		MvcNamespaceUtils.registerDefaultComponents(parserContext, source);
public static void registerDefaultComponents(ParserContext parserContext, Object source) {
		registerBeanNameUrlHandlerMapping(parserContext, source);
		registerHttpRequestHandlerAdapter(parserContext, source);
		registerSimpleControllerHandlerAdapter(parserContext, source);
	}
private static void registerBeanNameUrlHandlerMapping(ParserContext parserContext, Object source) {
		if (!parserContext.getRegistry().containsBeanDefinition(BEAN_NAME_URL_HANDLER_MAPPING_BEAN_NAME)){
			RootBeanDefinition beanNameMappingDef = new RootBeanDefinition(BeanNameUrlHandlerMapping.class);
			beanNameMappingDef.setSource(source);
			beanNameMappingDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
			beanNameMappingDef.getPropertyValues().add("order", 2);	// consistent with WebMvcConfigurationSupport
			RuntimeBeanReference corsConfigurationsRef = MvcNamespaceUtils.registerCorsConfigurations(null, parserContext, source);
			beanNameMappingDef.getPropertyValues().add("corsConfigurations", corsConfigurationsRef);
//BEAN_NAME_URL_HANDLER_MAPPING_BEAN_NAME   ===>org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping 注册完成
            parserContext.getRegistry().registerBeanDefinition(BEAN_NAME_URL_HANDLER_MAPPING_BEAN_NAME, beanNameMappingDef);
			parserContext.registerComponent(new BeanComponentDefinition(beanNameMappingDef, BEAN_NAME_URL_HANDLER_MAPPING_BEAN_NAME));
		}
	}
  1. [mvc:default-servlet-handler: null]
public BeanDefinition parse(Element element, ParserContext parserContext) {
    //element==>mvc:default-servlet-handler: null
    //findParserForElement(element, parserContext)==>class org.springframework.web.servlet.config.DefaultServletHandlerBeanDefinitionParser
		return findParserForElement(element, parserContext).parse(element, parserContext);
	}
@Override
	public BeanDefinition parse(Element element, ParserContext parserContext) {
        String handlerMappingBeanName = parserContext.getReaderContext().generateBeanName(handlerMappingDef);
        //handlerMappingBeanName==>org.springframework.web.servlet.handler.SimpleUrlHandlerMapping#0
		parserContext.getRegistry().registerBeanDefinition(handlerMappingBeanName, handlerMappingDef);
    }

至此 可以大致可以知道为什么在最开的是方法中 在ioc容器里面可以获取到三个默认的

  • RequestMappingHandlerMapping

  • BeanNameUrlHandlerMapping

  • SimpleUrlHandlerMapping

也可以从细节中可以看出来前两个的HandlerMapping 的初始化时在web.xml 定义了开启注解开发 <mvc:annotation-driven/>

后面 SimpleUrlHandlerMapping是开启了默认资源访问 <mvc:default-servlet-handler />

最后我还是抱有严谨的态度测试了以下 在配置文件的 <mvc:default-servlet-handler /> 删除 IOC 容器也没有初始化SimpleUrlHandlerMapping 把 基于注解 <mvc:default-servlet-handler /> 删除 也使得前面两个HandlerMapping 没有初始化 。

你以为这个方法到这里结束了吗 <mvc:default-servlet-handler /><mvc:default-servlet-handler /> 都没有配置的时候 ,springMVC 就不会给我默认的实现这个组件了吗? 答案是否定的 看代码 3.3.4 第一个28 行处 当在容器内部找不到 HandlerMapping 的时候,开会默认策略,读取配置文件里面的内容,也就是3.3.1第四代码块 里面的两个HandlerMapping

protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {
		String key = strategyInterface.getName();
    //在配置文件里面获取 //org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping
		String value = defaultStrategies.getProperty(key);
		if (value != null) {
			String[] classNames = StringUtils.commaDelimitedListToStringArray(value);
			List<T> strategies = new ArrayList<T>(classNames.length);
			for (String className : classNames) {
				try {//反射创建对象
					Class<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());
					Object strategy = createDefaultStrategy(context, clazz);
					strategies.add((T) strategy);
				}
				catch (ClassNotFoundException ex) {
					throw new BeanInitializationException(
							"Could not find DispatcherServlet's default strategy class [" + className +
									"] for interface [" + key + "]", ex);
				}
				catch (LinkageError err) {
					throw new BeanInitializationException(
							"Error loading DispatcherServlet's default strategy class [" + className +
									"] for interface [" + key + "]: problem with class file or dependent class", err);
				}
			}
			return strategies;
		}
		else {
			return new LinkedList<T>();
		}
	}

终于到此 这个方法大致告一段落

3.3.5 初始化 处理器适配器 HandlerAdapters

这个方法答题类似于3.3.4 下面看代码结构 也是类似于在容器里面获取 获取不到的话就有默认的实现

private void initHandlerAdapters(ApplicationContext context) {
		this.handlerAdapters = null;
		if (this.detectAllHandlerAdapters) {
 //在容器获取
			Map<String, HandlerAdapter> matchingBeans =
					BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerAdapter.class, true, false);
			if (!matchingBeans.isEmpty()) {
				this.handlerAdapters = new ArrayList<HandlerAdapter>(matchingBeans.values());
				// We keep HandlerAdapters in sorted order.
				AnnotationAwareOrderComparator.sort(this.handlerAdapters);
			}
		}
		else {
			try {
				HandlerAdapter ha = context.getBean(HANDLER_ADAPTER_BEAN_NAME, HandlerAdapter.class);
				this.handlerAdapters = Collections.singletonList(ha);
			}
			catch (NoSuchBeanDefinitionException ex) {
				// Ignore, we'll add a default HandlerAdapter later.
			}
		}
		if (this.handlerAdapters == null) {//有默认的实现
			this.handlerAdapters = getDefaultStrategies(context, HandlerAdapter.class);
			if (logger.isDebugEnabled()) {
				logger.debug("No HandlerAdapters found in servlet '" + getServletName() + "': using default");
			}
		}
	}
3.3.6 后续初始化

后续的初始化方法 都是和上述逻辑一致 这里就不详述了, 到此框架也都启动完成!!!!!

initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
3.4 MVC 处理HTTP 请求

示例代码

@Controller
@RequestMapping("/demo")
public class DemoController {
 
    @RequestMapping("/index")
    public String index(){
        return "/demo/index";
    }

//    @RequestMapping("/index1")
//    @ResponseBody
//    public String index1(){
//        publisher.publish();
//        return "OK";
//    }

    @RequestMapping("/{index1}")
    @ResponseBody
    public String index1(@PathVariable("index1") String index1 ){
        return index1;
    }
}
3.4.1 HandlerMapping 设计原理

作为SpringMVC的核心组件之一,它在框架初始化的时候已经完成了自己的创建,在上面创建 HandlerMapping 的的过程中,我们可以看到不止是创建了HandlerMapping 它还有个属性Order 这些HandlerMapping 最后放在一个List里面并且根据Order 排序

SpringMVC 提供了对HandlerMapping 的具体实现 下面是一些HandlerMapping 的继承关系

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZtEVZZWl-1609773404650)(C:\Users\sff\AppData\Roaming\Typora\typora-user-images\1605621095742.png)]

下面就是SimpleUrlHandlerMapping 来分析 HandlerMapping 的设计和实现

在顶层的HandlerMapping 接口中 只定义了一个接口方法

HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;

我们再看看这个HandlerExecutionChain的定义

/**
 * 处理器链,包括处理器handler对象 包括这个handler的所有的拦截器
 * Handler execution chain, consisting of handler object and any handler interceptors.
 * Returned by HandlerMapping's {@link HandlerMapping#getHandler} method.
 *
 * @author Juergen Hoeller
 * @since 20.06.2003
 * @see HandlerInterceptor
 */
public class HandlerExecutionChain {

正在使用实例代码测试的时候 进行debug 在这里输入栏输入的地址是

http://localhost:8080/yyqdata/demo/index1

根据我们的经验应该走的是下面在这个方法

 @RequestMapping("/{index1}")
    @ResponseBody
    public String index1(@PathVariable("index1") String index1 ){
        return index1;
    }
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
		............

				// Determine handler for the current request.
            //决定使用那个handler来处理请求
				mappedHandler = getHandler(processedRequest);===if (mappedHandler == null || mappedHandler.getHandler() == null) {
					noHandlerFound(processedRequest, response);
					return;
				}

注意: hm.getHandler() 根据请求获得封装好的 HandlerExecutionChain

protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    //handlerMappings 默认情况下面是三个 一个是开启注解的一个 还有两个是默认的在配置文件里面欧配置好的
    // HandlerMapping 不同类型的HandlerMapping 用于不同的请求处理  有的是基于Servlet 有的是方法的注解@requestMapping
    //有的是实现MVC 的Controller 接口的,在容器启动的时候会基于不同的请求出开启不同和HandlerMapping 在处理请求的时候用请求的地址去不同的HandlerMapping里面去匹配 匹配到第一个就再去 封装  HandlerExecutionChain 
		for (HandlerMapping hm : this.handlerMappings) {
		.....//===>在这里由不同优先级别的 handlerMapping 来处理请求
           //一旦有高优先级的处理好了  就不需要后面的来处理了
			HandlerExecutionChain handler = hm.getHandler(request);
			if (handler != null) {
				return handler;
			}
		}
		return null;
	}

AbstractHandlerMapping 里面的getHandler

@Override
	public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
        //===>  下面
		Object handler = getHandlerInternal(request);
		.......

        //获取到handler后再封装结果对象(主要是handler 方法和拦截器对象)    
		HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
		if (CorsUtils.isCorsRequest(request)) {
			CorsConfiguration globalConfig = this.corsConfigSource.getCorsConfiguration(request);
			CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
			CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);
			executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
		}
		return executionChain;

AbstractHandlerMethodMapping getHandlerInternal ===> lookupHandlerMethod

protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
		List<Match> matches = new ArrayList<Match>();
    //第一个直接根据url来匹配
		List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
		if (directPathMatches != null) {
			addMatchingMappings(directPathMatches, matches, request);
		}
		if (matches.isEmpty()) {
			// No choice but to go through all mappings...除了浏览所有映射外别无选择...
            //遍历所有的url 映射关系来获取匹配到到的方法  吧匹配到的handler添加到matches
            //把所有的遍历 通过各种规则  这里就不详细描述了
			addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
		}
		//如果匹配的到了多个而且 不知知道用使用那个 也是会报错
		if (!matches.isEmpty()) {
			Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
			Collections.sort(matches, comparator);
			 
			Match bestMatch = matches.get(0);
			if (matches.size() > 1) {
				if (CorsUtils.isPreFlightRequest(request)) {
					return PREFLIGHT_AMBIGUOUS_MATCH;
				}
				Match secondBestMatch = matches.get(1);
				if (comparator.compare(bestMatch, secondBestMatch) == 0) {
					Method m1 = bestMatch.handlerMethod.getMethod();
					Method m2 = secondBestMatch.handlerMethod.getMethod();
					throw new IllegalStateException("Ambiguous handler methods mapped for HTTP path '" +//如果匹配的到了多个而且 不知知道用使用那个 也是会报错
							request.getRequestURL() + "': {" + m1 + ", " + m2 + "}");
				}
			}
			handleMatch(bestMatch.mapping, lookupPath, request);
			return bestMatch.handlerMethod;
		}
		else {
			return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
		}

上面的mappingRegistry :AbstractHandlerMethodMapping 中维护的

private final MappingRegistry mappingRegistry = new MappingRegistry();

再来看看 MappingRegistry 类的定义

	/**
	 * A registry that maintains all mappings to handler methods, exposing methods
	 * to perform lookups and providing concurrent access.
	 *一个注册表,用于维护到处理程序方法的所有映射,公开用于执行查找的方法并提供并发访问
	 * <p>Package-private for testing purposes.
	 */

我看看这个对象 org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping 的初始化就知道了

  1. . super.afterPropertiesSet();

2). 对所有的子容器的bean 也就SpringMVC的bean 进行扫描方法

for (String beanName : beanNames) {
			if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
				Class<?> beanType = null;
				try {
					beanType = getApplicationContext().getType(beanName);
				}
				catch (Throwable ex) {
					// An unresolvable bean type, probably from a lazy bean - let's ignore it.
					if (logger.isDebugEnabled()) {
						logger.debug("Could not resolve target class for bean with name '" + beanName + "'", ex);
					}
				}
				if (beanType != null && isHandler(beanType)) {
                    //===>方法
					detectHandlerMethods(beanName);
				}
			}
		}

3) 在对bean里面的所有方法处理

protected void detectHandlerMethods(final Object handler) {
		Class<?> handlerType = (handler instanceof String ?
				getApplicationContext().getType((String) handler) : handler.getClass());
		final Class<?> userType = ClassUtils.getUserClass(handlerType);
		// 获取bean的所有的方法
		Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
				new MethodIntrospector.MetadataLookup<T>() {
					@Override
					public T inspect(Method method) {
						return getMappingForMethod(method, userType);
					}
				});

		if (logger.isDebugEnabled()) {
			logger.debug(methods.size() + " request handler methods found on " + userType + ": " + methods);
		}
		for (Map.Entry<Method, T> entry : methods.entrySet()) {
			Method invocableMethod = AopUtils.selectInvocableMethod(entry.getKey(), userType);
			T mapping = entry.getValue();
            // 对符合要求的控制层方法进行处理
			registerHandlerMethod(handler, invocableMethod, mapping);
		}
	}

4). 保存映射关系

public void register(T mapping, Object handler, Method method) {
			this.readWriteLock.writeLock().lock();
			try {
				HandlerMethod handlerMethod = createHandlerMethod(handler, method);
				assertUniqueMethodMapping(handlerMethod, mapping);

				if (logger.isInfoEnabled()) {
					logger.info("Mapped \"" + mapping + "\" onto " + handlerMethod);
				}
                //重点看这个方法 也是上面那个对象的核心的一个成员变量 
				this.mappingLookup.put(mapping, handlerMethod);

				List<String> directUrls = getDirectUrls(mapping);
				for (String url : directUrls) {
					this.urlLookup.add(url, mapping);
				}

				String name = null;
				if (getNamingStrategy() != null) {
					name = getNamingStrategy().getName(handlerMethod, mapping);
					addMappingName(name, handlerMethod);
				}

				CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
				if (corsConfig != null) {
					this.corsLookup.put(handlerMethod, corsConfig);
				}

				this.registry.put(mapping, new MappingRegistration<T>(mapping, handlerMethod, directUrls, name));
			}
			finally {
				this.readWriteLock.writeLock().unlock();
			}
		}

this.mappingLookup.put(mapping, handlerMethod); 这样的一个对象 他是维护什么的呢?

我的理解 key : 是一个 HandlerMappingInfo 这个对象是什么呢 可以用这个对象里面的各种匹配模式去匹配请求

value: 就是具体的请求方法 也就是controller里面的方法

如此这样就是 单请求来的时候 我们去匹配key 里面的的对象 一旦是匹配上了,就可以获取到对应的value ;也就是获取到请求应该看具体执行放入方法是哪一个。

3.5 SpringMVC 对请求的分发处理

纵观整个过程,DispatchServlet 是SpringMVC 里面可以说是最核心的一个类,包括建立了一个IOC的子容器,对HTTP的请求处理;因为他也是HttpServlet 的子类 通过doService 方法来响应HTTP的请求

@Override
	protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
 		//保存系统的一些配置 快照
		// Keep a snapshot of the request attributes in case of an include,
		// to be able to restore the original attributes after the include.
		Map<String, Object> attributesSnapshot = null;
		if (WebUtils.isIncludeRequest(request)) {
			attributesSnapshot = new HashMap<String, Object>();
			Enumeration<?> attrNames = request.getAttributeNames();
			while (attrNames.hasMoreElements()) {
				String attrName = (String) attrNames.nextElement();
				if (this.cleanupAfterInclude || attrName.startsWith("org.springframework.web.servlet")) {
					attributesSnapshot.put(attrName, request.getAttribute(attrName));
				}
			}
		}
		// Make framework objects available to handlers and view objects.
		request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
		request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
		request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
		request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());

		FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
		if (inputFlashMap != null) {
			request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
		}
		request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
		request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);

		try {
            //核心方法==>
			doDispatch(request, response);
		}
		finally {
			if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
				// Restore the original attribute snapshot, in case of an include.
				if (attributesSnapshot != null) {
					restoreAttributesAfterInclude(request, attributesSnapshot);
				}
			}
		}
	}
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
		HttpServletRequest processedRequest = request;
		HandlerExecutionChain mappedHandler = null;
		boolean multipartRequestParsed = false;
		WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
		try {
            //ModelAndView 对象,接受handler处理请求的结果 
			ModelAndView mv = null;
			Exception dispatchException = null;
			try {
                //   >>>>>> 请求里面是不是有文件  在根据配置的文件解析器来
                //获取文件  
				processedRequest = checkMultipart(request);
				multipartRequestParsed = (processedRequest != request);
				//  >>>>>>  mappedHandler 根据请求获取到相对于的handler 和封装响应的拦截器
				mappedHandler = getHandler(processedRequest);
				if (mappedHandler == null || mappedHandler.getHandler() == null) {
					noHandlerFound(processedRequest, response);
					return;
				}
				//获取到处理器适配器 在执行handler之前  使用HandlerAdapter检查handler的服务器
                //3 >>>>>>>>是不是按照Spring 的要求编写handler 
				HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
				// Process last-modified header, if supported by the handler.
				String meth sGet = "GET".equals(method);
				if (isGet || "HEAD".equals(method)) {
				}
                // 拦截器的  PreHandle 方法执行
				if (!mappedHandler.applyPreHandle(processedRequest, response)) {
					return;
				}

				//  >>>>>>>>> 通过 调用 ha.handle 实际上是触发了Controller的handleRequest 方法
				mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

				if (asyncManager.isConcurrentHandlingStarted()) {
					return;
				}
				//判断是不是需要进行视图名的解析翻译
				applyDefaultViewName(processedRequest, mv);
                //>>>>拦截器的 请求后处理方法
				mappedHandler.applyPostHandle(processedRequest, response, mv);
			}
			catch (Exception ex) {
				dispatchException = ex;
			}
			catch (Throwable err) {
				// As of 4.3, we're processing Errors thrown from handler methods as well,
				dispatchException = new NestedServletException("Handler dispatch failed", err);
			}
            //拦截器的最后一个方法被调用 	
			processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
		}
		catch (Exception ex) {
			triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
		}
		catch (Throwable err) {
			triggerAfterCompletion(processedRequest, response, mappedHandler,
					new NestedServletException("Handler processing failed", err));
		}
		finally {
			if (asyncManager.isConcurrentHandlingStarted()) {
				// Instead of postHandle and afterCompletion
				if (mappedHandler != null) {
					mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
				}
			}
			else {
				// Clean up any resources used by a multipart request.
				if (multipartRequestParsed) {
					cleanupMultipart(processedRequest);
				}
			}
		}
	}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ttWTCiax-1609773404651)(C:\Users\sff\AppData\Roaming\Typora\typora-user-images\1609254671708.png)]

3.6 适配器模式

在前面的mappedHandler = getHandler(processedRequest) 方法里面可以看到 容器里面的不同类型的HandlerMapping 会以遍历的方式去获取和请求地址去匹配 以获取到与之对应的 切封装好拦截器 处理器 的对象 HandlerExecutionChain;

我们知道handler (处理请求的方式)的类型可能有多种 有基于类的 比如说是Servlet ,类实现Controller的 有基于方法的,也是我们日常开发使用的最多的一种 @requestMapping 的方式 也可以看到 mappedHandler.getHandler() 该接口返回的对象是一个Object 类型 ,下面的获取适配器来执行也是这个思想

试验代码
@Component("/home")
public class Test1 implements Controller {

    @Override
    public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
        ModelAndView modelAndView = new ModelAndView();
        Map<String,String> stringMap = new HashMap<>();
        stringMap.put("aa","bb");
        modelAndView.addAllObjects(stringMap);
        return modelAndView;
    }
}

// 更具请求的Handler类型来获取不同的 适配器
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
		if (this.handlerAdapters != null) {
   //遍历所有的 handlerAdapters 来期望获取与之对应的适配器 好执行对应的方法
  //adapter.supports(handler) 方法的核心就是各种判断  handler 是否是各种数据类型 是Controller 还是HandlerMethod(@RequestMapping  这种也是最复杂的)
            //有或者是HttpHanlder  来决定使用何种的适配器  ,比如说这里是 是Controller类型 后期执行方法的时候就是
            //强转成这个Controller类型 在执行接口方法 当然这个是最简单的  结合试验代码 基于
			for (HandlerAdapter adapter : this.handlerAdapters) {
				if (adapter.supports(handler)) {
					return adapter;
				}
			}
		}
		throw new ServletException("No adapter for handler [" + handler +
				"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
	}
	@Override
	@Nullable
	public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {
//这里发生的强转
		return ((Controller) handler).handleRequest(request, response);
	}
3.7 SpringMVC 数据/视图的呈现
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
//方法 根据 mv的返回值来判断 是否有 判断是否需要视图的展现
//现在目前都用ajax 请求就是没有这方面的
//如果没有视图就是执行返回的数据 会填充到response 里面去
3.7.1 model

下面以ajax 请求为例

public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {

		return handleInternal(request, response, (HandlerMethod) handler);
	}
@Override
	protected ModelAndView handleInternal(HttpServletRequest request,
			HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

	..........
			// No synchronization on session demanded at all...
		mav = invokeHandlerMethod(request, response, handlerMethod);
	 
		if (!response.containsHeader(HEADER_CACHE_CONTROL)) {
			if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
				applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
			}
			else {
				prepareResponse(response);
			}
		}

		return mav;
	}
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
			Object... providedArgs) throws Exception {

		Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
		setResponseStatus(webRequest);

		if (returnValue == null) {
			if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {
				disableContentCachingIfNecessary(webRequest);
				mavContainer.setRequestHandled(true);
				return;
			}
		}
		else if (StringUtils.hasText(getResponseStatusReason())) {
			mavContainer.setRequestHandled(true);
			return;
		}

		mavContainer.setRequestHandled(false);
		Assert.state(this.returnValueHandlers != null, "No return value handlers");
		try {
            //对返回的数据添加到 webRequest  细节就没看了  各种解析器解析GenericHttpMessageConverter数据 挺复杂的 
			this.returnValueHandlers.handleReturnValue(
					returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
		}
		catch (Exception ex) {
			if (logger.isTraceEnabled()) {
				logger.trace(formatErrorForReturnValue(returnValue), ex);
			}
			throw ex;
		}
	}
@Override
	public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
			ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
			throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {

		mavContainer.setRequestHandled(true);
		ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
		ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);

		// Try even with null return value. ResponseBodyAdvice could get involved.
		writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
	}
//请求数据填充
body = getAdvice().beforeBodyWrite(body, returnType, selectedMediaType,
							(Class<? extends HttpMessageConverter<?>>) converter.getClass(),
							inputMessage, outputMessage);
					if (body != null) {
						Object theBody = body;
						LogFormatUtils.traceDebug(logger, traceOn ->
								"Writing [" + LogFormatUtils.formatValue(theBody, !traceOn) + "]");
						addContentDispositionHeader(inputMessage, outputMessage);
						if (genericConverter != null) {
							genericConverter.write(body, targetType, selectedMediaType, outputMessage);
						}
						else {
							((HttpMessageConverter) converter).write(body, selectedMediaType, outputMessage);
						}
					}
3.7.2 view 视图的展现

页面的地址对应后台是个页面跳转 方法 比如

  @GetMapping("/index")
    public String index(){
        return "index";
    }
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

此时mv 的viewName 属性就是index 而不是和上面一样的是null

processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
/**
	 * Handle the result of handler selection and handler invocation, which is
	 * either a ModelAndView or an Exception to be resolved to a ModelAndView.
	 */
	private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
			@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
			@Nullable Exception exception) throws Exception {

		boolean errorView = false;

	。。。。。。

		// Did the handler return a view to render?处理器是不是返回了视图去处理
		if (mv != null && !mv.wasCleared()) {
            //=====>>>>
			render(mv, request, response);
			if (errorView) {
				WebUtils.clearErrorRequestAttributes(request);
			}
		}
		else {
			if (logger.isTraceEnabled()) {
				logger.trace("No view rendering, null ModelAndView returned.");
			}
		}

		if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
			// Concurrent handling started during a forward
			return;
		}

		if (mappedHandler != null) {
			// 拦截器的最后一个方法
			mappedHandler.triggerAfterCompletion(request, response, null);
		}
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
		// Determine locale for request and apply it to the response.
		Locale locale =
				(this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale());
		response.setLocale(locale);

		View view;
		String viewName = mv.getViewName();
		if (viewName != null) {
			// We need to resolve the view name. 各种视图解析器解析  
			view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
			if (view == null) {
				throw new ServletException("Could not resolve view with name '" + mv.getViewName() +
						"' in servlet with name '" + getServletName() + "'");
			}
		}
		else {
			// No need to lookup: the ModelAndView object contains the actual View object.
			view = mv.getView();
			if (view == null) {
				throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " +
						"View object in servlet with name '" + getServletName() + "'");
			}
		}

		// Delegate to the View object for rendering.
		if (logger.isTraceEnabled()) {
			logger.trace("Rendering view [" + view + "] ");
		}
		try {
			if (mv.getStatus() != null) {
				response.setStatus(mv.getStatus().value());
			}
            // view 具体实现的组件 解析视图>>>>>
			view.render(mv.getModelInternal(), request, response);
		}
		catch (Exception ex) {
			if (logger.isDebugEnabled()) {
				logger.debug("Error rendering view [" + view + "]", ex);
			}
			throw ex;
		}
	}

这里是ThymeleafView

 public void render(final Map<String, ?> model, final HttpServletRequest request, final HttpServletResponse response)
            throws Exception {
        renderFragment(this.markupSelectors, model, request, response);
    }

renderFragment 的具体实现比较长 我的总结就是来说把数据通过模板引擎来加载到页面 然后再把页面数据写到响应数据里面去;在返回到前台到此 页面得以显示;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值