• 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的优点
  • 相关阅读:
    阿里早期Android加固代码的实现分析
    如何利用C++的time头文件获取系统时间
    Python编写基于socket的非阻塞多人聊天室程序(单线程&多线程)
    Dalvik模式下在Android so库文件.init段、.init_array段构造函数上下断点
    手动绕过百度加固Debug.isDebuggerConnected反调试的方法
    request使用代理
    requests爬取豆瓣热门电视剧
    scrapy-继承默认的user-agent 中间件
    scrapy-下载器中间件 随机切换user_agent
    scrapy 直接在编辑器运行
  • 原文地址:https://www.cnblogs.com/col-smile/p/13289378.html
Copyright © 2020-2023  润新知