面试官:说一下Spring MVC的启动流程呗

简介: 基于XML配置的容器启动过程我们常用的Spring MVC是基于Servlet规范实现的,所以我们先来回顾一下Servlet相关的内容。

基于XML配置的容器启动过程

我们常用的Spring MVC是基于Servlet规范实现的,所以我们先来回顾一下Servlet相关的内容。

网络异常,图片无法展示
|

如果我们直接用Servlet来开发web应用,只需要继承HttpServlet,实现service方法即可,HttpServlet继承自Servlet,Servlet中常用的方法如下

public interface Servlet {
    // 初始化,只会被调用一次,在service方法调用之前完成
    void init(ServletConfig config) throws ServletException;
    ServletConfig getServletConfig();
    // 处理请求
    void service(ServletRequest req, ServletResponse res)throws ServletException, IOException;
    String getServletInfo();
    // 销毁
    void destroy();
}

每个Servlet有一个ServletConfig,用来保存和Servlet相关的配置每个Web应用有一个ServletContext,用来保存和容器相关的配置

考虑到很多小伙伴可能对Servlet的很多用法不熟悉了,简单介绍一下,就用xml配置了,当然你可以用JavaConfig的方式改一下

项目结构如下

网络异常,图片无法展示
|

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_3_1.xsd"
         version="3.1">
  <context-param>
    <param-name>configLocation</param-name>
    <param-value>test</param-value>
  </context-param>
  <servlet>
    <servlet-name>userServlet</servlet-name>
    <servlet-class>com.javashitang.controller.UserServlet</servlet-class>
    <init-param>
      <param-name>helloWord</param-name>
      <param-value>hello sir</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>userServlet</servlet-name>
    <url-pattern>/user.do</url-pattern>
  </servlet-mapping>
  <listener>
    <listener-class>com.javashitang.listener.MyServletContextListener</listener-class>
  </listener>
</web-app>
public class UserServlet extends HttpServlet {
    private String helloWord;
    @Override
    public void init(ServletConfig config) throws ServletException {
        this.helloWord = config.getInitParameter("helloWord");
    }
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html");
        PrintWriter out = resp.getWriter();
        String userId = req.getParameter("userId");
        out.println(helloWord + " " + userId);
    }
}

xml配置文件中可以用init-param标签给Servlet设置一些配置,然后在init方法中通过ServletConfig来获取这些配置,做初始化

访问
http://localhost:8080/user.do?userId=1
返回
hello sir 1

可以看到我们针对这个servlet还配置了load-on-startup这个标签,那么这个标签有什么用呢?

load-on-startup表示当容器启动时就初始化这个Servlet,数组越小,启动优先级越啊高。当不配置这个标签的时候则在第一次请求到达的时候才会初始化这个Servlet

context-param标签是容器的初始化配置,可以调用容器的getInitParameter方法获取属性值

Listener是一种扩展机制,当Web应用启动或者停止时会发送各种事件,我们可以用Listener来监听这些事件,做一些初始化工作。如监听启动事件,来初始化数据库连接等。

我这个demo只是获取了一下配置文件的位置,并打印出来。

public class MyServletContextListener implements ServletContextListener {
    // 容器启动
    public void contextInitialized(ServletContextEvent sce) {
        ServletContext sc = sce.getServletContext();
        String location = sc.getInitParameter("configLocation");
        System.out.println(location);
    }
    // 容器销毁
    public void contextDestroyed(ServletContextEvent sce) {
    }
}

基于Xml写一个Spring MVC应用

我们基于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_3_1.xsd"
     version="3.1">
  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:spring-context.xml</param-value>
  </context-param>
  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>
  <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: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-context.xml(配置service,dao层)

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xmlns:context="http://www.springframework.org/schema/context"
     xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
  <context:component-scan base-package="com.javashitang.service"/>
</beans>

spring-mvc.xml(配置和spring mvc相关的配置)

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xmlns:context="http://www.springframework.org/schema/context"
     xmlns:mvc="http://www.springframework.org/schema/mvc"
     xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd  http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">
  <context:component-scan base-package="com.javashitang.controller"/>
  <mvc:annotation-driven/>
</beans>
@RestController
public class UserController implements ApplicationContextAware {
  @Resource
  private UserService userService;
  private ApplicationContext context;
  @RequestMapping("user")
  public String index(@RequestParam("userId") String userId) {
    return userService.getUsername(userId);
  }
  @Override
  public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
    this.context = applicationContext;
    System.out.println("UserController " + context.getId());
  }
}
public interface UserService {
  String getUsername(String userId);
}
@Service
public class UserServiceImpl implements UserService, ApplicationContextAware {
  private ApplicationContext context;
  @Override
  public String getUsername(String userId) {
    return userId;
  }
  @Override
  public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
    this.context = applicationContext;
    System.out.println("UserServiceImpl " + context.getId());
  }
}

我们之所以要用2个配置文件,是因为在Spring MVC中有2个容器

父容器由ContextLoaderListener来初始化,一般用来存放一些dao层和service层的Bean

子容器由DispatcherServlet来初始化,一般用来存放controller层的Bean

项目启动后从打印出的值就可以看出来,Service和Controller是从2个容器获取的

UserServiceImpl org.springframework.web.context.WebApplicationContext:
UserController org.springframework.web.context.WebApplicationContext:/dispatcher

子容器可以访问父容器中的Bean,父容器不能访问子容器中的Bean。当从子容器找不到对应的Bean时,会从父容器中找

父容器启动

父容器由ContextLoaderListener来初始化,当tomcat启动的时候,发布启动事件,调用ContextLoaderListener#contextInitialized方法,接着调用initWebApplicationContext方法

网络异常,图片无法展示
|

子容器启动

子容器的启动在DispatcherServlet#init方法中

DispatcherServlet中并没有重写init方法,那就实在父类中了,HttpServletBean重写了init方法

网络异常,图片无法展示
|

用流程图总结一下过程

网络异常,图片无法展示
|

如果你觉得父容器没啥作用的话,可以把所有的Bean都放在子容器中

当配置父子容器的时候还是比较容易踩坑的,比如在子容器中配置了Bean A,在父容器中配置了Bean B,Bean B使用自动注入依赖了Bean A,此时因为父容器无法查找子容器的Bean,就会抛出找不到Bean A的异常。

可能觉得父子容器这种设计并不是特别好,所以在Spirng MVC用JavaConfig的方式配置时或者用Spirng Boot开发时,都只存在单一的ApplicationContext

基于JavaConfig配置的容器启动过程

Servlet3.0以后出了新规范,Servlet容器容器在启动的时候需要回掉
javax.servlet.ServletContainerInitializer接口的onStartup方法,方法的实现类放在META-IN/services/javax.servlet.ServletContainerInitializer文件中,典型的spi代码

这个接口特别重要,Spring Boot中不用web.xml也能启动注册DispatcherServlet的奥秘就在这个接口上

网络异常,图片无法展示
|

Servlet3.0并且还提供了一个@HandlesTypes注解,里面指定一个类型,servlet容器会把该类型的子类或者实现类,放到
ServletContainerInitializer#onStartup方法中的webAppInitializerClasses参数中,然后实现自己的逻辑

网络异常,图片无法展示
|

基于JavaConfig写一个Spring MVC应用

用JavaConfig写一个Spring MVC应用超级简单

public class MyWebApplicationInitializer implements WebApplicationInitializer {
  @Override
  public void onStartup(ServletContext servletContext) throws ServletException {
    // Load Spring web application configuration
    AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
    context.register(AppConfig.class);
    // Create and register the DispatcherServlet
    DispatcherServlet servlet = new DispatcherServlet(context);
    ServletRegistration.Dynamic registration = servletContext.addServlet("app", servlet);
    registration.setLoadOnStartup(1);
    registration.addMapping("/*");
  }
}
@EnableWebMvc
@ComponentScan("com.javashitang")
public class AppConfig {
}
@RestController
public class UserController {
  @RequestMapping("user")
  public String index(@RequestParam("userId") String userId) {
    return "hello " + userId;
  }
}

启动流程如下

  1. tomcat启动过程中回调ServletContainerInitializer#onStartup方法,并把@HandlesTypes中的WebApplicationInitializer实现类作为参数传入
  2. ServletContainerInitializer#onStartup方法会调用WebApplicationInitializer#onStartup,完成容器的初始化工作(我们只设置了一个容器哈)

启动过程除了Spring容器初始化是在Web容器回掉WebApplicationInitializer接口时发生的,其余的都一样

相关文章
|
3月前
|
前端开发 Java 微服务
《深入理解Spring》:Spring、Spring MVC与Spring Boot的深度解析
Spring Framework是Java生态的基石,提供IoC、AOP等核心功能;Spring MVC基于其构建,实现Web层MVC架构;Spring Boot则通过自动配置和内嵌服务器,极大简化了开发与部署。三者层层演进,Spring Boot并非替代,而是对前者的高效封装与增强,适用于微服务与快速开发,而深入理解Spring Framework有助于更好驾驭整体技术栈。
|
10月前
|
前端开发 Java 测试技术
微服务——SpringBoot使用归纳——Spring Boot中的MVC支持——@RequestParam
本文介绍了 `@RequestParam` 注解的使用方法及其与 `@PathVariable` 的区别。`@RequestParam` 用于从请求中获取参数值(如 GET 请求的 URL 参数或 POST 请求的表单数据),而 `@PathVariable` 用于从 URL 模板中提取参数。文章通过示例代码详细说明了 `@RequestParam` 的常用属性,如 `required` 和 `defaultValue`,并展示了如何用实体类封装大量表单参数以简化处理流程。最后,结合 Postman 测试工具验证了接口的功能。
599 0
微服务——SpringBoot使用归纳——Spring Boot中的MVC支持——@RequestParam
|
10月前
|
JSON 前端开发 Java
微服务——SpringBoot使用归纳——Spring Boot中的MVC支持——@RequestBody
`@RequestBody` 是 Spring 框架中的注解,用于将 HTTP 请求体中的 JSON 数据自动映射为 Java 对象。例如,前端通过 POST 请求发送包含 `username` 和 `password` 的 JSON 数据,后端可通过带有 `@RequestBody` 注解的方法参数接收并处理。此注解适用于传递复杂对象的场景,简化了数据解析过程。与表单提交不同,它主要用于接收 JSON 格式的实体数据。
1046 0
|
10月前
|
前端开发 Java 微服务
微服务——SpringBoot使用归纳——Spring Boot中的MVC支持——@PathVariable
`@PathVariable` 是 Spring Boot 中用于从 URL 中提取参数的注解,支持 RESTful 风格接口开发。例如,通过 `@GetMapping(&quot;/user/{id}&quot;)` 可以将 URL 中的 `{id}` 参数自动映射到方法参数中。若参数名不一致,可通过 `@PathVariable(&quot;自定义名&quot;)` 指定绑定关系。此外,还支持多参数占位符,如 `/user/{id}/{name}`,分别映射到方法中的多个参数。运行项目后,访问指定 URL 即可验证参数是否正确接收。
648 0
|
10月前
|
JSON 前端开发 Java
微服务——SpringBoot使用归纳——Spring Boot中的MVC支持——@RequestMapping
@RequestMapping 是 Spring MVC 中用于请求地址映射的注解,可作用于类或方法上。类级别定义控制器父路径,方法级别进一步指定处理逻辑。常用属性包括 value(请求地址)、method(请求类型,如 GET/POST 等,默认 GET)和 produces(返回内容类型)。例如:`@RequestMapping(value = &quot;/test&quot;, produces = &quot;application/json; charset=UTF-8&quot;)`。此外,针对不同请求方式还有简化注解,如 @GetMapping、@PostMapping 等。
541 0
|
10月前
|
JSON 前端开发 Java
微服务——SpringBoot使用归纳——Spring Boot中的MVC支持——@RestController
本文主要介绍 Spring Boot 中 MVC 开发常用的几个注解及其使用方式,包括 `@RestController`、`@RequestMapping`、`@PathVariable`、`@RequestParam` 和 `@RequestBody`。其中重点讲解了 `@RestController` 注解的构成与特点:它是 `@Controller` 和 `@ResponseBody` 的结合体,适用于返回 JSON 数据的场景。文章还指出,在需要模板渲染(如 Thymeleaf)而非前后端分离的情况下,应使用 `@Controller` 而非 `@RestController`
434 0
|
算法 测试技术 持续交付
面试的流程,面试的重点
本文介绍了面试流程及各轮面试的重点。通常面试为1-5轮,首轮关注技术实力与项目经验,次轮深入考察技术细节,第三轮侧重项目协调、创新及价值观等软性问题,如职业规划和沟通能力。面试题分为开放型(如项目经验、解决问题思路)和非开放型(如技术细节、手撕算法),需提前准备。测试类问题涉及自动化测试、持续集成等实际应用。
|
6月前
|
前端开发 Java API
Spring Cloud Gateway Server Web MVC报错“Unsupported transfer encoding: chunked”解决
本文解析了Spring Cloud Gateway中出现“Unsupported transfer encoding: chunked”错误的原因,指出该问题源于Feign依赖的HTTP客户端与服务端的`chunked`传输编码不兼容,并提供了具体的解决方案。通过规范Feign客户端接口的返回类型,可有效避免该异常,提升系统兼容性与稳定性。
472 0
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
413 2
|
6月前
|
SQL Java 数据库连接
Spring、SpringMVC 与 MyBatis 核心知识点解析
我梳理的这些内容,涵盖了 Spring、SpringMVC 和 MyBatis 的核心知识点。 在 Spring 中,我了解到 IOC 是控制反转,把对象控制权交容器;DI 是依赖注入,有三种实现方式。Bean 有五种作用域,单例 bean 的线程安全问题及自动装配方式也清晰了。事务基于数据库和 AOP,有失效场景和七种传播行为。AOP 是面向切面编程,动态代理有 JDK 和 CGLIB 两种。 SpringMVC 的 11 步执行流程我烂熟于心,还有那些常用注解的用法。 MyBatis 里,#{} 和 ${} 的区别很关键,获取主键、处理字段与属性名不匹配的方法也掌握了。多表查询、动态
198 0

热门文章

最新文章