3.SpringBoot学习(三)——Spring Boot WebMVC及其工作原理

1.简介

1.1 概述

The Spring portfolio provides two parallel stacks. One is based on a Servlet API with Spring MVC and Spring Data constructs. The other is a fully reactive stack that takes advantage of Spring WebFlux and Spring Data’s reactive repositories. In both cases, Spring Security has you covered with native support for both stacks. https://spring.io/reactive

Spring产品组合提供了两个并行技术栈。一种基于带有 Spring MVC 和 Spring Data 结构的 Servlet API。另一个是完全响应式技术栈,该栈利用了 Spring WebFlux 和 Spring Data 的响应式存储库。在这两种情况下,Spring Security 都为两个堆栈提供了本机支持。

1.2 特点

  1. 清晰的角色划分:控制器(controller)、验证器(validator)、命令对象(command obect)、表单对象(form object)、模型对象(model object)、Servlet分发器(DispatcherServlet)、处理器映射(handler mapping)、试图解析器(view resoler)等等。每一个角色都可以由一个专门的对象来实现。
  2. 强大而直接的配置方式:将框架类和应用程序类都能作为JavaBean配置,支持跨多个context的引用,例如,在web控制器中对业务对象和验证器validator)的引用。
  3. 可适配、非侵入:可以根据不同的应用场景,选择合适的控制器子类(simple型、command型、from型、wizard型、multi-action型或者自定义),而不是一个单一控制器(比如Action/ActionForm)继承。
  4. 可重用的业务代码:可以使用现有的业务对象作为命令或表单对象,而不需要去扩展某个特定框架的基类。
  5. 可定制的绑定(binding)和验证(validation):比如将类型不匹配作为应用级的验证错误,这可以保证错误的值。再比如本地化的日期和数字绑定等等。在其他某些框架中,你只能使用字符串表单对象,需要手动解析它并转换到业务对象。
  6. 可定制的handler mapping和view resolution:Spring提供从最简单的URL映射,到复杂的、专用的定制策略。与某些web MVC框架强制开发人员使用单一特定技术相比,Spring显得更加灵活。
  7. 灵活的model转换:在Springweb框架中,使用基于Map的键/值对来达到轻易的与各种视图技术集成。
  8. 可定制的本地化和主题(theme)解析:支持在JSP中可选择地使用Spring标签库、支持JSTL、支持Velocity(不需要额外的中间层)等等。
  9. 简单而强大的JSP标签库(Spring Tag Library):支持包括诸如数据绑定和主题(theme)之类的许多功能。他提供在标记方面的最大灵活性。
  10. JSP表单标签库:在Spring2.0中引入的表单标签库,使用在JSP编写表单更加容易。
  11. Spring Bean的生命周期可以被限制在当前的HTTp Request或者HTTp Session。准确的说,这并非Spring MVC框架本身特性,而应归属于Spring MVC使用的WebApplicationContext容器。

1.3 对比 WebFlux

image-20200712115043143

2.环境

  1. JDK 1.8.0_201
  2. Spring Boot 2.2.0.RELEASE
  3. 构建工具(apache maven 3.6.3)
  4. 开发工具(IntelliJ IDEA )

3.代码

3.1 代码结构

image-20200712115216437

3.2 maven 依赖

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

3.3 java代码

User.java

public class User {

    /**
     * id
     */
    private Integer id;

    /**
     * 姓名
     */
    private String name;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public User(Integer id, String name) {
        this.id = id;
        this.name = name;
    }

    public User() {
    }
}

UserRepository.java

@Repository
public class UserRepository {

    // 生成id
    private static final AtomicInteger ID_GENERATOR = new AtomicInteger();

    // 模拟内存数据库
    private static final Map<Integer, User> USER_MAP = new HashMap<>();

    public List<User> selectAll() {
        return new ArrayList<>(USER_MAP.values());
    }

    public User getUserById(Integer id) {
        return USER_MAP.get(id);
    }

    public User addUser(User user) {
        if (Objects.isNull(user.getId())) {
            user.setId(ID_GENERATOR.incrementAndGet());
        }
        USER_MAP.put(user.getId(), user);
        return user;
    }

    public User update(User user) {
        USER_MAP.put(user.getId(), user);
        return user;
    }

    public User delete(Integer id) {
        return USER_MAP.remove(id);
    }

    public boolean exist(User user) {
        List<String> nameList = USER_MAP.values().stream().map(User::getName).collect(Collectors.toList());
        return nameList.contains(user.getName());
    }
}

UserServiceImpl.java

@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserRepository userRepository;

    @Override
    public List<User> selectAll() {
        return userRepository.selectAll();
    }

    @Override
    public User getUserById(Integer id) {
        return userRepository.getUserById(id);
    }

    @Override
    public User addUser(User user) {
        return userRepository.addUser(user);
    }

    @Override
    public User update(User user) {
        return userRepository.update(user);
    }

    @Override
    public User delete(Integer id) {
        return userRepository.delete(id);
    }

    @Override
    public boolean exist(User user) {
        return userRepository.exist(user);
    }
}

UserController.java

@RestController
@RequestMapping(value = "/user")
public class UserController {

    private static final Logger LOGGER = LoggerFactory.getLogger(UserController.class);

    @Autowired
    private UserService userService;

    @RequestMapping(value = "/list", method = RequestMethod.GET)
    public List<User> list() {
        return userService.selectAll();
    }

    @RequestMapping(value = "/get/{id}", method = RequestMethod.GET)
    public User get(@PathVariable Integer id) {
        return userService.getUserById(id);
    }

    @RequestMapping(value = "/add", method = RequestMethod.POST)
    public ResponseEntity<Void> create(@RequestBody User user, UriComponentsBuilder builder) {
        if ("duplicated".equals(user.getName())) {
            LOGGER.warn("the user already exist");
            return new ResponseEntity<>(HttpStatus.ALREADY_REPORTED);
        }

        userService.addUser(user);

        HttpHeaders headers = new HttpHeaders();
        headers.setLocation(builder.path("/user/get/{id}").buildAndExpand(user.getId()).toUri());
        return new ResponseEntity<>(headers, HttpStatus.CREATED);
    }

    @RequestMapping(value = "/update", method = RequestMethod.PUT)
    public User update(@RequestBody User user) {
        return userService.update(user);
    }

    @RequestMapping(value = "/delete/{id}", method = RequestMethod.DELETE)
    public User delete(@PathVariable Integer id) {
        return userService.delete(id);
    }
}

3.4 git 地址

spring-boot/spring-boot-03-webmvc

4.结果

启动 SpringBoot03WebApplication.main 方法,在 spring-boot-03-webmvc.http 访问下列地址,观察输出信息是否符合预期。

### GET /user/list
GET http://localhost:8080/user/list

image-20200712121034670

由于数据保存在内存中,最开始没有数据,所以返回为空。可以调用 /add 添加数据后再查询

### GET /user/get/{id}
GET http://localhost:8080/user/get/1

通过 id 查询同样为空,可以调用 /add 添加数据后再查询

### POST /user/add
POST http://localhost:8080/user/add
Content-Type: application/json

{
  "name": "zhangsan"
}

image-20200712121240211

这里响应码为 201,同时响应头中 location 设定为一个新的地址

### PUT /user/update
PUT http://localhost:8080/user/update
Content-Type: application/json
Accept: application/json

{
  "id": 1,
  "name": "lisi"
}

image-20200712121406103

### DELETE /user/delete/{id}
DELETE http://localhost:8080/user/delete/1

image-20200712121427892

5.源码分析

5.1 Spring WebMvc 运行流程

image-20200712125443758

  1. 用户发送请求至前端控制器 DispatcherServlet。
  2. DispatcherServlet 收到请求调用 HandlerMapping 处理器映射器。
  3. 处理器映射器找到具体的处理器,生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet,一般使用的是 url 映射器。
  4. DispatcherServlet 调用 HandlerAdapter 处理器适配器。
  5. HandlerAdapter 经过适配调用具体的处理器(Controller,也叫后端控制器)。
  6. Controller 执行完成返回 ModelAndView。
  7. HandlerAdapter 将 ModelAndView 返回给 DispatcherServlet。
  8. DispatcherServlet 将 ModelAndView 传给 ViewReslover 视图解析器。
  9. ViewReslover 解析后返回具体 View。
  10. DispatcherServlet 根据 View 进行渲染视图(即将模型数据填充至视图中)。
  11. DispatcherServlet 返回响应给用户。

5.2 Spring WebMvc 的原理

DispatcherServlet 其实也是一个 HttpServlet,它的类图如下

image-20200712130010099

在传统的 HttpServlet 中,它的生命周期包含 init、service、destroy,在 service 中一般有 doGet、doPost 分别来处理 get、post 请求。DispatcherServlet 即是在 HttpServlet 上面进行的扩展。

SpringMvc 的初始化时序图:

image-20200712135114830

protected void initStrategies(ApplicationContext context) {
    // 初始化上传组件,用于文件上传等
    initMultipartResolver(context);
    // 初始化本地化组件,用于国际化
    initLocaleResolver(context);
    // 初始化主题组件
    initThemeResolver(context);
    // 初始化处理器映射器
    initHandlerMappings(context);
    // 初始化处理器适配器
    initHandlerAdapters(context);
    // 初始化异常处理器
    initHandlerExceptionResolvers(context);
    // 初始化请求-视图名称翻译器
    initRequestToViewNameTranslator(context);
    // 初始化视图处理器
    initViewResolvers(context);
    // 初始化 FlashMapManager
    initFlashMapManager(context);
}

SpringMvc 的运行时序图:

image-20200712133642964

5.3 DispatcherServlet 如何初始化?

在一般的 Spring WebMvc 项目中,通常会在 web.xml 中配置好 DispatcherServlet,如下所示

<servlet>  
    <!-- 配置DispatcherServlet -->  
    <servlet-name>springMvc</servlet-name>  
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>  
    <!-- 指定spring mvc配置文件位置 不指定使用默认情况 -->  
    <init-param>     
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:application-context.xml</param-value>
    </init-param>  
    <!-- 设置启动顺序 -->  
    <load-on-startup>1</load-on-startup>  
</servlet>

<!-- ServLet 匹配映射 -->
<servlet-mapping>
    <servlet-name>springMvc</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>

那么,在 Spring Boot 项目中,DispatcherServlet 又是如何生效的呢?

其实,在 spring-boot-autoconfigure/META-INF/spring.factories 中有这样一个配置

image-20200712140206806

这个 DispatcherServletAutoConfiguration 即是 DispatcherServlet 的自动装配类

@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass(DispatcherServlet.class)
@AutoConfigureAfter(ServletWebServerFactoryAutoConfiguration.class)
public class DispatcherServletAutoConfiguration {

    public static final String DEFAULT_DISPATCHER_SERVLET_BEAN_NAME = "dispatcherServlet";

    @Configuration(proxyBeanMethods = false)
    @Conditional(DefaultDispatcherServletCondition.class)
    @ConditionalOnClass(ServletRegistration.class)
    @EnableConfigurationProperties({ HttpProperties.class, WebMvcProperties.class })
    protected static class DispatcherServletConfiguration {

        @Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
        public DispatcherServlet dispatcherServlet(HttpProperties httpProperties, WebMvcProperties webMvcProperties) {
            DispatcherServlet dispatcherServlet = new DispatcherServlet();
            dispatcherServlet.setDispatchOptionsRequest(webMvcProperties.isDispatchOptionsRequest());
            dispatcherServlet.setDispatchTraceRequest(webMvcProperties.isDispatchTraceRequest());
            dispatcherServlet.setThrowExceptionIfNoHandlerFound(webMvcProperties.isThrowExceptionIfNoHandlerFound());
            dispatcherServlet.setPublishEvents(webMvcProperties.isPublishRequestHandledEvents());
            dispatcherServlet.setEnableLoggingRequestDetails(httpProperties.isLogRequestDetails());
            return dispatcherServlet;
        }
    }

    // ...
}

6.参考

  1. SpringMVC的优点
posted @ 2020-07-12 18:47  Soulballad  阅读(677)  评论(0编辑  收藏  举报