28-SpringMVC源码解析

news/2024/9/21 12:22:36

Quiz:

  1. Spring和SpringMVC整合使用时,会创建一个容器还是两个容器(父子容器?)
  2. DispatcherServlet初始化过程中做了什么?
  3. 请求的执行流程是怎么样的?

SpringMVC 是基于 Servlet 和 Spring 容器设计的 Web 框架。

1. Servlet 回顾

Servlet 接口及其实现类结构:

public interface Servlet {public void init(ServletConfig config) throws ServletException;public ServletConfig getServletConfig();public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException;public String getServletInfo();public void destroy();}

ServletConfig 是一个和 Servlet 配置相关的接口,在配置 Spring MVC 的 DispatcherServlet 时,会通过 ServletConfig 将配置文件的位置告知 DispatcherServlet。

<servlet><servlet-name>dispatcher</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>
</servlet>

如上,标签内的配置信息最终会被放入 ServletConfig 实现类对象中。DispatcherServlet 通过 ServletConfig 接口中的方法,就能获取到 contextConfigLocation 对应的值。

DispatcherServlet 类图(红色框是 Servlet 中的接口和类,蓝色框中则是 Spring 中的接口和类):

SpringMVC 测试环境(web.xml 的文件在内容在#2有写):

2. 根容器初始化[父容器]

2.1 Web 应用部署初始化过程

参考 Oracle 官方文档,可知 Web 应用部署的相关步骤如下:

可以发现,在 tomcat 下 Web 应用的初始化流程是,先初始化 Listener 接着初始化 Filter 最后初始化 Servlet,当我们清楚认识到 Web 应用部署到容器后的初始化过程后,就可以进一步深入探讨 SpringMVC 的启动过程。

web.xml 配置进行 Spring MVC 启动过程的分析,web.xml 配置内容如下:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"version="4.0"><!-- Spring监听器 --><context-param><param-name>contextConfigLocation</param-name><param-value>classpath:applicationContext.xml</param-value></context-param><listener><listener-class>org.springframework.web.context.ContextLoaderListener</listener-class></listener><!-- SpringMVC前端控制器 --><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><!-- 该 Servlet 随容器启动实例化 --><load-on-startup>2</load-on-startup></servlet><servlet-mapping><servlet-name>dispatcherServlet</servlet-name><url-pattern>/test/hello</url-pattern></servlet-mapping></web-app>

2.2 ContextLoaderListener 的初始化过程

首先定义了 <context-param> 标签,用于配置一个全局变量,<context-param> 标签的内容读取后会被放进 application 中,作为 Web 应用的全局变量使用,接下来创建 listener 时会使用到这个全局变量,因此,Web 应用在容器中部署后,进行初始化时会先读取这个全局变量,之后再进行上述讲解的初始化启动过程。

接着定义了一个 ContextLoaderListener 类型的 listener。查看 ContextLoaderListener 的类声明源码如下图:

ServletContextListener 接口:

public interface ServletContextListener extends java.util.EventListener {void contextInitialized(javax.servlet.ServletContextEvent servletContextEvent);void contextDestroyed(javax.servlet.ServletContextEvent servletContextEvent);
}

这里采用的是观察者模式,也称为为订阅-发布模式,实现了该接口的 listener 会向发布者进行订阅,当 Web 应用初始化或销毁时会分别调用上述两个方法。

继续看 ContextLoaderListener,该 listener 实现了 ServletContextListener 接口,因此在 Web 应用初始化时会调用该方法,该方法的具体实现如下:

/*** Initialize the root web application context.*/
@Override
public void contextInitialized(ServletContextEvent event) {initWebApplicationContext(event.getServletContext());
}

ContextLoaderListener 的 contextInitialized() 方法直接调用了 initWebApplicationContext() 方法,这个方法是继承自 ContextLoader,通过函数名可以知道,该方法是用于初始化 Web 应用上下文,即「IoC 容器」,这里使用的是代理模式,继续查看 ContextLoader 类的 initWebApplicationContext() 方法:

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 {// 将上下文存储在本地实例变量中,以确保它在ServletContext关闭时可用。// Store context in local instance variable, to guarantee that it is available on ServletContext shutdown.if (this.context == null) {// => 1.创建web应用上线文环境this.context = createWebApplicationContext(servletContext);}if (this.context instanceof ConfigurableWebApplicationContext) {ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;// 如果当前上下文环境未激活,那么其只能提供例如设置父上下文、设置上下文id等功能if (!cwac.isActive()) {// The context has not yet been refreshed -> provide services such as// setting the parent context, setting the application context id, etcif (cwac.getParent() == null) {// The context instance was injected without an explicit parent ->// determine parent for root web application context, if any.ApplicationContext parent = loadParentContext(servletContext);cwac.setParent(parent);}// => 2.配置并刷新当前上下文环境configureAndRefreshWebApplicationContext(cwac, servletContext);}}// 将当前上下文环境存储到ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE变量中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);}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;}
}

a. 创建Web应用上线文环境

/*** 为当前类加载器实例化根WebApplicationContext,可以是默认上线文加载类或者自定义上线文加载类*/
protected WebApplicationContext createWebApplicationContext(ServletContext sc) {// 1.确定实例化WebApplicationContext所需的类Class<?> contextClass = determineContextClass(sc);if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {throw new ApplicationContextException("Custom context class [" + contextClass.getName() +"] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");}// 2.实例化得到的WebApplicationContext类return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
}

逻辑很简单,得到一个类,将其实例化。

那么要得到或者明确哪个类呢? 继续看代码:

/*** 返回WebApplicationContext(web应用上线文环境)实现类,如果没有自定义默认返回XmlWebApplicationContext类。** 两种方式:* 1.非自定义*     通过ContextLoader类的静态代码块加载ContextLoader.properties配置文件并解析,*     该配置文件中的默认类即XmlWebApplicationContext。* 2.自定义*     通过在web.xml文件中配置context-param节点,并配置param-name为contextClass的自己点,如:*      <context-param>*          <param-name>contextClass</param-name>*          <param-value>org.springframework.web.context.support.MyWebApplicationContext</param-value>*      </context-param>** Return the WebApplicationContext implementation class to use, either the* default XmlWebApplicationContext or a custom context class if specified.* @param servletContext current servlet context* @return the WebApplicationContext implementation class to use*/
protected Class<?> determineContextClass(ServletContext servletContext) {String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);// 1.自定义if (contextClassName != null) {try {return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());}catch (ClassNotFoundException ex) {throw new ApplicationContextException("Failed to load custom context class [" + contextClassName + "]", ex);}}// 2.默认else {// 根据静态代码块的加载这里 contextClassName = XmlWebApplicationContextcontextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());try {return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());}catch (ClassNotFoundException ex) {throw new ApplicationContextException("Failed to load default context class [" + contextClassName + "]", ex);}}
}

自定义方式注释里已经写的很清晰了,我们来看默认方式,这里涉及到了一个静态变量 defaultStrategies,并在下面的静态代码块中对其进行了初始化操作:

private static final String DEFAULT_STRATEGIES_PATH = "ContextLoader.properties";private static final Properties defaultStrategies;static {try {// 静态代码加载默认策略, 即默认的web应用上下文 [DEFAULT_STRATEGIES_PATH -> ContextLoader.properties]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());}
}

这段代码对 ContextLoader.properties 进行了解析,那么 ContextLoader.properties 中存储的内容是什么呢?

# Default WebApplicationContext implementation class for ContextLoader.
# Used as fallback when no explicit context implementation has been specified as context-param.
# Not meant to be customized by application developers.org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext

很简单,通过上面的操作,我们就可以确定 contextClassName 是 XmlWebApplicationContext,跟我们之前分析的 ApplicationContext 差不多,只是在其基础上又提供了对 Web 的支持。接下来通过 BeanUtils.instantiateClass(contextClass) 将其实例化即可。

initWebApplicationContext() 方法如上注解讲述,主要目的就是创建 root WebApplicationContext 对象即「根 IoC 容器」,其中比较重要的就是,整个 Web 应用如果存在「根IoC容器」,则有且只能有一个,「根 IoC 容器」作为全局变量存储在 ServletContext 即 application对象中。将「根IoC容器」放入到 application 对象之前进行了 IoC 容器的配置和刷新操作,调用了 configureAndRefreshWebApplicationContext() 方法,该方法源码如下。

b. 配置并刷新当前上下文环境

/*** 配置并刷新当前web应用上下文*/
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {/*** 1.配置应用程序上下文id* 如果当前应用程序上下文id仍然设置为其原始默认值,则尝试为其设置自定义上下文id,如果有的话。* 在web.xml中配置:* <context-param>*      <param-name>contextId</param-name>*      <param-value>jack-2019-01-02</param-value>*  </context-param>*/if (ObjectUtils.identityToString(wac).equals(wac.getId())) {String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);if (idParam != null) {wac.setId(idParam);}// 无自定义id则为其生成默认idelse {wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +ObjectUtils.getDisplayString(sc.getContextPath()));}}wac.setServletContext(sc);/*** 2.设置配置文件路径* >>> applicationContext.xml <<<* <context-param>*      <param-name>contextConfigLocation</param-name>*      <param-value>classpath:spring-context.xml</param-value>*  </context-param>*/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// 3.创建ConfigurableEnvironment并配置初始化参数ConfigurableEnvironment env = wac.getEnvironment();if (env instanceof ConfigurableWebEnvironment) {((ConfigurableWebEnvironment) env).initPropertySources(sc, null);}// 4.自定义配置上下文环境customizeContext(sc, wac);// 5.刷新上下文环境wac.refresh();
}

比较重要的就是获取到了 web.xml 中的 <context-param> 标签配置的全局变量 contextConfigLocation,并最后一行调用了 refresh() 方法,ConfigurableWebApplicationContext 是一个接口,通过对常用实现类 ClassPathXmlApplicationContext 逐层查找后可以找到一个抽象类 AbstractApplicationContext 实现了 refresh() 方法。

public void refresh() throws BeansException, IllegalStateException {synchronized (this.startupShutdownMonitor) {// 1、准备刷新上下文环境prepareRefresh();// 2、读取xml并初始化BeanFactoryConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();// 3、填充BeanFactory功能prepareBeanFactory(beanFactory);try {// 4、子类覆盖方法额外处理(空方法)postProcessBeanFactory(beanFactory);// 5、调用BeanFactoryPostProcessorinvokeBeanFactoryPostProcessors(beanFactory);// 6、注册BeanPostProcessorsregisterBeanPostProcessors(beanFactory);// 7、初始化Message资源initMessageSource();// 8、初始事件广播器initApplicationEventMulticaster();// 9、留给子类初始化其他Bean(空的模板方法)onRefresh();// 10、注册事件监听器registerListeners();// 11、初始化其他的单例Bean(非延迟加载的)finishBeanFactoryInitialization(beanFactory);// 12、完成刷新过程,通知生命周期处理器lifecycleProcessor刷新过程,同时发出ContextRefreshEvent通知finishRefresh();}catch (BeansException ex) {// 13、销毁已经创建的BeandestroyBeans();// 14、重置容器激活标签cancelRefresh(ex);throw ex;}finally {resetCommonCaches();}}
}

该方法主要用于创建并初始化 contextConfigLocation 类配置的 xml 文件中的 Bean,因此,如果我们在配置 Bean 时出错,在 Web 应用启动时就会抛出异常,而不是等到运行时才抛出异常。

整个 ContextLoaderListener 类的启动过程到此就结束了,可以发现,创建 ContextLoaderListener 是比较核心的一个步骤,主要工作就是为了创建「根 IoC 容器」并使用特定的 key 将其放入到 application 对象中,供整个 Web 应用使用,由于在 ContextLoaderListener 类中构造的「根 IoC 容器」配置的 Bean 是全局共享的,因此在 <context-param> 标识的 contextConfigLocation 的 xml 配置文件一般包括:数据库 DataSource、DAO 层、Service 层、事务等相关 Bean。

3. DispatcherServlet 初始化[子容器&九组件]

3.1 HttpServletBean 初始化

Web 应用启动的最后一个步骤就是创建和初始化相关 Servlet,我们配置了 DispatcherServlet 类前端控制器,前端控制器作为中央控制器是整个 Web 应用的核心,用于获取分发用户请求并返回响应。

通过类图可以看出 DispatcherServlet 类的间接父类实现了 Servlet 接口,因此其本质上依旧是一个 Servlet。

由于 DispatcherServlet 类的本质是 Servlet,所以在 Web 应用部署到容器后进行 Servlet 初始化时会调用相关的 init(ServletConfig) 方法,因此 DispatchServlet 类的初始化过程也由该方法开始(DispatcherServelt 没有 init 方法,会走到父类 HttpServletBean 的 init 方法):

/*** DispatcherServlet 初始化入口*/
@Override
public final void init() throws ServletException {/*** Set bean properties from init parameters.* 1.加载初始化参数,这里会解析init-param列表* <servlet>*      <servlet-name>example</servlet-name>*      <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>*      <init-param>*          <param-name>name</param-name>*          <param-value>jack</param-value>*      </init-param>*      <load-on-startup>1</load-on-startup>*  </servlet>*/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());// 注册自定义属性编辑器,一旦遇到 Resource 类型的属性将会使用 ResourceEditor 进行解析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;}}// => 2.留给子类覆盖的模板方法initServletBean();
}

该方法最主要的作用就是初始化 init-param,如果我们没有配置任何 init-param,那么该方法不会执行任何操作。从这里我们没有拿到有用的信息,但是在该方法结尾有 initServletBean(),这是一个模板方法,可以由子类来实现,那么接下来我们就去看其子类 FrameworkServlet 中的 initServletBean()。

3.2 FrameworkServlet 初始化

继续查看 initServletBean()。父类 FrameworkServlet 覆盖了 HttpServletBean 中的 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 {// => 为当前servlet初始化web应用上下文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");}
}

initWebApplicationContext:

protected WebApplicationContext initWebApplicationContext() {// 获取rootContext,该Context就是上面通过ContextLoaderListener创建的XmlWebApplicationContextWebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(getServletContext());WebApplicationContext wac = null;// 如果当前webApplicationContext不为null,则为其设置父容器if (this.webApplicationContext != null) {// A context instance was injected at construction time -> use itwac = 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, etcif (cwac.getParent() == null) {// 如果当前Servelt存在一个WebApplicationContext即子IoC容器// 并且上文获取的根IoC容器存在,则将根IoC容器作为子IoC容器的父容器cwac.setParent(rootContext);}// 配置并刷新当前的子IoC容器,用于构建相关BeanconfigureAndRefreshWebApplicationContext(cwac);}}}// 未能通过构造函数注入,则尝试去ServletContext容器中查找有无WebApplicationContextif (wac == null) {// 如果当前Servlet不存在一个子IoC容器则去查找一下wac = findWebApplicationContext();}// => 以上均无WebApplicationContext,则创建一个新的WebApplicationContextif (wac == null) {wac = createWebApplicationContext(rootContext);}// 刷新上下文容器,空的模板方法,留给子类实现if (!this.refreshEventReceived) {onRefresh(wac);}if (this.publishContext) {// Publish the context as a servlet context attribute.String attrName = getServletContextAttributeName();getServletContext().setAttribute(attrName, wac);}return wac;
}

通过函数名不难发现,该方法的主要作用同样是创建一个 WebApplicationContext 对象,即 IoC 容器,不过前面讲过每个 Web 应用最多只能存在一个根 IoC 容器,这里创建的则是特定 Servlet 拥有的子 IoC 容器

为什么需要多个 IOC 容器呢?

答:父子容器类似于类的继承关系,子类可以访问父类中的成员变量,而父类不可访问子类的成员变量,同样的,子容器可以访问父容器中定义的 Bean,但父容器无法访问子容器定义的 Bean。

根 IoC 容器做为全局共享的 IoC 容器放入 Web 应用需要共享的 Bean,而子 IoC 容器根据需求的不同,放入不同的 Bean,这样能够做到隔离,保证系统的安全性。

DispatcherServlet 类的子 IoC 容器创建过程,如果当前 Servlet 存在一个 IoC 容器则为其设置根 IoC 容器作为其父类,并配置刷新该容器,用于构造其定义的 Bean,这里的方法与前文讲述的根 IoC 容器类似,同样会读取用户在 web.xml 中配置的中的值,用于查找相关的 xml 配置文件用于构造定义的 Bean,这里不再赘述了。如果当前 Servlet 不存在一个子 IoC 容器就去查找一个,如果仍然没有查找到则调用 createWebApplicationContext() 方法去创建一个,查看该方法的源码如下图所示:

protected WebApplicationContext createWebApplicationContext(@Nullable WebApplicationContext parent) {return createWebApplicationContext((ApplicationContext) parent);
}protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {// 获取 servlet 的初始化参数 contextClass,如果没有配置默认为 XMLWebApplicationContext.ClassClass<?> contextClass = getContextClass();// 校验必须是ConfigurableWebApplicationContext的子类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");}// 通过反射方式实例化 contextClassConfigurableWebApplicationContext wac =(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);// 设置好父容器、Enviroment等等wac.setEnvironment(getEnvironment());wac.setParent(parent);// 获取 contextConfigLocation 属性,配置在 servlet 初始化参数中String configLocation = getContextConfigLocation();if (configLocation != null) {wac.setConfigLocation(configLocation);}// => 这个是重点!如完善、初始化、刷新容器configureAndRefreshWebApplicationContext(wac);return wac;
}

进入 configureAndRefreshWebApplicationContext():

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {if (ObjectUtils.identityToString(wac).equals(wac.getId())) {// The application context id is still set to its original default value// -> assign a more useful id based on available informationif (this.contextId != null) {wac.setId(this.contextId);}else {// 默认的id  这里面和contextpath有关了wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +ObjectUtils.getDisplayString(getServletContext().getContextPath()) + '/' + getServletName());}}// 关联到了Namespace/Servlet等等wac.setServletContext(getServletContext());wac.setServletConfig(getServletConfig());wac.setNamespace(getNamespace());// 添加了一个容器监听器 此监听器SourceFilteringListener在后面还会碰到wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));// 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 #refreshConfigurableEnvironment env = wac.getEnvironment();if (env instanceof ConfigurableWebEnvironment) {((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());}// 留给子类,可以复写此方法,做一些初始化时候自己的实行postProcessWebApplicationContext(wac);// 执行一些初始化类们(一般也是用不着)applyInitializers(wac);// => 加载配置文件及整合 parent 到 wac(该方法就是 Spring 启动入口的那个 refresh 方法~)// e.g. 回忆该方法,其中一步是loadBeanDefinitions=>解析spring-mvc.xml文件=>解析<mvc:annotation-driven>注解wac.refresh();
}

该方法用于创建一个「子 IoC 容器」并将「根 IoC 容器」做为其父容器,接着进行配置和刷新操作用于构造相关的 Bean。至此,「根 IoC 容器」以及相关 Servlet 的「子 IoC 容器」已经配置完成,子容器中管理的 Bean 一般只被该 Servlet 使用,因此,其中管理的 Bean 一般是“局部”的,如 SpringMVC 中需要的各种重要组件,包括 Controller、Interceptor、Converter、ExceptionResolver 等。相关关系如下图所示:

3.3 DispatcherServlet 初始化

了解 DispatcherServlet 之前,先回顾一下 DispatcherServlet 的内置组件及其作用。

DispatcherServlet#onRefresh();

当 IoC 子容器构造完成后调用了onRefresh() 方法,该方法的调用与 initServletBean() 方法的调用相同,由父类调用但具体实现由子类覆盖,调用 onRefresh() 方法时将前文创建的 IoC 子容器作为参数传入,查看 DispatcherServletBean 类的 onRefresh() 方法:

@Override
protected void onRefresh(ApplicationContext context) {initStrategies(context);
}protected void initStrategies(ApplicationContext context) {// 1.初始化 MultipartResolverinitMultipartResolver(context);// 2.初始化 LocaleResolverinitLocaleResolver(context);// 3.初始化 ThemeResolverinitThemeResolver(context);// 4.初始化 HandlerMappingsinitHandlerMappings(context);// 5.初始化 HandlerAdaptersinitHandlerAdapters(context);// 6.初始化 HandlerExceptionResolverinitHandlerExceptionResolvers(context);// 7.初始化 RequestToViewNameTranslatorinitRequestToViewNameTranslator(context);// 8.初始化 ViewResolversinitViewResolvers(context);// 9.初始化 FlashMapManagerinitFlashMapManager(context);
}

onRefresh() 方法直接调用了 initStrategies() 方法,源码如上,通过函数名可以判断,该方法用于初始化创建 multipartResovler 来支持图片等文件的上传、本地化解析器、主题解析器、HandlerMapping 处理器映射器、HandlerAdapter 处理器适配器、异常解析器、视图解析器、flashMap 管理器等,这些组件都是 SpringMVC 开发中的重要组件,相关组件的初始化创建过程均在此完成。

着重看下 initHandlerMappings:

  • Handler : 绑定了 @RequestMapping 和 @Controller 的类;
  • HandlerMethod:就是 Handler 下某个绑定 @RequestMapping 注解的方法(@GetMapping、@PostMapping 等都绑定的注解 @RequestMapping,SpringMVC 在做注解解析处理生成代理对象等的时候,会做值的合并等处理,所以最终都是用 @RequestMapping 来计算,所以 @Controller 和 @RestController 的处理等同)。
/*** Initialize the HandlerMappings used by this class.* <p>If no HandlerMapping beans are defined in the BeanFactory for this namespace,* we default to BeanNameUrlHandlerMapping.* 实例化HandlerMappings,如果没有自定义HandlerMappings,则默认使用BeanNameUrlHandlerMapping*/
private void initHandlerMappings(ApplicationContext context) {// 初始化记录 HandlerMapping 对象的属性变量为nullthis.handlerMappings = null;// 1.根据属性detectAllHandlerMappings决定是检测所有的 HandlerMapping 对象,还是使用指定名称的 HandlerMapping 对象if (this.detectAllHandlerMappings) {// Find all HandlerMappings in the ApplicationContext, including ancestor contexts.// 从容器及其祖先容器查找所有类型为 HandlerMapping 的 HandlerMapping 对象,记录到   handlerMappings 并排序Map<String, HandlerMapping> matchingBeans =BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);if (!matchingBeans.isEmpty()) {this.handlerMappings = new ArrayList<>(matchingBeans.values());// We keep HandlerMappings in sorted order.// 关于这里的排序可以参考 WebMvcConfigurationSupport 类中对各种 HandlerMapping bean进行定义时所使用的order属性AnnotationAwareOrderComparator.sort(this.handlerMappings);}}// 2.否则只获取当前上下文自定义配置的handlerMappingelse {try {// 获取名称为  handlerMapping 的 HandlerMapping bean 并记录到 handlerMappingsHandlerMapping 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.// 3.上述两步都未能获取到handlerMapping,则使用默认的handlerMapping,if (this.handlerMappings == null) {// 如果上面步骤从容器获取 HandlerMapping 失败,则使用缺省策略创建 HandlerMapping 对象记录到// handlerMappingsthis.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);if (logger.isTraceEnabled()) {logger.trace("No HandlerMappings declared for servlet '" + getServletName() +"': using default strategies from DispatcherServlet.properties");}}
}

(1)RequestMappingHandlerMapping

这个就是我们常见的基于注解的映射方式,例如:

@Controller
@RequestMapping("/testA")
public class MappingTest1 {@ResponseBody@RequestMapping("/index")public String index(){return "RequestMappingHandlerMapping test!";}
}

Springboot 在初始化 RequestMappingHandlerMapping 时,会扫描容器中的 bean,判断它上面是否存在 @Controller 或 @RequestMapping 两种注解,通过上面的方法,判断该 bean 是否是一个 handler,如果是,则会将其注册到 RequestMappingHandlerMapping,用来处理和它匹配的请求。

(2)SimpleUrlHandlerMapping

这种方式直接通过简单的 url 匹配的方式将其映射到一个处理器。首先像容器注册一个自定义的 SimpleUrlHandlerMapping。

@Configuration
public class MyConfig extends SimpleUrlHandlerMapping{@Beanpublic SimpleUrlHandlerMapping simpleUrlHandlerMapping() {SimpleUrlHandlerMapping simpleUrlHandlerMapping = new SimpleUrlHandlerMapping();Properties properties = new Properties();properties.setProperty("simpleUrl","mappingTest2");simpleUrlHandlerMapping.setMappings(properties);// 设置该handlermapping的优先级为1,否则会被默认的覆盖,导致访问无效simpleUrlHandlerMapping.setOrder(1);return simpleUrlHandlerMapping;}
}

定义一个名称为 mappingTest2 的 bean,并实现 org.springframework.web.servlet.mvc.Controller 接口。

@Component("mappingTest2")
public class MappingTest2 implements Controller {@Overridepublic ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {response.getWriter().write("SimpleUrlHandlerMapping test!");return null;}
}

在这个例子中,我们访问 localhost/simpleUrl 就会直接进入容器中名称为 mappingTest2 的 bean 的 handleRequest 方法。

(3)BeanNameUrlHandlerMapping

这个最简单,直接以 bean 的名称作为访问路径,但有个硬性条件就是 bean 的名称必须以 / 开始。

@Component("/mappingTest3")
public class MappingTest3 implements Controller {@Overridepublic ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {response.getWriter().write("BeanNameUrlHandlerMapping test!");return null;}
}

【小结】

  1. HttpServletBean 主要做一些初始化的工作,将 web.xml 中配置的参数设置到 Servlet 中。比如 servlet 标签的子标签 init-param 标签中配置的参数。
  2. FrameworkServlet 将 Servlet 与 Spring 容器上下文关联。其实也就是初始化 FrameworkServlet 的属性 webApplicationContext,这个属性代表 SpringMVC 上下文,它有个父类上下文,即 web.xml 中配置的 ContextLoaderListener 监听器初始化的容器上下文。
  3. DispatcherServlet 初始化各个功能的实现类。比如异常处理、视图处理、请求映射处理等。

DispatcherServlet准备HandlerMapping的流程如下:

  • 当 detectAllHandlerMappings 为 true 时,从容器(以及祖先容器)获取所有类型为 HandlerMapping 的 bean 组件,记录到 handlerMappings 并排序;
  • 当 detectAllHandlerMappings 为 false 时,从容器(以及祖先容器)获取名称为 handlerMapping 的 bean 组件,记录到 handlerMappings,这种情况下 handlerMappings 中最多有一个元素;
  • 如果上面步骤结束时 handlerMappings 为空则创建缺省 HandlerMapping 对象记录到 handlerMappings。

4. mvc:annotation-driven 标签解析

mvc:annotation-driven 标签默认会开启 SpringMVC 的注解驱动模式,默认注册一个 RequestMappingHandlerMapping、一个 RequestMappingHandlerAdapter、一个 ExceptionHandlerExceptionResolver 以支持对使用了 @RequestMapping、@ExceptionHandler 及其他注解的控制器方法的请求处理。

@Override
public void refresh() throws BeansException, IllegalStateException {// .../*2、初始化新BeanFactory(1)如果存在旧 BeanFactory,则销毁(2)创建新的 BeanFactory(DefaluListbaleBeanFactory)(3)解析xml/加载 Bean 定义、注册 Bean定义到beanFactory(不初始化)(4)返回新的 BeanFactory(DefaluListbaleBeanFactory)*/ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();// ...// 11、实例化所有剩余的(非惰性初始化)单例// (1)初始化所有的 singleton beans,反射生成对象/填充// (2)调用Bean的前置处理器和后置处理器finishBeanFactoryInitialization(beanFactory);// 12、结束refresh操作: 发布事件 & 清除上下文环境finishRefresh();}

关于定位自定义标签解析的过程,IoC 中已经说明过,这里直接打开 AnnotationDrivenBeanDefinitionParser 类并定位到其 parse 方法。

/** 解析 mvc:annotation-driven 标签 */
@Override
@Nullable
public BeanDefinition parse(Element element, ParserContext parserContext) {Object source = parserContext.extractSource(element);XmlReaderContext readerContext = parserContext.getReaderContext();CompositeComponentDefinition compDefinition = new CompositeComponentDefinition(element.getTagName(), source);parserContext.pushContainingComponent(compDefinition);/*** 获取协商内容视图配置*/RuntimeBeanReference contentNegotiationManager = getContentNegotiationManager(element, source, parserContext);/*** [1] 创建RequestMappingHandlerMapping的RootBeanDefinition*   > 从这里也可以看出,开启mvc:annotation-driven标签后,将会默认注册RequestMappingHandlerMapping作为默认的HandlerMapping*/RootBeanDefinition handlerMappingDef = new RootBeanDefinition(RequestMappingHandlerMapping.class);handlerMappingDef.setSource(source);handlerMappingDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);handlerMappingDef.getPropertyValues().add("order", 0);handlerMappingDef.getPropertyValues().add("contentNegotiationManager", contentNegotiationManager);// 是否开启矩阵变量if (element.hasAttribute("enable-matrix-variables")) {Boolean enableMatrixVariables = Boolean.valueOf(element.getAttribute("enable-matrix-variables"));handlerMappingDef.getPropertyValues().add("removeSemicolonContent", !enableMatrixVariables);}// 解析path-matching路径匹配标签configurePathMatchingProperties(handlerMappingDef, element, parserContext);readerContext.getRegistry().registerBeanDefinition(HANDLER_MAPPING_BEAN_NAME , handlerMappingDef);// 解析cors跨域标签RuntimeBeanReference corsRef = MvcNamespaceUtils.registerCorsConfigurations(null, parserContext, source);handlerMappingDef.getPropertyValues().add("corsConfigurations", corsRef);// 解析conversion-service数据转换、格式化标签RuntimeBeanReference conversionService = getConversionService(element, source, parserContext);// 解析validator标签RuntimeBeanReference validator = getValidator(element, source, parserContext);// 解析message-codes-resolver标签RuntimeBeanReference messageCodesResolver = getMessageCodesResolver(element);/*** [2] 创建ConfigurableWebBindingInitializer的RootBeanDefinition对象* > 并将上一步解析的conversionService、validator、messageCodesResolver作为属性注入到该对象中*/RootBeanDefinition bindingDef = new RootBeanDefinition(ConfigurableWebBindingInitializer.class);bindingDef.setSource(source);bindingDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);bindingDef.getPropertyValues().add("conversionService", conversionService);bindingDef.getPropertyValues().add("validator", validator);bindingDef.getPropertyValues().add("messageCodesResolver", messageCodesResolver);// 解析message-converters标签ManagedList<?> messageConverters = getMessageConverters(element, source, parserContext);// 解析argument-resolvers标签ManagedList<?> argumentResolvers = getArgumentResolvers(element, parserContext);// 解析return-value-handlers标签ManagedList<?> returnValueHandlers = getReturnValueHandlers(element, parserContext);// 解析async-support标签String asyncTimeout = getAsyncTimeout(element);// 解析async-support的task-executor子标签RuntimeBeanReference asyncExecutor = getAsyncExecutor(element);// 解析async-support的callable-interceptors子标签ManagedList<?> callableInterceptors = getCallableInterceptors(element, source, parserContext);// 解析async-support的deferred-result-interceptors子标签ManagedList<?> deferredResultInterceptors = getDeferredResultInterceptors(element, source, parserContext);/*** [3] 创建RequestMappingHandlerAdapter的RootBeanDefinition*   > 从这里也可以看出,开启mvc:annotation-driven标签后,将会默认注册RequestMappingHandlerAdapter*     作为默认的HandlerAdapter,并将上面解析的内容绑定到该HandlerAdapter中。*/RootBeanDefinition handlerAdapterDef = new RootBeanDefinition(RequestMappingHandlerAdapter.class);handlerAdapterDef.setSource(source);handlerAdapterDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);handlerAdapterDef.getPropertyValues().add("contentNegotiationManager", contentNegotiationManager);handlerAdapterDef.getPropertyValues().add("webBindingInitializer", bindingDef);handlerAdapterDef.getPropertyValues().add("messageConverters", messageConverters);addRequestBodyAdvice(handlerAdapterDef);addResponseBodyAdvice(handlerAdapterDef);if (element.hasAttribute("ignore-default-model-on-redirect")) {Boolean ignoreDefaultModel = Boolean.valueOf(element.getAttribute("ignore-default-model-on-redirect"));handlerAdapterDef.getPropertyValues().add("ignoreDefaultModelOnRedirect", ignoreDefaultModel);}if (argumentResolvers != null) {handlerAdapterDef.getPropertyValues().add("customArgumentResolvers", argumentResolvers);}if (returnValueHandlers != null) {handlerAdapterDef.getPropertyValues().add("customReturnValueHandlers", returnValueHandlers);}if (asyncTimeout != null) {handlerAdapterDef.getPropertyValues().add("asyncRequestTimeout", asyncTimeout);}if (asyncExecutor != null) {handlerAdapterDef.getPropertyValues().add("taskExecutor", asyncExecutor);}handlerAdapterDef.getPropertyValues().add("callableInterceptors", callableInterceptors);handlerAdapterDef.getPropertyValues().add("deferredResultInterceptors", deferredResultInterceptors);readerContext.getRegistry().registerBeanDefinition(HANDLER_ADAPTER_BEAN_NAME , handlerAdapterDef);/*** [4] 创建CompositeUriComponentsContributorFactoryBean的RootBeanDefinition*   > CompositeUriComponentsContributorFactoryBean是一个工厂bean,可以用来获取*     RequestMappingHandlerAdapter中的HandlerMethodArgumentResolver配置。*/RootBeanDefinition uriContributorDef = new RootBeanDefinition(CompositeUriComponentsContributorFactoryBean.class);uriContributorDef.setSource(source);uriContributorDef.getPropertyValues().addPropertyValue("handlerAdapter", handlerAdapterDef);uriContributorDef.getPropertyValues().addPropertyValue("conversionService", conversionService);String uriContributorName = MvcUriComponentsBuilder.MVC_URI_COMPONENTS_CONTRIBUTOR_BEAN_NAME;readerContext.getRegistry().registerBeanDefinition(uriContributorName, uriContributorDef);/*** [5] 创建ConversionServiceExposingInterceptor的RootBeanDefinition*   > 主要用来解析spring:eval标签*/RootBeanDefinition csInterceptorDef = new RootBeanDefinition(ConversionServiceExposingInterceptor.class);csInterceptorDef.setSource(source);csInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(0, conversionService);RootBeanDefinition mappedInterceptorDef = new RootBeanDefinition(MappedInterceptor.class);mappedInterceptorDef.setSource(source);mappedInterceptorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);mappedInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(0, (Object) null);mappedInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(1, csInterceptorDef);String mappedInterceptorName = readerContext.registerWithGeneratedName(mappedInterceptorDef);/*** [6] 创建ExceptionHandlerExceptionResolver的RootBeanDefinition*/RootBeanDefinition methodExceptionResolver = new RootBeanDefinition(ExceptionHandlerExceptionResolver.class);methodExceptionResolver.setSource(source);methodExceptionResolver.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);methodExceptionResolver.getPropertyValues().add("contentNegotiationManager", contentNegotiationManager);methodExceptionResolver.getPropertyValues().add("messageConverters", messageConverters);methodExceptionResolver.getPropertyValues().add("order", 0);addResponseBodyAdvice(methodExceptionResolver);if (argumentResolvers != null) {methodExceptionResolver.getPropertyValues().add("customArgumentResolvers", argumentResolvers);}if (returnValueHandlers != null) {methodExceptionResolver.getPropertyValues().add("customReturnValueHandlers", returnValueHandlers);}String methodExResolverName = readerContext.registerWithGeneratedName(methodExceptionResolver);/*** [7] 创建ResponseStatusExceptionResolver的RootBeanDefinition*/RootBeanDefinition statusExceptionResolver = new RootBeanDefinition(ResponseStatusExceptionResolver.class);statusExceptionResolver.setSource(source);statusExceptionResolver.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);statusExceptionResolver.getPropertyValues().add("order", 1);String statusExResolverName = readerContext.registerWithGeneratedName(statusExceptionResolver);/*** [8] 创建DefaultHandlerExceptionResolver的RootBeanDefinition*   > 该类是HandlerExceptionResolver的默认实现,可以解析http异常并将相应的http状态码返回,如404*/RootBeanDefinition defaultExceptionResolver = new RootBeanDefinition(DefaultHandlerExceptionResolver.class);defaultExceptionResolver.setSource(source);defaultExceptionResolver.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);defaultExceptionResolver.getPropertyValues().add("order", 2);String defaultExResolverName = readerContext.registerWithGeneratedName(defaultExceptionResolver);/*** 将上面创建的RootBeanDefinition以组件形式纳入SpringIOC容器*/parserContext.registerComponent(new BeanComponentDefinition(handlerMappingDef, HANDLER_MAPPING_BEAN_NAME));parserContext.registerComponent(new BeanComponentDefinition(handlerAdapterDef, HANDLER_ADAPTER_BEAN_NAME));parserContext.registerComponent(new BeanComponentDefinition(uriContributorDef, uriContributorName));parserContext.registerComponent(new BeanComponentDefinition(mappedInterceptorDef, mappedInterceptorName));parserContext.registerComponent(new BeanComponentDefinition(methodExceptionResolver, methodExResolverName));parserContext.registerComponent(new BeanComponentDefinition(statusExceptionResolver, statusExResolverName));parserContext.registerComponent(new BeanComponentDefinition(defaultExceptionResolver, defaultExResolverName));/*** 注册默认组件*/MvcNamespaceUtils.registerDefaultComponents(parserContext, source);parserContext.popAndRegisterContainingComponent();return null;
}

那么接下来我们需要总结一下,如果 mvc:annotation-driven 没有配置任何子标签的话,Spring 会如何处理呢?

RootBeanDefinition handlerMappingDef = new RootBeanDefinition(RequestMappingHandlerMapping.class);
RootBeanDefinition bindingDef = new RootBeanDefinition(ConfigurableWebBindingInitializer.class);
RootBeanDefinition handlerAdapterDef = new RootBeanDefinition(RequestMappingHandlerAdapter.class);
RootBeanDefinition uriContributorDef = new RootBeanDefinition(CompositeUriComponentsContributorFactoryBean.class);
RootBeanDefinition csInterceptorDef = new RootBeanDefinition(ConversionServiceExposingInterceptor.class);
RootBeanDefinition mappedInterceptorDef = new RootBeanDefinition(MappedInterceptor.class);
RootBeanDefinition methodExceptionResolver = new RootBeanDefinition(ExceptionHandlerExceptionResolver.class);
RootBeanDefinition statusExceptionResolver = new RootBeanDefinition(ResponseStatusExceptionResolver.class);
RootBeanDefinition defaultExceptionResolver = new RootBeanDefinition(DefaultHandlerExceptionResolver.class);

可以看到即使不做任何子标签的配置,SpringMVC 默认也会创建上述 9 个内部 bean 的实例。

下面来看下这俩个 RootBeanDefinition 走 Bean 实例化生命周期那一套,其中一步就是 InitializingBean#afterPropertiesSet。

4.1 RequestMappingHandlerMapping

HandlerMapping 在 SpringMVC 扮演着相当重要的角色,它可以为 HTTP 请求找到对应的 Controller 控制器。

HandlerMapping 是一个接口,其中包含一个 getHandler 方法,能够通过该方法获得与 HTTP 请求对应的 handlerExecutionChain,而这个 handlerExecutionChain 对象中持有 handler 和 interceptorList,以及和设置拦截器相关的方法。可以判断是通过这些配置的拦截器对 handler 对象提供的功能进行了一波增强。

RequestMappingHandlerMapping 类继承结构:

public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappingimplements MatchableHandlerMapping, EmbeddedValueResolverAware {}
/* |extends| */
public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMethodMapping<RequestMappingInfo> {}
/* |extends| */
public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMapping implements InitializingBean {}

可以看到 RequestMappingHandlerMapping 实现了 InitializingBean。AbstractHandlerMethodMapping 中当 bean 被注入到容器后会执行一系列的初始化过程。

@Override
public void afterPropertiesSet() {// 创建 BuilderConfigurationthis.config = new RequestMappingInfo.BuilderConfiguration();this.config.setUrlPathHelper(getUrlPathHelper());this.config.setPathMatcher(getPathMatcher());this.config.setSuffixPatternMatch(useSuffixPatternMatch());this.config.setTrailingSlashMatch(useTrailingSlashMatch());this.config.setRegisteredSuffixPatternMatch(useRegisteredSuffixPatternMatch());this.config.setContentNegotiationManager(getContentNegotiationManager());// => [0]super.afterPropertiesSet();
}

进行 HandlerMethod 的注册操作,简单来说就是从 SpringMVC 的容器中获取所有的 beanName,注册 url 和实现方法 HandlerMethod 的对应关系。

/* AbstractHandlerMethodMapping.java */@Override
public void afterPropertiesSet() {initHandlerMethods();
}// -[0]-
protected void initHandlerMethods() {// 获取所有的 BeanNamesfor (String beanName : getCandidateBeanNames()) {// 判断不是已 scopedTarget 开头if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {// => [1]processCandidateBean(beanName);}}handlerMethodsInitialized(getHandlerMethods());
}// -[1]-
protected void processCandidateBean(String beanName) {Class<?> beanType = null;try {// 获取具体的类型beanType = obtainApplicationContext().getType(beanName);} catch (Throwable ex) {// 一个无法解析的bean类型,可能来自一个lazy bean,此时忽略它if (logger.isTraceEnabled()) {logger.trace("Could not resolve type for bean '" + beanName + "'", ex);}}// 不是 null 并且 类型是存在 @Controller/@RequestMapping 注解if (beanType != null && isHandler(beanType)) {// => [2]detectHandlerMethods(beanName);}
}@Override
protected boolean isHandler(Class<?> beanType) {// 存在 Controller / RequestMapping 注解return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class)|| AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
}// -[2]-
protected void detectHandlerMethods(Object handler) {// 如果传递是 String 则获取其类型 ,如果是 class 则直接返回Class<?> handlerType = (handler instanceof String ?obtainApplicationContext().getType((String) handler) : handler.getClass());if (handlerType != null) {// 对类型再次进行处理,主要是针对cglibClass<?> userType = ClassUtils.getUserClass(handlerType);// 遍历方法,对注解中的信息进行处理,得到RequestMappingInfo对象,得到methods集Map<Method, T> methods = MethodIntrospector.selectMethods(userType,(MethodIntrospector.MetadataLookup<T>) method -> {try {// => [5] 里面主要是获取方法和类上的 @RequestMapping 将其合并// 如果没有的话则会返回 null,而由于是 Lambda 这里主要是制订过滤规则。返回null则selectMethods不会将其放入到Map中return getMappingForMethod(method, userType);} catch (Throwable ex) {throw new IllegalStateException("Invalid mapping on handler class [" + userType.getName() + "]: " + method, ex);}});if (logger.isTraceEnabled()) {logger.trace(formatMappings(userType, methods));}// 遍历methods[Method,{path}]methods.forEach((method, mapping) -> {// 对方法的可访问性进行校验,如private,static,SpringProxy,获取最终请求路径Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);// => [4] 注册到全局的MappingRegistry实例里!registerHandlerMethod(handler, invocableMethod, mapping);});}
}// -[4]-
protected void registerHandlerMethod(Object handler, Method method, T mapping) {// registerHandlerMethod 的注册操作是将 beanName、Method 及创建的 RequestMappingInfo 之间的关系保存this.mappingRegistry.register(mapping, handler, method);
}/* RequestMappingHandlerMapping.java */
// -[5]-
@Override
@Nullable
/* getMappingForMethod方法是在子类RequestMappingHandlerMapping中实现的,具体实现就是创建一个RequestMappingInfo */
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {RequestMappingInfo info = createRequestMappingInfo(method);if (info != null) {// => [6]RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);if (typeInfo != null) {info = typeInfo.combine(info);}String prefix = getPathPrefix(handlerType);if (prefix != null) {info = RequestMappingInfo.paths(prefix).options(this.config).build().combine(info);}}return info;
}// -[6]-
@Nullable
private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) {RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class);RequestCondition<?> condition = (element instanceof Class? getCustomTypeCondition((Class<?>) element): getCustomMethodCondition((Method) element));// => [7]return (requestMapping != null ? createRequestMappingInfo(requestMapping, condition) : null);
}// -[7]-
protected RequestMappingInfo createRequestMappingInfo(RequestMapping requestMapping, @Nullable RequestCondition<?> customCondition) {RequestMappingInfo.Builder builder = RequestMappingInfo.paths(resolveEmbeddedValuesInPatterns(requestMapping.path())).methods(requestMapping.method()).params(requestMapping.params()).headers(requestMapping.headers()).consumes(requestMapping.consumes()).produces(requestMapping.produces()).mappingName(requestMapping.name());if (customCondition != null) {builder.customCondition(customCondition);}return builder.options(this.config).build();
}

[4] 实现了将 url 和 HandlerMethod 的对应关系注册到 mappingRegistry 中。MappingRegistry 中的注册实现如下,并且 MappingRegistry 定义了几个 map 结构,用来存储注册信息。

/*** A registry that maintains all mappings to handler methods, exposing methods* to perform lookups and providing concurrent access.*/
class MappingRegistry {private final Map<T, MappingRegistration<T>> registry = new HashMap<>();private final Map<T, HandlerMethod> mappingLookup = new LinkedHashMap<>();private final MultiValueMap<String, T> urlLookup = new LinkedMultiValueMap<>();private final Map<String, List<HandlerMethod>> nameLookup = new ConcurrentHashMap<>();private final Map<HandlerMethod, CorsConfiguration> corsLookup = new ConcurrentHashMap<>();private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();/** 完成 beanName、HandlerMethod 及 RequestMappingInfo 之间的对应关系注册 */public void register(T mapping, Object handler, Method method) {// Assert that the handler method is not a suspending one.if (KotlinDetector.isKotlinType(method.getDeclaringClass())) {Class<?>[] parameterTypes = method.getParameterTypes();if ((parameterTypes.length > 0) && "kotlin.coroutines.Continuation".equals(parameterTypes[parameterTypes.length - 1].getName())) {throw new IllegalStateException("Unsupported suspending handler method detected: " + method);}}this.readWriteLock.writeLock().lock();try {// 处理方法的对象HandlerMethod handlerMethod = createHandlerMethod(handler, method);// 判断映射的唯一性validateMethodMapping(handlerMethod, mapping);// 将mapping信息和控制器方法对应this.mappingLookup.put(mapping, handlerMethod);// 将path与处理器映射(一个方法可能可以处理多个url)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);}// 将所有配置统一注册到registry中this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod, directUrls, name));} finally {this.readWriteLock.writeLock().unlock();}}
}

4.2 RequestMappingHandlerAdapter

public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapterimplements BeanFactoryAware, InitializingBean {@Overridepublic void afterPropertiesSet() {// Do this first, it may add ResponseBody advice beansinitControllerAdviceCache();if (this.argumentResolvers == null) {List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);}if (this.initBinderArgumentResolvers == null) {List<HandlerMethodArgumentResolver> resolvers = getDefaultInitBinderArgumentResolvers();this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);}if (this.returnValueHandlers == null) {List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);}}}

(1)ArgumentResolver

private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>(30);// Annotation-based argument resolutionresolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false));resolvers.add(new RequestParamMapMethodArgumentResolver());resolvers.add(new PathVariableMethodArgumentResolver());resolvers.add(new PathVariableMapMethodArgumentResolver());resolvers.add(new MatrixVariableMethodArgumentResolver());resolvers.add(new MatrixVariableMapMethodArgumentResolver());resolvers.add(new ServletModelAttributeMethodProcessor(false));resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));resolvers.add(new RequestPartMethodArgumentResolver(getMessageConverters(), this.requestResponseBodyAdvice));resolvers.add(new RequestHeaderMethodArgumentResolver(getBeanFactory()));resolvers.add(new RequestHeaderMapMethodArgumentResolver());resolvers.add(new ServletCookieValueMethodArgumentResolver(getBeanFactory()));resolvers.add(new ExpressionValueMethodArgumentResolver(getBeanFactory()));resolvers.add(new SessionAttributeMethodArgumentResolver());resolvers.add(new RequestAttributeMethodArgumentResolver());// Type-based argument resolutionresolvers.add(new ServletRequestMethodArgumentResolver());resolvers.add(new ServletResponseMethodArgumentResolver());resolvers.add(new HttpEntityMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));resolvers.add(new RedirectAttributesMethodArgumentResolver());resolvers.add(new ModelMethodProcessor());resolvers.add(new MapMethodProcessor());resolvers.add(new ErrorsMethodArgumentResolver());resolvers.add(new SessionStatusMethodArgumentResolver());resolvers.add(new UriComponentsBuilderMethodArgumentResolver());// Custom argumentsif (getCustomArgumentResolvers() != null) {resolvers.addAll(getCustomArgumentResolvers());}// Catch-allresolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true));resolvers.add(new ServletModelAttributeMethodProcessor(true));return resolvers;
}

(2)BinderArgumentResolver

private List<HandlerMethodArgumentResolver> getDefaultInitBinderArgumentResolvers() {List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>(20);// Annotation-based argument resolutionresolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false));resolvers.add(new RequestParamMapMethodArgumentResolver());resolvers.add(new PathVariableMethodArgumentResolver());resolvers.add(new PathVariableMapMethodArgumentResolver());resolvers.add(new MatrixVariableMethodArgumentResolver());resolvers.add(new MatrixVariableMapMethodArgumentResolver());resolvers.add(new ExpressionValueMethodArgumentResolver(getBeanFactory()));resolvers.add(new SessionAttributeMethodArgumentResolver());resolvers.add(new RequestAttributeMethodArgumentResolver());// Type-based argument resolutionresolvers.add(new ServletRequestMethodArgumentResolver());resolvers.add(new ServletResponseMethodArgumentResolver());// Custom argumentsif (getCustomArgumentResolvers() != null) {resolvers.addAll(getCustomArgumentResolvers());}// Catch-allresolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true));return resolvers;
}

(3)ReturnValueHandler

private List<HandlerMethodReturnValueHandler> getDefaultReturnValueHandlers() {List<HandlerMethodReturnValueHandler> handlers = new ArrayList<>(20);// Single-purpose return value typeshandlers.add(new ModelAndViewMethodReturnValueHandler());handlers.add(new ModelMethodProcessor());handlers.add(new ViewMethodReturnValueHandler());handlers.add(new ResponseBodyEmitterReturnValueHandler(getMessageConverters(),this.reactiveAdapterRegistry, this.taskExecutor, this.contentNegotiationManager));handlers.add(new StreamingResponseBodyReturnValueHandler());handlers.add(new HttpEntityMethodProcessor(getMessageConverters(),this.contentNegotiationManager, this.requestResponseBodyAdvice));handlers.add(new HttpHeadersReturnValueHandler());handlers.add(new CallableMethodReturnValueHandler());handlers.add(new DeferredResultMethodReturnValueHandler());handlers.add(new AsyncTaskMethodReturnValueHandler(this.beanFactory));// Annotation-based return value typeshandlers.add(new ModelAttributeMethodProcessor(false));handlers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(),this.contentNegotiationManager, this.requestResponseBodyAdvice));// Multi-purpose return value typeshandlers.add(new ViewNameMethodReturnValueHandler());handlers.add(new MapMethodProcessor());// Custom return value typesif (getCustomReturnValueHandlers() != null) {handlers.addAll(getCustomReturnValueHandlers());}// Catch-allif (!CollectionUtils.isEmpty(getModelAndViewResolvers())) {handlers.add(new ModelAndViewResolverMethodReturnValueHandler(getModelAndViewResolvers()));} else {handlers.add(new ModelAttributeMethodProcessor(true));}return handlers;
}

4.3 refresh -: 事件推送

/* Last step: publish corresponding event. */
protected void finishRefresh() {// Clear context-level resource caches (such as ASM metadata from scanning).// 清除resourceCaches资源缓存中的数据clearResourceCaches();// Initialize lifecycle processor for this context.// 为此上下文初始化生命周期处理器initLifecycleProcessor();// Propagate refresh to lifecycle processor first.// 首先将刷新完毕事件传播到生命周期处理器(触发isAutoStartup方法返回true的SmartLifecycle的start方法)getLifecycleProcessor().onRefresh();// Publish the final event.// => 推送上下文刷新完毕事件到相应的监听器  ===> DispatcherServlet#onRefresh() - 见#3.3publishEvent(new ContextRefreshedEvent(this));// Participate in LiveBeansView MBean, if active.LiveBeansView.registerApplicationContext(this);
}

SpringMVC启动过程:

Web 容器启动时会去读取 web.xml 这样的部署描述文件,相关组件启动顺序为:解析 <context-param> => 解析 <listener> => 解析 <filter> => 解析 <servlet> ,具体初始化过程如下:

1、解析 <context-param> 里的键值对。
2、创建一个 application 内置对象即 ServletContext,servlet 上下文,用于全局共享。
3、将 <context-param> 的键值对放入 ServletContext 即 application 中,Web 应用内全局共享。
4、读取 <listener> 标签创建监听器,一般会使用 ContextLoaderListener 类,Spring 就会创建一个 WebApplicationContext 类的对象,WebApplicationContext 类就是 IoC 容器,ContextLoaderListener 类创建的 IoC 容器是根 IoC 容器为全局性的,并将其放置在 appication 中,作为应用内全局`共享,键名为 WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,可以通过以下两种方法获取:

// [1]
WebApplicationContext applicationContext = (WebApplicationContext) application.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
// [2]
WebApplicationContext applicationContext1 = WebApplicationContextUtils.getWebApplicationContext(application); 

这个全局的根 IoC 容器只能获取到在该容器中创建的 Bean 不能访问到其他容器创建的 Bean,也就是读取 web.xml 配置的 contextConfigLocation 参数的 xml 文件来创建对应的 Bean。

5、listener 创建完成后如果有 <filter> 则会去创建 filter。
6、初始化创建 <servlet> ,一般使用 DispatchServlet 类。
7、DispatchServlet 的父类 FrameworkServlet 会重写其父类的 initServletBean 方法,并调用 initWebApplicationContext() 以及 onRefresh() 方法。
8、initWebApplicationContext() 方法会创建一个当前 Servlet 的一个 IoC 子容器,如果存在上述的全局 WebApplicationContext 则将其设置为父容器,如果不存在上述全局的则父容器为 null。
9、读取 <servlet> 标签的 <init-param> 配置的 xml 文件并加载相关 Bean。
10、onRefresh() 方法创建 Web 应用相关组件。

5. DispatcherServlet 请求分析

image-20240514083720744

5.1 入口分析

通过前面的分析,我们知道 DispatcherServlet 其本质还是 Servlet,那么当客户端的请求到达时,根据 Servlet 生命周期,其应该会调用其或者其父类中的 service 方法。在其父类 FrameworkServlet 中我们找到了 service 方法。

@Override
protected void service(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {/***  获取HttpMethod类型,*  HttpMethod为枚举类,支持的Http请求类型有GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE*/HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());if (httpMethod == HttpMethod.PATCH || httpMethod == null) {processRequest(request, response);}else {super.service(request, response);}
}

但是在这里似乎没有看到我们最想要的东西,那么我们来看一下其 doGet 和 doPost 方法。

protected final void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {processRequest(request, response);
}protected final void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {processRequest(request, response);
}

从这里我们可以分析到,doGet、doPost 等 Http 请求委托给了 processRequest() 方法进行处理。

protected final void processRequest(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {// 记录开始时间long startTime = System.currentTimeMillis();Throwable failureCause = null;// 提取LocaleContext和RequestAttributes属性,以便在请求结束后能从当前线程中恢复LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();LocaleContext localeContext = buildLocaleContext(request);RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());// 初始化ContextHolder,将当前线程的LocaleContext和RequestAttributes绑定到ContextHolderinitContextHolders(request, localeContext, requestAttributes);try {// => 调用doService方法做下一步处理doService(request, response);}catch (ServletException | IOException ex) {failureCause = ex;throw ex;}catch (Throwable ex) {failureCause = ex;throw new NestedServletException("Request processing failed", ex);}finally {resetContextHolders(request, previousLocaleContext, previousAttributes);if (requestAttributes != null) {requestAttributes.requestCompleted();}logResult(request, response, failureCause, asyncManager);// 发布事件通知publishRequestHandledEvent(request, response, startTime, failureCause);}
}

该方法只是做了一些变量提取绑定、恢复、事件发布等工作,具体工作委托给了 doService 方法。

protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {logRequest(request);/*** 如果当前请求是一个 include request(不好翻译),如:<jsp:incluede page="xxx.jsp"/>* 则为此请求属性建立快照,以便include request结束后能够将其恢复*/// 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<>();Enumeration<?> attrNames = request.getAttributeNames();while (attrNames.hasMoreElements()) {String attrName = (String) attrNames.nextElement();if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {attributesSnapshot.put(attrName, request.getAttribute(attrName));}}}// Make framework objects available to handlers and view objects.// 将下列对象保存到request中,以便使用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());if (this.flashMapManager != null) {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 {// => 真正开始处理HTTP请求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);}}}
}

该方法中依然没有看到对核心流程的处理,请求处理进一步委托给了 doDispatch 方法。

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 mv = null;Exception dispatchException = null;try {// 1.尝试将当前请求转换为MultipartHttpServletRequestprocessedRequest = checkMultipart(request);multipartRequestParsed = (processedRequest != request);// Determine handler for the current request.// => 2.查找当前请求对应的handler,包括Handler(控制器)本身和Handler拦截器mappedHandler = getHandler(processedRequest);// 未能找到对应的handler,抛出NoHandlerFoundException异常并返回404if (mappedHandler == null) {noHandlerFound(processedRequest, response);return;}// Determine handler adapter for the current request.// 3.查找当前请求对应的HandlerAdapterHandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());// Process last-modified header, if supported by the handler.// 4.处理last-modified请求头,如果当前请求支持的话String method = request.getMethod();boolean isGet = "GET".equals(method);if (isGet || "HEAD".equals(method)) {long lastModified = ha.getLastModified(request, mappedHandler.getHandler());if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {return;}}// 5.应用前置拦截器// 如果有拦截器返回false,则表明该拦截器已经处理了返回结果,直接返回;if (!mappedHandler.applyPreHandle(processedRequest, response)) {return;}// Actually invoke the handler.// 6.调用HandlerAdapter的handler方法,真正开始处理Controllermv = ha.handle(processedRequest, response, mappedHandler.getHandler());// 7.如果当前请求是并发处理,直接返回if (asyncManager.isConcurrentHandlingStarted()) {return;}// 8.为返回值设定默认视图名,如果当前返回值中不包含视图名的话applyDefaultViewName(processedRequest, mv);// 9.应用已注册拦截器的后置方法。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,// making them available for @ExceptionHandler methods and other scenarios.dispatchException = new NestedServletException("Handler dispatch failed", err);}// 10.处理分发调用结果,如视图模型解析、返回等工作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 afterCompletionif (mappedHandler != null) {mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);}}else {// Clean up any resources used by a multipart request.if (multipartRequestParsed) {cleanupMultipart(processedRequest);}}}
}

历经 service–>doGet–>processRequest–>doService–>doDispatch 终于到了核心方法。doDispatch 方法看似简单,但是其背后有复杂的业务逻辑支撑。

5.2 获取 Handler&HandlerAdapter

// TODO

5.3 HandlerAdapter#handle

// TODO

5.4 视图解析渲染

// TODO

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.ryyt.cn/news/31673.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈,一经查实,立即删除!

相关文章

读人工智能时代与人类未来笔记03_演变

读人工智能时代与人类未来笔记03_演变1. 演变 1.1. 每个社会都找到了属于自己的一套适应世界的方法 1.1.1. 适应的核心,是有关人类心智与现实之间关系的概念 1.1.2. 人类认识周围环境的能力 1.1.2.1. 这种能力通过知识获得,同时…

爱芯通元产品与关键技术

爱芯通元产品与关键技术 混合精度NPU 以算子为原子指令集的AI计算处理器爱芯通元NPU采用多线程异构多核设计,实现算子、网络微结构、数据流和内存访问优化,高效支持混合精度算法设计,natively支持Transformer网络结构,为大模型在边缘侧、端侧的应用提供良好的基础。 经过多…

ffmpeg 去除音频中的静音

去除音频中的静音 //去除所有超过0.3秒的静音部分 ffmpeg -i input.mp3 -af silenceremove=stop_periods=-1:stop_duration=0.3:stop_threshold=-30dB -y output.mp3ffmpeg音频处理常用命令:调节音量大小ffmpeg -y -i input.mp3 -af volume=-10dB output.mp3 \n 主要是volume参…

基于异步协程的增量式微博网页版爬虫(一)思路篇

项目介绍 本项目旨在利用高级搜索功能,爬取微博网页版的详细数据。而大多数爬虫以单线程为主,但单线程存在资源利用率低的不足,针对这以问题,本项目主要使用如下技术: (1)多线程+协程技术+Redis实现增量式爬虫。实现过程中存在两个技术难点:一是使用redis数据传输时开销…

(9)FIFO ip核

一、FIFO简介 二、FIFO ip使用 使用方案: 操作: 在IP Catalog中找到这个 配置fifo: 生成ip:

imx6ull Linux内核构建

IMX6ULL的Linux内核编译步骤用户文档下载 在NXP官网的文档搜索界面,搜索Linux和User两个关键字就能查询到相关的文档 官网文档搜索地址:https://www.nxp.com.cn/design/documentation:DOCUMENTATION#/内核下载 在上面文档的第七章有交怎么下载以及编译内核#git安装 sudo apt-…

MASM中Group的作用

Masm5以后推出的simplified segment模式及.model标准模型中,都将段组合成一个group,group的作用及优点是什么呢? 一、Group的作用: 将组(group)后的所有段加入一个组,位于这些段内的的label(标号)或variables(变量)的偏移地址都参照Group的起始地址进行计算,而不是所在…

数量性状遗传参数的提出

001、 此间的代表人物主要有美国的动物育种学家 J. L. 勒什 ( Lush) , 他提出了数量遗传学中的两个重要参数: 遗传力 ( heritability) 和重复力 ( repeatability) 的概念以及合并选择理论; 其后, 他的学生 L. N.赫兹 ( Hazel) 提出了另一个重要参数: 遗传相关 ( genetic correl…