首页 > 编程语言 > 详细

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

时间:2020-07-12 19:39:47      阅读:53      评论:0      收藏:0      [点我收藏+]

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

技术分享图片

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 代码结构

技术分享图片

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

技术分享图片

由于数据保存在内存中,最开始没有数据,所以返回为空。可以调用 /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"
}

技术分享图片

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

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

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

技术分享图片

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

技术分享图片

5.源码分析

5.1 Spring WebMvc 运行流程

技术分享图片

  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,它的类图如下

技术分享图片

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

SpringMvc 的初始化时序图:

技术分享图片

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 的运行时序图:

技术分享图片

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 中有这样一个配置

技术分享图片

这个 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的优点

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

原文:https://www.cnblogs.com/col-smile/p/13289378.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!