一、关于servlet

  1. 详解servlet,https://www.runoob.com/servlet/servlet-tutorial.html
  2. 总览一下:
  • servlet与servlet容器

Java Servlet(Java服务器小程序)是一个基于Java技术的Web组件,运行在服务器端,它由Servlet容器所管理,用于生成动态的内容。 Servlet是平台独立的Java类,编写一个Servlet,实际上就是按照Servlet规范编写一个Java类。Servlet被编译为平台独立 的字节码,可以被动态地加载到支持Java技术的Web服务器中运行。

  • Servlet容器也叫做Servlet引擎,是Web服务器或应用程序服务器的一部分,用于在发送的请求和响应之上提供网络服务,解码基于 MIME的请求,格式化基于MIME的响应。Servlet没有main方法,不能独立运行,它必须被部署到Servlet容器中,由容器来实例化和调用 Servlet的方法(如doGet()和doPost()),Servlet容器在Servlet的生命周期内包容和管理Servlet。在JSP技术 推出后,管理和运行Servlet/JSP的容器也称为Web容器。

(注:常用的MIME类型:text/html,application/pdf,video/quicktime,application /java,image/jpeg,application/jar,application/octet-stream,application/x- zip)

有了servlet之后,用户通过单击某个链接或者直接在浏览器的地址栏中输入URL来访问Servlet,Web服务器接收到该请求后,并不是将 请求直接交给Servlet,而是交给Servlet容器。Servlet容器实例化Servlet,调用Servlet的一个特定方法对请求进行处理, 并产生一个响应。这个响应由Servlet容器返回给Web服务器,Web服务器包装这个响应,以HTTP响应的形式发送给Web浏览器。

  • servlet容器能提供什么?

我们知道需要由servlet容器来管理和运行servlet,但是为什么要这样做呢?使用servlet容器的原因有:

  • 通信支持:利用容器提供的方法,你能轻松的让servlet与web服务器对话,而不用自己建立serversocket、监听某个端口、创建流等等。容器知道自己与web服务器之间的协议,所以你的servlet不用担心web服务器(如Apache,jetty)和你自己的web代码之间的API,只需要考 虑如何在servlet中实现业务逻辑(如处理一个订单)。

  • 生命周期管理:servlet容器控制着servlet的生与死,它负责加载类、实例化和初始化servlet,调用servlet方法,以及使servlet实例被垃圾回收,有了servlet容器,你不需要太多的考虑资源管理。

  • 多线程支持:容器会自动为它所接收的每个servlet请求创建一个新的java线程。针对用户的请求,如果servlet已经运行完相应的http服务方法,这个线程就会结束。这并不是说你不需要考虑线程安全性,其实你还会遇到同步问题,不过这样能使你少做很多工作。

  • 声明方式实现安全:利用servlet容器,你可以使用xml部署描述文件来配置和修改安全性,而不必将其硬编码写到servlet类代码中

  • JSP支持:servlet容器负责将jsp代码翻译为真正的java代码。

  • Servlet具有以下优点:
  • Servlet是单实例多线程的运行方式,每个请求在一个独立的线程中运行,而提供服务的Servlet实例只有一个。
  • Servlet具有可升级性,能响应更多的请求,因为Servlet容器使用一个线程而不是操作系统进程,而线程仅占用有限的系统资源。
  • Servlet使用标准的API,被更多的Web服务器所支持。
  • Servlet使用Java语言编写,因此拥有Java程序语言的所有优点,包括容易开发和平台独立性。
  • Servlet可以访问Java平台丰富的类库,使得各种应用的开发更为容易。
  • Servlet容器给Servlet提供额外的功能,如错误处理和安全。

其实,servlet就是一种使用http协议在服务器与客户端之间通信的技术。是Socket的一种应用

  • Tomcat

学习Servlet技术,就需要有一个Servlet运行环境,也就是需要有一个Servlet容器,本文用的是Tomcat。还有其他如jetty。Tomcat和IIS、Apache等Web服务器一样,具有处理HTML页面的功能,另外它还是一个Servlet和JSP容器,独立的 Servlet容器是Tomcat的默认模式。不过,Tomcat处理静态HTML的能力不如Apache,我们可以将Apache和Tomcat集成在 一起使用,Apache作为HTTP Web服务器,Tomcat作为Web容器。关于apache和tomcat的区别。

Tomcat服务器接受客户请求并做出响应的过程如下:

  1. 客户端(通常都是浏览器)访问Web服务器,发送HTTP请求。
  2. Web服务器接收到请求后,传递给Servlet容器。
  3. Servlet容器加载Servlet,产生Servlet实例后,向其传递表示请求和响应的对象。
  4. Servlet实例使用请求对象得到客户端的请求信息,然后进行相应的处理。
  5. Servlet实例将处理结果通过响应对象发送回客户端,容器负责确保响应正确送出,同时将控制返回给Web服务器。

二、从web.xml说起

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_5_0.xsd"version="5.0"><!-- spring配置文件,包括开启bean扫描,aop,事务 --><!-- Spring加载的xml文件,不配置默认为applicationContext.xml --><context-param><param-name>contextConfigLocation</param-name><param-value>/WEB-INF/spring-config.xml</param-value></context-param><!--ContextLoaderListener用于在启动web容器的时候,去上面的位置 读取配置文件并初始化Spring容器。启动父容器,即IOC容器,管理Dao,Service--><!-- 该类作为spring的listener使用,它会在创建时自动查找web.xml配置的applicationContext.xml文件 --><listener><listener-class>org.springframework.web.context.ContextLoaderListener</listener-class></listener><!--spring mvc配置--><!-- 配置Spring MVC的DispatcherServlet,也可以配置为继承了DispatcherServlet的自定义类,这里配置spring mvc的配置(扫描controller) --><!--用于启动子容器,也即是springMVC容器--><servlet><servlet-name>dispatcher</servlet-name><servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class><init-param><param-name>contextConfigLocation</param-name><param-value>/WEB-INF/spring-mvc.xml</param-value></init-param><load-on-startup>1</load-on-startup></servlet><servlet-mapping><servlet-name>dispatcher</servlet-name><url-pattern>/</url-pattern></servlet-mapping>
</web-app>
  • spring父容器的加载

Tomcat启动的时候会依次加载web.xml中配置的Listener、Filter和Servlet。所以根据上面的配置,会首先加载ContextLoaderListener,这个类继承了ContextLoader,用来初始化Spring根上下文,并将其放入ServletContext中。

实现 javax.servlet.ServletContextListener 接口,继承 ContextLoader 类,实现 Servlet 容器启动和关闭时,分别初始化和销毁 WebApplicationContext 容器

public class ContextLoaderListener extends ContextLoader implements ServletContextListener {public ContextLoaderListener() {}/*** As of Spring 3.1, supports injecting the root web application context*/public ContextLoaderListener(WebApplicationContext context) {super(context);}/*** Initialize the root web application context.*/@Overridepublic void contextInitialized(ServletContextEvent event) {// <1> 初始化 Root WebApplicationContextinitWebApplicationContext(event.getServletContext());}/*** Close the root web application context.*/@Overridepublic void contextDestroyed(ServletContextEvent event) {// <2> 销毁 Root WebApplicationContextcloseWebApplicationContext(event.getServletContext());ContextCleanupListener.cleanupAttributes(event.getServletContext());}
}

下面就以这个为入口分析下代码。Tomcat容器首先会调用ContextLoadListener的contextInitialized()方法,这个方法又调用了父类ContextLoader的initWebApplicationContext()方法。下面是这个方法的源代码。

public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {//如果ServletContext中已经存在Spring容器则报错
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!");}Log logger = LogFactory.getLog(ContextLoader.class);servletContext.log("Initializing Spring root WebApplicationContext");if (logger.isInfoEnabled()) {logger.info("Root WebApplicationContext: initialization started");}long startTime = System.currentTimeMillis();try {// Store context in local instance variable, to guarantee that// it is available on ServletContext shutdown.if (this.context == null) {//这里创建了webApplicationContext,默认创建的是XmlWebApplicationContext//如果想要自定义实现类,可以在web.xml的<context-param>中配置contextClass这个参数//此时的Context还没进行配置,相当于只是个"空壳"this.context = createWebApplicationContext(servletContext);}if (this.context instanceof ConfigurableWebApplicationContext) {ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;if (!cwac.isActive()) {// The context has not yet been refreshed -> provide services such as// setting the parent context, setting the application context id, 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);}//读取Spring的配置文件,初始化父上下文环境configureAndRefreshWebApplicationContext(cwac, servletContext);}}//将根上下文存入ServletContext中。servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);ClassLoader ccl = Thread.currentThread().getContextClassLoader();if (ccl == ContextLoader.class.getClassLoader()) {currentContext = this.context;}else if (ccl != null) {currentContextPerThread.put(ccl, this.context);}if (logger.isDebugEnabled()) {logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" +WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");}if (logger.isInfoEnabled()) {long elapsedTime = System.currentTimeMillis() - startTime;logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");}return this.context;}catch (RuntimeException ex) {logger.error("Context initialization failed", ex);servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);throw ex;}catch (Error err) {logger.error("Context initialization failed", err);servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err);throw err;}
}

spring体系下,ApplicationContext的集成体系

执行时序图:

至此,Spring的父(根)上下文已经初始化完毕,并且已经存在ServletContext中。

  • springMVC子容器的加载

DispatcherServlet的继承图如下

我们知道,加载web.xml中配置的Listener、Filter后,就会加载Servlet。在我们的web.xml中就是org.springframework.web.servlet.DispatcherServlet,而通过继承树我们知道,DispatcherServlet也是Servlet的实现,所以加载流程也是Servlet的流程。因为DispatcherServlet继承了FrameworkServlet,FrameworkServlet又继承了HttpServletBean,HttpServletBean又继承HttpServlet并且重写了init方法,所以创建子上下文时的入口就在这个init方法。

  • HttpServletBean.init(): 重写 GenericServlet 中的方法,负责将 ServletConfig 设置到当前 Servlet 对象中
public final void init() throws ServletException {//读取Servlet配置的init-param,创建DispatcherServlet实例PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);if (!pvs.isEmpty()) {try {BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));initBeanWrapper(bw);bw.setPropertyValues(pvs, true);}catch (BeansException ex) {if (logger.isErrorEnabled()) {logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);}throw ex;}}// HttpServletBean的这个方法中没有做任何事情,子类FrameWorkServlet这个类的initServletBean()方法重写了// 这个类,所以后续工作会在这个方法中执行。initServletBean();}

下面是FrameWorkServlet这个类的initServletBean()方法

//FrameWorkServlet.initServletBean()
protected final void initServletBean() throws ServletException {getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'");if (this.logger.isInfoEnabled()) {this.logger.info("FrameworkServlet '" + getServletName() + "': initialization started");}long startTime = System.currentTimeMillis();try {//这里是重点,初始化子Spring上下文this.webApplicationContext = initWebApplicationContext();initFrameworkServlet();}catch (ServletException ex) {this.logger.error("Context initialization failed", ex);throw ex;}catch (RuntimeException ex) {this.logger.error("Context initialization failed", ex);throw ex;}if (this.logger.isInfoEnabled()) {long elapsedTime = System.currentTimeMillis() - startTime;this.logger.info("FrameworkServlet '" + getServletName() + "': initialization completed in " +elapsedTime + " ms");}
}

下面是initWebApplicationContext()方法的具体代码

protected WebApplicationContext initWebApplicationContext() {// <1> 获得根 WebApplicationContext 对象WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(getServletContext());// <2> 获得 WebApplicationContext wac 对象WebApplicationContext wac = null;// 第一种情况,如果构造方法已经传入 webApplicationContext 属性,则直接使用if (this.webApplicationContext != null) {// A context instance was injected at construction time -> use itwac = this.webApplicationContext;// 如果是 ConfigurableWebApplicationContext 类型,并且未激活,则进行初始化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) {// The context instance was injected without an explicit parent -> set// the root application context (if any; may be null) as the parentcwac.setParent(rootContext);}// 配置和初始化 wacconfigureAndRefreshWebApplicationContext(cwac);}}}// 第二种情况,从 ServletContext 获取对应的 WebApplicationContext 对象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 idwac = findWebApplicationContext();}// 第三种,创建一个 WebApplicationContext 对象if (wac == null) {// No context instance is defined for this servlet -> create a local onewac = createWebApplicationContext(rootContext);}// <3> 如果未触发刷新事件,则主动触发刷新事件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.synchronized (this.onRefreshMonitor) {onRefresh(wac);}}// <4> 将 context 设置到 ServletContext 中if (this.publishContext) {// 将Spring子上下文存入ServletContextString attrName = getServletContextAttributeName();getServletContext().setAttribute(attrName, wac);}return wac;
}

最后看下DispatcherServlet中的onRefresh()方法,这个方法初始化了很多策略:
注意:FrameWorkServlet.onRefresh()只有DispatcherServlet中进行重写

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

上面都是通过类似的机制,加载所有实现了该接口的类:

到此为止,SpringMVC的启动过程结束了。这边做下SpringMVC初始化总结:

  1. HttpServletBean的主要做一些初始化工作,将我们在web.xml中配置的参数设置到Servlet中;
  2. FrameworkServlet主要作用是初始化Spring子上下文,设置其父上下文,并将其和ServletContext关联;
  3. DispatcherServlet:初始化各个功能的实现类。比如异常处理、视图处理、请求映射处理等。

时序图:

三、传统的Spring MVC项目启动流程

  1. 如果在web.xml中配置了org.springframework.web.context.ContextLoaderListener,那么Tomcat在启动的时候会先加载父容器,并将其放到ServletContext中
  2. 然后会加载DispatcherServlet(这块流程建议从init方法一步步往下看,流程还是很清晰的),因为DispatcherServlet实质是一个Servlet,所以会先执行它的init方法。这个init方法在HttpServletBean这个类中实现,其主要工作是做一些初始化工作,将我们在web.xml中配置的参数书设置到Servlet中,然后再触发FrameworkServlet的initServletBean()方法;
  3. FrameworkServlet主要作用是初始化Spring子上下文,设置其父上下文,并将其放入ServletContext中;
  4. FrameworkServlet在调用initServletBean()的过程中同时会触发DispatcherServlet的onRefresh()方法,这个方法会初始化Spring MVC的各个功能组件。比如异常处理器、视图处理器、请求映射处理等。

四、SpringBoot集成SpringMVC

我们知道,SpringBoot的自动导入机制就是依赖spring framework内部使用的通用的工厂加载机制。其导入链如下:

@SpringBootApplication->
@EnableAutoConfiguration>
@Import({AutoConfigurationImportSelector.class})
AutoConfigurationImportSelector类下的List<String>configurations=this.getCandidateConfigurations(annotationMetadata,attributes);
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),getBeanClassLoader());Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "+ "are using a custom packaging, make sure that file is correct.");return configurations;}

springFactoriesLoader是spring framework内部使用的通用的工厂加载机制,其可加载并实例化可能出现在classpath上的多个jar包中的META-INF/spring.factories文件中定义的指定类型的工厂,可视为一种类似于SPI的接口。 SpringBoot利用这种SPI接口实现了autoconfiguration机制:委托SpringFactoriesLoader来加载所有配置在META-INF/spring.factories中的org.springframework.boot.autoconfigure.EnableAutoConfiguration对应的值,spring-boot-autoconfiguration jar包中的META-INF/spring.factories中的EnableAutoConfiguration配置了众多供springboot导入的类。我们先回想一下SpringBoot预先读取的可配置Config类有哪个是与WebMVC相关的?经过比对我们发现在Key=EnableAutoConfiguration,Value=WebMvcAutoConfiguration就是关于SpringBoot自动配置WebMVC的可配置类,关于SpringBoot自动配置WebMVC的玄机也正是在这个配置类里面。

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration,\

于是,我们必须好好看一下这个可配置类到底做了什么事情,能让SpringBoot启动完成之后也自动初始化好了一个WebMVC的环境。

点开可以看到这个可配置类上有很多注解修饰,这些注解分别限定在某些条件满足或不满足情况下,spring才会初始化并配置这个Config类。关于这些注解和文字解释如下:

//声明为配置类,并且Bean的方法不进行代理
@Configuration(proxyBeanMethods = false)//判断当前环境是一个SERVLET环境,也就是说是Web环境下这个配置类才生效
@ConditionalOnWebApplication(type = Type.SERVLET)//判断当前环境是否含有Servlet,DispatcherServlet,WebMvcConfigurer实例(前面SpringMVC章节零XML方式介绍的接口,
//类似引入web.xml的Java实例),这些都是WebMVC必不可少的组件,只有这些都存在这个配置类才生效
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })//判断当前环境是否存在WebMvcConfigurationSupport,如果不存在这个配置类才生效
//为什么这里不存在WebMvcConfigurationSupport时才能让配置类生效呢?因为这个接口是一个自定义配置WebMVC的接口,
//如果实现了这个接口就意味着开发者自己手动进行了webMVC的配置,那么SpringBoot就不再帮你自动配置了,防止了配置冲突。
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)//自动配置顺序:数值越低越优先配置
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)//WebMVC配置的核心注解,主要是对DispatcherServlet进行Java配置,以及对任务执行和校验器进行Java配置。
//当这个配置类生效之后,就会接着进行DispatcherServletAutoConfiguration,TaskExecutionAutoConfiguration,
//ValidationAutoConfiguration的配置,重点看DispatcherServletAutoConfiguration
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,ValidationAutoConfiguration.class })public class WebMvcAutoConfiguration {.....
}

从上面可以看到,SpringBoot要想自动配置WebMVC环境,那么是需要满足上述注解定义的一些条件:

  • 处于Web环境下
  • 容器中已经初始化好了WebMVC必须的组件
  • 用户没有自己手工配置过WebMVC

这些条件都满足后,SpringBoot就会为我们自动配置一个WebMVC环境,但是这个环境是怎么样的呢(比如采用什么容器,端口号是什么,DispatchServlet怎么配置,Resover怎么配置,Converter怎么配置,等等)?这个就是最后一个注解@AutoConfigureAfter内声明的来定义了,实际上也就是由DispatcherServletAutoConfiguration.class通过Java零XML配置方式在代码里为我们提前写好定义的环境。

对于核心DispatcherServlet的自动配置,点开这个核心DispatcherServletAutoConfiguration.class

可以看到这个DispatcherServlet自动配置类也有很多注解修饰:

@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
//如果环境中已经有了用户自己配置的DispatcherServlet,就不再自动配置
@ConditionalOnClass(DispatcherServlet.class)
//在这个DispatcherServlet自动配置之后,通过ServletWebServerFactoryAutoConfiguration对web容器进行配置
@AutoConfigureAfter(ServletWebServerFactoryAutoConfiguration.class)

在这个DispatcherServlet自动配置类中,最核心的就是对DispatcherServlet的配置,可以看到对DispatcherServlet配置的核心代码在静态内部类DispatcherServletConfiguration的dispatcherServlet方法上,通过@Bean返回给Spring容器:

从这个方法内部我们就看出了,SpringBoot自动配置DispatcherServlet时是直接new的,然后设置各种参数最终通过@Bean交给Spring容器。在上面的方法执行结束将一个DispatcherServlet交给Spring容器后,接下来会调用静态内部类DispatcherServletRegistrationConfiguration的dispatcherServletRegistration方法,对DispatcherServlet的参数进行配置

这里设置DispatcherServlet名字为dispatcherServlet;启动顺序是默认值-1(使用时加载);请求接收路径是/;配置完成后交给Spring容器,这就是对DispatcherServlet的Java代码配置了

不过这里有一个问题,就是这里返回给Spring容器的是一个DispatcherServletRegistrationBean,那么Tomcat等Web容器时怎么将这个DispatcherServletRegistrationBean内包含的DispatcherServlet信息解析出来并生效运行呢?这个知识点有必要做一下说明。

首先看这个DispatcherServletRegistrationBean的构造是怎么样的:

这个DispatcherServletRegistrationBean继承了ServletRegistrationBean,这个ServletRegistrationBean是携带了供Tomcat容器解析加载的DispatcherServlet,让我们看下这个ServletRegistrationBean的继承关系图:

就可以知道这个DispatcherServletRegistrationBean实际最终是实现了ServletContextInitializer接口,看到这里如果前面对Servlet的SPI有所了解很快就能反应过来,这里是有一个onStartup方法通过SPI机制让Tomcat启动时能自动调用到onStartup里面来,通过下面调用链最终Spring容器进行了addServlet操作将DispatchServlet添加到Spring容器中并生效,最后进行config方法对DispatcherServlet进行设置:

关于SPI机制可以参考这篇文章



但是读了上面的源码又引发了两个很值得深思的问题

  1. 在静态内部类DispatcherServletConfiguration的dispatcherServlet方法上,通过new一个DispatcherServlet之后直接用@Bean返回给Spring容器了。在学习SpringMVC时我们知道DispatcherServlet需要传入一个ApplicationContext上下文,如果没有传递则会去默认配置文件解析出一个上下文。但是这里这个DispatcherServlet却没有关联任何Spring上下文的地方却能使DispatcherServlet起作用,那这个关联操作到底是在哪里进行的呢?

这需要我们看SpringBoot中DispatcherServlet的源码是怎么写的,让我们看下有参的构造是怎么样的,因为无参构造只是一个空方法:



使用IDEAJ的查找工具,看下这个this.webApplicationContext在哪些地方被设置,可以找到是在setApplicationContext方法内进行设置的:

看到这个重写方法以及方法注释,根据Spring的知识我们应该猜想到这个类应该是实现了ApplicationContextAware接口。ApplicationContextAware接口是做什么的呢?这个接口是当Spring容器初始化结束之后,实现了ApplicationContextAware接口的实现类就会被调用并且执行setApplicationContext方法。回到这里也就是说当Spring容器初始化完成之后,由于实现了ApplicationContextAware接口,于是会执行setApplicationContext方法,在这个方法中将初始化完成的Spring上下文赋值给this.webApplicationContext变量(这个变量就是DispatchServlet内部的Spring上下文变量),于是就解释了为什么SpringBoot在new一个DispatchServlet时不需要传入Spring上下文的原因

  1. 理解了上面的问题和答案,就引出第二个问题:

前面学习SpringMVC时知道对DispatcherServlet的配置是将Spring容器对象作为参数传给DispatcherServlet;但是SpringBoot自动配置WebMVC时却是将DispatcherServlet对象传给Spring容器;这两种场景的实现是反过来的,为什么会这样设计呢?其实这两种方式的区别就在于它们分别是怎么接在Spring容器的。

回答为什么SpringBoot是将DispatchServlet传给Spring容器的问题,就要结合上面第一个问题的解析。这是因为要调用实现了ApplicationContextAware接口的实现类,则必须保证这个实现类在Spring容器当中才能生效。也就是说SpringBoot中实现了ApplicationContextAware接口的DispatchServlet要想触发接口方法,则必须作为一个Bean存在于Spring容器中,这就是SpringBoot为什么要将DispatchServlet作为Bean传入到Spring容器中的原因了

  1. 上面介绍完SpringBoot对DispatchServlet进行自动配置的细节,那么还为我们做了哪些组件配置呢?这里要回过来看WebMvcAutoConfiguration

在这个自动配置类中,定义了一个静态内部类WebMvcAutoConfigurationAdapter,实现了WebMvcConfigurer接口。这个就和之前学习SpringMVC应用时介绍的一样,通过实现WebMvcConfigurer接口就可以拥有一个类似web.xml功能,可以对其它组件进行配置操作:

public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer

在这个里面实现了默认的Resolver,MessageConverters,ViewResolver等组件。如果我们要添加自定义的组件怎么办呢?对于SpringMVC而言我们必须在方法中将这个自定义的组件添加到对应组件集合中,但是SpringBoot对此进行的拓展,使得我们可以直接通过@Bean的方式就可以添加自定义组件(这一点在SpringMVC是做不到自定义添加的):

五、总结一下SpringBoot除了自动配置DispatchServlet之外,还配置了以下

  1. spring boot会默认注入一个视图解析器:ContentNegotiatingViewResolver
    主要做2个事情:
  • 整合所有的视图解析器
  • 遍历所有的视图解析器选一个最佳的方案

spring boot 会在当前的spring容器里面找到所有HttpMessageConverter类型的Bean 封装成一个集合

  • spring boot会自己注入一个默认的实现 jackson
  • 如果用户需要自己配置的话 只需要@Bean
  1. SpringBoot实现一个字符串到日期的参数转换器:

每当前端要将字符串日期传递到后台之后,要自动转换成日期格式类型时,可以写一个Converter进行转换。


至此,关于SpringBoot自动配置WebMVC的源码原理就分享到这里了。

  • 以springmvc自动装配为例看底层源码细节:配置类WebMvcAutoConfiguration.
  • 这个配置类有多个注解分别限定在某些条件满足或不满足情况下,spring才会初始化并配置这个Config类,
  • 其中注解@AutoConfigureAfter中DispatcherServletAutoConfiguration是自动装配的核心类,
  • 这里主要对DispatcherServlet进行配置,和servlet被tomcat调用onStartup()一样
  • 需要先new出DispatcherServlet并放入Spring容器,然后在tomcat调用前进行addXXX参数的操作,
  • 在springboot实现的javaConfig里,filter,listener,DispatcherServlet,servlet这些servler子类都用XXXRegistrationBean来封装,
  • 这些XXXRegistrationBean都实现了RegistrationBean超类(这个超类拥有onStartup方法),于是保证这些Bean都可以被 tomcat识别并执行,在执行各种不同类型的Bean时,会调用不同的register或configure方法进行add各自不同参数的方法,
  • 从而实现了各种servlet组件参数的自动配置和加载步骤。对于web容器的配置是ServletWebServerFactoryAutoConfiguration配置类中进行主要对web容器类型IP端口连接数等参数进行配置。

参考文章

从web.xml谈谈SpringMVC集成spring的初始化流程及SpringBoot集成SpringMVC相关推荐

  1. web.xml 通过contextConfigLocation配置spring 的方式

    部署到tomcat后,src目录下的配置文件会和class文件一样,自动copy到应用的 classes目录下 spring的 配置文件在启动时,加载的是web-info目录下的application ...

  2. 透过源码详解Spring Security 初始化流程

    Spring Security在3.2版本之后支持Java Configuration,即:通过Java编码形式配置Spring Security,可不再依赖XML文件配置,本文采用Java Conf ...

  3. Spring Boot笔记(七) springboot 集成 JavaMail 实现邮箱认证

    个人博客网:https://wushaopei.github.io/    (你想要这里多有) 一.JavaMail 1.什么是JavaMail? JavaMail,顾名思义,提供给开发者处理 电子邮 ...

  4. Spring Beans 初始化流程分析

    测试用例 依然使用这个官网上的用例,来进行调试: Person.java package org.shangyang.spring.container;/**- - @author shangyang ...

  5. Tomcat原理系列之四:Tomat如何启动spring(加载web.xml)

    Tomcat原理系列之四:Tomat如何启动spring 熟悉的web.xml ContextLoaderListener Tomcat的初始化StandardContext.startInterna ...

  6. 从使用传统Web框架到切换到Spring Boot后的总结

    1.前言 其实我接触 Spring Boot 的时间并不长,所以还算一个初学者,这篇文章也算是我对 Spring Boot 学习以及使用过程中的复盘,如果文章出现描述错误或表达不清晰的地方,欢迎大家在 ...

  7. SpringMVC(2)—SpringMVC整合Spring的HelloWorld

    一.这是一个SpringMVC框架的案例HelloWorld 功能:HelloWorld 二.SpringMVC运行流程 1.流程 请求-->springDispatcherServlet的ur ...

  8. SpringMVC、Spring、Mybatis框架整合及使用

    首先需要创建一个动态web工程 引入所需的jar包 创建所需要的数据库表,并插入数据 1 CREATE DATABASE how2java; 2 USE how2java; 3 4 CREATE TA ...

  9. web.xml里,classpath使用范围

    比如说在web.xml里,配置spring监听. 在标签<param-value>里,classpath指向的配置文件路径应该是在config资源文件夹下的applicationConte ...

最新文章

  1. wordpress中文乱码处理方法
  2. qt.targets(66,3):元素<Import>中的“Project”特性的值无效
  3. Bruce Eckel最新演讲:调bug就是浪费生命!放弃吧!(附完整PPT)
  4. 行为设计模式 - 状态设计模式
  5. Ubuntu 16.04安装PPA图形化管理工具Y PPA Manager
  6. 【网络通信 -- SIP 电话】项目实战记录 -- FreeSwitch 服务器搭建与典型 SIP 电话应用
  7. 【软件与系统安全】栈溢出利用的分析
  8. 除以用计算机按哪个键,键盘上除以号是哪个键
  9. C#使用Windows全局钩子(Winform) SetWindowsHookEx
  10. 【2021 最新】100 道大厂大数据必考面试题+答案详解
  11. 玻璃幕墙LED透明屏多少钱一平方?为你解答
  12. 下一代防火墙(NGFW)已死!
  13. Promise对象的resolve回调函数和reject回调函数使用
  14. CSS的2D旋转效果
  15. 适用的验厂考勤工资AB账系统软件这样选择
  16. Linux添加用户练习
  17. badboy录制时弹框提示“当前页面的脚本发生错误”
  18. 导数能不能通过除法 dy/dx 的求出?
  19. 好书短评之《你就是极客!》
  20. 手机电池的保护电路详细介绍

热门文章

  1. 使用Picasso实现图片圆角和图片圆形
  2. 线上日志集中化可视化管理:ELK
  3. centos 安装nginx笔记
  4. nyoj 202红黑树 (搜索)
  5. Windows10 UWP开发 - 响应式设计
  6. linux下tomcat的安装和配置
  7. POJ 2411 Mondriaan's Dream [经典状态压缩dp]
  8. 标题要在3~5字之间-三年总结
  9. 吴恩达 coursera ML 第十七课总结+作业答案
  10. YOLO v3解析与实现