18.Springboot(2)

本文详细介绍了SpringBoot中Thymeleaf模板引擎的使用,包括语法、配置原理及实践操作,同时还涵盖了拦截器、文件上传、异常处理、服务器切换、数据访问等多个方面。从Thymeleaf表达式到自动配置,再到MyBatis整合,深入浅出地讲解了SpringBoot的诸多核心功能。此外,文章还探讨了JUnit5单元测试和Actuator指标监控,以及Swagger的API文档自动化,全面展示了SpringBoot的开发与测试技巧。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

七.Thymeleaf模板引擎

1.模板引擎

由于SpringBoot默认不支持 JSP(因为默认打包方式为jar包,jsp不支持在jar包内运行),需要引入第三方模板引擎技术实现页面渲染。

其实jsp就是一个模板引擎,还有用的比较多的freemarker、Thymeleaf。但再多的模板引擎,他们的思想都是一样的,什么样一个思想呢我们来看一下这张图:
在这里插入图片描述
模板引擎的作用:写一个页面模板,比如有些值是动态的,我们可以在这些值的地方写一些表达式。而这些值,从哪来呢,就是我们在后台封装一些数据。然后把这个数据和页面模板交给我们模板引擎,模板引擎按照我们这个数据帮你把表达式解析、填充到我们指定的位置,最终把这个数据生成一个我们想要的内容给我们写展现出去
SpringBoot推荐的是Thymeleaf模板引擎

2.Thymeleaf语法

1、表达式
表达式部分官方文档:https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html#standard-expression-syntax

表达式名字语法用途
变量取值 表达式${…}获取请求域、session域、对象等值
选择变量 表达式*{…}获取上下文对象值
消息 表达式#{…}消息(获取国际化等值)
链接 表达式@{…}生成适应当前项目根路径的链接
片段 表达式~{…}导入html片段,获取公共页面片段

2、字面量
文本值: `‘one text’ , ‘Another one!’ ,…数字: 0 , 34 , 3.0 , 12.3 ,…布尔值: true , false
空值: null
变量: one,two,… 变量不能有空格

3、文本操作
字符串拼接: +
变量替换: |The name is ${name}|

4、数学运算
运算符: + , - , * , / , %

5、布尔运算
运算符: and , or
一元运算: ! , not

6、比较运算
比较: > , < , >= , <= ( gt , lt , ge , le )等式: == , != ( eq , ne )

7、条件运算
If-then: (if) ? (then)
If-then-else: (if) ? (then) : (else)
Default: (value) ?: (defaultvalue)

8、特殊操作
无操作: _

9、设置属性值: th:attr
设置单个值

<form action="subscribe.html" th:attr="action=@{/subscribe}">
  <fieldset>
    <input type="text" name="email" />
    <input type="submit" value="Subscribe!" th:attr="value=#{subscribe.submit}"/>
  </fieldset>
</form>

设置多个值

<img src="../../images/gtvglogo.png"  th:attr="src=@{/images/gtvglogo.png},title=#{logo},alt=#{logo}" />

以上两个的代替写法 th:xxxx

<input type="submit" value="Subscribe!" th:value="#{subscribe.submit}"/>
<form action="subscribe.html" th:action="@{/subscribe}">

所有h5标签属性的兼容写法:(基本h5标签属性都能使用th:xxx代替
https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html#setting-value-to-specific-attributes

10、循环遍历
https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html#iteration

<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>

11、条件运算
https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html#conditional-evaluation

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

12、属性优先级
https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html#attribute-precedence
在这里插入图片描述

3.使用Thymeleaf

Thymeleaf官网使用文档:https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html#standard-expression-syntax

1、pom.xml中引入Thymeleaf模板引擎启动器依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

或者在构建项目是,勾选Thymeleaf模板引擎,它会自动导入Thymeleaf模板引擎启动器
在这里插入图片描述
2、编写一个HelloController

@Controller
public class HelloController {

    @GetMapping("/hello")
    public String hello(){
        return "hello"; //classpath:/templates/test.html
    }
}

3、编写一个测试页面 hello.html 放在 templates 目录下在这里插入图片描述

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h1>hello,Thymeleaf!</h1>
</body>
</html>

4、启动项目请求测试:
在这里插入图片描述

4.Thymeleaf自动配置原理

前面呢,我们已经引入了Thymeleaf,并成功通过controller方法跳转页面,其原理是什么?
按照SpringBoot的自动配置原理特点,肯定有个ThymeleafAutoConfiguration自动配置类来进行Thymeleaf模板引擎的自动配置。有一个ThymeleafProperties类绑定了配置文件,用于简单的自定义Thymeleaf配置修改

ThymeleafAutoConfiguration:

@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(ThymeleafProperties.class)
@ConditionalOnClass({ TemplateMode.class, SpringTemplateEngine.class })
@AutoConfigureAfter({ WebMvcAutoConfiguration.class, WebFluxAutoConfiguration.class })
public class ThymeleafAutoConfiguration {

ThymeleafProperties:

@ConfigurationProperties(prefix = "spring.thymeleaf")
public class ThymeleafProperties {
	private static final Charset DEFAULT_ENCODING = StandardCharsets.UTF_8;
	public static final String DEFAULT_PREFIX = "classpath:/templates/";
	public static final String DEFAULT_SUFFIX = ".html";

我们可以在其中看到默认的前缀DEFAULT_PREFIX和后缀DEFAULT_SUFFIX
上面就说明了为什么只要导入Thymeleaf启动器,然后在resources/templates/下写html页面,thymeleaf就可以自动渲染,就可以通过controller方法转发/重定向到指定页面

总的来说:

  • 所有thymeleaf的配置值都在 ThymeleafProperties
  • 自动配置类配置好了SpringTemplateEngine、ThymeleafViewResolver
  • 我们只需要直接开发页面
5.练习

1、修改测试请求,增加数据传输;

@GetMapping("/t1")
    public String test1(Model model){
        //存入数据
        model.addAttribute("msg","test,Thymeleaf!");
        model.addAttribute("users", Arrays.asList("唐三","小舞","胖子"));

        return "test";  //classpath:/templates/test.html
    }

2、我们要使用thymeleaf,需要在html文件中导入命名空间的约束,方便提示。

我们可以去官方文档的中看一下命名空间拿来过来:

 xmlns:th="http://www.thymeleaf.org"

3、我们去编写下前端页面test.html

<!DOCTYPE html>
<html lang="en"  xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <!--[[${xxx}]]:取值的行内写法,和jsp的${msg}一样-->
    <h1>[[${msg}]]</h1>
    <!--th:text:将div标签的内容设置为${xxx}中xxx的值-->
    <div th:text="${msg}"></div>

    <!-- th:each:循环遍历${users}取值的值   遍历的第二个参数state,是状态,count是计数值,每遍历一次+1-->
    <div th:each="user : ${users}" th:text="${user}"></div>
    <div th:each="user : ${users}">[[${user}]]</div>
    <div th:each="user,state : ${users}" th:text="${state.count}"></div>
</body>
</html>

4、启动测试!
在这里插入图片描述
我们看完语法,很多样式,我们即使现在学习了,也会忘记。所以我们在学习过程中,需要使用什么,根据官方文档来查询,才是最重要的,要熟练使用官方文档!

八.拦截器

1.实现

1、实现 HandlerInterceptor 接口
LoginInterceptor类:自定义用来拦截登录认证的拦截器

public class LoginInterceptor implements HandlerInterceptor {

	//目标方法执行之前
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        String requestURI = request.getRequestURI();
        System.out.println("preHandle执行了");

        //登录检查逻辑
        HttpSession session = request.getSession();
        Object loginUser = session.getAttribute("loginUser");

        if(loginUser != null){
            //放行
            return true;
        }

        //拦截住。未登录。跳转到登录页
        request.setAttribute("msg","请先登录");
//        re.sendRedirect("/");
        request.getRequestDispatcher("/").forward(request,response);
        return false;
    }

	//目标方法执行完成以后
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("postHandle执行了");
    }

	//页面渲染以后
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("afterCompletion执行了");
    }
}

2、在自定义的配置类中添加配置好的LoginInterceptor拦截器

  • 自定义的配置类必须实现WebMvcConfigurer接口
  • @Configuration注解该类,表示为一个配置类,会注册到spring容器中
  • 在自定义配置类重写addInterceptors方法
  • addInterceptor方法:添加自定义的LoginInterceptor拦截器
  • addPathPatterns方法:添加拦截的路径
  • excludePathPatterns方法:添加不拦截的路径
/**
 - 1、编写一个拦截器实现HandlerInterceptor接口
 - 2、拦截器注册到容器中(实现WebMvcConfigurer的addInterceptors)
 - 3、指定拦截规则【如果是拦截所有,静态资源也会被拦截】
 */
@Configuration
public class AdminWebConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor())
                .addPathPatterns("/**")  //所有请求都被拦截(包括静态资源)
.excludePathPatterns("/","/login","/css/**","/fonts/**","/images/**","/js/**"); //不拦截的请求
    }
}

3.前端页面登录验证,测试ok

2.原理
  • 根据当前请求,找到HandlerExecutionChain【处理器执行链:可以处理请求的handler以及handler的所有拦截器】
  • 先来顺序执行所有拦截器的preHandle方法
    ● 1、如果当前拦截器prehandler返回为true。则执行下一个拦截器的preHandle
    ● 2、如果当前拦截器返回为false。直接倒序执行所有已经执行了的拦截器的afterCompletion方法
  • 如果任何一个拦截器返回false(表示不放行),则直接跳出不执行目标方法
  • 所有拦截器都返回True,则执行目标方法
  • 倒序执行所有拦截器的postHandle方法。
  • 前面的步骤有任何异常都会直接倒序触发 afterCompletion
  • 页面成功渲染完成以后,也会倒序触发 afterCompletion
    在这里插入图片描述
    在这里插入图片描述

九.文件上传

1.实现

1、编写页面表单,

  • enctype="multipart/form-data"是文件上传必须加的,且为post方式提交
  • 上传单个文件 type="file"
  • 上传多个文件 type="file",并添加multiple属性
<form method="post" th:action="@{/fileupload}" enctype="multipart/form-data">
    <input type="file" name="headerImg">
    <input type="file" name="photos" multiple>
    <input type="submit" value="提交">
</form>

2、文件上传的controller方法

	//MultipartFile 自动封装上传过来的文件
    @PostMapping("/fileupload")
    public String fileUpload(@RequestParam("headerImg") MultipartFile headerImg,
                             @RequestParam("photos") MultipartFile[] photos) throws IOException {

        System.out.println("headerImg大小:"+headerImg.getSize());
        System.out.println("photos个数:"+photos.length);

        //保存单个文件
        if(!headerImg.isEmpty()){
            //利用transferTo方法传入指定文件路径即可将headerImg输入流数据写出
            //getOriginalFilename:获得文件名
            System.out.println(headerImg.getOriginalFilename());
            headerImg.transferTo(new File("C:\\Users\\秋天的思念\\Desktop\\file\\"+headerImg.getOriginalFilename()));
        }
        //保存多个文件
        if(photos.length>0){
            for (MultipartFile photo : photos) {
                if(!photo.isEmpty()){
                    //利用transferTo方法传入指定文件路径即可将headerImg输入流数据写出
                    //getOriginalFilename:获得文件名
                    System.out.println(photo.getOriginalFilename());
                    photo.transferTo(new File("C:\\Users\\秋天的思念\\Desktop\\file\\"+photo.getOriginalFilename()));
                }
            }
        }

        return "main";
    }
2.原理

按照springboot自动配置原理,同样有一个文件上传自动配置类:MultipartAutoConfiguration
以及文件上传绑定配置文件的类:MultipartProperties
MultipartAutoConfiguration自动配置好了 StandardServletMultipartResolver (文件上传解析器)

原理步骤:

  • 请求进来,使用文件上传解析器判断是否是文件类型(isMultipart),是就将该Request请求封装为MultipartHttpServletRequest请求
  • 找到对应的参数解析器解析请求中的文件内容,并封装成MultipartFile
  • 将request中文件信息封装为一个Map(即MultiValueMap<String,MultipartFile>FileCopyUtils)实现文件流的拷贝

在springmvc中,我们除了要写文件上传的controller,还要在spring容器中注册一个CommonsMultipartResolver类型的bean,id必须为multipartResolver现在我们只需要编写controller,不需要注册bean,更简单了

十.异常错误处理

1.实现

1、在templates/error/xxresources/public(static...)/error/xx下创建4xx、5xx页面
在这里插入图片描述
2.测试(在登录的情况下,访问一个不存在的请求,就会出现自定义的404页面error页面)
在这里插入图片描述

2.原理

1、默认规则

  • 默认情况下SpringBoot提供/error请求,用来处理所有错误的映射

  • 对于机器客户端,它将生成JSON响应。其中包含错误,HTTP状态和异常消息的详细信息。
    在这里插入图片描述

  • 对于浏览器客户端,响应一个“ whitelabel”错误视图页面,以HTML格式呈现相同的数据
    在这里插入图片描述

  • 要对其进行自定义,添加View解析为error

  • 要完全替换默认行为,可以实现 ErrorController并注册该类型的Bean定义,或添加ErrorAttributes类型的组件以使用现有机制但替换其内容。

  • error/下的4xx、5xx页面会被自动解析,也就是说在resources/templates/error/xxresources/public(static...)/error/xx下的4xx、5xx页面也能自动解析
    在这里插入图片描述
    2、异常错误处理自动配置原理
    按照自动配置原理,存在一个ErrorMvcAutoConfiguration自动配置类来配置异常处理规则

  • ErrorMvcAutoConfiguration自动配置类里的errorAttributes bean组件,类型是DefaultErrorAttributes。DefaultErrorAttributes类定义了发送错误时,返回的错误页面中可以包含的数据。
    在这里插入图片描述
    在这里插入图片描述

  • ErrorMvcAutoConfiguration自动配置类里的basicErrorController bean组件,类型是BasicErrorController 。BasicErrorController 类定义了发送错误时,以json响应还是以页面响应
    ○ 如果发送错误异常,遍历所有的handlerExceptionResolvers异常解析器,看谁能处理当前错误异常。如果没有任何能处理服务器产生的错误异常,最终就会发送 /error 请求,会被底层的BasicErrorController处理
    ○ 该类默认处理 /error 请求,如果是页面响应则new ModelAndView(“error”, model);
    ○ ErrorMvcAutoConfiguration中有error bean组件,类型为View,功能是响应默认错误页面
    ○ErrorMvcAutoConfiguration中有BeanNameViewResolver(视图解析器)bean 组件,其功能是按照返回的视图名作为组件的id去容器中找View对象。
    在这里插入图片描述
    ○ ErrorMvcAutoConfiguration中有conventionErrorViewResolver bean组件,类型为DefaultErrorViewResolver。其功能是如果发生错误,则会以HTTP的状态码作为视图名(viewName)去找到真正的页面。比如错误状态码为404,则会在resources/templates/error/xxresources/public(static...)/error/xx下的去找404页面,如果找不到就找4xx.html,都没有就返回默认的error错误页面

十一. 切换服务器

默认支持的webServer有Tomcat、Jetty、or Undertow

1.使用

默认web场景启动器导入了tomcat启动器依赖,所以默认启动的是tomcat服务器
如下是在spring-boot-starter-web启动器下找到的tomcat启动器

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-tomcat</artifactId>
    <version>2.4.11</version>
    <scope>compile</scope>
</dependency>

所以,如果要切换服务器,只需要排除当前tomcat启动器,然后导入其他服务器的依赖(这里我们切换为undertow服务器,只需要在pom.xml配置如下:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
        </exclusion>
    </exclusions>
</dependency>

<dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-undertow</artifactId>
</dependency>
2.原理
  • ServletWebServerApplicationContext(即项目的spring容器)启动会寻找ServletWebServerFactory 并引导创建服务器
  • SpringBoot应用启动发现当前是Web应用(因为导入了web场景启动器(它又会导入tomcat依赖))
  • web应用会创建一个web版的spring ioc容器 ServletWebServerApplicationContext
  • ServletWebServerApplicationContext 启动的时候寻找 ServletWebServerFactory(Servlet 的web服务器工厂:会生产对应的Servlet web服务器)
  • SpringBoot底层默认有很多的WebServer工厂:TomcatServletWebServerFactory,JettyServletWebServerFactory, or UndertowServletWebServerFactory
  • 底层直接会有一个自动配置类:ServletWebServerFactoryAutoConfiguration
  • ServletWebServerFactoryAutoConfiguration导入了ServletWebServerFactoryConfiguration(配置类)
  • ServletWebServerFactoryConfiguration 配置类:根据动态判断系统中到底导入了那个Web服务器的包。(web启动器导入的是tomcat依赖包),容器中就有TomcatServletWebServerFactory
  • TomcatServletWebServerFactory 创建出Tomcat服务器并启动;TomcatWebServer
    的构造器拥有初始化方法initialize—this.tomcat.start();
  • springboot以jar包方式打包的web项目之所以有tomcat内嵌服务器,就是因为有tomcat jar包存在
3.总结springboot原理

如果不想用默认的自动配置,需要自定义,常见的方式:

  1. 修改配置文件
  2. 编写自定义的配置类:实现WebMvcConfigurer接口,使用 @Configuration+ @Bean替换或增加spring容器中默认bean组件;
  3. 实现Customizer接口,重写方法
  4. @EnableWebMvc + WebMvcConfigurer+ @Bean 全面接管SpringMVC(所有规则全部自己重新配置; 实现定制和扩展功能)。是因WebMvcAutoConfiguration 里面的配置要能生效必须 @ConditionalOnMissingBean(WebMvcConfigurationSupport.class) ,而@EnableWebMvc WebMvcConfigurationSupport,所以导致WebMvcAutoConfiguration没有生效。

原理分析套路:场景starter - xxxxAutoConfiguration - 导入xxx bean组件 - 绑定xxxProperties – 绑定配置文件

十二.数据访问

1.jdbc
1.使用

1.导入jdbc启动器和数据库驱动

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

看jdbc启动器导入了哪些依赖:
在这里插入图片描述
发现一个问题,为什么官方没有导入数据库驱动?原因是:官方不知道我们接下要操作什么数据库,所以接下来我们要还要导入数据库驱动

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

不过在springboot 父类启动器里默认给mysql-connector-java版本:是8.0以上版本,如果要到5.0以上版本,可以显示添加版本信息(因为maven的就近依赖原则、所以会选择自己添加的版本)

 		<dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
        </dependency>

以上jdbc和数据库驱动也可以通过在构建工程时勾选如图两个功能,IDEA会帮我们导入jdbc和数据库驱动的依赖,和上面手动导入的依赖是一样的:
在这里插入图片描述
3.在配置文件配置数据源(数据连接池)
由于DataSourceProperties中没有对url、username、password、driverClassName进行默认赋值,所以我们得在配置文件中配置其值

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/ssmbuild?useSSL=false&useUnicode=true&characterEncoding=UTF-8
    password: 123456
    driver-class-name: com.mysql.jdbc.Driver

4、测试,连接数据OK

 @Test
    void contextLoads() throws SQLException {
        System.out.println(dataSource.getClass());  //class com.zaxxer.hikari.HikariDataSource

        Connection connection = dataSource.getConnection();
        System.out.println(connection);  //HikariProxyConnection@206142037 wrapping com.mysql.jdbc.JDBC4Connection@7493d937
        connection.close();
    }
2.原理
  • DataSourceAutoConfiguration : 数据源的自动配置类
    ○ 修改数据源相关的配置都在DataSourceProperties类里面,以spring.datasource为前缀
    ○ 数据库连接池的配置,在spring容器中没有DataSource.class才自动配置的
    数据库连接池的配置都在PooledDataSourceConfiguration 内部配置类里,由于springboot默认导入了HikariDataSource类,所以Hikari数据库连接池默认生效的(即springboot默认使用的是Hikari数据库连接池
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })
@ConditionalOnMissingBean(type = "io.r2dbc.spi.ConnectionFactory")
@EnableConfigurationProperties(DataSourceProperties.class)
@Import({ DataSourcePoolMetadataProvidersConfiguration.class, DataSourceInitializationConfiguration.class })
public class DataSourceAutoConfiguration {
@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的自动配置类,是spring用来简化crud操作的
    ○ 可以修改这个配置项@ConfigurationProperties(prefix = “spring.jdbc”) 来修改JdbcTemplate
    ○ JdbcTemplateConfiguration类将jdbcTemplate注册到了spring容器
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ DataSource.class, JdbcTemplate.class })
@ConditionalOnSingleCandidate(DataSource.class)
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
@EnableConfigurationProperties(JdbcProperties.class)
@Import({ JdbcTemplateConfiguration.class, NamedParameterJdbcTemplateConfiguration.class })
public class JdbcTemplateAutoConfiguration {
2.Druid数据库连接池

Druidgithub中文文档:https://github.com/alibaba/druid/wiki/常见问题
为了提高性能操作数据库的时候,又不得不使用数据库连接池。Druid 是阿里巴巴开源平台上一个数据库连接池实现,结合了 C3P0、DBCP 等 DB 池的优点,同时加入了日志监控。Druid 可以很好的监控 DB 池连接和 SQL 的执行情况,天生就是针对监控而生的 数据库连接池。Druid已经在阿里巴巴部署了超过600个应用,经过一年多生产环境大规模部署的严苛考验。Spring Boot 2.0 以上默认使用 Hikari 数据源,可以说 Hikari 与 Driud 都是当前 Java Web 上最优秀的数据源

1手动配置(比较繁琐)

导入druid依赖

		<dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.17</version>
        </dependency>

比如开启Druid的监控页面,官方是这样的:

 <servlet>
      <servlet-name>DruidStatView</servlet-name>
      <servlet-class>com.alibaba.druid.support.http.StatViewServlet</servlet-class>
  </servlet>
  <servlet-mapping>
      <servlet-name>DruidStatView</servlet-name>
      <url-pattern>/druid/*</url-pattern>
  </servlet-mapping>

由于我们使用了springboot,所以一切都只有在配置类里配置:(上面的web.xml对应下面的statViewServlet方法配置)

@Configuration
public class MyDruidDataSourceConfig {

    // 默认的数据源自动配置类中是判断容器中没有才会配:@ConditionalOnMissingBean(DataSource.class)
    //现在我们自己注册了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的监控页功能
    @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;
    }
}
2.导入druid启动器依赖(推荐)

1、引入druid-starter

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
    <version>1.1.22</version>
</dependency>

2、配置

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/ssmbuild?useSSL=false&amp;useUnicode=true&amp;characterEncoding=utf8
    username: root
    password: 123456
    driver-class-name: com.mysql.jdbc.Driver

    druid:
      aop-patterns: com.example.pojo.*  #监控SpringBean
      filters: stat,wall     # 底层开启功能,stat(sql监控),wall(防火墙)
      stat-view-servlet:   # 配置监控页
        enabled: true
        login-username: admin #监控页面登录账号
        login-password: 123456  #监控页面登录密码
      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

3、url请求:http://localhost:8080/MrL/druid/index.html ,输入登陆账号密码,即可
在这里插入图片描述
原理:
在我们导入的druid启动器依赖里有个DruidDataSourceAutoConfigure:Druid的自动配置类,自动注册了DataSource(所以导致默认的Hikari数据库连接池失效)

@Configuration
@ConditionalOnClass({DruidDataSource.class})
@AutoConfigureBefore({DataSourceAutoConfiguration.class})
@EnableConfigurationProperties({DruidStatProperties.class, DataSourceProperties.class})
@Import({DruidSpringAopConfiguration.class, DruidStatViewServletConfiguration.class, DruidWebStatFilterConfiguration.class, DruidFilterConfiguration.class})
public class DruidDataSourceAutoConfigure {
	......
    @Bean(initMethod = "init")
    @ConditionalOnMissingBean
    public DataSource dataSource() {
        LOGGER.info("Init DruidDataSource");
        return new DruidDataSourceWrapper();
    }
}
  • DruidSpringAopConfiguration.class:监控SpringBean的。配置项:spring.datasource.druid.aop-patterns
  • DruidStatViewServletConfiguration.class: 监控页的配置。@ConditionalOnProperty( name = {"spring.datasource.druid.stat-view-servlet.enabled"},havingValue = "true");所以监控页面默认是开启的
  • DruidWebStatFilterConfiguration.class:web监控的配置。@ConditionalOnProperty(name = {"spring.datasource.druid.web-stat-filter.enabled"},havingValue = "true");所以web监控默认是开启的
  • DruidFilterConfiguration.class:所有Druid自己filter的配置
public class DruidFilterConfiguration {
    private static final String FILTER_STAT_PREFIX = "spring.datasource.druid.filter.stat";
    private static final String FILTER_CONFIG_PREFIX = "spring.datasource.druid.filter.config";
    private static final String FILTER_ENCODING_PREFIX = "spring.datasource.druid.filter.encoding";
    private static final String FILTER_SLF4J_PREFIX = "spring.datasource.druid.filter.slf4j";
    private static final String FILTER_LOG4J_PREFIX = "spring.datasource.druid.filter.log4j";
    private static final String FILTER_LOG4J2_PREFIX = "spring.datasource.druid.filter.log4j2";
    private static final String FILTER_COMMONS_LOG_PREFIX = "spring.datasource.druid.filter.commons-log";
    private static final String FILTER_WALL_PREFIX = "spring.datasource.druid.filter.wall";
    private static final String FILTER_WALL_CONFIG_PREFIX = "spring.datasource.druid.filter.wall.config";

所以看到,比起手动配置,自动配置帮我们配置了好部分基本的功能,所以只要导入druid启动器,配置文件中配置以下druid的一些功能(比如监控页面的登录账号密码,就可以使用了)

3.整合MyBatis
1.使用

1、导入MyBatis启动器依赖

	<dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>2.1.4</version>
    </dependency>

在这里插入图片描述
2、根据数据库表建pojo类

public class Books {
    private int bookID;
    private String bookName;
    private int bookCounts;
    private String detail;
    //tostring、get、set

3、编写mapper(dao)层接口和对应的xxxmapper.xml sql映射文件
使用@Mapper标注该类会被mybatis自动配置类识别
使用@Repository标注该类是mapper层bean组件,会注册到spring容器中

@Mapper
@Repository
public interface BooksMapper {
    List<Books> getBooks();
}
<?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.example.mapper.BooksMapper">

    <select id="getBooks" resultType="com.example.pojo.Books">
       select * from books;
    </select>

</mapper>

这两个必须在同一个包!!!
在这里插入图片描述
在pom.xml中配置资源过滤:

<resources>
    <resource>
        <directory>src/main/java</directory>
        <includes>
            <include>**/*.xml</include>
        </includes>
        <filtering>true</filtering>
    </resource>
</resources>

4、创建service业务层接口及实现类(这一步可以不要,mybatis整合就可以用了,只是为了规范mvc思想)

public interface BooksService {

    List<Books> getBooks();
}
@Service
public class BooksServiceImpl implements BooksService{

    @Autowired
    BooksMapper booksMapper;

    @Override
    public List<Books> getBooks() {
        return booksMapper.getBooks();
    }
}

在这里插入图片描述
5、测试(使用springboot test类模拟controller),能访问数据库OK

	@Autowired
    BooksServiceImpl booksService;

    @Test
    void contextLoads() throws SQLException {
        List<Books> books = booksService.getBooks();
        for (Books book : books) {
            System.out.println(book);
        }
    }

整个过程我们都没有使用mybatis的核心配置文件mybatis.xml,只用到了mapper接口的sql映射xxxmapper.xml文件
如果第3步mapper(dao)层接口和对应的xxxmapper.xml sql映射文件不在同一个包下,那我们又没有核心配置文件mybatis.xml去映射该xxxmapper.xml,怎么做?(比如下图这样,放在resources的mybatis目录下了)
在这里插入图片描述
mybatis的核心配置文件mybatis.xml全部功能都被整合到了application.properties(yml)配置文件中
所以上图解决办法,可以在配置文件中配置如下:

mybatis:
  mapper-locations: classpath:mybatis/*.xml

如果我们想开启驼峰命名映射和开启日志功能(选择标准日志输出),可以这样做(这就相当于mybatis的核心配置文件mybatis.xml中开启驼峰命名映射和开启日志功能)

mybatis:
  configuration:
    map-underscore-to-camel-case: true #开启驼峰命名映射功能
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #开启控制台打印日志功能

比如给pojo包下所有类取别名:(这样在sql中指定类型不用写全类名了)

mybatis:
  type-aliases-package: com.example.pojo
2.原理
  • 只要导入了MyBatis启动器,里面有个MybatisAutoConfiguration自动配置类,它会将SqlSessionFactorySqlSessionTemplate(也就是SqlSession)自动配置好并注册为bean组件
  • MybatisAutoConfiguration自动配置类有个@Import(AutoConfiguredMapperScannerRegistrar.class)
    它将AutoConfiguredMapperScannerRegistrar类注册到spring容器,并且该类功能:只要我们定义的mapper接口使用了@Mapper注解,就会被自动扫描进来
  • MybatisProperties.class: MyBatis绑定配置文件的类(可以在配置文件中修改以mybatis开始的所有属性)
@ConfigurationProperties(prefix = "mybatis")
public class MybatisProperties {

十三.JUnit5单元测试

SpringBoot 2.2.0 版本开始引入 JUnit 5 作为单元测试默认库作为最新版本的JUnit框架,集成在了spring-boot-starter-test启动器,JUnit5与之前版本的Junit框架有很大的不同,由三个不同子项目的几个不同模块组成。
JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit VintageJUnit 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 以上版本移除了默认对 JUnit Vintage 的依赖。如果需要兼容junit4需要自行引入(不能使用junit4的功能 @Test)
JUnit 5的s JUnit Vintage:依赖是从spring-boot-starter-test移除的,如果需要继续兼容junit4需要自行引入JUnit Vintage

<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项目创建自带的一个测试类。以前版本是@SpringBootTest + @RunWith(SpringTest.class)

@SpringBootTest
class Boot05WebAdminApplicationTests {

    @Test
    void contextLoads() {

    }
}

@Transactional 标注测试方法,测试完成后自动回滚

1.JUnit5常用注解
  • @Test: 表示方法是测试方法。但是与JUnit4的@Test不同,他的职责非常单一不能声明任何属性,拓展的测试将会由Jupiter提供额外测试
  • @ParameterizedTest : 表示方法是参数化测试,下方会有详细介绍
  • @RepeatedTest : 表示方法可重复执行,下方会有详细介绍
  • @DisplayName : 为测试类或者测试方法设置展示名称
  • @BeforeEach : 表示在每个单元测试之前执行
  • @AfterEach : 表示在每个单元测试之后执行
  • @BeforeAll : 表示在所有单元测试之前执行
  • @AfterAll : 表示在所有单元测试之后执行
  • @Tag : 表示单元测试类别,类似于JUnit4中的@Categories
  • @Disabled : 表示测试类或测试方法不执行,类似于JUnit4中的@Ignore
  • @Timeout : 表示测试方法运行如果超过了指定时间将会返回错误
  • @ExtendWith : 为测试类或测试方法提供扩展类引用
	@Test
    @Disabled
    void disabled(){
        System.out.println("在执行MyTest总测试时会跳过这个测试方法");
    }
2.断言

断言(assertions)是测试方法中的核心部分,用来对测试需要满足的条件进行验证。这些断言方法都是 org.junit.jupiter.api.Assertions 的静态方法。
JUnit 5 内置的断言可以分成如下几个类别:1.检查业务逻辑返回的数据是否合理;2.所有的测试运行结束以后,会有一个详细的测试报告;

1、简单断言(用来对单个值进行简单的验证)

方法功能
assertEquals判断两个对象或两个原始类型是否相等
assertNotEquals判断两个对象或两个原始类型是否不相等
assertSame判断两个对象引用是否指向同一个对象
assertNotSame判断两个对象引用是否指向不同的对象
assertTrue判断给定的布尔值是否为 true
assertFalse判断给定的布尔值是否为 false
assertNull判断给定的对象引用是否为 null
assertNotNull判断给定的对象引用是否不为 null

测试以下:

	@Test
    @DisplayName("simple assertion")
    public void simple() {
        Assertions.assertEquals(3, 1 + 2, "simple math");
        Assertions.assertNotEquals(3, 1 + 1);

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

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

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

2、数组断言
通过 assertArrayEquals 方法来判断两个对象或原始类型的数组是否相等

	@Test
    @DisplayName("array assertion")
    public void array() {
        Assertions.assertArrayEquals(new int[]{1, 2}, new int[] {1, 2});
    }
}

3、组合断言
assertAll 方法接受多个 org.junit.jupiter.api.Executable 函数式接口的实例作为要验证的断言,可以通过 lambda 表达式很容易的提供这些断言

 	@Test
    @DisplayName("assert all")
    public void all() {
        Assertions.assertAll("Math",
                () -> Assertions.assertEquals(2, 1 + 1),
                () -> Assertions.assertTrue(1 > 0)
        );
    }

4、异常断言
在JUnit4时期,想要测试方法的异常情况时,需要用@Rule注解的ExpectedException变量还是比较麻烦的。而JUnit5提供了一种新的断言方式Assertions.assertThrows() ,配合函数式编程就可以进行使用

	@Test
    @DisplayName("异常测试")
    public void exceptionTest() {
        ArithmeticException exception = Assertions.assertThrows(
                //扔出断言异常
                ArithmeticException.class, () -> System.out.println(2%0));
    }

5、超时断言
Junit5还提供了Assertions.assertTimeout() 为测试方法设置了超时时间

	@Test
	@DisplayName("超时测试")
	public void timeoutTest() {
	    //如果测试方法时间超过1s将会异常
	    Assertions.assertTimeout(Duration.ofMillis(1000), () -> Thread.sleep(500));
	}

6、快速失败
通过 fail 方法直接使得测试失败

	@Test
    @DisplayName("fail")
    public void shouldFail() {
        Assertions.fail("This should fail");
    }
3.前置条件(assumptions)

JUnit 5 中的前置条件(assumptions【假设】)类似于断言,不同之处在于不满足的断言会使得测试方法失败,而不满足的前置条件只会使得测试方法的执行终止。前置条件可以看成是测试方法执行的前提,当该前提不满足时,就没有继续执行的必要。

@DisplayName("前置条件")
public class AssumptionsTest {
	 private final String environment = "DEV";
	 
	 @Test
	 @DisplayName("simple")
	 public void simpleAssume() {
	    assumeTrue(Objects.equals(this.environment, "DEV"));
	    assumeFalse(() -> Objects.equals(this.environment, "PROD"));
	 }
	 
	 @Test
	 @DisplayName("assume then do")
	 public void assumeThenDo() {
	    assumingThat(
	       Objects.equals(this.environment, "DEV"),
	       () -> System.out.println("In DEV")
	    );
	 }
}

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

4.嵌套测试

JUnit 5 可以通过 Java 中的内部类和@Nested 注解实现嵌套测试,从而可以更好的把相关的测试方法组织在一起。在内部类中可以使用@BeforeEach 和@AfterEach 注解,而且嵌套的层次没有限制。

@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());
            }
        }
    }
}
5、参数化测试

参数化测试是JUnit5很重要的一个新特性,它使得用不同的参数多次运行测试成为了可能,也为我们的单元测试带来许多便利。利用 @ValueSource等注解,指定入参,我们将可以使用不同的参数进行多次单元测试,而不需要每新增一个参数就新增一个单元测试,省去了很多冗余代码。
使用@ParameterizedTest表示这个方法是一个参数化测试

  • @ValueSource: 为参数化测试指定入参来源,支持八大基础类以及String类型,Class类型
  • @NullSource: 表示为参数化测试提供一个null的入参
  • @EnumSource: 表示为参数化测试提供一个枚举入参
  • @CsvFileSource: 表示读取指定CSV文件内容作为参数化测试入参
    @MethodSource: 表示读取指定方法的返回值作为参数化测试入参(注意方法返回需要是一个流)

当然如果参数化测试仅仅只能做到指定普通的入参还达不到让我觉得惊艳的地步。让我真正感到他的强大之处的地方在于他可以支持外部的各类入参。如:CSV,YML,JSON 文件甚至方法的返回值也可以作为入参。只需要去实现ArgumentsProvider接口,任何外部文件都可以作为它的入参。

@ParameterizedTest
@ValueSource(strings = {"one", "two", "three"})
@DisplayName("参数化测试1")
public void parameterizedTest1(String string) {
    System.out.println(string);
    Assertions.assertTrue(StringUtils.isNotBlank(string));
}

@ParameterizedTest
@MethodSource("method")    //指定方法名
@DisplayName("方法来源参数")
public void testWithExplicitLocalMethodSource(String name) {
    System.out.println(name);
    Assertions.assertNotNull(name);
}

static Stream<String> method() {
    return Stream.of("apple", "banana");
}

十四.指标监控

官网文档:https://docs.spring.io/spring-boot/docs/2.4.11/reference/html/production-ready-features.html#production-ready
未来每一个微服务应用在云上部署以后,我们都需要对其进行监控、追踪、审计、控制等。SpringBoot就抽取了Actuator场景,使得我们每个微服务应用快速引用Actuator即可获得生产级别的应用监控、审计等功能。
springboot2.0以上版本使用的是springboot Actuator2.0以上版本:
在这里插入图片描述

1.使用

1、引入actuator启动器依赖

	<dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>

2、访问 http://localhost:8080/actuator/**(发现只暴露了2个端点的监控指标)
在这里插入图片描述
3.以web页面方式暴露所有默认端点

management:
  endpoints:
    enabled-by-default: true #暴露所有端点信息,默认也是开启的
    web:
      exposure:
        include: '*'  #以web方式暴露所有端点

4.再访问 http://localhost:8080/actuator/**(所有默认端点都可以看到了)
在这里插入图片描述
查看env端点的项目环境:http://localhost:8080/MrL/actuator/env
查看metrics端点的cpu总数:http://localhost:8080/MrL/actuator/metrics/system.cpu.count
。。。。。。

2.Actuator端点

官网文档:https://docs.spring.io/spring-boot/docs/2.4.11/reference/html/production-ready-features.html#production-ready-endpoints
所有默认端点官网均有介绍,这里只列举常用的端点

ID描述
beans显示应用程序中所有Spring容器Bean的完整列表
caches暴露可用的缓存
conditions显示自动配置的所有条件信息,包括匹配或不匹配的原因
env暴露Spring的属性ConfigurableEnvironment
health(最常用)显示应用程序运行状况信息
info显示应用程序信息
loggers(最常用)显示和修改应用程序中日志的配置
metrics(最常用)显示当前应用程序的“指标”信息
mappings显示所有@RequestMapping路径列表
threaddump执行下载,线程转储文件
heapdump执行下载,返回hprof堆转储文件

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

  • health endpoint返回的结果,应该是一系列健康检查后的一个汇总报告
  • 很多的健康检查默认已经自动配置好了,比如:数据库、redis等
  • 可以很容易的添加自定义的健康检查机制

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

  • 通过Metrics对接多种监控系统
  • 简化核心Metrics开发
  • 添加自定义Metrics或者扩展已有Metrics
3.端点管理

1、开启与禁用端点

  • 默认所有的endpoint除了shutdown都是开启的。
  • 需要开启或者禁用某个endpoint,可以按照配置模式为 management.endpoint.endpointName.enabled = true进行配置

比如开启beans端点

management:
  endpoint:
    beans:
      enabled: true

或者禁用所有的Endpoint然后手动开启指定的Endpoint
比如关闭所有端点,只开启beans和health端点

management:
  endpoints:
    enabled-by-default: false
  endpoint:
    beans:
      enabled: true
    health:
      enabled: true

2、暴露端点
支持的暴露方式

  • Web暴露方式:默认只暴露health和info端点
  • JMX暴露方式:默认暴露所有Endpoint
management:
    web:
      exposure:
        include: '*'  #以web方式暴露所有端点
        exclude: "env,beans"  #不暴露env,beans端点
4.自定义端点

1、定制 health端点信息

@Component
public class MyHealthIndicator implements HealthIndicator {

    @Override
    public Health health() {
        int errorCode = check(); // perform some specific health check
        if (errorCode != 0) {
            return Health.down().withDetail("Error Code", errorCode).build();
        }
        return Health.up().build();
    }

}
management:
    health:
      enabled: true
      show-details: always #总是显示详细信息。可显示每个模块的状态信息
@Component
public class MyComHealthIndicator extends AbstractHealthIndicator {


    //真实的检查方法
    @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);
    }
}

2、定制info端点信息
常用两种方式
第一种方式:编写配置文件

info:
  appName: boot-admin
  version: 2.0.1
  mavenProjectName: @project.artifactId@  #使用@@可以获取maven的pom.xml文件值
  mavenProjectVersion: @project.version@

第二种方式:实现InfoContributor

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"));
    }

}

访问http://localhost:8080/actuator/info 会显示以上两种方式定制的所有info信息

3、定制Metrics端点信息

class MyService{
    Counter counter;
    public MyService(MeterRegistry meterRegistry){
         counter = meterRegistry.counter("myservice.method.running.counter");
    }

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

4、定制端点

@Component
@Endpoint(id = "container")
public class DockerEndpoint {

    @ReadOperation
    public Map getDockerInfo(){
        return Collections.singletonMap("info","docker started...");
    }

    @WriteOperation
    private void restartDocker(){
        System.out.println("docker restarted....");
    }
}
5.指标监控可视化平台

SpringBoot Admin是开源社区孵化的项目,用于对SpringBoot应用的管理和监控

  • SpringBoot Admin分为服务端(spring-boot-admin-server)和客户端(spring-boot-admin-client)
  • 服务端和客户端之间采用http通讯方式实现数据交互
  • 单体项目中需要整合spring-boot-admin-client才能被服务端监控
  • 在SpringCloud项目中,spring-boot-admin-server是直接从注册中心抓取应用信息,不需要每个微服务应用整合spring-boot-admin-client就可以实现应用的管理和监控
1.SpringBoot Admin服务端的搭建

创建新的springboor工程项目,作为服务端(必须勾选一个web场景启动器)
引入spring-boot-admin-starter-server场景启动器(由于我们的springboot版本是2.4.11,所以选择2.4版本(这里我们现在2.4.4版本))。而且,spring-boot-admin-starter-server场景启动器已经包含了springboot actuator的依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
   <groupId>de.codecentric</groupId>
   <artifactId>spring-boot-admin-starter-server</artifactId>
   <version>2.4.4</version>
</dependency>

在application.properties配置文件中进行配置端口和根目录访问路径

#指定服务器端口
server.port=8888
#项目根路径访问访问路径
server.servlet.context-path= /MrL

在主启动类上添加@EnableAdminServer注解,表示当前web应用作为springboot admin服务端

@EnableAdminServer  //开启 springboot admin服务端
@SpringBootApplication
public class AdminserverApplication {
    public static void main(String[] args) {
        SpringApplication.run(AdminserverApplication.class, args);
    }
}

访问:http://localhost:8888/MrL/applications。由于客户端还没被服务端监听,这里服务端可视化页面并没有获取到任何客户端应用信息
在这里插入图片描述

2.SpringBoot Admin客户端的搭建

创建新的springboor工程项目,作为客户端(必须勾选一个web场景启动器)
spring-boot-admin-starter-client场景启动器已经包含了springboot actuator的依赖,所以我们不用导入spring-boot-starter-actuator场景启动器了

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>de.codecentric</groupId>
    <artifactId>spring-boot-admin-starter-client</artifactId>
    <version>2.4.4</version>
</dependency>

在application.yml配置文件中配置

spring:
  application:
    name: admin-client  #给client应用取个名字
  boot:
    admin:
      client:
        url: http://localhost:8888/MrL #指定服务端的URL
#        instance:
#          prefer-ip: true		#如果出现注册不到服务端,可能是localhost没有映射为ip地址,这时再开启这里的prefer-ip: true,表示使用ip地址注册到服务端

#actuator监控的相关配置,打开并暴露全部端点信息,这样服务端就可以获得所以端点信息并可视化展示
management:
  endpoints:
    enabled-by-default: true
    web:
      exposure:
        include: '*'

当我们的客户端项目启动后,在服务端就能看到admin-client这个应用了
在这里插入图片描述
这里我们没有进行登录授权认证,进一步的改进可参考https://www.cnblogs.com/zeng1994/p/de3232ab0e1ce857b57eebb8e8922f0f.html

十五.Swagger

在前后端分离时代,前后端通过API接口(通熟点说就是controller方法)进行交互,所以前后端相对独立且松耦合。但同时产生了一个问题,前端需求变更(比如数据库多一个字段),后端无法及时知道这次变更去更改数据库,导致业务错误
解决方案:首先定义计划表,并实时跟踪最新的API接口,降低前后端不一致风险。
Swagger就是为了解决这个问题的,Swagger特点如下:

  • 号称世界上最流行的API框架
  • Restful Api 文档在线自动生成器 (API 接口文档 与API 接口定义同步更新)
  • 可以直接运行,在线测试API接口
  • 支持多种语言 (如:Java,PHP等)
1. 使用

导入Swagger依赖

<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger2</artifactId>
    <version>2.9.2</version>
</dependency>
<dependency>
   <groupId>io.springfox</groupId>
   <artifactId>springfox-swagger-ui</artifactId>
   <version>2.9.2</version>
</dependency>

编写一个swagger配置类,使用 @EnableSwagger2注解开启swagger服务

@Configuration
@EnableSwagger2     //开启swagger服务
public class Swagger2Config {
}

访问测试 :http://localhost:8080/swagger-ui.html(基本功能就能使用了)
在这里插入图片描述
这里我们可以找到处理/login的方法,点击测试
在这里插入图片描述
然后输入参数username和password
在这里插入图片描述
在这里插入图片描述
最后能看到响应内容
在这里插入图片描述
相较于传统的Postman或Curl方式测试API接口(controller方法),使用swagger简直就是傻瓜式操作。不需要额外说明文档(写得好本身就是文档)而且更不容易出错,只需要传入参数的值然后点击Execute。
Swagger是个优秀的工具,现在国内已经有很多的中小型互联网公司都在使用它,相较于传统的要先出Word接口文档再测试的方式,显然这样也更符合现在的快速迭代开发行情。当然了,提醒下大家在正式环境要记得关闭Swagger,一来出于安全考虑二来也可以节省运行时内存

2.功能扩展

Swagger通过注册Docket 类型bean组件,来替换默认的配置

1、配置info消息

可以通过Docket的apiInfo()方法配置文档信息

//配置文档信息
@Configuration
@EnableSwagger2     //开启swagger服务
public class Swagger2Config {

    @Bean
    public Docket docket(){
        Docket docket = new Docket(DocumentationType.SWAGGER_2);
        docket.apiInfo(new ApiInfo(
                "swagger2", //标题
                "API接口文档",  //描述
                "1.0",  //版本
                "urn:tos", //组织链接
                new Contact("LiQingfeng", "", "1164623618@qq.com"), //联系人消息
                "Apache 2.0", //许可
                "http://www.apache.org/licenses/LICENSE-2.0", //许可链接
                new ArrayList())); //扩展

        return docket;
    }
}

重启项目,访问测试 http://localhost:8080/MrL/swagger-ui.html 看下效果(发现红框内的内容发生了改变)
在这里插入图片描述

2、配置只扫描指定controller接口

通过select()方法配置扫描指定的接口包,RequestHandlerSelectors配置如何扫描接口

@Bean
public Docket docket() {
   Docket docket = new Docket(DocumentationType.SWAGGER_2);
     docket.select()
     .apis(RequestHandlerSelectors.basePackage("com.liqingfeng.controller"))
     .build();
      
   return docket;
}

重启项目测试,由于我们配置根据包的路径扫描接口,所以我们只能看到controller包下的controller及方法

3、配置Swagger开关

通过enable()方法配置是否启用swagger。false则swagger服务将不能在浏览器中访问了

@Bean
public Docket docket() {
    Docket docket = new Docket(DocumentationType.SWAGGER_2);
    docket.enable(false) ; //配置是否启用Swagger,如果是false,在浏览器将无法访问
    return docket;
}

再次访问:发现不能访问swagger服务了
在这里插入图片描述

4、配置项目处于test、dev环境时启动swagger服务,处于prod时不显示
@Bean
public Docket docket(Environment environment) {
   // 设置要显示swagger的环境
   Profiles of = Profiles.of("dev", "test");
   // 判断当前是否处于该环境"dev",或则"test"环境,处于返回true
   boolean b = environment.acceptsProfiles(of);
 
   Docket docket = new Docket(DocumentationType.SWAGGER_2);
   docket.enable(b) ; //配置是否启用Swagger,如果是false,在浏览器将无法访问
   return docket;
}
5、配置API分组

默认情况下只有default组
分组一般用与协同开发时,每个人有自己的一个组,可以在自己组中通过上面指定controller包,来查看不同组的API接口信息
在这里插入图片描述
通过groupName()方法以及注册多个Docket bean组件,即可配置多个分组

@Bean
public Docket docket1(){
   return new Docket(DocumentationType.SWAGGER_2).groupName("group1");
}
@Bean
public Docket docket2(){
   return new Docket(DocumentationType.SWAGGER_2).groupName("group2");
}
@Bean
public Docket docket3(){
   return new Docket(DocumentationType.SWAGGER_2).groupName("group3");
}

再次访问
在这里插入图片描述

6、配置实体类

在swagger服务中现models没有显示自己的User实体类
在这里插入图片描述
我们在User类使用一下注解

@ApiModel("用户实体")
public class User {
   @ApiModelProperty("用户名")
   public String username;
   @ApiModelProperty("密码")
   public String password;
}

只要这个User实体类在某个请求方法的返回值上,就能被swagger识别并放在Models项里

	@GetMapping("/user")
    public User user() {
        return new User();
    }
}

重启查看测试
在这里插入图片描述
注:并不是因为@ApiModel这个注解让实体显示在这里了,而是只要出现在接口方法的返回值上的实体都会显示在这里,而@ApiModel和@ApiModelProperty这两个注解只是为实体添加注释的。
Swagger常用注解:

  • @Api(tags = “xxx模块说明”) 作用在模块类上
  • @ApiOperation(“xxx接口说明”) 作用在controller方法上
  • @ApiModel(“xxxPOJO说明”) 作用在模型类上:如VO、BO
  • @ApiModelProperty(value = “xxx属性说明”,hidden = true) 作用在类方法和属性上,hidden设置为true可以隐藏该属性
  • @ApiParam(“xxx参数说明”) 作用在参数、方法和字段上,类似@ApiModelProperty我们也可以给请求的接口配置一些注释
7、配置swagget服务页面的风格

我们可以导入不同的包实现不同的皮肤定义:
1、默认的(访问 http://localhost:8080/MrL/swagger-ui.html

<dependency> 
   <groupId>io.springfox</groupId>
   <artifactId>springfox-swagger-ui</artifactId>
   <version>2.9.2</version>
</dependency>

在这里插入图片描述
2、bootstrap-ui (访问 http://localhost:8080/MrL/doc.html)

<dependency>
   <groupId>com.github.xiaoymin</groupId>
   <artifactId>swagger-bootstrap-ui</artifactId>
   <version>1.9.1</version>
</dependency>

图片
3、mg-ui (访问 http://localhost:8080/MrL/document.html)

<dependency>
   <groupId>com.zyplayer</groupId>
   <artifactId>swagger-mg-ui</artifactId>
   <version>1.0.6</version>
</dependency>

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值