Spring Boot 与 Web 开发

  1. SpringMVC 快速使用

    • 基于 restful http 接口的 CURD

      @RestController
      @RequestMapping("/user")
      public class UserController {
      
          @Autowired
          UserService userService;
      
          @GetMapping("/{id}")
          public Result getUser(@PathVariable Integer id) {
              User user = userService.getUserById(id);
              return new Result<>(200, "查询成功", user);
          }
      
          @PostMapping("/add")
          public Result getUser(User user) {
              userService.add(user);
              return new Result<>(200, "添加成功");
          }
      
          @PutMapping("/{id}")
          public Result editUser(User user) {
              userService.update(user);
              return new Result(200, "修改成功");
          }
      
          @DeleteMapping("/{id}")
          public Result deleteUser(@PathVariable Integer id) {
              userService.delete(id);
              return new Result<>(200, "删除成功");
          }
      }
    • 调用 rest http 接口

      • 通过 restTemplate 调用

        RestTemplate 类可用于在应用中调用 rest 服务,它简化了与 http 服务的通信方式,统一了 RESTful 的标准,封装了 http 链接,我们只需要传入 url 及返回值类型即可。

        适用于微服务架构下,服务之间的远程调用。 ps:以后使用微服务架构,使用 spring cloud feign 组件

        restTemplate 和 webClient 都可以调用远程服务,区别:webclient 依赖 webflux,webclient 请求远程服务是无阻塞的,响应的。RestTemplate 是阻塞的,需要等待请求响应后才能执行下一句代码。

        DELETEdelete
        GETgetForObject 按照指定 Class 返回对象
        getForEntity 返回对象为 ResponseEntity 对象,包含了响应中的一些重要信息,比如响应头、响应状态码、响应体等。
        HEADheadForHeaders
        OPTIONSoptionForAllow
        POSTpostForLocaltion
        postForObject
        PUTput
        any
        支持任何请求方法类型
        exchange
        execute
        • 基于 restTemplate 调用查询

          // 声明了 RestTemplate
          private final RestTemplate restTemplate;
          
          // 当 Bean 没有无参构造函数的时候, spring 将自动拿到有参的构造函数,参数进行自动注入
          public OrderController(RestTemplateBuilder restTemplateBuilder) {
              this.restTemplate = restTemplateBuilder.build();
          }
          
          @RequestMapping("/order")
          public String order() {
              Result forObject = restTemplate.getForObject("http://localhost:8080/user/{id}", Result.class, 1);
          
              return forObject.toString();
          }
        • 基于 restTemplate 调用查询

          @RequestMapping("/order")
          public String order() {
              //基于 restTemplate 调用查询
              User user = new User("AZhang", "wyu");
              // url: 请求的远程 rest url
              // object: post 请求的参数
              // Class<T>: 返回的类型
              // ...Object: 是 @PathVariable 占位符的参数
              ResponseEntity<Result> resultResponseEntity = restTemplate.postForEntity("http://localhost:8080/user/add", user, Result.class);
              System.out.println(resultResponseEntity);
              return resultResponseEntity.getBody().toString();
          }
        • 基于 restTemplate 调用修改

          @RequestMapping("/order")
          public String order() {
              //基于 restTemplate 调用修改
              User user = new User(1, "AZhang", "wyu");
              //restTemplate.put("http://localhost:8080/user/{id}", user, Result.class);
              HttpEntity<User> httpEntity = new HttpEntity<>(user);
              ResponseEntity<Result> resultResponseEntity = restTemplate.exchange("http://localhost:8080/user/{id}", HttpMethod.PUT, httpEntity, Result.class, 1);
              System.out.println(resultResponseEntity);
              return resultResponseEntity.getBody().toString();
          }
        • 基于 restTemplate 调用删除

          @RequestMapping("/order")
          public String order() {
              //基于 restTemplate 调用删除
              ResponseEntity<Result> resultResponseEntity = restTemplate.exchange("http://localhost:8080/user/{id}", HttpMethod.DELETE, null, Result.class, 1);
              System.out.println(resultResponseEntity);
              return resultResponseEntity.getBody().toString();
          }
     

 - 通过 postman 调用

 - 通过 MockMvc 测试

   MockMvc 是由 spring-test 包提供,实现了对 Http 请求的模拟,能够直接使用网络的形式,转换到 Controller 的调用,使得测试速度快、不依赖网络环境。同时提供了一套验证的工具,结果的验证十分方便。
  1. SpringMVC 自动配置原理分析

    ​ Spring Boot 为 Spring MVC 提供了自动配置,可与大多数应用程序完美配合。

    自动配置在 Spring 的默认值之上添加了以下功能:

    • 包含 ContentNegotiatingViewResolverBeanNameViewResolver

      • ViewResolver 都是 SpringMVC 内置的视图解析器

        • ContentNegotiatingViewResolver

          • 不会解析视图,而是委派给其他视图解析器进行解析。
          • 所有的视图解析器,都会根据返回的视图名称进行解析视图 resolveViewName

            public View resolveViewName(String viewName, Locale locale) throws Exception {
                RequestAttributes attrs = RequestContextHolder.getRequestAttributes();
                Assert.state(attrs instanceof ServletRequestAttributes, "No current ServletRequestAttributes");
                List<MediaType> requestedMediaTypes = getMediaTypes(((ServletRequestAttributes) attrs).getRequest());
                if (requestedMediaTypes != null) {
                    // 获得所有匹配的视图
                    List<View> candidateViews = getCandidateViews(viewName, locale, requestedMediaTypes);
                    // 获取最终的这个
                    View bestView = getBestView(candidateViews, requestedMediaTypes, attrs);
                    if (bestView != null) {
                        return bestView;
                    }
                }

            委派给其他视图解析器进行解析:

            @Override
            protected void initServletContext(ServletContext servletContext) {
                Collection<ViewResolver> matchingBeans =
                        BeanFactoryUtils.beansOfTypeIncludingAncestors(obtainApplicationContext(), ViewResolver.class).values();
                if (this.viewResolvers == null) {
                    this.viewResolvers = new ArrayList<>(matchingBeans.size());
                    for (ViewResolver viewResolver : matchingBeans) {
                        if (this != viewResolver) {
                            this.viewResolvers.add(viewResolver);
                        }
                    }
                }
        • BeanNameViewResolver

          • 会根据 handler 方法返回的视图名称,去 IOC 容器中找到名字叫 azhang 的一个 Bean,并且这个 Bean要实现了 View接口。
          • 示例:

            @GetMapping("/test")
            public String getUser() {
                return "azhang";
            }

            可以配置一个名字叫 azhang 的视图(View)

            @Component
            public class Azhang implements View {
                @Override
                public String getContentType() {
                    return "text/html";
                }
            
                @Override
                public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
                    response.getWriter().print("Welcome to AzhangView");
                }
            }
       

   - 由以上代码可以得出结论,它是从 Spring IOC 容器获得 ViewResolve 类型 Bean,name我们可以自己定制一个 ViewResolver,ContentNegotiatingViewResolver 也会帮我们委派解析:

     ```java
     @Bean
     public ViewResolver AZhangViewResolve() {
         InternalResourceViewResolver resolver = new InternalResourceViewResolver();
         resolver.setPrefix("/");
         resolver.setSuffix(".html");
         return resolver;
     }
     ```

     ![image-20220506164908963](https://static-upyun.hackerjk.top/markdown/image-20220506164908963.png!)
  • 支持提供静态资源,包括对 WebJars 的支持。

    • 以前要访问 jpg / css / js 等这些静态资源文件,需要在 web.xml 配置,但现在在 SpringBoot 中不需要配置,只需要放在约定文件夹中就可以(约定大于配置)
    • 原理:

      • WebJars:就是将静态资源放在 jar 包中进行访问
      • WebJars 官网:https://www.webjars.org/
      • @Override
        public void addResourceHandlers(ResourceHandlerRegistry registry) {
            if (!this.resourceProperties.isAddMappings()) {
                logger.debug("Default resource handling disabled");
                return;
            }
            addResourceHandler(registry, "/webjars/**", "classpath:/META-INF/resources/webjars/");
            addResourceHandler(registry, this.mvcProperties.getStaticPathPattern(), (registration) -> {
                registration.addResourceLocations(this.resourceProperties.getStaticLocations());
                if (this.servletContext != null) {
                    ServletContextResource resource = new ServletContextResource(this.servletContext, SERVLET_LOCATION);
                    registration.addResourceLocations(resource);
                }
            });
        }
        
        private void addResourceHandler(ResourceHandlerRegistry registry, String pattern, String... locations) {
            addResourceHandler(registry, pattern, (registration) -> registration.addResourceLocations(locations));
        }
        
        private void addResourceHandler(ResourceHandlerRegistry registry, String pattern,
                Consumer<ResourceHandlerRegistration> customizer) {
            if (registry.hasMappingForPattern(pattern)) {
                return;
            }
            ResourceHandlerRegistration registration = registry.addResourceHandler(pattern);
            customizer.accept(registration);
            registration.setCachePeriod(getSeconds(this.resourceProperties.getCache().getPeriod()));
            registration.setCacheControl(this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl());
            registration.setUseLastModified(this.resourceProperties.getCache().isUseLastModified());
            customizeResourceHandlerRegistration(registration);
        }
      • 当访问 /webjars/** 时,就会去 classpath:/META-INF/resources/webjars/ 对应进行映射

        当访问 http://localhost:8080/webjars/jquery/3.5.1/jquery.js 对应映射到 /META-INF/resources/webjars/jquery/3.5.1/jquery.js

        image-20220506171959211

        • 在 static 文件中访问的静态资源,默认的四个静态资源路径 (对应的映射地址) :
        { "classpath:/META-INF/resources/",
                "classpath:/resources/",
                "classpath:/static/",
    • 配置首页欢迎页:

      private Resource getWelcomePage() {
          for (String location : this.resourceProperties.getStaticLocations()) {    // 拿到上面静态资源地址
              Resource indexHtml = getIndexHtml(location);
              if (indexHtml != null) {
                  return indexHtml;
              }
          }
          ServletContext servletContext = getServletContext();
          // 去里面找一个 index.html 的首页文件
          if (servletContext != null) {
              return getIndexHtml(new ServletContextResource(servletContext, SERVLET_LOCATION));
          }
          return null;
      }
      
      private Resource getIndexHtml(String location) {
          return getIndexHtml(this.resourceLoader.getResource(location));
      }
      
      private Resource getIndexHtml(Resource location) {
          try {
              Resource resource = location.createRelative("index.html");
              if (resource.exists() && (resource.getURL() != null)) {
                  return resource;
              }
          }
          catch (Exception ex) {
          }
          return null;
      }
    • 也可以通过配置文件指定具体的静态资源地址:

  • 自动注册 ConverterGenericConverterFormatter Bean 类。

    • 使用方式
  • 支持 HttpMessageConverters

    • HttpMessageConverters 负责 http 请求和响应的报文处理。
    • image-20220507011511095
  • 自动注册 MessageCodesResolver

    • 修改 4xx 错误下格式转换出错、类型转换出错的错误代码
    • 以前的格式是 errorCode + "." + object name + "." + field

      例:typeMismatch.user.birthday

    • 可以通过 spring.mvc.message-codes-resolver-format=postfix_error_code 将格式修改为:object name + "." + field + "." + errorCode
  • 静态 index.html 支持。

    • 在 springboot 中可以直接返回 html 的视图,因为在 WebMvcAutoConfiguration 配置类配置了:

      @Bean
      @ConditionalOnMissingBean
      public InternalResourceViewResolver defaultViewResolver() {
          InternalResourceViewResolver resolver = new InternalResourceViewResolver();
          resolver.setPrefix(this.mvcProperties.getView().getPrefix());
          resolver.setSuffix(this.mvcProperties.getView().getSuffix());
          return resolver;
      }

      所以可以通过在配置文件中完成:

      spring.mvc.view.prefix=/pages/
      spring.mvc.view.suffix=.html
  • 自动使用 ConfigurableWebBindingInitializer bean。

    1. 定制 SpringMVC 的自动配置

      SpringMVC 的自动配置类:WebMvcAutoConfiguration

  • 在大多数情况下,SpringBoot 在自动配置类中标记了很多 @ConditionalOnMissingBean(xxxxxxxxxx.class) ;(意思就是如果容器中没有,当前的 Bean 才会生效)。只需要在自己的配置类中配置对应的一个 @Bean 就可以覆盖默认自动配置。
  • 通过 WebMvcConfigurer 进行拓展

    • 扩展视图控制器
    • 扩展拦截器
    • 扩展全局 CORS
    • @Configuration
      public class MyWebMvcConfigurer implements WebMvcConfigurer {
          /*@Override
          public void configurePathMatch(PathMatchConfigurer configurer) {
              configurer.setUseTrailingSlashMatch(false);
          }*/
      
          @Override
          public void addInterceptors(InterceptorRegistry registry) {
              registry.addInterceptor(new TimeInterceptor())      // 添加拦截器
                  .addPathPatterns("/**")      // 拦截映射规则  /**拦截所有的请求
                  .excludePathPatterns("/pages/**");        // 排除 pages 下面所有的请求
          }
      
          /**
           *  CORS 配置
           *  全局跨域请求配置
           */
          /*@Override
          public void addCorsMappings(CorsRegistry registry) {
              registry.addMapping("/user/*")       // 映射服务器中哪些 http 接口允许运行跨域访问
                      .allowedOrigins("http://localhost:8081")                // 配置哪些来源有权限跨域
                      .allowedMethods("GET", "POST", "PUT", "DELETE");        // 配置允许跨域访问的请求方法
          }*/
      
          @Bean
          public InternalResourceViewResolver AZhangViewResolver() {
              InternalResourceViewResolver resolver = new InternalResourceViewResolver();
              resolver.setPrefix("/pages");
              resolver.setSuffix(".html");
              return resolver;
          }
      }
    • WebMvcConfigurer 原理

      实现 WebMvcConfigurer 接口可以扩展 Mvc 实现,又既保留了 SpringBoot 的自动配置

      • WebMvcAutoConfiguration 也有一个实现了 WebMvcConfigurer 的配置类
      • WebMvcAutoConfigurationAdapter 它也是利用这种方式去进行扩展,所以我们通过查看这个类我们发现它帮我们实现了其他不常用的方法,帮助我们进行自动配置,我们只需要定制(拦截器、视图控制器、CORS 等在开发中需要额外定制的功能)

        @Configuration(proxyBeanMethods = false)
        @Import(EnableWebMvcConfiguration.class)
        @EnableConfigurationProperties({ WebMvcProperties.class, WebProperties.class })
        @Order(0)
        public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer, ServletContextAware {
      • 导入了 EnableWebMvcConfiguration

        @Import(EnableWebMvcConfiguration.class)

        EnableWebMvcConfiguration 它的父类上 setConfigurers 使用了 @AutoWired 注解

        • 它会去容器中将所有实现了 WebMvcConfigurer 接口的 Bean 都自动注入进来,添加到 configurers 变量中
      public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
      
          private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();
      
          @Autowired(required = false)
          public void setConfigurers(List<WebMvcConfigurer> configurers) {
              if (!CollectionUtils.isEmpty(configurers)) {
                  this.configurers.addWebMvcConfigurers(configurers);
              }
          }
      ```
    
    - 添加到 ``delegates`` 委派器中
    
      ```java
      public void addWebMvcConfigurers(List<WebMvcConfigurer> configurers) {
          if (!CollectionUtils.isEmpty(configurers)) {
              this.delegates.addAll(configurers);
          }
      }
      ```
    
    - 底层调用 WebMvcConfigurer 对应的方法时,就是去拿到之前注入到 delegates 的 WebMvcConfigurer,依次调用
    
      ```java
      @Override
      public void addInterceptors(InterceptorRegistry registry) {
          for (WebMvcConfigurer delegate : this.delegates) {
              delegate.addInterceptors(registry);
          }
      }
      ```
    
    • 当添加了 @EnableWebMvc 就不会使用 SpringMVC 自动配置类的默认配置了

      原理:

      • 在 EnableMvc 中 @Import(DelegatingWebMvcConfiguration.class)

        @Retention(RetentionPolicy.RUNTIME)
        @Target(ElementType.TYPE)
        @Documented
        @Import(DelegatingWebMvcConfiguration.class)
        public @interface EnableWebMvc {
        }
      • 在 DelegatingWebMvcConfiguration 中继承了 WebMvcConfigurationSupport

        @Configuration(proxyBeanMethods = false)
        public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
      • 在 WebMvcAutoConfiguration 中 @ConditionalOnMissingBean(WebMvcConfigurationSupport.class)

        ​ 当容器中不存在 WebMvcConfigurationSupport 这个 Bean 的时候自动配置类才会生效

        ​ 正因为通过 @EnableWebMvc 导入了 DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport 从而才使自动配置类失效

        @Configuration(proxyBeanMethods = false)
        @ConditionalOnWebApplication(type = Type.SERVLET)
        @ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
        @ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
        @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
        @AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
                ValidationAutoConfiguration.class })
        public class WebMvcAutoConfiguration {
  • Json 开发

    • Spring Boot 提供了与三个 JSON 映射库的集成:

      • Gson

        • Jackson 性能最好
      • JSON-B

      Jackson 是我们使用的默认 json 库

    • jackson 的使用

      • @JsonIgnore

        • 进行排除 json 序列化,将它标注在属性上将不会进行 json 格式化
  • 国际化
  • 统一异常处理
  1. SpringBoot 的嵌入式 Servlet 容器