0%

springboot学习笔记2

前言

  之前学的springboot笔记2,放上来方便查看

1.配置文件

1.1 properties

当配置文件中的中文乱码时,可以在setting里面进行修改,IDEA中设置 File Encodings

修改之后还是不失效,可以把配置文件删了,再重新创一个

1.2 yaml

application.yml也可以用来当作配置文件使用

配置文件在同一位置同时具有.properties.yml格式,.properties则优先。

application.properties和application.yml都会生效

基本语法如下:

  • key: value;kv之间有空格

  • 大小写敏感

  • 使用缩进表示层级关系

  • 缩进不允许使用tab,只允许空格,idea会自动转换

  • 缩进的空格数不重要,只要相同层级的元素左对齐即可

  • ‘#’表示注释

  • 字符串无需加引号,如果要加,’’与””表示字符串内容 会被 转义/不转义

    • 双引号不会转义
    • 单引号会转义

1.3 配置提示

直接在yaml文件中编写配置,没有提示,可以加入相应的依赖来实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>


<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<!--打包时忽略自定义类配置提示工具-->
<exclude>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>

2.Web开发

2.1 SpringMvc 自动配置

详见官网:https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.developing-web-applications

(下面为谷歌翻译原文档)

Spring Boot 为 Spring MVC 提供了自动配置,适用于大多数应用程序。

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

  • 包括ContentNegotiatingViewResolverBeanNameViewResolverbeans。
  • 支持提供静态资源,包括对 WebJars 的支持(本文档稍后介绍)。
  • 自动注册ConverterGenericConverterFormatterbeans。
  • 支持HttpMessageConverters在本文档后面介绍)。
  • MessageCodesResolver的自动注册(在本文档后面介绍)。
  • 静态index.html支持。
  • ConfigurableWebBindingInitializerbean 的自动使用(本文档稍后介绍)。

如果您想保留那些 Spring Boot MVC 自定义并进行更多MVC 自定义(拦截器、格式化程序、视图控制器和其他功能),您可以添加自己@Configuration的类型类,WebMvcConfigurer不添加 @EnableWebMvc.

如果你想提供的定制情况RequestMappingHandlerMappingRequestMappingHandlerAdapter或者ExceptionHandlerExceptionResolver,仍然保持弹簧引导MVC自定义,你可以声明类型的beanWebMvcRegistrations,并用它来提供这些组件的定制实例。

如果你想利用Spring MVC中的完全控制,你可以添加自己的@Configuration注解为@EnableWebMvc,或者添加自己的@Configuration-annotatedDelegatingWebMvcConfiguration中的Javadoc中所述@EnableWebMvc

需要SpringMVC的什么功能。编写一个入口给容器中添加一个 WebMvcConfigurer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
//1、WebMvcConfigurer定制化SpringMVC的功能
@Bean
public WebMvcConfigurer webMvcConfigurer(){
return new WebMvcConfigurer() {

/**
* 自定义内容协商策略
* @param configurer
*/
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
//Map<String, MediaType> mediaTypes
Map<String, MediaType> mediaTypes = new HashMap<>();
mediaTypes.put("json",MediaType.APPLICATION_JSON);
mediaTypes.put("xml",MediaType.APPLICATION_XML);
mediaTypes.put("gg",MediaType.parseMediaType("application/x-guigu"));
//指定支持解析哪些参数对应的哪些媒体类型
ParameterContentNegotiationStrategy parameterStrategy = new ParameterContentNegotiationStrategy(mediaTypes);
// parameterStrategy.setParameterName("ff");

HeaderContentNegotiationStrategy headeStrategy = new HeaderContentNegotiationStrategy();

configurer.strategies(Arrays.asList(parameterStrategy,headeStrategy));
}

@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(new GuiguMessageConverter());
}

@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
UrlPathHelper urlPathHelper = new UrlPathHelper();
// 不移除;后面的内容。矩阵变量功能就可以生效
urlPathHelper.setRemoveSemicolonContent(false);
configurer.setUrlPathHelper(urlPathHelper);
}



};
}

2.2 简单功能分析

1. 静态资源的访问

  • 默认情况下,Spring Boot 可以访问名为/static(或/public/resources/META-INF/resources)下的静态资源

  • 访问路径:localhost:8080/静态资源文件名

  • 原理: 静态映射/**

    请求进来,先去找Controller看能不能处理。不能处理的所有请求又都交给静态资源处理器。静态资源也找不到则响应404页面

1
2
3
4
5
6
7
8
spring:
#访问路径加resources前缀
mvc:
static-path-pattern: "/resources/**"
#默认静态资源文件夹改变
web:
resources:
static-locations: [ classpath:/haha/ ]

​ 注意点:程序重新启动记得在maven生存周期里面,clean

image-20210714124006860

2. Webjars

https://www.webjars.org/

  • 先导入依赖
1
2
3
4
5
<dependency>
<groupId>org.webjars</groupId>
<artifactId>jquery</artifactId>
<version>3.5.1</version>
</dependency>
  • 原理,访问META-INF/resources下面的路径

    image-20210714124849592

访问地址:http://localhost:8080/webjars/jquery/3.6.0/jquery.js 后面地址要按照依赖里面的包路径

3. 欢迎页与小图标

  • SpringBoot支持从静态资源直接访问欢迎页,或者从Controller层获取
  • 将index.html文件放在静态资源目录下面即可
    • 可以配置静态资源路径
    • 但是不可以配置静态资源的访问前缀。否则导致 index.html不能被默认访问
1
2
3
4
5
6
spring:
# mvc:
# static-path-pattern: /res/** 这个会导致welcome page功能失效

resources:
static-locations: [classpath:/haha/]
  • 小图标favicon.ico文件放在静态资源目录下面即可
  • 与欢迎页类似
  • 最新springboot版本已经移除对小图标直接访问的介绍

4. 静态资源源码分析

  • SpringBoot启动默认加载 xxxAutoConfiguration 类(自动配置类)

  • SpringMVC功能的自动配置类 WebMvcAutoConfiguration,生效

    1
    2
    3
    4
    5
    6
    spring:
    # mvc:
    # static-path-pattern: /res/**

    resources:
    add-mappings: false 禁用所有静态资源规则

2.3 请求参数处理

1. Rest风格

  • 以前:/getUser 获取用户 /deleteUser 删除用户 /editUser 修改用户 /saveUser 保存用户

  • 现在: /user *GET-*获取用户 *DELETE-*删除用户 *PUT-*修改用户 *POST-*保存用户

  • 使用如下:

    • ~~~Java
      @RestController
      public class HelloController {

      //    @RequestMapping(value = "/user",method = RequestMethod.GET)
      @GetMapping("/user")
      public String getUser(){
      
          return "GET-张三";
      }
      
      //    @RequestMapping(value = "/user",method = RequestMethod.POST)
      @PostMapping("/user")
      public String saveUser(){
          return "POST-张三";
      }
      
      
      //    @RequestMapping(value = "/user",method = RequestMethod.PUT)
      @PutMapping("/user")
      public String putUser(){
      
          return "PUT-张三";
      }
      
      @DeleteMapping("/user")
      

      // @RequestMapping(value = “/user”,method = RequestMethod.DELETE)

      public String deleteUser(){
          return "DELETE-张三";
      }
      

      }

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18

      - ~~~html
      测试REST风格;
      <form action="/user" method="get">
      <input value="REST-GET 提交" type="submit"/>
      </form>
      <form action="/user" method="post">
      <input value="REST-POST 提交" type="submit"/>
      </form>
      <form action="/user" method="post">
      <input name="_method" type="hidden" value="delete"/>
      <input name="_m" type="hidden" value="delete"/>
      <input value="REST-DELETE 提交" type="submit"/>
      </form>
      <form action="/user" method="post">
      <input name="_method" type="hidden" value="PUT"/>
      <input value="REST-PUT 提交" type="submit"/>
      </form>
    • get和post 可以直接识别,其他的不行

    • 其他的需要写成post,加上,源码如下

      image-20210714181616569

  • 使用Rest风格需要手动开启

    1
    2
    3
    4
    5
    spring:
    mvc:
    hiddenmethod:
    filter:
    enabled: true #开启页面表单的Rest功能,默认是false
  • Rest原理(表单提交要使用REST的时候)

    • 表单提交会带上**_method=PUT**
    • 请求过来被HiddenHttpMethodFilter拦截
      • 请求是否正常,并且是POST
      • 获取到**_method**的值。
      • 兼容以下请求;PUT.DELETE.PATCH
      • 原生request(post),包装模式requesWrapper重写了getMethod方法,返回的是传入的值。
      • 过滤器链放行的时候用wrapper。以后的方法调用getMethod是调用requesWrapper的。
  • Rest使用客户端工具,

    • 如PostMan直接发送Put、delete等方式请求,无需Filter。
  • 拓展,修改默认的_method

    • 自己重写配置类
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    @Configuration(proxyBeanMethods = false)
    public class WebConfig /*implements WebMvcConfigurer*/ {

    @Bean
    public HiddenHttpMethodFilter hiddenHttpMethodFilter(){
    HiddenHttpMethodFilter methodFilter = new HiddenHttpMethodFilter();
    methodFilter.setMethodParam("_m");
    return methodFilter;
    }
    }

2. 请求映射原理

SpringMVC功能分析都从 org.springframework.web.servlet.DispatcherServlet–>doDispatch()

image.png

3. 常用的注解

  • @PathVariable 路径变量 @PathVariable(“id”) Integer id,

    • 路径变量和请求参数的不同
    • 路径变量:/{id}
    • 请求参数:?age=18
  • @RequestHeader 请求头 @RequestHeader(“User-Agent”) String userAgent

    • image-20210715185208518
  • @RequestParam 请求参数 @RequestParam(“age”) Integer age,

  • @CookieValue 获取cookie值 @CookieValue(“Pycharm-40937f47”) Cookie cookie

    • image-20210715185526781
  • @RequestBody 获取请求体[必须为POST] @RequestBody String content

  • 上述五个注解,除了可以单独获取其中的参数,也可以使用**Map<String,String>**方式来获取整个map

  • @RequestAttribute 获取request域属性 @RequestAttribute(value = “msg”,required = false) String msg

    • required的取值如下:
    • Both the MVC namespace and the MVC Java configuration keep this flag set to false, to maintain backwards compatibility. However, for new applications, we recommend setting it to true.
    • MVC 命名空间和 MVC Java 配置都将此标志设置为false,以保持向后兼容性。但是,对于新应用程序,我们建议将其设置为true.
  • @MatrixVariable 矩阵变量 @MatrixVariable(“low”) Integer low

    • 语法: 请求路径:/cars/sell;low=34;brand=byd,audi,yd

    • SpringBoot默认是禁用了矩阵变量的功能

    • 手动开启:原理。

      • 对于路径的处理。UrlPathHelper进行解析。

      • removeSemicolonContent(移除分号内容)支持矩阵变量的

      • ~~~java
        @Override

                public void configurePathMatch(PathMatchConfigurer configurer) {
                    UrlPathHelper urlPathHelper = new UrlPathHelper();
                    // 不移除;后面的内容。矩阵变量功能就可以生效
                    urlPathHelper.setRemoveSemicolonContent(false);
                    configurer.setUrlPathHelper(urlPathHelper);
                }
        
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        36
        37
        38
        39
        40
        41
        42
        43
        44
        45
        46
        47
        48
        49
        50
        51
        52
        53
        54
        55
        56
        57
        58
        59
        60
        61
        62
        63
        64
        65
        66
        67
        68
        69
        70
        71
        72
        73
        74
        75
        76
        77

        - 矩阵变量必须有url路径变量才能被解析

        - ~~~Java
        @RestController
        public class ParameterTestController {


        // car/2/owner/zhangsan
        @GetMapping("/car/{id}/owner/{username}")
        public Map<String,Object> getCar(@PathVariable("id") Integer id,
        @PathVariable("username") String name,
        @PathVariable Map<String,String> pv,
        @RequestHeader("User-Agent") String userAgent,
        @RequestHeader Map<String,String> header,
        @RequestParam("age") Integer age,
        @RequestParam("inters") List<String> inters,
        @RequestParam Map<String,String> params,
        @CookieValue("Pycharm-40937f47") String _ga,
        @CookieValue("Pycharm-40937f47") Cookie cookie){


        Map<String,Object> map = new HashMap<>();

        // map.put("id",id);
        // map.put("name",name);
        // map.put("pv",pv);
        map.put("userAgent",userAgent);
        map.put("headers",header);
        map.put("age",age);
        map.put("inters",inters);
        map.put("params",params);
        map.put("_ga",_ga);
        System.out.println(cookie.getName()+"===>"+cookie.getValue());
        return map;
        }


        @PostMapping("/save")
        public Map postMethod(@RequestBody String content){
        Map<String,Object> map = new HashMap<>();
        map.put("content",content);
        return map;
        }


        //1、语法: 请求路径:/cars/sell;low=34;brand=byd,audi,yd
        //2、SpringBoot默认是禁用了矩阵变量的功能
        // 手动开启:原理。对于路径的处理。UrlPathHelper进行解析。
        // removeSemicolonContent(移除分号内容)支持矩阵变量的
        //3、矩阵变量必须有url路径变量才能被解析
        @GetMapping("/cars/{path}")
        public Map carsSell(@MatrixVariable("low") Integer low,
        @MatrixVariable("brand") List<String> brand,
        @PathVariable("path") String path){
        Map<String,Object> map = new HashMap<>();

        map.put("low",low);
        map.put("brand",brand);
        map.put("path",path);
        return map;
        }

        // /boss/1;age=20/2;age=10

        @GetMapping("/boss/{bossId}/{empId}")
        public Map boss(@MatrixVariable(value = "age",pathVar = "bossId") Integer bossAge,
        @MatrixVariable(value = "age",pathVar = "empId") Integer empAge){
        Map<String,Object> map = new HashMap<>();

        map.put("bossAge",bossAge);
        map.put("empAge",empAge);
        return map;

        }

        }

4. Servlet API

WebRequest、ServletRequest、MultipartRequest、 HttpSession、javax.servlet.http.PushBuilder、Principal、InputStream、Reader、HttpMethod、Locale、TimeZone、ZoneId

ServletRequestMethodArgumentResolver 以上的部分参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Override
public boolean supportsParameter(MethodParameter parameter) {
Class<?> paramType = parameter.getParameterType();
return (WebRequest.class.isAssignableFrom(paramType) ||
ServletRequest.class.isAssignableFrom(paramType) ||
MultipartRequest.class.isAssignableFrom(paramType) ||
HttpSession.class.isAssignableFrom(paramType) ||
(pushBuilder != null && pushBuilder.isAssignableFrom(paramType)) ||
Principal.class.isAssignableFrom(paramType) ||
InputStream.class.isAssignableFrom(paramType) ||
Reader.class.isAssignableFrom(paramType) ||
HttpMethod.class == paramType ||
Locale.class == paramType ||
TimeZone.class == paramType ||
ZoneId.class == paramType);
}

5. 复杂参数

MapModel(map、model里面的数据会被放在request的请求域 request.setAttribute)、Errors/BindingResult、RedirectAttributes( 重定向携带数据)ServletResponse(response)、SessionStatus、UriComponentsBuilder、ServletUriComponentsBuilder

1
2
Map<String,Object> map,  Model model, HttpServletRequest request 都是可以给request域中放数据,
request.getAttribute();

6. pojo封装

1
2
3
4
5
6
7
8
9
10
/**
* 数据绑定:页面提交的请求数据(GET、POST)都可以和对象属性进行绑定
* @param person
* @return
*/
@PostMapping("/saveuser")
public Person saveuser(Person person){

return person;
}

可以自定义类型参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new Converter<String, Pet>() {

@Override
public Pet convert(String source) {
// 啊猫,3
if(!StringUtils.isEmpty(source)){
Pet pet = new Pet();
String[] split = source.split(",");
pet.setName(split[0]);
// pet.setAge(Integer.parseInt(split[1]));
pet.setAge(split[1]);
return pet;
}
return null;
}
});
}

2.4 数据响应

1. 响应JSON数据

  • 主要通过jackson.jar+@ResponseBody
  • spring-boot-starter-web场景自动引入了json场景

2. springmvc支持响应的数据类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
ModelAndView
Model
View
ResponseEntity
ResponseBodyEmitter
StreamingResponseBody
HttpEntity
HttpHeaders
Callable
DeferredResult
ListenableFuture
CompletionStage
WebAsyncTask
@ModelAttribute 且为对象类型的
@ResponseBody 注解 ---> RequestResponseBodyMethodProcessor;

2.5 内容协商

根据客户端接收能力不同,返回不同媒体类型的数据。

  1. 首先导入相应依赖

    1
    2
    3
    4
     <dependency>
    <groupId>com.fasterxml.jackson.dataformat</groupId>
    <artifactId>jackson-dataformat-xml</artifactId>
    </dependency>
  2. 开启浏览器参数方式内容协商功能

    为了方便内容协商,开启基于请求参数的内容协商功能。

    1
    2
    3
    4
    spring:
    mvc:
    contentnegotiation:
    favor-parameter: true #开启请求参数内容协商模式

    发送请求:http://localhost:8080/test/person?format=json

    http://localhost:8080/test/person?format=xml

  3. 内容协商分为基于请求头和基于请求参数两种

    image.png

  4. 自定义MessageConverter

    • ~~~java
      /**

               * 自定义内容协商策略
               * @param configurer
               */
              @Override
              public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
                  //Map<String, MediaType> mediaTypes
                  Map<String, MediaType> mediaTypes = new HashMap<>();
                  mediaTypes.put("json",MediaType.APPLICATION_JSON);
                  mediaTypes.put("xml",MediaType.APPLICATION_XML);
                  mediaTypes.put("gg",MediaType.parseMediaType("application/x-guigu"));
                  //指定支持解析哪些参数对应的哪些媒体类型
                  ParameterContentNegotiationStrategy parameterStrategy = new ParameterContentNegotiationStrategy(mediaTypes);
      

      // parameterStrategy.setParameterName(“ff”);

                  //保证之前请求头的方式不失效
                  HeaderContentNegotiationStrategy headeStrategy = new HeaderContentNegotiationStrategy();
      
                  configurer.strategies(Arrays.asList(parameterStrategy,headeStrategy));
              }
      
              @Override
              public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
                  converters.add(new GuiguMessageConverter());
              }
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44

      - ~~~java
      /**
      * 自定义的Converter
      */
      public class GuiguMessageConverter implements HttpMessageConverter<Person> {

      @Override
      public boolean canRead(Class<?> clazz, MediaType mediaType) {
      return false;
      }

      @Override
      public boolean canWrite(Class<?> clazz, MediaType mediaType) {
      return clazz.isAssignableFrom(Person.class);
      }

      /**
      * 服务器要统计所有MessageConverter都能写出哪些内容类型
      *
      * application/x-guigu
      * @return
      */
      @Override
      public List<MediaType> getSupportedMediaTypes() {
      return MediaType.parseMediaTypes("application/x-guigu");
      }

      @Override
      public Person read(Class<? extends Person> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
      return null;
      }

      @Override
      public void write(Person person, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
      //自定义协议数据的写出
      String data = person.getUserName()+";"+person.getAge()+";"+person.getBirth();


      //写出去
      OutputStream body = outputMessage.getBody();
      body.write(data.getBytes());
      }
      }
    • 有可能我们添加的自定义的功能会覆盖默认很多功能,导致一些默认的功能失效。比如上面添加自定义的请求参数协议时,请求头方式失效。

      上述功能除了我们完全自定义外?SpringBoot有没有为我们提供基于配置文件的快速修改媒体类型功能?怎么配置呢?【提示:参照SpringBoot官方文档web开发内容协商章节】

2.6 视图解析

  • 视图解析流程

2.7 模板引擎-Thymeleaf

1. 介绍

springboot不支持jsp页面(可通过额外配置),支持模板引擎thymeleaf。推荐前后端分离开发。

官网:https://www.thymeleaf.org/

官方文档:https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html

2. 基本使用

  • 引入相关依赖

    1
    2
    3
    4
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
  • 已经自动配置好了thymeleaf

    • 1、所有thymeleaf的配置值都在 ThymeleafProperties

    • 2、配置好了 SpringTemplateEngine

    • 3、配好了 ThymeleafViewResolver

    • 4、我们只需要直接开发页面

    • ~~~java

      private String prefix = "classpath:/templates/";
      private String suffix = ".html";
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18

      - 编写html文件,记得加上 xmlns:th="http://www.thymeleaf.org"

      ~~~html
      <!DOCTYPE html>
      <html lang="en" xmlns:th="http://www.thymeleaf.org">
      <head>
      <meta charset="UTF-8">
      <title>Title</title>
      </head>
      <body>
      <h1 th:text="${msg}">哈哈</h1>
      <h2>
      <a href="www.atguigu.com" th:href="${link}">去百度</a> <br/>
      <a href="www.atguigu.com" th:href="@{link}">去百度2</a>
      </h2>
      </body>
      </html>
  • 编写controller层

  • 注:可以在配置文件给所有路径加上前缀

    1
    2
    3
    4
    #给页面路径加前缀
    server:
    servlet:
    context-path: /study

3. 后台管理系统练习

  • 项目创建

    • 导入thymeleaf、web-starter、lombok等
  • 添加静态资源放在static目录下面

  • 已经写好的前端页面放在templates目录下面

    • html文件记得加上 xmlns:th=”http://www.thymeleaf.org"
    • 路径构建th:action=”@{/login}”
    • 标签里面的写法:**[[${}]]**,比如[[${session.loginUser.userName}]]
  • 编写简单逻辑的登录controller

  • 抽取公共页面,减少代码冗余

    • 编写公共页面,包括重复使用的页面,样式等
    • 公共部分中有两种方式来标识
      • th:fragment=”commonheader” 对应页面th:replace=”common :: commonheader
      • 直接使用id选择器 对应页面th:replace=”common :: #commonheader
    • 调用公共部分三种方式
      • th:insert 是最简单的:它会简单地插入指定的片段作为其宿主标签的主体。
      • th:replace实际上用指定的片段替换其主机标记。(推荐)
      • th:include与 类似th:insert,但它不插入片段,而是仅插入此片段的内容。(3.0开始不推荐使用)
      • image-20210720183500471
        • 注:在抽取公共页面时,不要改变原来部分或者样式的前后位置(比如最下面的js样式),可能会出错!!!
  • thymeleaf中表格的遍历

    • 先传入几个测试数据

    • ```Java
      List users = Arrays.asList(new User(“zhangsan”, “123456”),

          new User("lisi", "123444"),
          new User("haha", "aaaaa"),
          new User("hehe ", "aaddd"));
      

      model.addAttribute(“users”,users);

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13

      - 遍历语法 th:each="user:${users},user里面有具体每个用户的数据

      - 可在user后面用逗号隔开,添加一个状态码,可以查看遍历的index,count等

      - ```html
      <tr class="gradeX" th:each="user,status:${users}">
      <td th:text="${status.count}">Trident</td>
      <td th:text="${user.userName}">Internet
      Explorer 4.0</td>
      <td th:text="${user.password}">Win 95+</td>

      </tr>

2.8 拦截器

1. springboot中拦截器的使用

  • 实现HandlerInterceptor接口,这里主要是实现登录拦截

  • ~~~java
    /**

    • 登录检查

    • 1、配置好拦截器要拦截哪些请求

    • 2、把这些配置放在容器中

    • /
      @Slf4j
      public class LoginInterceptor implements HandlerInterceptor {
      /**

      • 目标方法执行之前

      • @param request

      • @param response

      • @param handler

      • @return

      • @throws Exception

      • /
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //利用Lombok提供的日志功能查看拦截了哪些路径
        String requestURI = request.getRequestURI();
        log.info(“preHandle拦截的请求路径是{}”,requestURI);

        //登陆检查
        HttpSession session = request.getSession();
        Object loginUser = session.getAttribute(“loginUser”);

        if (loginUser != null){

          //放行
          return true;
        

        }else{

          //拦截,跳转到登录页
          //这里不能用session,前端接收不到
          request.setAttribute("msg","请登录!");
          request.getRequestDispatcher("/").forward(request,response);
          return false;
        

        }
        }

      //目标方法执行之后
      @Override
      public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

        log.info("postHandle执行{}",modelAndView);
        HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
      

      }

      //页面渲染之后
      @Override
      public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

        log.info("afterCompletion执行异常{}",ex);
        HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
      

      }
      }

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      72
      73
      74
      75
      76
      77
      78
      79
      80
      81
      82
      83
      84
      85
      86
      87
      88
      89
      90
      91
      92
      93
      94
      95
      96
      97
      98
      99
      100
      101
      102
      103
      104
      105
      106
      107
      108
      109
      110
      111
      112
      113
      114
      115
      116
      117
      118
      119
      120
      121
      122
      123
      124
      125
      126
      127
      128
      129
      130
      131
      132
      133
      134
      135
      136
      137
      138
      139
      140
      141
      142
      143
      144
      145
      146
      147
      148
      149
      150
      151
      152
      153
      154
      155
      156
      157
      158
      159
      160
      161
      162
      163
      164
      165
      166
      167
      168
      169
      170
      171
      172
      173
      174

      - 将拦截器注册到容器之中,并且指定拦截规则

      - ```java
      @Configuration
      public class AdminWebConfig implements WebMvcConfigurer {
      @Override
      public void addInterceptors(InterceptorRegistry registry) {
      registry.addInterceptor(new LoginInterceptor())
      .addPathPatterns("/**") //拦截的路径,/**拦截所有路径,包括静态资源
      //放行的资源路径,不能直接写/static,找不到因为页面编写没有带static
      .excludePathPatterns("/","/login","/css/**","/fonts/**","/images/**","/js/**");

      }
      }
      ```

      #### 2. 拦截器原理分析

      1、根据当前请求,找到**HandlerExecutionChain【**可以处理请求的handler以及handler的所有 拦截器】

      2、先来**顺序执行** 所有拦截器的 preHandle方法

      - 1、如果当前拦截器prehandler返回为true。则执行下一个拦截器的preHandle
      - 2、**如果当前拦截器返回为false。直接 倒序执行所有已经执行了的拦截器的 afterCompletion;**

      **3、如果任何一个拦截器返回false。直接跳出不执行目标方法**

      **4、所有拦截器都返回True。执行目标方法**

      **5、倒序执行所有拦截器的postHandle方法。**

      **6、前面的步骤有任何异常都会直接倒序触发** afterCompletion

      7、页面成功渲染完成以后,也会倒序触发 afterCompletion

      ![image.png](SpringBoot-2-02.assets/1605765121071-64cfc649-4892-49a3-ac08-88b52fb4286f.png)



      ### 2.9 文件上传

      #### 1. 文件上传的步骤

      - 页面中修改form表单

      - method="post" th:action="@{/upload}" enctype="multipart/form-data"

      - enctype="multipart/form-data" ,multipart/form-data是指表单数据有多部分构成,既有文本数据,又有文件等二进制数据的意思,可提供文件上传功能

      - ```html
      <!--multiple代表上传多文件-->
      <input type="file" name="photos" multiple>
      ```



      - controller层通过 MultipartFile 自动封装上传过来的文件

      - @RequestPart

      - 1 @RequestParam适用于name-value表单字段,而@RequestPart经常被用于处理复杂内容(例如JSON, XML)
      - 2 当方法的参数类型不是String或者原生的MultipartFile / Part,@RequstParam需要注册并使用 Converter or PropertyEditor 进行类型转换,而 RequestPart 可通过请求体中的“Content-Type“进行使用 HttpMessageConverters转换

      - headImg.transferTo方法可将文件上传到指定路径

      - ```java
      @PostMapping("/upload")
      public String upload(@RequestParam String email,
      @RequestParam String password,
      @RequestPart MultipartFile headImg,
      @RequestPart MultipartFile[] photos)throws IOException {
      log.info("上传的信息:email={},username={},headerImg={},photos={}",
      email,password,headImg.getSize(),photos.length);

      if(!headImg.isEmpty()){
      //保存到文件服务器,OSS服务器
      String originalFilename = headImg.getOriginalFilename();
      //需要抛出异常
      headImg.transferTo(new File("D:\\test\\"+originalFilename));
      }

      if(photos.length > 0){
      for (MultipartFile photo : photos) {
      if(!photo.isEmpty()){
      String originalFilename = photo.getOriginalFilename();
      photo.transferTo(new File("D:\\test\\"+originalFilename));
      }
      }
      }

      return "main";
      }
      ```

      #### 2. 文件上传原理分析

      **文件上传自动配置类-MultipartAutoConfiguration-**MultipartProperties



      ### 2.10 页面异常处理

      #### 1. 官网描述

      https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.developing-web-applications.spring-mvc.error-handling

      #### 2. 默认规则

      - 默认情况下,Spring Boot 提供了一个`/error`以合理方式处理所有错误的映射

      - 对于机器客户端,它会生成包含错误详细信息、HTTP 状态和异常消息的 JSON 响应

      - 对于浏览器客户端,有一个“whitelabel”错误视图,它以 HTML 格式呈现相同的数据(要自定义它,添加一个`View`解析为 的`error`)如下:![image.png](SpringBoot-2-02.assets/1606024616835-bc491bf0-c3b1-4ac3-b886-d4ff3c9874ce.png)

      - 可以将自定义的404.html,5xx.html页面放在下面目录下,当发生相应错误会自动解析

      - 5xx代表5开头的所有错误
      - 4xx代表4开头的所有错误

      ```
      resources/public/error/404.html
      resources/templates/error/404.html
      ```

      - 可以在页面中拿到下面的信息![image-20210721183513076](SpringBoot-2-02.assets/image-20210721183513076.png)

      #### 3. 异常处理自动配置原理

      - **ErrorMvcAutoConfiguration**

      - **容器中的组件:类型:DefaultErrorAttributes ->** **id:errorAttributes**
      - **public class** **DefaultErrorAttributes** **implements** **ErrorAttributes**, **HandlerExceptionResolver**
      - **DefaultErrorAttributes**:定义错误页面中可以包含哪些数据。
      - **容器中的组件:类型:**BasicErrorController --> id:basicErrorController(json+白页 适配响应)
      - **处理默认** **/error 路径的请求;页面响应** **new** ModelAndView(**"error"**, model);
      - **容器中有组件 View**->**id是error**;(响应默认错误页)
      - 容器中放组件 **BeanNameViewResolver(视图解析器);按照返回的视图名作为组件的id去容器中找View对象。**

      - **容器中的组件:**类型:**DefaultErrorViewResolver -> id:**conventionErrorViewResolver

      - 如果发生错误,会以HTTP的状态码 作为视图页地址(viewName),找到真正的页面

      - error/404、5xx.html



      #### 4. 定制错误处理逻辑

      1. 自定义错误页

      - error/404.html error/5xx.html;有精确的错误状态码页面就匹配精确,没有就找 4xx.html;如果都没有就触发白页
      - 如果5xx.html,4xx.html这些页面本身有编码错误执行不了,会出现![image-20210722163501060](SpringBoot-2-02.assets/image-20210722163501060.png)

      2. @ControllerAdvice+@ExceptionHandler处理全局异常

      - 底层是 **ExceptionHandlerExceptionResolver 支持的**

      - ~~~java
      /**
      * 处理整个web controller的异常
      * ArithmeticException数学异常,NullPointerException空指针异常
      */
      @Slf4j
      @ControllerAdvice
      public class GlobalExceptionHandler {

      @ExceptionHandler({ArithmeticException.class,NullPointerException.class}) //处理异常
      public String handleArithException(Exception e){

      log.error("异常是:{}",e);
      return "login"; //视图地址
      }
      }
  1. @ResponseStatus+自定义异常

    • 底层是 ResponseStatusExceptionResolver ,把responsestatus注解的信息底层调用 response.sendError(statusCode, resolvedReason);

    • tomcat发送的/error

    • ```java
      /**

      • 自定义一个异常

      • /
        @ResponseStatus(value= HttpStatus.FORBIDDEN,reason = “用户数量太多”)
        public class UserTooManyException extends RuntimeException {

        //无参
        public UserTooManyException(){

        }

        //有参
        public UserTooManyException(String message){

          super(message);
        

        }
        }

      //自己抛出一个异常

          if(users.size()>3){
              throw new UserTooManyException();
          }
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25

      4. Spring底层的异常

      - 如 参数类型转换异常;**DefaultHandlerExceptionResolver 处理框架底层的异常。**
      - response.sendError(HttpServletResponse.**SC_BAD_REQUEST**, ex.getMessage());

      5. 自定义实现 HandlerExceptionResolver 处理异常;可以作为默认的全局异常处理规则

      - ```Java
      @Order(value= Ordered.HIGHEST_PRECEDENCE) //优先级,数字越小优先级越高
      @Component
      public class CustomerHandlerExceptionResolver implements HandlerExceptionResolver {
      @Override
      public ModelAndView resolveException(HttpServletRequest request,
      HttpServletResponse response,
      Object handler, Exception ex) {

      try {
      response.sendError(511,"我喜欢的错误");
      } catch (IOException e) {
      e.printStackTrace();
      }
      return new ModelAndView();
      }
      }
    • 提高了优先级,大部分页面出现异常都会被该自定义处理(404页面不行)

2.11 web原生组件注入

1. 官方文档

2. 使用Servlet API(推荐使用)

  • 可以使用注解方式实现注入

  • 使用之前需要在主程序加上注解,扫描指定包 @ServletComponentScan(basePackages = “com.wjj”)

    • servlet组件 ,直接响应,不会经过拦截器 @WebServlet(urlPatterns = “/my”)

    • ```java
      @WebServlet(urlPatterns = “/my”)
      public class MyServlet extends HttpServlet {

      @Override
      protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
          resp.getWriter().write("66");
      }
      

      }

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24

      - filter组件,@WebFilter(urlPatterns = "/css/*")

      - ```java
      @Slf4j
      @WebFilter(urlPatterns = "/css/*") // /*是servlet中的写法,/**是spring中的写法
      public class MyFilter implements Filter {

      @Override
      public void init(FilterConfig filterConfig) throws ServletException {
      log.info("Filter初始化");
      }

      @Override
      public void destroy() {
      log.info("Filter销毁");
      }

      @Override
      public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
      log.info("Filter工作");
      filterChain.doFilter(servletRequest,servletResponse);
      }
      }
    • listener组件,@WebListener

    • ```Java
      @Slf4j
      @WebListener
      public class MyServletContextListener implements ServletContextListener {

      @Override
      public void contextInitialized(ServletContextEvent sce) {
          log.info("监听到项目初始化");
      }
      
      @Override
      public void contextDestroyed(ServletContextEvent sce) {
          log.info("监听到项目销毁");
      }
      

      }

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42

      - servlet不经过拦截器的原因:

      - 容器中自动配置了 DispatcherServlet 属性绑定到 WebMvcProperties;对应的配置文件配置项是 **spring.mvc。**
      - **通过** **ServletRegistrationBean**<DispatcherServlet> 把 DispatcherServlet 配置进来。
      - 默认映射的是 / 路径
      - 我们这里的servlet路径是/my,Tomcat-Servlet在多个servlet都能处理同一路径时,会采用精度优先的原则
      - 因此就不会通过spring中设置的拦截器

      #### 2. 使用RegistrationBean方式

      - ServletRegistrationBean,FilterRegistrationBean以及ServletListenerRegistrationBean注入容器

      - ```Java
      //(proxyBeanMethods = true) 保证组件依赖始终是单实例,默认就是true
      @Configuration
      public class MyRegistConfig {

      @Bean
      public ServletRegistrationBean myServlet(){
      MyServlet myServlet = new MyServlet();

      return new ServletRegistrationBean(myServlet,"/my","/my02");
      }


      @Bean
      public FilterRegistrationBean myFilter(){

      MyFilter myFilter = new MyFilter();
      // return new FilterRegistrationBean(myFilter,myServlet()); 直接过滤myServlet()中的路径
      FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(myFilter);
      filterRegistrationBean.setUrlPatterns(Arrays.asList("/my","/css/*"));
      return filterRegistrationBean;
      }

      @Bean
      public ServletListenerRegistrationBean myListener(){
      MyServletContextListener myServletContextListener = new MyServletContextListener();
      return new ServletListenerRegistrationBean(myServletContextListener);
      }
      }

2.12 嵌入式servlet容器

1. 切换嵌入式Servlet容器

  • 嵌入式服务器就是指我们不需要额外再去部署服务器,springboot默认为我们提供了

  • 默认支持的webServerTomcat, Jetty, or Undertow

  • web场景默认导入tomcat

  • 切换方法

  • ~~~xml

    org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-tomcat org.springframework.boot spring-boot-starter-jetty
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119

    - 原理
    - SpringBoot应用启动发现当前是Web应用。web场景包-导入tomcat
    - web应用会创建一个web版的ioc容器 `ServletWebServerApplicationContext`
    - `ServletWebServerApplicationContext` 启动的时候寻找 `**ServletWebServerFactory**``(Servlet 的web服务器工厂---> Servlet 的web服务器)`
    - SpringBoot底层默认有很多的WebServer工厂;`TomcatServletWebServerFactory`, `JettyServletWebServerFactory`, or `UndertowServletWebServerFactory`
    - `底层直接会有一个自动配置类。ServletWebServerFactoryAutoConfiguration`
    - `ServletWebServerFactoryAutoConfiguration导入了ServletWebServerFactoryConfiguration(配置类)`
    - `ServletWebServerFactoryConfiguration 配置类 根据动态判断系统中到底导入了那个Web服务器的包。(默认是web-starter导入tomcat包),容器中就有 TomcatServletWebServerFactory`
    - `TomcatServletWebServerFactory 创建出Tomcat服务器并启动;TomcatWebServer 的构造器拥有初始化方法initialize---this.tomcat.start();`
    - `内嵌服务器,就是手动把启动服务器的代码调用(tomcat核心jar包存在)`

    #### 2. 定制Servlet容器

    - 实现 **WebServerFactoryCu**stomizer<ConfigurableServletWebServerFactory>

    - 把配置文件的值和`**ServletWebServerFactory 进行绑定**`

    - 修改配置文件 **server.xxx**
    - 直接自定义 **ConfigurableServletWebServerFactory**



    **xxxxxCustomizer:定制化器,可以改变xxxx的默认规则**

    ```java
    import org.springframework.boot.web.server.WebServerFactoryCustomizer;
    import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory;
    import org.springframework.stereotype.Component;

    @Component
    public class CustomizationBean implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> {

    @Override
    public void customize(ConfigurableServletWebServerFactory server) {
    server.setPort(9000);
    }

    }
    ```



    ### 2.13 定制化配置

    #### 1.定制化的主要方式

    1. **修改配置文件**
    - 直接在application.properties或者application.yml文件中修改
    2. **xxxxxCustomizer:定制化器,可以改变xxxx的默认规则**
    3. **编写自定义的配置类 xxxConfiguration;+** **@Bean替换、增加容器中默认组件;视图解析器**
    4. **Web应用 编写一个配置类实现** **WebMvcConfigurer 即可定制化web功能;+ @Bean给容器中再扩展一些组件**(常用)

    ```java
    @Configuration
    public class AdminWebConfig implements WebMvcConfigurer
    ```

    5. **@EnableWebMvc + WebMvcConfigurer —— @Bean**

    - 添加@EnableWebMvc注解会使之前的所有规则失效,比如静态资源,欢迎页等。需要自己全面重新配置

    - 原理

    - 1、WebMvcAutoConfiguration 默认的SpringMVC的自动配置功能类。静态资源、欢迎页.....
    - 2、一旦使用 @EnableWebMvc 、。会 @Import(DelegatingWebMvcConfiguration.**class**)

    - 3、**DelegatingWebMvcConfiguration** 的 作用,只保证SpringMVC最基本的使用把所有系统中的WebMvcConfigurer 拿过来。所有功能的定制都是这些 WebMvcConfigurer 合起来一起生效自动配置了一些非常底层的组件。比如**RequestMappingHandlerMapping**、这些组件依赖的组件都是从容器中获取**public class** DelegatingWebMvcConfiguration **extends** **WebMvcConfigurationSupport**

    - 4、**WebMvcAutoConfiguration** 里面的配置要能生效 必须 @ConditionalOnMissingBean(**WebMvcConfigurationSupport**.**class**)
    - 5、@EnableWebMvc 导致了 **WebMvcAutoConfiguration 没有生效。**

    #### 2. 一般的原理分析套路

    **场景starter** **- xxxxAutoConfiguration - 导入xxx组件 - 绑定xxxProperties --** **绑定配置文件项**

    - 导入相应的场景
    - 找到对应的自动配置文件,命名主要是xxxxAutoConfiguration
    - 配置文件里面一般使用@bean导入各种组件
    - 组件里的默认属性都跟xxxProperties绑定
    - 最后绑定在配置文件之下,我们需要修改只需要改配置文件即可



    ## 3.数据访问

    ### 3.1 SQL

    #### 1.数据源的自动配置

    - 导入场景依赖

    ```xml
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jdbc</artifactId>
    </dependency>
    ```

    <img src="SpringBoot-2-02.assets/image-20210723165222136.png" alt="image-20210723165222136" style="zoom: 80%;" />

    - jdbc场景并没有导入驱动,因为无法判断实际使用的是哪个数据库

    - 自行导入mysql

    ~~~xml
    <dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    </dependency>

    1.导入之后默认会有一个8版本的
    2.根据自己使用的mysql版本更改
    1、直接依赖引入具体版本(maven的就近依赖原则)
    2、重新声明版本(maven的属性的就近优先原则)
    <properties>
    <java.version>1.8</java.version>
    <mysql.version>5.1.49</mysql.version>
    </properties>

    数据库和数据源

    1.数据库中存放数据,数据源相当于连接到数据库的一条路径

    2.DataSource 通常被称为数据源,它包含连接池和连接池管理两个部分,习惯上也经常把 DataSource 称为连接池。

    3.数据库连接池就是准备一个池子,里面放着很多生成好的Connection,用户请求获得连接,就不需要getConnection,只要从池子里拿一个给他就行了,这样省掉了生成Connection的时间,效率上会有很大提高

  • 自动配置原理

    • 自动配置的类

      • DataSourceAutoConfiguration : 数据源的自动配置
        • 修改数据源相关的配置:spring.datasource
        • 数据库连接池的配置,是自己容器中没有DataSource才自动配置的
        • 底层配置好的连接池是:HikariDataSource
      1
      2
      3
      4
      5
      6
      7
      @Configuration(proxyBeanMethods = false)
      @Conditional(PooledDataSourceCondition.class)
      @ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
      @Import({ DataSourceConfiguration.Hikari.class, DataSourceConfiguration.Tomcat.class,
      DataSourceConfiguration.Dbcp2.class, DataSourceConfiguration.OracleUcp.class,
      DataSourceConfiguration.Generic.class, DataSourceJmxConfiguration.class })
      protected static class PooledDataSourceConfiguration
      • DataSourceTransactionManagerAutoConfiguration: 事务管理器的自动配置
      • JdbcTemplateAutoConfiguration: JdbcTemplate的自动配置,可以来对数据库进行crud
        • 可以修改这个配置项@ConfigurationProperties(prefix = “spring.jdbc”) 来修改JdbcTemplate
        • @Bean@Primary JdbcTemplate;容器中有这个组件
      • JndiDataSourceAutoConfiguration: jndi的自动配置
      • XADataSourceAutoConfiguration: 分布式事务相关的
  • 配置文件中修改配置

    • ```yaml
      spring:
      datasource:
      url: jdbc:mysql://localhost:3306/student
      username: root
      password: 1234567
      driver-class-name: com.mysql.jdbc.Driver
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20

      - 测试数据库

      - ```Java
      @Slf4j
      @SpringBootTest
      class Boot04WebAdminApplicationTests {

      @Autowired
      JdbcTemplate jdbcTemplate;

      @Test
      void contextLoads() {

      // jdbcTemplate.queryForObject("select * from account_tbl")
      // jdbcTemplate.queryForList("select * from account_tbl",)
      Long aLong = jdbcTemplate.queryForObject("select count(*) from admin", Long.class);
      log.info("记录总数:{}",aLong);
      }
      }

2. Druid数据源的使用

  • 自定义方式

    • 先导入数据源依赖

    • ```xml

      com.alibaba druid 1.2.6
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54

      - 编写配置类

      - ```java
      @Configuration
      public class MyDataSourceConfig {

      // 默认的自动配置是判断容器中没有才会配@ConditionalOnMissingBean(DataSource.class)
      //把我们数据源和配置文件spring.datasource里面的值进行绑定,不用再写用户名密码等信息
      @ConfigurationProperties("spring.datasource")
      @Bean
      public DataSource dataSource() throws SQLException {
      DruidDataSource druidDataSource = new DruidDataSource();

      // druidDataSource.setUrl();
      // druidDataSource.setUsername();
      // druidDataSource.setPassword();
      //加入监控功能
      druidDataSource.setFilters("stat,wall");

      // druidDataSource.setMaxActive(10);
      return druidDataSource;
      }

      /**
      * 配置 druid的监控页功能
      * @return
      */
      @Bean
      public ServletRegistrationBean statViewServlet(){
      StatViewServlet statViewServlet = new StatViewServlet();
      ServletRegistrationBean<StatViewServlet> registrationBean = new ServletRegistrationBean<>(statViewServlet, "/druid/*");

      registrationBean.addInitParameter("loginUsername","admin");
      registrationBean.addInitParameter("loginPassword","123456");


      return registrationBean;
      }

      /**
      * WebStatFilter 用于采集web-jdbc关联监控的数据。
      */
      @Bean
      public FilterRegistrationBean webStatFilter(){
      WebStatFilter webStatFilter = new WebStatFilter();

      FilterRegistrationBean<WebStatFilter> filterRegistrationBean = new FilterRegistrationBean<>(webStatFilter);
      filterRegistrationBean.setUrlPatterns(Arrays.asList("/*"));
      filterRegistrationBean.addInitParameter("exclusions","*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");

      return filterRegistrationBean;
      }
      }
  • 官方starter方式(推荐)

    • 官方文档:https://github.com/alibaba/druid/tree/master/druid-spring-boot-starter

    • 配置项列表: https://github.com/alibaba/druid/wiki/DruidDataSource%E9%85%8D%E7%BD%AE%E5%B1%9E%E6%80%A7%E5%88%97%E8%A1%A8

    • 先引入starter依赖

    • ```xml

      com.alibaba druid-spring-boot-starter 1.2.6
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16

      - 分析自动配置

      - ```java
      @Configuration
      @ConditionalOnClass({DruidDataSource.class})
      @AutoConfigureBefore({DataSourceAutoConfiguration.class})
      @EnableConfigurationProperties({DruidStatProperties.class, DataSourceProperties.class})
      @Import({DruidSpringAopConfiguration.class, DruidStatViewServletConfiguration.class, DruidWebStatFilterConfiguration.class, DruidFilterConfiguration.class})

      1.可在配置文件中配置 spring.datasource.druid
      2.
      DruidSpringAopConfiguration.class, 监控SpringBean的;配置项:spring.datasource.druid.aop-patterns
      DruidStatViewServletConfiguration.class, 监控页的配置:spring.datasource.druid.stat-view-servlet;默认开启
      DruidWebStatFilterConfiguration.class, web监控配置;spring.datasource.druid.web-stat-filter;默认开启
      DruidFilterConfiguration.class}) 所有Druid自己filter的配置
    • 配置示例

    • ```yaml
      spring:
      datasource:

      url: jdbc:mysql://localhost:3306/student?characterEncoding=utf-8&useSSL=false
      username: root
      password: 1234567
      driver-class-name: com.mysql.jdbc.Driver
      
      #druid配置
      druid:
        aop-patterns: com.wjj.boot.*  #监控SpringBean
        filters: stat,wall     # 底层开启功能,stat(sql监控),wall(防火墙)
      
        stat-view-servlet: # 配置监控页功能
          enabled: true
          login-username: admin
          login-password: 123
          resetEnable: false
      
        web-stat-filter: # 监控web
          enabled: true
          urlPattern: /*
          exclusions: '*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*'
      
      
        filter:
          stat: # 对上面filters里面的stat的详细配置
            slow-sql-millis: 1000
            logSlowSql: true
            enabled: true
          wall:
            enabled: true
            config:
              drop-table-allow: false
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22



      #### 3. 整合Mybatis

      - 官方GitHub地址: https://github.com/mybatis/spring-boot-starter/

      - starter命名

      - SpringBoot官方的Starter:spring-boot-starter-*
      - 第三方的: *-spring-boot-starter

      - 导入mybatis-starter

      - 注意版本,每个版本都有对应版本要求

      - ```xml
      <dependency>
      <groupId>org.mybatis.spring.boot</groupId>
      <artifactId>mybatis-spring-boot-starter</artifactId>
      <version>2.2.0</version>
      </dependency>
  • 直接SPRING INITIALIZR上勾选

  1. 配置模式

    • 导入mybatis-starter

    • 编写mapper接口。标注**@Mapper注解**

      • ```Java
        @Mapper
        public interface AccountMapper {
        Account queryById(int id);
        
        }
        1
        2
        3
        4
        5
        6
        7
        8
        9

        - 编写sql映射文件并绑定mapper接口

        - ```xml
        <mapper namespace="com.wjj.boot.mapper.AccountMapper">
        <select id="queryById" resultType="com.wjj.boot.pojo.Account">
        select * from account where id = #{id}
        </select>
        </mapper>
    • 在application.yaml中指定Mapper配置文件的位置,以及指定全局配置文件的信息

      • ```yaml
        mybatis:

        config-location: classpath:mybatis/mybatisConfig.xml

        mapper-locations: classpath:mybatis/mapper/*.xml
        configuration:
        map-underscore-to-camel-case: true    #数据库写法转驼峰写法
        
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21

        - 可以在application.yaml指定mybatis配置文件的位置,在mybatis-config.xml里面写配置

        - 也可以直接在application.yaml里面写配置,不通过mybatis-config.xml

        - 两者有冲突,推荐后者

        - 编写service,自动装配mapper接口

        - 实际中应该写service接口,再写一个serviceImpl实现类,此处简化

        - ```java
        @Service
        public class AccountService {
        @Autowired
        AccountMapper accountMapper;

        public Account queryById(int id){
        return accountMapper.queryById(id);
        }
        }
    • controller代码测试

      • ```java
        @Autowired
        AccountService accountService;@ResponseBody
        @GetMapping(“/query”)
        public Account queryById(@RequestParam(“id”) int id){
        return accountService.queryById(id);
        
        }
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14



        2. 注解模式

        - 在配置模式中不用额外编写sql映射文件,直接在mapper接口上写注解即可

        - ```Java
        @Mapper
        public interface CityMapper {

        @Select("select * from city where id=#{id}")
        public City queryCityById(int id);
        }
    • 缺点

      • 不适合复杂sql语句
      • 增加耦合
  2. 混合模式

    • 实际上就是上面两种模式可以共存

    • SQL语句简单时可以使用注解,复杂时使用sql映射文件

    • ```Java
      @Mapper
      public interface CityMapper {

      @Select("select * from city where id=#{id}")
      public City queryCityById(int id);
      
      public void insert(City city);
      

      }

      //sql映射文件
      //useGeneratedKeys=”true” keyProperty=”id” id自增
      //@Options(useGeneratedKeys = true, keyProperty = “id”)注解方式的自增

      insert into city(`name`,`state`,`country`) values(#{name},#{state},#{country})
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
         
      - 最佳实战方式

      - 引入mybatis-starter
      - **配置application.yaml中,指定mapper-location位置即可**
      - 编写Mapper接口并标注@Mapper注解
      - 简单方法直接注解方式
      - 复杂方法编写mapper.xml进行绑定映射
      - 在主程序上使用*@MapperScan("com.atguigu.admin.mapper") 简化,其他的接口就可以不用标注@Mapper注解*



      #### 4. 整合Mybatis-Plus

      1. 官方介绍:https://mp.baomidou.com/guide

      gitee源码地址:https://gitee.com/baomidou/mybatis-plus

      2. 快速实现

      - 导入依赖

      - ~~~xml
      <dependency>
      <groupId>com.baomidou</groupId>
      <artifactId>mybatis-plus-boot-starter</artifactId>
      <version>3.4.3.1</version>
      </dependency>
      ~~~

      - 编写配置类,默认已经配置好了不少

      - 注:使用mybatis-plus之后,原先配置的mybatis会失效,需要重新在mybatis-plus配置

      - 编写实体类

      - ```Java
      //该注解可以表明该字段不在数据库,否则mybatis-plus使用报错
      @TableField(exist = false)
      private String userName;
      @TableField(exist = false)
      private String password;

      //@TableName("user_tbl"),默认根据类名获取数据库中的表名,当名字不一致,可以使用该注解指定数据库表
      //@TableName("user_tbl")注解加在类前面
    • 编写mapper类

      • ```java
        public interface UserMapper extends BaseMapper {}//BaseMapper里面包括了基本的CRUD方法,我们不用额外再写
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14

        - 测试输出

        - ```java
        @Autowired
        private UserMapper userMapper;

        @Test
        public void testSelect() {
        System.out.println(("----- selectAll method test ------"));
        List<User> userList = userMapper.selectList(null);
        Assert.assertEquals(5, userList.size());
        userList.forEach(System.out::println);
        }
    • 具体可参照官方文档

  3. CRUD功能的实现

    • 在上一部分的基础上进行

    • service层 Userservice 继承IService

      • ```java
        public interface UserService extends IService {
        }
        1
        2
        3
        4
        5
        6
        7

        - 实现service接口

        - ```Java
        @Service
        public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
        }
    • 控制层实现数据的展示和数据删除

      • RedirectAttributes重定向之后可以携带参数

      • ```Java

        @GetMapping("/user/delete/{id}")
        public String deleteUser(@PathVariable("id") Integer id,
                                 @RequestParam("pn") Integer pn,
                                 RedirectAttributes rn){
        
            userService.removeById(id);
            rn.addAttribute(pn);
            return "redirect:/dynamic_table";
        }
        
        @GetMapping("/dynamic_table")
        public String dynamic_table(@RequestParam(value = "pn",defaultValue = "1") Integer pn, Model model){
            
            List<User> list = userService.list();
        

        // model.addAttribute(“list”,list);

            //分页查询数据
            Page<User> page = new Page<>(pn,2);
        
            //分页查询结果
            Page<User> userPage = userService.page(page, null);
            model.addAttribute("page",userPage);
            return "data_tables/dynamic_table";
        }
        
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        36
        37
        38
        39
        40
        41
        42
        43
        44
        45
        46
        47
        48
        49
        50
        51
        52
        53
        54
        55
        56
        57
        58
        59
        60
        61
        62
        63
        64

        - html页面

        - 传入page,page.records表示总分页数据

        - 遍历加状态写法:th:each="user,status:${page.records}",user表示每个遍历出来的用户,status表示状态

        - 页面转发并携带参数写法:th:href="@{/dynamic_table(pn=${num})}" th:href="@{/user/delete/{id}(id=${user.id},pn=${page.current})}

        - 条件判断:th:if th:switch

        - 生成数字序列:#numbers.sequence(1,page.pages),从一到末页

        - ```html
        <thead>
        <th>#</th>
        <th>编号</th>
        <th>用户名</th>
        <th>年龄</th>
        <th>邮箱</th>
        <th>操作</th>

        </thead>
        <tbody>
        <tr class="gradeX" th:each="user,status:${page.records}">
        <td th:text="${status.count}">Trident</td>
        <td th:text="${user.id}">Internet</td>
        <td th:text="${user.name}">Internet
        Explorer 4.0</td>
        <td th:text="${user.age}">Win 95+</td>
        <td th:text="${user.email}">Win 95+</td>
        <td>
        <a th:href="@{/user/delete/{id}(id=${user.id},pn=${page.current})}" class="btn btn-danger btn-sm" type="button">删除</a>
        </td>
        </tr>
        </tbody>
        </table>
        <!--分页条-->
        <div class="row-fluid">
        <div class="span6">
        <div class="dataTables_info" id="dynamic-table_info">
        当前第[[${page.current}]]页 总计 [[${page.pages}]]页 共[[${page.total}]]条记录
        </div>
        </div>
        <div class="span6">
        <div class="dataTables_paginate paging_bootstrap pagination">
        <ul>
        <!--判断是否有上一页-->
        <li th:switch="${page.hasPrevious()}">
        <a th:href="@{/dynamic_table(pn=${page.getCurrent()-1})}" th:case="true">← 前一页</a>
        <a th:href="@{/dynamic_table(pn=${page.getCurrent()})}" th:case="false">← 前一页</a>
        </li>
        <li th:class="${num == page.current?'active':''}" th:each="num:${#numbers.sequence(1,page.pages)}" >
        <a th:href="@{/dynamic_table(pn=${num})}">[[${num}]]</a>
        </li>
        <!--判断是否还有下一页-->
        <li th:switch="${page.hasNext()}">
        <a th:href="@{/dynamic_table(pn=${page.getCurrent()+1})}" th:case="true">下一页 → </a>
        <a th:href="@{/dynamic_table(pn=${page.getCurrent()})}" th:case="false">下一页 → </a>
        </li>
        </ul>
        </div>
        </div>
        </div>

3.2 NOSQL

  • NOSQL是指非关系型数据库
  • Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。 它支持多种类型的数据结构,如 字符串(strings), 散列(hashes), 列表(lists), 集合(sets), 有序集合(sorted sets) 与范围查询, bitmaps, hyperloglogs 和 地理空间(geospatial) 索引半径查询。 Redis 内置了 复制(replication),LUA脚本(Lua scripting), LRU驱动事件(LRU eviction),事务(transactions) 和不同级别的 磁盘持久化(persistence), 并通过 Redis哨兵(Sentinel)和自动 分区(Cluster)提供高可用性(high availability)。

4.单元测试

4.1. Junit5的基本介绍

  • Spring Boot 2.2.0 版本开始引入 JUnit 5 作为单元测试默认库

  • 作为最新版本的JUnit框架,JUnit5与之前版本的Junit框架有很大的不同。由三个不同子项目的几个不同模块组成。

    • JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage
    • JUnit Platform: Junit Platform是在JVM上启动测试框架的基础,不仅支持Junit自制的测试引擎,其他测试引擎也都可以接入。
    • JUnit Jupiter: JUnit Jupiter提供了JUnit5的新的编程模型,是JUnit5新特性的核心。内部 包含了一个测试引擎,用于在Junit Platform上运行。
    • JUnit Vintage: 由于JUint已经发展多年,为了照顾老的项目,JUnit Vintage提供了兼容JUnit4.x,Junit3.x的测试引擎。
  • 注意:

    SpringBoot 2.4 以上版本移除了默认对 Vintage 的依赖。如果需要兼容junit4需要自行引入(不能使用junit4的功能 @Test****)

    JUnit 5’s Vintage Engine Removed from **spring-boot-starter-test,如果需要继续兼容junit4需要自行引入vintage**

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <dependency>
    <groupId>org.junit.vintage</groupId>
    <artifactId>junit-vintage-engine</artifactId>
    <scope>test</scope>
    <exclusions>
    <exclusion>
    <groupId>org.hamcrest</groupId>
    <artifactId>hamcrest-core</artifactId>
    </exclusion>
    </exclusions>
    </dependency>
  • SpringBoot整合Junit5之后,项目创建就会自动生成一个测试类

  • 编写新的测试只需要加上@Test注解(注意区分不同版本的@Test,需要Junit5的)

  • Junit类中包括Spring的功能,比如

    • @Autowired 自动装配
    • @Transactional 标注测试方法,测试完成后自动回滚
  • 测试完成之后,可进行汇总报告,查看有多少断言异常,多少跳过

    • 断言一般都是产生异常错误
    • 跳过包括前置条件和@Disabled

    image-20210727180518664

4.2 Juint5常用注解

  • **@Test :**表示方法是测试方法。但是与JUnit4的@Test不同,他的职责非常单一不能声明任何属性,拓展的测试将会由Jupiter提供额外测试

    • @Test测试执行的顺序跟方法名有关
  • **@ParameterizedTest :**表示方法是参数化测试,下方会有详细介绍

  • **@RepeatedTest :**表示方法可重复执行,@RepeatedTest(5)

  • **@DisplayName :**为测试类或者测试方法设置展示名称

  • @BeforeEach :表示在每个单元测试之前执行

  • @AfterEach :表示在每个单元测试之后执行

  • @BeforeAll :表示在所有单元测试之前执行,必须是static

  • @AfterAll :表示在所有单元测试之后执行,必须是static

  • **@Tag :**表示单元测试类别,类似于JUnit4中的@Categories

  • **@Disabled :**表示测试类或测试方法不执行,类似于JUnit4中的@Ignore

  • **@Timeout :**表示测试方法运行如果超过了指定时间将会返回错误

  • **@ExtendWith :**为测试类或测试方法提供扩展类引用

    • @SpringBoot注解中就有

    • ```java
      /**

      • @BootstrapWith(SpringBootTestContextBootstrapper.class)
      • @ExtendWith(SpringExtension.class)
      • /
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        36
        37
        38
        39
        40
        41
        42
        43
        44
        45
        46

        ```java
        import org.junit.jupiter.api.Test; //注意这里使用的是jupiter的Test注解!!


        public class TestDemo {

        @Test
        @DisplayName("第一次测试")
        public void firstTest() {
        System.out.println("hello world");
        }

        /**
        * 规定方法超时时间。超出时间测试出异常
        *
        * @throws InterruptedException
        */
        @Timeout(value = 500, unit = TimeUnit.MILLISECONDS)
        @Test
        void testTimeout() throws InterruptedException {
        //只要超时就抛出异常
        Thread.sleep(600);
        }

        @BeforeEach
        public void testBeforeEach(){
        System.out.println("每个测试前");
        }

        @AfterEach
        public void testAfterEach(){
        System.out.println("每个测试后");
        }

        @BeforeAll
        static void testBeforeAll() {
        System.out.println("所有测试就要开始了...");
        }

        @AfterAll
        static void testAfterAll() {
        System.out.println("所有测试以及结束了...");

        }
        }

4.3 断言机制

官方文档:https://junit.org/junit5/docs/current/user-guide/#writing-tests-assertions

  • *这些断言方法都是 org.junit.jupiter.api.Assertions .的静态方法

1. 简单断言

  • 对单个值进行简单的判断

  • 当断言执行之后,测试不会继续往下走

  • 方法 说明
    assertEquals 判断两个对象或两个原始类型是否相等
    assertNotEquals 判断两个对象或两个原始类型是否不相等
    assertSame 判断两个对象引用是否指向同一个对象
    assertNotSame 判断两个对象引用是否指向不同的对象
    assertTrue 判断给定的布尔值是否为 true
    assertFalse 判断给定的布尔值是否为 false
    assertNull 判断给定的对象引用是否为 null
    assertNotNull 判断给定的对象引用是否不为 null
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    @Test
    @DisplayName("simple assertion")
    public void simple() {
    //前面的值时期盼值,后面的是实际值,最后面是自定义信息
    assertEquals(3, 1 + 2, "simple math");
    assertNotEquals(3, 1 + 1);

    assertNotSame(new Object(), new Object());
    Object obj = new Object();
    assertSame(obj, obj);

    assertFalse(1 > 2);
    assertTrue(1 < 2);

    assertNull(null);
    assertNotNull(new Object());
    }

2. 数组断言

  • 通过 assertArrayEquals 方法来判断两个对象或原始类型的数组是否相等
1
2
3
4
5
@Test
@DisplayName("array assertion")
public void array() {
assertArrayEquals(new int[]{1, 2}, new int[] {1, 2});
}

3. 组合断言

  • 所有断言全部需要生效

    1
    2
    3
    4
    5
    6
    7
    8
    @Test
    @DisplayName("assert all")
    public void all() {
    assertAll("Math",
    () -> assertEquals(2, 1 + 1),
    () -> assertTrue(1 > 0)
    );
    }

4. 异常断言

  • JUnit5提供了一种新的断言方式Assertions.assertThrows() ,配合函数式编程就可以进行使用。
  • 业务逻辑异常则不抛出异常,反之业务逻辑正常抛出异常
1
2
3
4
5
6
7
8
@Test
@DisplayName("异常测试")
public void exceptionTest() {
assertThrows(
//扔出断言异常
ArithmeticException.class, () -> System.out.println(1 % 0));

}

5. 超时断言

1
2
3
4
5
6
@Test
@DisplayName("超时测试")
public void timeoutTest(){
//Duration持续时间
assertTimeout(Duration.ofMillis(1000), () -> Thread.sleep(500));
}

6. 快速失败

通过 fail 方法直接使得测试失败

1
2
3
4
5
@Test
@DisplayName("fail")
public void shouldFail() {
fail("This should fail");
}

4.4 前置条件(assumptions)

  • 前置条件和断言机制类似

  • 不同之处在于不满足的断言会使得测试方法失败,而不满足的前置条件只会使得测试方法的执行终止。前置条件可以看成是测试方法执行的前提,当该前提不满足时,就没有继续执行的必要。

    1
    2
    3
    4
    5
    6
    7
    @Test
    @DisplayName("前置条件")
    public void AssumptionsTest(){
    assumeTrue(true);
    assumingThat(true,() -> System.out.println("成功"));
    System.out.println("111");
    }

    assumeTrue 和 assumFalse 确保给定的条件为 true 或 false,不满足条件会使得测试执行终止。assumingThat 的参数是表示条件的布尔值和对应的 Executable 接口的实现对象。只有条件满足时,Executable 对象才会被执行;当条件不满足时,测试执行并不会终止。

4.5 嵌套测试

  • JUnit 5 可以通过 Java 中的内部类和@Nested 注解实现嵌套测试

  • 嵌套外面的测试调用不了里面的@BeforeEach等,内层的可以调用外面的

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    @DisplayName("A stack")
    class TestingAStackDemo {

    //栈
    Stack<Object> stack;

    @Test
    @DisplayName("is instantiated with new Stack()")
    void isInstantiatedWithNew() {
    new Stack<>();
    }

    @Nested
    @DisplayName("when new")
    class WhenNew {

    @BeforeEach
    void createNewStack() {
    stack = new Stack<>();
    }

    @Test
    @DisplayName("is empty")
    void isEmpty() {
    assertTrue(stack.isEmpty());
    }

    @Test
    @DisplayName("throws EmptyStackException when popped")
    void throwsExceptionWhenPopped() {
    assertThrows(EmptyStackException.class, stack::pop);
    }

    @Test
    @DisplayName("throws EmptyStackException when peeked")
    void throwsExceptionWhenPeeked() {
    assertThrows(EmptyStackException.class, stack::peek);
    }

    @Nested
    @DisplayName("after pushing an element")
    class AfterPushing {

    String anElement = "an element";

    @BeforeEach
    void pushAnElement() {
    stack.push(anElement);
    }

    @Test
    @DisplayName("it is no longer empty")
    void isNotEmpty() {
    assertFalse(stack.isEmpty());
    }

    @Test
    @DisplayName("returns the element when popped and is empty")
    void returnElementWhenPopped() {
    assertEquals(anElement, stack.pop());
    assertTrue(stack.isEmpty());
    }

    @Test
    @DisplayName("returns the element when peeked but remains not empty")
    void returnElementWhenPeeked() {
    assertEquals(anElement, stack.peek());
    assertFalse(stack.isEmpty());
    }
    }
    }
    }

4.6 参数化测试

官方文档:https://junit.org/junit5/docs/current/user-guide/#writing-tests-parameterized-tests

利用**@ValueSource**等注解,指定入参,我们将可以使用不同的参数进行多次单元测试,而不需要每新增一个参数就新增一个单元测试,省去了很多冗余代码。

@ValueSource: 为参数化测试指定入参来源,支持八大基础类以及String类型,Class类型

@NullSource: 表示为参数化测试提供一个null的入参

@EnumSource: 表示为参数化测试提供一个枚举入参

@CsvFileSource:表示读取指定CSV文件内容作为参数化测试入参

@MethodSource:表示读取指定方法的返回值作为参数化测试入参(注意方法返回需要是一个流)

1
2
3
4
5
6
@ParameterizedTest
@DisplayName("参数化测试")
@ValueSource(strings = { "racecar", "radar", "able was I ere I saw elba" })
void palindromes(String candidate) {
System.out.println(candidate);
}

4.7 迁移注意

官方文档内容:

以下是在将现有 JUnit 4 测试迁移到 JUnit Jupiter 时应该注意的主题。

  • Annotations 驻留在org.junit.jupiter.api包中。
  • Assertions 驻留在org.junit.jupiter.api.Assertions.
    • 请注意,您可以继续使用来自org.junit.Assert或任何其他断言库(例如AssertJHamcrestTruth等)的断言方法。
  • Assumptions 存在于org.junit.jupiter.api.Assumptions.
    • 请注意,JUnit Jupiter 5.4 和更高版本支持来自 JUnit 4 org.junit.Assume类的假设方法。具体来说,JUnit Jupiter 支持 JUnit 4AssumptionViolatedException发出信号,表明应该中止测试而不是将其标记为失败。
  • @Before@After不再存在;使用@BeforeEach@AfterEach代替。
  • @BeforeClass@AfterClass不再存在;使用@BeforeAll@AfterAll 代替。
  • @Ignore不再存在:使用@Disabled或其他内置一个 执行条件,而不是
  • @Category不复存在; 使用@Tag来代替。
  • @RunWith不复存在; 由 @ExtendWith取代
  • @Rule@ClassRule不再存在;被@ExtendWith@RegisterExtension取代

5.指标监控

5.1 SpringBoot Actuator

  1. 简介

    未来每一个微服务在云上部署以后,我们都需要对其进行监控、追踪、审计、控制等。

    SpringBoot就抽取了Actuator场景,使得我们每个微服务快速引用即可获得生产级别的应用监控、审计等功能。

  2. 1.x 和 2.x 的不同

    image.png

  1. 简单使用

    1. 导入依赖

      1
      2
      3
      4
      5
      <!--指标监控-->
      <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-actuator</artifactId>
      </dependency>
    2. 访问 http://localhost:8080/actuator/

      访问 http://localhost:8080/actuator/+具体的端口即可

      image-20210728123104731

    3. 端点

      • 官方文档:https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html#actuator.endpoints

      • Actuator 端点可让您监控应用程序并与之交互。

      • Spring Boot 包含许多内置端点,并允许您添加自己的端点。例如,health端点提供基本的应用程序健康信息。

      • 默认情况下,除shutdown之外的所有端点都是启动的

      • 暴露端点,有JMX形式和web形式

        • 默认是否暴露,参见官方文档

        • JMX形式指

          image-20210728154004054

        • 暴露所有形式为web

          1
          2
          3
          4
          5
          6
          management:
          endpoints:
          enabled-by-default: true #启用所有端点信息
          web:
          exposure:
          include: '*' #以web方式暴露

5.2 常用端口

  1. Health Endpoint

    • 健康检查端点,我们一般用于在云平台,平台会定时的检查应用的健康状况,我们就需要Health Endpoint可以为平台返回当前应用的一系列组件健康状况的集合。

    • health endpoint返回的结果,应该是一系列健康检查后的一个汇总报告

    • 很多的健康检查默认已经自动配置好了,比如:数据库、redis等

    • 可以很容易的添加自定义的健康检查机制

    • 默认只有status可以自行打开

      1
      2
      3
      4
      management:
      endpoint: #配置单个端点
      health:
      show-details: always #默认never

      image-20210728160953697

  2. Metrics Endpoint

    • 提供详细的、层级的、空间指标信息,这些信息可以被pull(主动推送)或者push(被动获取)方式得到;

      • 通过Metrics对接多种监控系统

      • 简化核心Metrics开发

      • 添加自定义Metrics或者扩展已有Metrics

    • 可以进一步访问,比如:http://localhost:8080/actuator/metrics/jvm.memory.max

    image-20210728161146945

5.3 定制端口

1. 定制Health信息

  1. 实现HealthIndicator接口

  2. 通过继承AbstractHealthIndicator类,最终命名为MyComHealthIndicator前缀MyCom

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    @Component
    public class MyComHealthIndicator extends AbstractHealthIndicator {

    /**
    * 真实的检查方法
    * @param builder
    * @throws Exception
    */
    @Override
    protected void doHealthCheck(Health.Builder builder) throws Exception {
    //mongodb。 获取连接进行测试
    Map<String,Object> map = new HashMap<>();
    // 检查完成
    if(1 == 2){
    // builder.up(); //健康
    builder.status(Status.UP);
    map.put("count",1);
    map.put("ms",100);
    }else {
    // builder.down();
    builder.status(Status.OUT_OF_SERVICE);
    map.put("err","连接超时");
    map.put("ms",3000);
    }


    builder.withDetail("code",100)
    .withDetails(map);

    }
    }

    image-20210728165426782

2. 定制info信息

  1. 编写配置文件

    1
    2
    3
    4
    5
    info:
    appName: boot-admin
    version: 2.0.1
    mavenProjectName: @project.artifactId@ #使用@@可以获取maven的pom文件值
    mavenProjectVersion: @project.version@
  2. 编写InfoContributor

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    import java.util.Collections;

    import org.springframework.boot.actuate.info.Info;
    import org.springframework.boot.actuate.info.InfoContributor;
    import org.springframework.stereotype.Component;

    @Component
    public class ExampleInfoContributor implements InfoContributor {

    @Override
    public void contribute(Info.Builder builder) {
    builder.withDetail("example",
    Collections.singletonMap("key", "value"));
    }

    }

    image-20210728170239632

3. 定制Metrics信息

增加定制Metrics

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class MyService{
Counter counter;
public MyService(MeterRegistry meterRegistry){
counter = meterRegistry.counter("myservice.method.running.counter");
}

public void hello() {
counter.increment();
}
}


//也可以使用下面的方式
@Bean
MeterBinder queueSize(Queue queue) {
return (registry) -> Gauge.builder("queueSize", queue::size).register(registry);
}

4. 定制端口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Component
@Endpoint(id = "myService")
public class MyServiceEndpoint {


@ReadOperation
public Map getDockerInfo(){
//端点的读操作 http://localhost:8080/actuator/myService
return Collections.singletonMap("info","docker started...");
}

@WriteOperation
private void restartDocker(){
System.out.println("docker restarted....");
}

}

5.4 实现可视化

官方GitHub:https://github.com/codecentric/spring-boot-admin

快速开始: https://codecentric.github.io/spring-boot-admin/2.3.1/#getting-started

  • 创建一个项目专门用来监控

    • 该项目导入依赖

      1
      2
      3
      4
      5
      6
      7
      8
      9
      <dependency>
      <groupId>de.codecentric</groupId>
      <artifactId>spring-boot-admin-starter-server</artifactId>
      <version>2.3.1</version>
      </dependency>
      <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
      </dependency>
    • 给主程序加上注解 @EnableAdminServer,同时设置不同的端口号方便区分

  • 给需要监控的项目配置

    • ```xml

      de.codecentric spring-boot-admin-starter-client 2.3.1
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12

      - 配置文件指向用于监控的项目,同时改变uri和应用名,保证监控在线

      ```yaml
      boot:
      admin:
      client:
      url: http://localhost:8888/ #指向用于监控的端口
      instance:
      prefer-ip: true #改为使用server.address,默认hostname
      application:
      name: admin #改变应用名,默认名spring-boot-application
    • 注:程序的health必须是up,负责也会导致离线

6.原理解析

6.1 Profiles功能

  • 该功能主要是为了便于存在不同生产环境时,可以方便快速的切换

1 . application-profile功能

  • 在配置文件中可以对生产环境进行切换,命名通常为application-xxx.yaml

  • ```yaml
    #指定激活的生产环境,两个配置文件都会生效,出现同名优先profiles
    spring.profiles.active=test

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30

    - 默认的application.yaml默认都会生效

    - 激活指定环境

    - 使用上述配置文件指定
    - 使用命令行进行激活:
    - java -jar xxx.jar --spring.profiles.active=prod --person.name=haha
    - 同样可以在命令行中修改配置文件的指定值,**命令行优先级最高**



    #### 2. @Profile条件装配功能

    - 当有多个类都与配置进行绑定

    - 可使用@Profile("xxx")注解,不同类绑定不同的环境

    - ```java
    @Profile(value = {"prod","default"})
    @Component
    @ConfigurationProperties("person")
    @Data
    public class Boss implements Person {

    private String name;
    private Integer age;


    }

3. profile分组

  • 可以通过分组同时加载多个环境

  • spring.profiles.group.production[0]=proddb
    spring.profiles.group.production[1]=prodmq
    
    使用:--spring.profiles.active=production  激活
    

6.2 外部化配置

  • 官方文档:https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.external-config

  • 可以在不同环境中使用相同的应用程序代码。可以使用各种外部配置源,包括 Java 属性文件、YAML 文件、环境变量和命令行参数。

  • Spring Boot 使用一个非常特殊的PropertySource顺序,旨在允许合理地覆盖值。属性按以下顺序考虑(较低项目的值覆盖较早的项目):

    1. 默认属性(由设置指定SpringApplication.setDefaultProperties)。
    2. @PropertySource你的@Configuration类的注释。请注意,Environment在刷新应用程序上下文之前,不会将此类属性源添加到。现在配置某些属性(例如在刷新开始之前读取的logging.*和)为时已晚spring.main.*
    3. 配置数据(如application.properties文件)
    4. RandomValuePropertySource,只有在拥有性能random.*
    5. 操作系统环境变量。
    6. Java 系统属性 ( System.getProperties())。
    7. JNDI 属性来自java:comp/env.
    8. ServletContext 初始化参数。
    9. ServletConfig 初始化参数。
    10. 来自SPRING_APPLICATION_JSON(嵌入在环境变量或系统属性中的内联 JSON)的属性。
    11. 命令行参数。
    12. properties属性在您的测试中。可用于测试应用程序的特定部分@SpringBootTest测试注释
    13. @TestPropertySource 测试中的注释。
    14. $HOME/.config/spring-boot当 devtools 处于活动状态时,目录中的Devtools 全局设置属性
  1. 配置数据文件可以从下面获取

    (1) classpath 根路径

    (2) classpath 根路径下config目录

    (3) jar包当前目录

    (4) jar包当前目录的config目录

    (5) /config子目录的直接子目录

    下面的配置会覆盖上面的

  2. 配置文件加载顺序

    1. 当前jar包内部的application.properties和application.yml
    2. 当前jar包内部的application-{profile}.properties 和 application-{profile}.yml
    3. 引用的外部jar包的application.properties和application.yml
    4. 引用的外部jar包的application-{profile}.properties 和 application-{profile}.yml
  3. 指定环境优先,外部优先,后面的可以覆盖前面的同名配置项

6.3 自定义starter

  1. starter的原理

    • starter的pom引入autoconfigurer

    • autoconfigure包中配置使用 META-INF/spring.factoriesEnableAutoConfiguration 的值,使得项目启动加载指定的自动配置类

    • 编写自动配置类 xxxAutoConfiguration -> xxxxProperties

      • @Configuration
      • @Conditional
      • @EnableConfigurationProperties
      • @Bean
    • ……

    引入starter — xxxAutoConfiguration — 容器中放入组件 —- 绑定xxxProperties —- 配置项

  2. 自定义starter

    1. 创建一个atguigu-hello-spring-boot-starter(启动器)

      再创建一个atguigu-hello-spring-boot-starter-autoconfigure(自动配置包)

    2. 启动器里面只需要导入自动配置包中的依赖

    3. 自动配置包里面写业务

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      /**
      * 默认不要放在容器中
      */
      public class HelloService {

      @Autowired
      HelloProperties helloProperties;

      public String sayHello(String userName) {
      return helloProperties.getPrefix() + ":" + userName + "》" + helloProperties.getSuffix();
      }
      }

      再写一个HelloProperties

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      @ConfigurationProperties("wjj.hello")
      public class HelloProperties {

      private String prefix;
      private String suffix;

      public String getPrefix() {
      return prefix;
      }

      public void setPrefix(String prefix) {
      this.prefix = prefix;
      }

      public String getSuffix() {
      return suffix;
      }

      public void setSuffix(String suffix) {
      this.suffix = suffix;
      }
      }

      写HelloServiceAutoConfiguration

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      @Configuration
      @EnableConfigurationProperties(HelloProperties.class) //默认HelloProperties放在容器中
      public class HelloServiceAutoConfiguration{

      @ConditionalOnMissingBean(HelloService.class)
      @Bean
      public HelloService helloService(){
      HelloService helloService = new HelloService();
      return helloService;
      }

      }

      spring.factories

      1
      2
      3
      # Auto Configure
      org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
      com.wjj.hello.auto.HelloServiceAutoConfiguration

      打包到本地仓库即可使用

      • 先打包自动配置包,再打包启动器
      • 打包clean+install
    4. 使用自定义starter

      • 导入相关starter依赖

      • 使用

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        public class HelloService {
        @Autowired
        HelloProperties helloProperties;

        public HelloService() {
        }

        public String sayHello(String userName) {
        return this.helloProperties.getPrefix() + ":" + userName + "》" + this.helloProperties.getSuffix();
        }
        }

        配置文件可以写相关内容

        1
        2
        wjj.hello.prefix = 好好学习
        wjj.hello.suffix = 人

6.4 SpringBoot原理

-------------本文结束感谢您的阅读-------------
您的支持将成为我创作的动力!