SpringBoot基础--04web场景

Web 场景

Spring mvc 自动配置原理

mvc 自动配置类

org.springframework.boot.autoconfigure.web.servlet包下有WebMvcAutoConfiguration类,用于完成 web 场景的自动化配置

// 指明在以下自动配置后执行
@AutoConfiguration(
    after = {DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class, ValidationAutoConfiguration.class}
)
// 指明在类型为SERVLET,REACTIVE的web应用情况下生效
@ConditionalOnWebApplication(
    type = Type.SERVLET
)
// 指明当以下类存在时才创建
@ConditionalOnClass({Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class})
// 指明当以下bean不存在时才创建
@ConditionalOnMissingBean({WebMvcConfigurationSupport.class})
// 优先级
@AutoConfigureOrder(-2147483638)
// 引入运行时提示
@ImportRuntimeHints({WebResourcesRuntimeHints.class})
public class WebMvcAutoConfiguration{

    // 过滤页面表单提交的Rest请求(用以支持PUT,DELETE请求)
    @Bean
    @ConditionalOnMissingBean({HiddenHttpMethodFilter.class})
    @ConditionalOnProperty(
        prefix = "spring.mvc.hiddenmethod.filter",
        name = {"enabled"}
    )
    public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
        return new OrderedHiddenHttpMethodFilter();
    }

    // 过滤表单内容
    @Bean
    @ConditionalOnMissingBean({FormContentFilter.class})
    @ConditionalOnProperty(
        prefix = "spring.mvc.formcontent.filter",
        name = {"enabled"},
        matchIfMissing = true
    )
    public OrderedFormContentFilter formContentFilter() {
        return new OrderedFormContentFilter();
    }


    // 负责注册并配置 Spring MVC 的各种组件(管理着配置文件中 spring.web.xxx开头的配置项)
    // SpringBoot会给容器中放入WebMvcConfigurationSupport组件(是mvc的基本实现并包含了WebMvcConfigurer接口中的方法)
    // 若我们自己放了该组件,Boot的WebMvcAutoConfiguration就会失效
    // 当调用DelegatingWebMvcConfiguration的方法配置底层规则,它会调用WebMvcConfigurer的配置底层方法
    @Configuration(
      proxyBeanMethods = false
    )
    @EnableConfigurationProperties({WebProperties.class})
    public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration implements ResourceLoaderAware{}

    //在容器中放入`WebMvcConfigurer`组件,该组件给SpringMVC添加定制功能,所有功能最终会和配置文件进行绑定
    @Configuration(
        proxyBeanMethods = false
    )
    @Import({EnableWebMvcConfiguration.class})
    @EnableConfigurationProperties({WebMvcProperties.class, WebProperties.class})
    @Order(0)
    public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer, ServletContextAware{

        // 配置静态资源
         public void addResourceHandlers(ResourceHandlerRegistry registry) {
            if (!this.resourceProperties.isAddMappings()) {
                logger.debug("Default resource handling disabled");
            } else {
               // 访问this.mvcProperties.getWebjarsPathPattern()即"/webjars/**"
               // 去 "classpath:/META-INF/resources/webjars/"下寻找资源
                this.addResourceHandler(registry, this.mvcProperties.getWebjarsPathPattern(), "classpath:/META-INF/resources/webjars/");

               // 访问this.mvcProperties.getStaticPathPattern()即"/**"
               // 去this.resourceProperties即WebProperties.getResources()即{"classpath:/META-INF/resources/", "classpath:/resources/", "classpath:/static/", "classpath:/public/"}下寻找资源
                this.addResourceHandler(registry, this.mvcProperties.getStaticPathPattern(), (registration) -> {
                    registration.addResourceLocations(this.resourceProperties.getStaticLocations());
                    if (this.servletContext != null) {
                        ServletContextResource resource = new ServletContextResource(this.servletContext, "/");
                        registration.addResourceLocations(new Resource[]{resource});
                    }

                });
            }
         }
    }
}

WebMVC 配置接口

package org.springframework.web.servlet.config.annotation;

// 该接口提供了配置SpringMVC底层的所有组件入口
public interface WebMvcConfigurer {
  // 路径匹配
    default void configurePathMatch(PathMatchConfigurer configurer) {
    }
  // 内容协商
    default void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
    }
  // 异步支持
    default void configureAsyncSupport(AsyncSupportConfigurer configurer) {
    }
  // 默认处理,默认接受'/'
    default void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
    }
  // 格式化器
    default void addFormatters(FormatterRegistry registry) {
    }
  // 拦截器
    default void addInterceptors(InterceptorRegistry registry) {
    }
  // 添加资源处理器,处理静态资源规则
    default void addResourceHandlers(ResourceHandlerRegistry registry) {
    }
  // 跨域
    default void addCorsMappings(CorsRegistry registry) {
    }
  // 视图控制器
    default void addViewControllers(ViewControllerRegistry registry) {
    }
  // 视图解析
    default void configureViewResolvers(ViewResolverRegistry registry) {
    }
  // 参数解析器
    default void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
    }
  // 返回值处理器
    default void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> handlers) {
    }
  // 消息转换器
    default void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
    }
  // 扩展异常解析器
    default void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
    }
  // 配置异常解析器
    default void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
    }
  // 异常解析器
    default void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
    }

    @Nullable
    default Validator getValidator() {
        return null;
    }

    @Nullable
    default MessageCodesResolver getMessageCodesResolver() {
        return null;
    }
}

WebMvcConfigurationSupport配置类

是SpringMVC提供的扩展类,用于提供拦截器、资源处理器等注册功能

若有配置类继承了该类,则会使自动配置类WebMvcAutoConfiguration失效

public class WebMvcConfigurationSupport implements ApplicationContextAware, ServletContextAware{

}

配置类和配置接口区别

  • WebMvcConfigurer:
    • 是一个接口,它提供了多个回调方法,可以用于自定义Spring MVC的配置(如消息转换器、拦截器等)。
    • 我们在使用时只需要实现该接口,重写其中的方法即可。
    • 主要用于添加或修改Spring MVC的配置,如添加拦截器,自定义消息转换器等。
  • WebMvcConfigurationSupport:
    • 是一个抽象类,它也提供了多个回调方法,用于自定义Spring MVC的配置,但是需要继承该类并重写其中的方法。
    • 主要用于完全自定义Spring MVC的配置
    • 继承该类会覆盖Spring MVC的部分默认配置

web开发

自动配置

  1. 整合web场景
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
  1. 引入autoconfigure功能
  2. @EnableAutoConfiguration注解使用@Import(AutoConfigurationImportSelector.class)批量导入组件
  3. 加载 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件中配置的所有组件
  4. 绑定配置文件的配置项

默认效果

默认配置:

  1. 包含了 ContentNegotiatingViewResolver 和 BeanNameViewResolver 组件,方便视图解析
  2. 默认的静态资源处理机制: 静态资源放在 static 文件夹下即可直接访问
  3. 自动注册了 Converter,GenericConverter,Formatter组件,适配常见数据类型转换和格式化需求
  4. 支持 HttpMessageConverters,可以方便返回json等数据类型
  5. 注册 MessageCodesResolver,方便国际化及错误消息处理
  6. 支持 静态 index.html
  7. 自动使用ConfigurableWebBindingInitializer,实现消息处理、数据绑定、类型转化、数据校验等功能

重要:

  • 如果想保持 boot mvc 的默认配置,并且自定义更多的 mvc 配置,如:interceptors, formatters, view controllers 等。可以使用@Configuration注解添加一个 WebMvcConfigurer 类型的配置类,并不要标注 @EnableWebMvc
  • 如果想保持 boot mvc 的默认配置,但要自定义核心组件实例,比如:RequestMappingHandlerMapping, RequestMappingHandlerAdapter, 或ExceptionHandlerExceptionResolver,给容器中放一个 WebMvcRegistrations 组件即可
  • 如果想全面接管 Spring MVC,@Configuration 标注一个配置类,并加上 @EnableWebMvc注解,实现 WebMvcConfigurer 接口

静态资源

默认规则

静态资源映射
静态资源映射规则在 WebMvcAutoConfiguration 中进行了定义:

  1. /webjars/** 的所有路径 资源都在 classpath:/META-INF/resources/webjars/
  2. /** 的所有路径 资源都在 classpath:/META-INF/resources/、classpath:/resources/、classpath:/static/、classpath:/public/
  3. 所有静态资源都定义了缓存规则。【浏览器访问过一次,就会缓存一段时间】,但此功能参数无默认值
    a. period: 缓存间隔。 默认 0S;
    b. cacheControl:缓存控制。 默认无;
    c. useLastModified:是否使用lastModified头。 默认 false;

静态资源缓存
如前面所述

  1. 所有静态资源都定义了缓存规则。【浏览器访问过一次,就会缓存一段时间】,但此功能参数无默认值
    a. period: 缓存间隔。 默认 0S;
    b. cacheControl:缓存控制。 默认无;
    c. useLastModified:是否使用lastModified头。 默认 false;

欢迎页
欢迎页规则在 WebMvcAutoConfiguration 中进行了定义:

  1. 在静态资源目录下找 index.html
  2. 没有就在 templates下找index模板页

Favicon

  1. 在静态资源目录下找 favicon.ico
自定义静态资源规则

配置方式

#1、spring.web:
# 1.配置国际化的区域信息
# 2.静态资源策略(开启、处理链、缓存)

#开启静态资源映射规则
spring.web.resources.add-mappings=true

#设置缓存
spring.web.resources.cache.period=3600
##缓存详细合并项控制,覆盖period配置:
## 浏览器第一次请求服务器,服务器告诉浏览器此资源缓存7200秒,7200秒以内的所有此资源访问不用发给服务器请求,7200秒以后发请求给服务器
spring.web.resources.cache.cachecontrol.max-age=7200
## 共享缓存
spring.web.resources.cache.cachecontrol.cache-public=true
#使用资源 last-modified 时间,来对比服务器和浏览器的资源是否相同没有变化。相同返回 304
spring.web.resources.cache.use-last-modified=true

#自定义静态资源文件夹位置
spring.web.resources.static-locations=classpath:/a/,classpath:/b/,classpath:/static/

#2、 spring.mvc
## 2.1. 自定义webjars路径前缀
spring.mvc.webjars-path-pattern=/wj/**
## 2.2. 静态资源访问路径前缀
spring.mvc.static-path-pattern=/static/**

java代码方式

  • 容器中只要有一个 WebMvcConfigurer 组件。配置的底层行为都会生效
  • @EnableWebMvc //禁用boot的默认配置
@Configuration //这是一个配置类
public class MyConfig implements WebMvcConfigurer {

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        //保留以前规则
        //自己写新的规则。
        registry.addResourceHandler("/static/**")
                .addResourceLocations("classpath:/a/","classpath:/b/")
                .setCacheControl(CacheControl.maxAge(1180, TimeUnit.SECONDS));
    }
}
@Configuration //这是一个配置类,给容器中放一个 WebMvcConfigurer 组件,就能自定义底层
public class MyConfig  /*implements WebMvcConfigurer*/ {

    @Bean
    public WebMvcConfigurer webMvcConfigurer(){
        return new WebMvcConfigurer() {
            @Override
            public void addResourceHandlers(ResourceHandlerRegistry registry) {
                registry.addResourceHandler("/static/**")
                        .addResourceLocations("classpath:/a/", "classpath:/b/")
                        .setCacheControl(CacheControl.maxAge(1180, TimeUnit.SECONDS));
            }
        };
    }

}

路径匹配

  1. Ant风格路径用法
    Ant 风格的路径模式语法具有以下规则:
  • *:表示任意数量的字符。
  • ?:表示任意一个字符。
  • **:表示任意数量的目录。
  • {}:表示一个命名的模式占位符。
  • []:表示字符集合,例如[a-z]表示小写字母。
    例如:
  • *.html 匹配任意名称,扩展名为.html的文件。
  • /folder1//.java 匹配在folder1目录下的任意两级目录下的.java文件。
  • /folder2/**/*.jsp 匹配在folder2目录下任意目录深度的.jsp文件。
  • /{type}/{id}.html 匹配任意文件名为{id}.html,在任意命名的{type}目录下的文件。
    注意:Ant 风格的路径模式语法中的特殊字符需要转义,如:
  • 要匹配文件路径中的星号,则需要转义为\*。
  • 要匹配文件路径中的问号,则需要转义为\?。
  1. 模式切换
    AntPathMatcher 与 PathPatternParser
  • PathPatternParser 兼容 AntPathMatcher语法,并支持更多类型的路径模式
  • PathPatternParser “**” 多段匹配的支持仅允许在模式末尾使用
@GetMapping("/a*/b?/{p1:[a-f]+}")
public String hello(HttpServletRequest request, 
                    @PathVariable("p1") String path) {

    log.info("路径变量p1: {}", path);
    //获取请求路径
    String uri = request.getRequestURI();
    return uri;
}
# 改变路径匹配策略:
# ant_path_matcher 老版策略;
# path_pattern_parser 新版策略;
spring.mvc.pathmatch.matching-strategy=ant_path_matcher

内容协商

多端内容适配

默认规则

  • 基于请求头的内容协商(默认开启)
    • 客户端向服务端发送请求,携带HTTP标准的Accept请求头
      • Accept: application/json、text/xml、text/yaml
      • 服务端根据客户端请求头期望的数据类型进行动态返回
  • 基于请求参数内容协商:(需要开启)
    • 发送请求 GET /projects/spring-boot?format=json
    • 匹配到 @GetMapping(“/projects/spring-boot”)
    • 根据参数协商,优先返回 json 类型数据【需要开启参数匹配设置】
    • 发送请求 GET /projects/spring-boot?format=xml,优先返回 xml 类型数据

配置协商规则与支持类型

#修改内容协商方式

#使用参数进行内容协商
spring.mvc.contentnegotiation.favor-parameter=true  
#自定义参数名,默认为format
spring.mvc.contentnegotiation.parameter-name=myparam 
#自定义内容类型
spring.mvc.contentnegotiation.media-types.yaml=text/yaml
自定义内容返回

添加yaml返回支持

  1. 导入依赖
<dependency>
    <groupId>com.fasterxml.jackson.dataformat</groupId>
    <artifactId>jackson-dataformat-yaml</artifactId>
</dependency>
  1. 将对象写出成yaml
    public static void main(String[] args) throws JsonProcessingException {
        Person person = new Person();
        person.setId(1L);
        person.setUserName("张三");
        person.setEmail("aaa@qq.com");
        person.setAge(18);

        YAMLFactory factory = new YAMLFactory().disable(YAMLGenerator.Feature.WRITE_DOC_START_MARKER);
        ObjectMapper mapper = new ObjectMapper(factory);

        String s = mapper.writeValueAsString(person);
        System.out.println(s);
    }
  1. 编写配置
#新增一种媒体类型
spring.mvc.contentnegotiation.media-types.yaml=text/yaml
  1. 添加HttpMessageConverter组件,专门负责把对象写出为yaml格式
    @Bean
    public WebMvcConfigurer webMvcConfigurer(){
        return new WebMvcConfigurer() {
            @Override //配置一个能把对象转为yaml的messageConverter
            public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
                converters.add(new MyYamlHttpMessageConverter());
            }
        };
    }
内容协商原理

@ResponseBody由HttpMessageConverter处理

  1. 如果controller方法的返回值标注了 @ResponseBody 注解
    1.1. 请求进来先来到DispatcherServlet的doDispatch()进行处理
    1.2. 找到一个 HandlerAdapter 适配器。利用适配器执行目标方法
    1.3. RequestMappingHandlerAdapter来执行,调用invokeHandlerMethod()来执行目标方法
    1.4. 目标方法执行之前,准备好两个东西
    1.4.1. HandlerMethodArgumentResolver:参数解析器,确定目标方法每个参数值
    1.4.2. HandlerMethodReturnValueHandler:返回值处理器,确定目标方法的返回值改怎么处理
    1.5. RequestMappingHandlerAdapter 里面的invokeAndHandle()真正执行目标方法
    1.6. 目标方法执行完成,会返回返回值对象
    1.7. 找到一个合适的返回值处理器 HandlerMethodReturnValueHandler
    1.8. 最终找到 RequestResponseBodyMethodProcessor能处理 标注了 @ResponseBody注解的方法
    1.9. RequestResponseBodyMethodProcessor 调用writeWithMessageConverters ,利用MessageConverter把返回值写出去
  2. HttpMessageConverter 会先进行内容协商
    2.1. 遍历所有的MessageConverter看谁支持这种内容类型的数据
    2.2. 最终因为要json所以MappingJackson2HttpMessageConverter支持写出json
    2.3. jackson用ObjectMapper把对象写出去

WebMvcAutoConfiguration提供几种默认HttpMessageConverters

  • EnableWebMvcConfiguration通过 addDefaultHttpMessageConverters添加了默认的MessageConverter;如下:
    • ByteArrayHttpMessageConverter: 支持字节数据读写
    • StringHttpMessageConverter: 支持字符串读写
    • ResourceHttpMessageConverter:支持资源读写
    • ResourceRegionHttpMessageConverter: 支持分区资源写出
    • AllEncompassingFormHttpMessageConverter:支持表单xml/json读写
    • MappingJackson2HttpMessageConverter: 支持请求响应体Json读写

模板引擎

模板引擎页面默认放在 src/main/resources/templates

SpringBoot 包含以下模板引擎的自动配置

  • FreeMarker
  • Groovy
  • Thymeleaf
  • Mustache
Thymeleaf整合
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

自动配置原理

  1. 开启了 org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration 自动配置
  2. 属性绑定在 ThymeleafProperties 中,对应配置文件 spring.thymeleaf 内容
  3. 所有的模板页面默认在 classpath:/templates文件夹下
  4. 默认效果
    a. 所有的模板页面在 classpath:/templates/下面找
    b. 找后缀名为.html的页面
基础语法
  1. 核心用法

th:xxx:动态渲染指定的 html 标签属性值、或者th指令(遍历、判断等)

  • th:text:标签体内文本值渲染
    • th:utext:不会转义,显示为html原本的样子。
  • th:属性:标签指定属性渲染
  • th:attr:标签任意属性渲染
  • th:ifth:each…:其他th指令

表达式:用来动态取值

  • :变量取值;使用 m o d e l 共享给页面的值都直接用 {}:变量取值;使用model共享给页面的值都直接用 :变量取值;使用model共享给页面的值都直接用{}
  • @{}:url路径;
  • #{}:国际化消息
  • ~{}:片段引用
  • *{}:变量选择:需要配合th:object绑定对象

系统工具&内置对象

  • param:请求参数对象
  • session:session对象
  • application:application对象
  • #execInfo:模板执行信息
  • #messages:国际化消息
  • #uris:uri/url工具
  • #conversions:类型转换工具
  • #dates:日期工具,是java.util.Date对象的工具类
  • #calendars:类似#dates,只不过是java.util.Calendar对象的工具类
  • #temporals: JDK8+ java.time API 工具类
  • #numbers:数字操作工具
  • #strings:字符串操作
  • #objects:对象操作
  • #bools:bool操作
  • #arrays:array工具
  • #lists:list工具
  • #sets:set工具
  • #maps:map工具
  • #aggregates:集合聚合工具(sum、avg)
  • #ids:id生成工具
<p th:text="${content}">原内容</p>
<a th:href="${url}">登录</a>
<img src="../../images/gtvglogo.png" 
     th:attr="src=@{/images/gtvglogo.png},title=#{logo},alt=#{logo}" />
  1. 属性设置
  • th:href=“@{/product/list}”
  • th:attr=“class=${active}”
  • th:attr=“src=@{/images/gtvglogo.png},title=${logo},alt=#{logo}”
  • th:checked=“${user.active}”
<p th:text="${content}">原内容</p>
<a th:href="${url}">登录</a>
<img src="../../images/gtvglogo.png" 
     th:attr="src=@{/images/gtvglogo.png},title=#{logo},alt=#{logo}" />
  1. 遍历
<tr th:each="prod : ${prods}">
  <td th:text="${prod.name}">Onions</td>
  <td th:text="${prod.price}">2.41</td>
  <td th:text="${prod.inStock}? #{true} : #{false}">yes</td>
</tr>

<tr th:each="prod,iterStat : ${prods}" th:class="${iterStat.odd}? 'odd'">
  <td th:text="${prod.name}">Onions</td>
  <td th:text="${prod.price}">2.41</td>
  <td th:text="${prod.inStock}? #{true} : #{false}">yes</td>
</tr>

iterStat 有以下属性:

  • index:当前遍历元素的索引,从0开始
  • count:当前遍历元素的索引,从1开始
  • size:需要遍历元素的总数量
  • current:当前正在遍历的元素对象
  • even/odd:是否偶数/奇数行
  • first:是否第一个元素
  • last:是否最后一个元素
  1. 判断

th:if

<a
  href="comments.html"
  th:href="@{/product/comments(prodId=${prod.id})}"
  th:if="${not #lists.isEmpty(prod.comments)}"
  >view</a

th:switch

<div th:switch="${user.role}">
  <p th:case="'admin'">User is an administrator</p>
  <p th:case="#{roles.manager}">User is a manager</p>
  <p th:case="*">User is some other thing</p>
</div>
  1. 行内写法
<p>Hello, [[${session.user.name}]]!</p>
  1. 变量选择
<div th:object="${session.user}">
  <p>Name: <span th:text="*{firstName}">Sebastian</span>.</p>
  <p>Surname: <span th:text="*{lastName}">Pepper</span>.</p>
  <p>Nationality: <span th:text="*{nationality}">Saturn</span>.</p>
</div>

<!-- 等同于 -->

<div>
  <p>Name: <span th:text="${session.user.firstName}">Sebastian</span>.</p>
  <p>Surname: <span th:text="${session.user.lastName}">Pepper</span>.</p>
  <p>Nationality: <span th:text="${session.user.nationality}">Saturn</span>.</p>
</div
  1. 模板布局
  • 定义模板: th:fragment
  • 引用模板:~{templatename::selector}
  • 插入模板:th:insert、th:replace
<footer th:fragment="copy">&copy; 2011 The Good Thymes Virtual Grocery</footer>

<body>
  <div th:insert="~{footer :: copy}"></div>
  <div th:replace="~{footer :: copy}"></div>
</body>
<body>
  结果:
  <body>
    <div>
      <footer>&copy; 2011 The Good Thymes Virtual Grocery</footer>
    </div>

    <footer>&copy; 2011 The Good Thymes Virtual Grocery</footer>
  </body>
</body>

错误处理

默认机制
业务发生错误
|
@ExceptionHandler机制能否处理——否——>@ResponseStatus能否处理——否——>SpringMVC定义的默认错误响应能否处理
|能                                 |能                           |能                               |
|____________________________响应错误处理结果_____________________|                                 |
                                                                                                    |
                                                                                          均不能处理|
                                                                                                    |
                                                                                                    |
——————————————————————————————请求转发/error————————————————————————————————————————————————————————|
                                    |
                                    |
                            BaseErrorController
                                    |
              需要json数据———————内容协商————————要页面
                    |                               |
            DefaultErrorAttributes            |精确匹配[404,500]————————————|——先找templates/error
              获取错误信息并返回        ______|等错误状态码对应的页面       |          |匹配失败
                    |                  |             |匹配失败              |      再找静态资源目录
                    |                  |______|匹配名为[4XX,5XX]的页面——————|
           响应错误处理结果<—匹配成功——|             |匹配失败
                                       |______|匹配error的视图
                                       |             |匹配失败
                                       |______|SpringBoot提供默认名为error的视图

BasicErrorController组件
SpringBoot底层专门用于处理转发给/error的请求

@Controller
@RequestMapping({"${server.error.path:${error.path:/error}}"})
public class BasicErrorController extends AbstractErrorController{
  // 用于返回html页面
  @RequestMapping(
      produces = {"text/html"}
  )
  public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
      HttpStatus status = this.getStatus(request);
      Map<String, Object> model = Collections.unmodifiableMap(this.getErrorAttributes(request, this.getErrorAttributeOptions(request, MediaType.TEXT_HTML)));
      response.setStatus(status.value());
      // 解析错误的自定义视图地址
      ModelAndView modelAndView = this.resolveErrorView(request, response, status, model);
      // 如果解析不到错误页面的地址,默认的错误页就是 error
      return modelAndView != null ? modelAndView : new ModelAndView("error", model);
  }

  // 用于返回ResponseEntity,JSON
  @RequestMapping
  public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
      HttpStatus status = this.getStatus(request);
      if (status == HttpStatus.NO_CONTENT) {
          return new ResponseEntity(status);
      } else {
          Map<String, Object> body = this.getErrorAttributes(request, this.getErrorAttributeOptions(request, MediaType.ALL));
          return new ResponseEntity(body, status);
      }
  }
}

错误视图解析器

@Bean
@ConditionalOnBean({DispatcherServlet.class})
@ConditionalOnMissingBean({ErrorViewResolver.class})
DefaultErrorViewResolver conventionErrorViewResolver() {
    return new DefaultErrorViewResolver(this.applicationContext, this.resources);
}

SpringBoot解析自定义错误页的默认规则

	@Override
	public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {
		ModelAndView modelAndView = resolve(String.valueOf(status.value()), model);
		if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) {
			modelAndView = resolve(SERIES_VIEWS.get(status.series()), model);
		}
		return modelAndView;
	}

	private ModelAndView resolve(String viewName, Map<String, Object> model) {
		String errorViewName = "error/" + viewName;
		TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.getProvider(errorViewName,
				this.applicationContext);
		if (provider != null) {
			return new ModelAndView(errorViewName, model);
		}
		return resolveResource(errorViewName, model);
	}

	private ModelAndView resolveResource(String viewName, Map<String, Object> model) {
		for (String location : this.resources.getStaticLocations()) {
			try {
				Resource resource = this.applicationContext.getResource(location);
				resource = resource.createRelative(viewName + ".html");
				if (resource.exists()) {
					return new ModelAndView(new HtmlResourceView(resource), model);
				}
			}
			catch (Exception ex) {
			}
		}
		return null;
	}

SpringBoot默认提供的error的view

@Bean(name = "error")
@ConditionalOnMissingBean(name = "error")
public View defaultErrorView() {
    return this.defaultErrorView;
}

封装了JSON格式的错误信息

	@Bean
	@ConditionalOnMissingBean(value = ErrorAttributes.class, search = SearchStrategy.CURRENT)
	public DefaultErrorAttributes errorAttributes() {
		return new DefaultErrorAttributes();
	}

嵌入式容器

自动配置原理
  • SpringBoot 默认嵌入Tomcat作为Servlet容器。
  • 自动配置类是ServletWebServerFactoryAutoConfiguration,EmbeddedWebServerFactoryCustomizerAutoConfiguration
  • 自动配置类开始分析功能。xxxxAutoConfiguration
@AutoConfiguration
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@ConditionalOnClass(ServletRequest.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(ServerProperties.class)
@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
		ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
		ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
		ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })
public class ServletWebServerFactoryAutoConfiguration {
    
}
  1. ServletWebServerFactoryAutoConfiguration 自动配置了嵌入式容器场景
  2. 绑定了ServerProperties配置类,所有和服务器有关的配置 server
  3. ServletWebServerFactoryAutoConfiguration 导入了 嵌入式的三大服务器 Tomcat、Jetty、Undertow
    a. 导入 Tomcat、Jetty、Undertow 都有条件注解。系统中有这个类才行(也就是导了包)
    b. 默认 Tomcat配置生效。给容器中放 TomcatServletWebServerFactory
    c. 都给容器中 ServletWebServerFactory放了一个 web服务器工厂(造web服务器的)
    d. web服务器工厂 都有一个功能,getWebServer获取web服务器
    e. TomcatServletWebServerFactory 创建了 tomcat。
  4. ServletWebServerFactory 什么时候会创建 webServer出来。
  5. ServletWebServerApplicationContextioc容器,启动的时候会调用创建web服务器
  6. Spring容器刷新(启动)的时候,会预留一个时机,刷新子容器。onRefresh()
  7. refresh() 容器刷新 十二大步的刷新子容器会调用 onRefresh();
	@Override
	protected void onRefresh() {
		super.onRefresh();
		try {
			createWebServer();
		}
		catch (Throwable ex) {
			throw new ApplicationContextException("Unable to start web server", ex);
		}
	}

Web场景的Spring容器启动,在onRefresh的时候,会调用创建web服务器的方法。
Web服务器的创建是通过WebServerFactory搞定的。容器中又会根据导了什么包条件注解,启动相关的 服务器配置,默认EmbeddedTomcat会给容器中放一个 TomcatServletWebServerFactory,导致项目启动,自动创建出Tomcat。

全手动配置SpringMVC

  • SpringBoot 默认配置好了 SpringMVC 的所有常用特性。
  • 如果我们需要全面接管SpringMVC的所有配置并禁用默认配置,仅需要编写一个WebMvcConfigurer配置类,并标注 @EnableWebMvc 即可
  • 全手动模式
    • @EnableWebMvc : 禁用默认配置
    • WebMvcConfigurer组件:定义MVC的底层行为
WebMvcAutoConfiguration自动配置的规则

WebMvcAutoConfigurationweb场景的自动配置类

  • 支持RESTful的filter:HiddenHttpMethodFilter
  • 支持非POST请求,请求体携带数据:FormContentFilter
  • 导入EnableWebMvcConfiguration:
    • RequestMappingHandlerAdapter
    • WelcomePageHandlerMapping: 欢迎页功能支持(模板引擎目录、静态资源目录放index.html),项目访问/ 就默认展示这个页面.
    • RequestMappingHandlerMapping:找每个请求由谁处理的映射关系
    • ExceptionHandlerExceptionResolver:默认的异常解析器
    • LocaleResolver:国际化解析器
    • ThemeResolver:主题解析器
    • FlashMapManager:临时数据共享
    • FormattingConversionService: 数据格式化 、类型转化
    • Validator: 数据校验JSR303提供的数据校验功能
    • WebBindingInitializer:请求参数的封装与绑定
    • ContentNegotiationManager:内容协商管理器
  • WebMvcAutoConfigurationAdapter配置生效,它是一个WebMvcConfigurer,定义mvc底层组件
    • 定义好 WebMvcConfigurer 底层组件默认功能;所有功能详见列表
    • 视图解析器:InternalResourceViewResolver
    • 视图解析器:BeanNameViewResolver,视图名(controller方法的返回值字符串)就是组件名
    • 内容协商解析器:ContentNegotiatingViewResolver
    • 请求上下文过滤器:RequestContextFilter: 任意位置直接获取当前请求
    • 静态资源链规则
    • ProblemDetailsExceptionHandler:错误详情
      • SpringMVC内部场景异常被它捕获:
  • 定义了MVC默认的底层行为: WebMvcConfigurer
@EnableWebMvc禁用默认行为
  1. @EnableWebMvc给容器中导入 DelegatingWebMvcConfiguration组件,
    他是 WebMvcConfigurationSupport
  2. WebMvcAutoConfiguration有一个核心的条件注解, @ConditionalOnMissingBean(WebMvcConfigurationSupport.class),容器中没有WebMvcConfigurationSupport,WebMvcAutoConfiguration才生效.
  3. @EnableWebMvc 导入 WebMvcConfigurationSupport 导致 WebMvcAutoConfiguration 失效。导致禁用了默认行为

web新特性

函数式web

  1. @Controller + @RequestMapping:耦合式 (路由、业务耦合)

  2. 函数式Web:分离式(路由、业务分离)

  3. 场景
    场景:User RESTful - CRUD

  • GET /user/1 获取1号用户
  • GET /users 获取所有用户
  • POST /user 请求体携带JSON,新增一个用户
  • PUT /user/1 请求体携带JSON,修改1号用户
  • DELETE /user/1 删除1号用户
  1. 核心类
  • RouterFunction:定义路由信息
  • RequestPredicate:请求谓语
  • ServerRequest:请求
  • ServerResponse:响应
  1. 实例
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.web.servlet.function.RequestPredicate;
import org.springframework.web.servlet.function.RouterFunction;
import org.springframework.web.servlet.function.ServerResponse;

import static org.springframework.web.servlet.function.RequestPredicates.accept;
import static org.springframework.web.servlet.function.RouterFunctions.route;

@Configuration(proxyBeanMethods = false)
public class MyRoutingConfiguration {

    private static final RequestPredicate ACCEPT_JSON = accept(MediaType.APPLICATION_JSON);

    @Bean
    public RouterFunction<ServerResponse> routerFunction(MyUserHandler userHandler) {
        return route()
                .GET("/{user}", ACCEPT_JSON, userHandler::getUser)
                .GET("/{user}/customers", ACCEPT_JSON, userHandler::getUserCustomers)
                .DELETE("/{user}", ACCEPT_JSON, userHandler::deleteUser)
                .build();
    }

}
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.function.ServerRequest;
import org.springframework.web.servlet.function.ServerResponse;

@Component
public class MyUserHandler {

    public ServerResponse getUser(ServerRequest request) {
        ...
        return ServerResponse.ok().build();
    }

    public ServerResponse getUserCustomers(ServerRequest request) {
        ...
        return ServerResponse.ok().build();
    }

    public ServerResponse deleteUser(ServerRequest request) {
        ...
        return ServerResponse.ok().build();
    }

}

整合SSM场景

例子

  • 创建Spring Boot项目,选择lombok,spring web,mybatis framework,mysql driver的依赖关系
  • 在properties配置文件中配置数据源
  • 编写数据库中实体类对象
  • 创建mapper接口,并利用mybatisx生成mapper.xml文件
  • 在生成的mapper.xml文件中编写sql
  • 在启动类中使用@MapperScan注解指定mapper接口的包名
  • 在properties配置文件中配置mapper映射文件(.xml文件)的位置
  • 编写controller层和service层

application.properties

# 配置数据源
spring.datasource.url=jdbc:mysql://localhost:3306/bookstore
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.type=com.zaxxer.hikari.HikariDataSource

# 配置MyBatis
#指定mapper映射文件位置
mybatis.mapper-locations=classpath:/mapper/*.xml
#开启驼峰命名映射
mybatis.configuration.map-underscore-to-camel-case=true

实体类

package com.atli.springbootssm.pojo;

@Data
@Compoment
public class User {
    private int id;
    private String UserName;
    private int capital;
}

mapper接口

package com.atli.springbootssm.mapper;

public interface UserMapper {
    User getUserById(@Param("id") int id);
    List<User> getUsers();
    int insertUser(@Param("user") User user);
    int updateUser(@Param("user") User user);
    int deleteUser(@Param("id") int id);
}

mapper映射文件

<!-- classpath: mapper/UserMapper.xml -->

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.atli.springbootssm.mapper.UserMapper">
    <insert id="insertUser">
        insert into customer(id,user_name,capital) value(#{id},#{userName},#{capital})
    </insert>
    <update id="updateUser">
        update customer set user_name=#{userName},capital=#{capital} where id=#{id}
    </update>
    <delete id="deleteUser">
        delete from customer where id=#{id}
    </delete>

    <select id="getUserById" resultType="com.atli.springbootssm.pojo.User">
        select * from customer where id=#{id}
    </select>
    <select id="getUsers" resultType="com.atli.springbootssm.pojo.User">
        select * from customer
    </select>
</mapper>

UserController

package com.atli.springbootssm.controller;

@RestController
public class UserController {

    @Autowired
    UserService userService;

    @GetMapping("/user/{id}")
    public User getUserById(@PathVariable("id") int id) {
        return userService.getUserById(id);
    }

    @GetMapping("/user")
    public List<User> getUsers() {
        return userService.getUsers();
    }

    @PostMapping("/user")
    public int insertUser(@RequestBody User user) {
        return userService.insertUser(user);
    }

    @PutMapping("/user")
    public int updateUser(@RequestBody User user) {
        return userService.updateUser(user);
    }

    @DeleteMapping("/user/{id}")
    public int deleteUser(@PathVariable("id") int id) {
        return userService.deleteUser(id);
    }

}

UserServiceImpl

package com.atli.springbootssm.service.impl;

@Service
public class UserServiceImpl implements UserService {

    /*
     *这里因为是springboot+mybatis,
     * boot底层已经帮我们完成了创建SqlSessionFactory,SqlSession,mapper等的操作
     * 我们只需要使用mapper点方法去调用即可
     */

    @Autowired
    private UserMapper userMapper;

    @Override
    public User getUserById(int id) {
        return userMapper.getUserById(id);
    }

    @Override
    public List<User> getUsers() {
        return userMapper.getUsers();
    }

    @Override
    public int insertUser(User user) {
        return userMapper.insertUser(user);
    }

    @Override
    public int updateUser(User user) {
        return userMapper.updateUser(user);
    }

    @Override
    public int deleteUser(int id) {
        return userMapper.deleteUser(id);
    }
}

jdbc+mybatis场景的自动配置

  • mybatis-spring-boot-starter导入 spring-boot-starter-jdbc,jdbc是操作数据库的场景

  • Jdbc场景的几个自动配置

    • org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
      • 数据源的自动配置
      • 所有和数据源有关的配置都绑定在DataSourceProperties
      • 默认使用 HikariDataSource
    • org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration
      • 给容器中放了JdbcTemplate操作数据库
    • org.springframework.boot.autoconfigure.jdbc.JndiDataSourceAutoConfiguration
    • org.springframework.boot.autoconfigure.jdbc.XADataSourceAutoConfiguration
      • 基于XA二阶提交协议的分布式事务数据源
    • org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration
      • 支持事务
  • MyBatisAutoConfiguration:配置了MyBatis的整合流程

    • mybatis-spring-boot-starter导入 mybatis-spring-boot-autoconfigure(mybatis的自动配置包),
    • 默认加载两个自动配置类:
      • org.mybatis.spring.boot.autoconfigure.MybatisLanguageDriverAutoConfiguration
      • org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration
        • 必须在数据源配置好之后才配置
        • 给容器中SqlSessionFactory组件。创建和数据库的一次会话
        • 给容器中SqlSessionTemplate组件。操作数据库
    • MyBatis的所有配置绑定在MybatisProperties
    • 每个Mapper接口的代理对象是怎么创建放到容器中。详见@MapperScan原理:
      • 利用@Import(MapperScannerRegistrar.class)批量给容器中注册组件。解析指定的包路径里面的每一个类,为每一个Mapper接口类,创建Bean定义信息,注册到容器中。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值