SpringBoot学习笔记

SpringBoot

一、快速入门

1.1 创建项目

1.左上文件==新建==项目==左侧选择Maven Archetype==输入项目名HelloWorld,位置~\IdeaProjects
2.选择maven-archetype-webapp==点开高级设置==鼠标往下拉==组ID输入com.tys和版本号1.0==点击创建
3.对着src文件==右键新建==目录==全选==回车

1.2 引入依赖

//pom.xml 复制进去,刷新Maven

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.tys</groupId>
  <artifactId>HelloWorld</artifactId>
  <version>1.0</version>
  <packaging>jar</packaging>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
  </properties>

  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.3.4.RELEASE</version>
  </parent>

  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-configuration-processor</artifactId>
      <optional>true</optional>
    </dependency>
  </dependencies>

</project>

1.3 编写业务

//对着src/main/java==右键新建==软件包==com.tys.boot.controller
//对着com.tys.boot.controller==右键新建==Java类==HelloController

package com.tys.boot.controller;

import org.springframework.stereotype.*;
import org.springframework.web.bind.annotation.*;

@Controller
@ResponseBody
public class HelloController {
    @RequestMapping("/hello")
    public String handle01(){
        return "Hello, Spring Boot 2!";
    }
}

1.4 创建主程序

//对着src/main/java==右键新建==Java类==com.tys.boot.MainApplication

package com.tys.boot;

import org.springframework.boot.*;
import org.springframework.boot.autoconfigure.*;

@SpringBootApplication
public class MainApplication {

    public static void main(String[] args) {
        SpringApplication.run(MainApplication.class, args);
    }
}

//点击第7行的小箭头,控制台出现多少秒,打开浏览器
//访问:localhost:8080/hello   看见网页出现 Hello, Spring Boot 2! 代表成功

二、底层注解

2.1 @Configuration

1、配置类里面使用@Bean标注在方法上给容器注册组件,默认也是单实例的
2、配置类本身也是组件
3、proxyBeanMethods:代理bean的方法,有两种
    
Full模式(proxyBeanMethods = true)(默认值,每个@Bean方法被调用多少次返回的组件都是单实例的)
Lite模式(proxyBeanMethods = false)(每个@Bean方法被调用多少次返回的组件都是新创建的)

//告诉SpringBoot这是一个配置类 == 配置文件
@Configuration(proxyBeanMethods = false)    
public class MyConfig {
    
    //给容器中添加组件。以方法名作为组件的id。返回类型就是组件类型
    @Bean 
    public User user01(){
        User zhangsan = new User("zhangsan", 18);
        zhangsan.setPet(tomcatPet());          //user组件依赖了Pet组件
        return zhangsan;
    }

    @Bean("tom")
    public Pet tomcatPet(){
        return new Pet("tomcat");
    }
}

2.2 @Import

//给容器中自动创建出这两个类型的组件,默认组件的名字就是全限定名

@Import({User.class, DBHelper.class})
@Configuration(proxyBeanMethods = false) 
public class MyConfig {
}

2.3 @Conditional

//@Conditional是条件装配:满足指定的条件,则进行组件注入

//以@ConditionalOnMissingBean进行举例说明
    
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(name = "tom")//没有tom名字的Bean时,MyConfig类的Bean才能生效。
public class MyConfig {

    @Bean
    public User user01(){
        User zhangsan = new User("zhangsan", 18);
        zhangsan.setPet(tomcatPet());
        return zhangsan;
    }

    @Bean("tom22")
    public Pet tomcatPet(){
        return new Pet("tomcat");
    }
}

2.4 @ImportResource

//beans.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans ...">

    <bean id="haha" class="com.lun.boot.bean.User">
        <property name="name" value="zhangsan"></property>
        <property name="age" value="18"></property>
    </bean>

    <bean id="hehe" class="com.lun.boot.bean.Pet">
        <property name="name" value="tomcat"></property>
    </bean>
</beans>
=======================================================================================   //比如公司使用bean.xml文件生成配置bean,而非注解@bean
//使用@ImportResource导入配置文件

@ImportResource("classpath:beans.xml")
public class MyConfig {
...
}                                         

2.5 @ConfigurationProperties

//配置文件application.properties

mycar.brand=BYD
mycar.price=100000
=======================================================================================   //配置绑定 如何使用Java读取到properties文件中的内容,并且把它封装到JavaBean中
//只有在容器中的组件,才会拥有SpringBoot提供的强大功能,所以需要@Component

@Component
@ConfigurationProperties(prefix = "mycar")
public class Car {
  Car的实体类
}    

三、web开发

3.1 静态资源

# 对着/src/main/resources==右键新建==目录==static   在这放一张图片01.png

# 对着/src/main/resources==右键新建==文件==application.yaml

# /*表示拦截当前目录,/**表示拦截所有文件包括子文件夹里的
# 前两行表示访问路径是以static开头,localhost:8080/static/xx.png
# 后两行表示告诉spring,静态资源在项目的static文件夹下

spring:
  mvc:
    static-path-pattern: /static/**
  resources:
    static-locations: [classpath:/static/]

# 浏览器访问:localhost:8080/static/01.png
=======================================================================================
# 欢迎页

# 在/src/main/resources/static路径下,新建index.html,也可以在controller层指定

spring:
#  mvc:
#    static-path-pattern: /static/**    有了url前缀,会导致欢迎页功能失效
  resources:
    static-locations: [classpath:/static/]
    
#访问:localhost:8080  会自动打开index.html
=======================================================================================
# Favicon图标

# 在/src/main/resources/static路径下,放入favicon.ico 

spring:
#  mvc:
#    static-path-pattern: /static/**    有了url前缀,会导致小图标功能失效
  resources:
    static-locations: [classpath:/static/]
    
#访问:localhost:8080  

3.2 Restful风格

spring:
  mvc:
    hiddenmethod:
      filter:
        enabled: true   #开启网页表单的Restful功能
<form action="/user" method="get">
    <input value="GET提交" type="submit" />
</form>

<form action="/user" method="post">
    <input value="POST提交" type="submit" />
</form>

<form action="/user" method="post">
    <input name="_method" type="hidden" value="DELETE"/>
    <input value="DELETE 提交" type="submit"/>
</form>

<form action="/user" method="post">
    <input name="_method" type="hidden" value="PUT" />
    <input value="PUT提交"type="submit" />
<form>
//springboot出了新注解,其实两种注解方式功能实现一样的

@GetMapping("/user")
//@RequestMapping(value = "/user",method = RequestMethod.GET)
public String getUser(){
    return "GET-张三";
}

@PostMapping("/user")
//@RequestMapping(value = "/user",method = RequestMethod.POST)
public String saveUser(){
    return "POST-张三";
}

@PutMapping("/user")
//@RequestMapping(value = "/user",method = RequestMethod.PUT)
public String putUser(){
    return "PUT-张三";
}

@DeleteMapping("/user")
//@RequestMapping(value = "/user",method = RequestMethod.DELETE)
public String deleteUser(){
    return "DELETE-张三";
}

3.3 获取request域中的值

//此注解@RequestAttribute,获取request域中的值

@Controller
public class RequestController {

    @GetMapping("/goto")
    public String goToPage(HttpServletRequest request){

        request.setAttribute("msg","成功了...");
        request.setAttribute("code",200);
        return "forward:/success";  //转发到success页面
    }

    @ResponseBody
    @GetMapping("/success")
    public Map success(@RequestAttribute(value = "msg",required = false) String msg,
                       @RequestAttribute(value = "code",required = false) Integer code){
        Map<String,Object> map = new HashMap<String,Object>();
        map.put("msg",msg);
        map.put("code",code);
		return map;
    }
}

3.4 自定义类型转换器

//Pet实体类,以前传name="啊猫" age=3  
//现在直接逗号分割,直接传"啊猫,3"

    @Bean
    public WebMvcConfigurer webMvcConfigurer(){return new WebMvcConfigurer() {
            @Override
            public void addFormatters(FormatterRegistry registry) {
                registry.addConverter(new Converter<String, Pet>() {
                    @Override
                    public Pet convert(String source) {
                        if(!StringUtils.isEmpty(source)){
                            Pet pet = new Pet();
                            String[] split = source.split(",");
                            pet.setName(split[0]);
                            pet.setAge(Integer.parseInt(split[1]));
                            return pet;
                        }
                        return null;
                    }
                });
            }
        };
    }

3.5 拦截器

//编写一个拦截器实现登录检查,没有账号登录,直接输入网址,进不了后台管理页面
//对着src/main/java==右键新建==软件包==com.tys.boot.interceptor
//对着com.tys.boot.interceptor==右键新建==Java类==LoginInterceptor

public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String requestURI = request.getRequestURI();
        HttpSession session = request.getSession();
        Object loginUser = session.getAttribute("loginUser");
        if(loginUser != null){
            return true;
        }
        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);
    }
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        log.info("afterCompletion执行异常{}",ex);
    }
}
//拦截器注册到容器中并且指定拦截规则
//对着src/main/java==右键新建==软件包==com.tys.boot.config
//对着com.tys.boot.config==右键新建==Java类==AdminWebConfig

@Configuration
public class AdminWebConfig implements WebMvcConfigurer{
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor())//拦截器注册到容器中
                .addPathPatterns("/**")  //所有请求都被拦截包括静态资源
                .excludePathPatterns("/","/login","/css/**","/fonts/**","/images/**",
                        "/js/**","/aa/**"); //放行的请求

3.6 文件上传

//页面代码/static/form/form_layouts.html

<form role="form" th:action="@{/upload}" method="post" enctype="multipart/form-data">
    <div class="form-group">
        <label for="exampleInputEmail1">邮箱</label>
        <input type="email" name="email" class="form-control" id="exampleInputEmail1" placeholder="Enter email">
    </div>
    
    <div class="form-group">
        <label for="exampleInputPassword1">名字</label>
        <input type="text" name="username" class="form-control" id="exampleInputPassword1" placeholder="Password">
    </div>
    
    <div class="form-group">
        <label for="exampleInputFile">头像</label>
        <input type="file" name="headerImg" id="exampleInputFile">
    </div>
    
    <div class="form-group">
        <label for="exampleInputFile">生活照</label>
        <input type="file" name="photos" multiple>
    </div>
    
    <div class="checkbox">
        <label>
            <input type="checkbox"> Check me out
        </label>
    </div>
    <button type="submit" class="btn btn-primary">提交</button>
</form>
//对着com.tys.boot.controller==右键新建==Java类==FormTestController

@Controller
public class FormTestController {
    
    @GetMapping("/form_layouts")
    public String form_layouts(){
        return "form/form_layouts";
    }
    
    @PostMapping("/upload")
    public String upload(@RequestParam("email") String email,
                         @RequestParam("username") String username,
                         @RequestPart("headerImg") MultipartFile headerImg,
                         @RequestPart("photos") MultipartFile[] photos) throws IOException {
        if(!headerImg.isEmpty()){
            String originalFilename = headerImg.getOriginalFilename();
            headerImg.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";
    }
}
//在application.properties修改文件上传大小,单文件上传最大10MB,全部文件上传最大100MB

spring.servlet.multipart.max-file-size=10MB
spring.servlet.multipart.max-request-size=100MB

3.7 异常处理

1.默认情况下,SpringBoot提供处理所有错误的映射
2.机器客户端,它将生成JSON响应,其中包含错误,HTTP状态和异常消息的详细信息,如下图
3.浏览器客户端,响应一个“whitelabel”错误视图,以HTML格式呈现相同的数据
4.自定义异常处理页:在/templates/error/文件夹下新建404.html 5xx.html即可
5.出现异常,springboot会自动跳转到该页面,404跳转到404.html,5开头的错误跳转到5xx.html

{
  "timestamp": "2020-11-22T05:53:28.416+00:00",
  "status": 404,
  "error": "Not Found",
  "message": "No message available",
  "path": "/asadada"
}

3.8 Servlet相关API

@Configuration(proxyBeanMethods = true)
public class MyRegistConfig {

    @Bean
    //Servlet
    public ServletRegistrationBean myServlet(){
        MyServlet myServlet = new MyServlet();
        return new ServletRegistrationBean(myServlet,"/my");
    }


    @Bean
    //Filter 拦截器
    public FilterRegistrationBean myFilter(){
        MyFilter myFilter = new MyFilter();
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(myFilter);
        filterRegistrationBean.setUrlPatterns(Arrays.asList("/my","/css/*"));
        return filterRegistrationBean;
    }

    @Bean
    //Listener 监听器
    public ServletListenerRegistrationBean myListener(){
        MySwervletContextListener mySwervletContextListener = new MySwervletContextListener();
        return new ServletListenerRegistrationBean(mySwervletContextListener);
    }
}

3.9 矩阵变量

//SpringBoot默认是禁用了矩阵变量的功能
//通过实现WebMvcConfigurer接口的方式,手动开启矩阵变量

@Configuration(proxyBeanMethods = false)
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void configurePathMatch(PathMatchConfigurer configurer) {
        UrlPathHelper urlPathHelper = new UrlPathHelper();
        urlPathHelper.setRemoveSemicolonContent(false);
        configurer.setUrlPathHelper(urlPathHelper);
    }
}
========================================================================================
//编写业务代码,矩阵变量主要用于禁用cookie,实现url重写
    
@RestController
public class ParameterTestController {

    //cars/sell;low=34;brand=byd,audi,yd
    
    @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<String,Object>();
        map.put("low",low);
        map.put("brand",brand);
        map.put("path",path);
        return map;
    }
}  

3.10 开启内容协商

spring:
  mvc:
    contentnegotiation:
      favor-parameter: true  #开启请求参数内容协商模式
http://localhost:8080/test/person?format=json
或
http://localhost:8080/test/person?format=xml
这样,后端会根据参数format的值,返回对应json或xml格式的数据。
实现多协议数据兼容。json、xml、x-guigu(这个是自创的)
1. @ResponseBody响应数据出去 调用 RequestResponseBodyMethodProcessor 处理
2. Processor 处理方法返回值。通过 MessageConverter处理
3. 所有 MessageConverter 合起来可以支持各种媒体类型数据的操作(读、写)
4. 内容协商找到最终的messageConverter

@Configuration(proxyBeanMethods = false)
public class WebConfig {
    @Bean
    public WebMvcConfigurer webMvcConfigurer(){
        return new WebMvcConfigurer() {
            @Override
            public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {          
                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);
                HeaderContentNegotiationStrategy headeStrategy = new HeaderContentNegotiationStrategy();
                configurer.strategies(Arrays.asList(parameterStrategy, headeStrategy));
            }
        }
    }  
}

四、单元测试

4.1 引入依赖

//pom.xml 复制进去,刷新Maven
//SpringBoot2.4以上版本移除了对 Vintage的依赖,如果需要继续兼容Junit4需要自行引入Vintage依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>
<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>

4.2 常用注解

//对着src/test/java==右键新建==软件包==com.tys.boot
//对着com.tys.boot==右键新建==Java类==Junit5Test

package com.tys.boot;

import org.junit.jupiter.api.*;
import java.util.*;
import java.time.*;
import java.util.concurrent.*;
import static org.junit.jupiter.api.Assertions.*;

@DisplayName("junit5功能测试类")
public class Junit5Test {

    @DisplayName("测试displayname注解")  //为测试类或者测试方法起名字
    @Test                               //表示该方法是测试方法
    void testDisplayName() {
        System.out.println(1);
    }

    @Disabled    //禁用测试,单元测试时不执行该方法
    @DisplayName("禁用测试")
    @Test
    void test2() {
        System.out.println(2);
    }

    @RepeatedTest(5)   //表示该测试方法重复执行5次
    @DisplayName("重复执行5次")
    @Test
    void test3() {
        System.out.println(5);
    }

    //表示测试方法运行如果超过了500毫秒,这儿设置了600毫秒,会抛出InterruptedException异常
    @DisplayName("超时测试")
    @Timeout(value = 500, unit = TimeUnit.MILLISECONDS)
    @Test
    void testTimeout() throws InterruptedException {
        Thread.sleep(600);
    }

    @BeforeEach               //@BeforeEach注解:表示在每个单元测试之前执行
    void testBeforeEach() {
        System.out.println("单元测试就要开始了...");
    }

    @AfterEach				  //@AfterEach注解:表示在每个单元测试之后执行
    void testAfterEach() {
        System.out.println("单元测试结束了...");
    }

    @BeforeAll				//@BeforeAll注解:表示在所有单元测试之前执行,需要static
    static void testBeforeAll() {
        System.out.println("所有测试就要开始了...");
    }

    @AfterAll				//@AfterAll注解:表示在所有单元测试之后执行,需要static
    static void testAfterAll() {
        System.out.println("所有测试已经结束了...");
    }
}

//点击第10行,绿色箭头,观察左侧
//绿色√代表单元测试成功,黄色×代表单元测试失败,灰色圆圈一杠代表单元测试跳过该方法,红色感叹号代表抛出异常

4.3 断言机制

//1.断言,第三个位置可以写提示信息
//2.如果中间有断言执行失败了,则后续的代码不会执行
//3.把上一个Junit5Test大括号里的内容清空,复制以下代码测试

@Test
@DisplayName("简单断言")
void simple() {
    
     assertEquals(3, 1 + 2, "计算结果相等"); //判断两个对象是否相等
     assertNotEquals(3, 1 + 1, "计算结果不等");//判断两个对象是否不等	
    
     Object obj1 = new Object();
     Object obj2 = new Object();
     assertSame(obj1, obj2);                 //判断两个对象引用是否指向同一个对象
	 assertNotSame(obj1, obj2);            //判断两个对象引用是否指向不同的对象 
     
     assertFalse(1 > 2);				//判断结果是否为 false
     assertTrue(1 < 2);					//判断结果是否为 true

     assertNull(null);					//判断结果是否为 null
     assertNotNull(new Object());		//判断结果是否不为 null
}

@Test
@DisplayName("数组断言")
void array() {
	assertArrayEquals(new int[]{1, 2}, new int[] {1, 2});
    //判断两个数组是否相等。
}

@Test
@DisplayName("组合断言")
void all() {
 assertAll("Math",
    () -> assertEquals(2, 1 + 1),
    () -> assertTrue(1 > 0)
 );
}

//改成1/0单元测试通过,抛出异常,1/1单元测试会报错
@Test
@DisplayName("异常断言")
void exceptionTest() {
   ArithmeticException exception = Assertions.assertThrows(
     ArithmeticException.class, () -> System.out.println(1 / 1),"居然正常运行了?");
}


//如果测试方法时间超过1000毫秒将会异常,这儿设置了500毫秒,所以测试通过
@Test
@DisplayName("超时断言")
void timeoutTest() {
    Assertions.assertTimeout(Duration.ofMillis(1000), () -> Thread.sleep(500));
}

//通过 fail 方法直接使得测试失败
@Test
@DisplayName("快速失败")
void shouldFail() {
	fail("测试失败");
}

4.4 前置条件

//前置条件可以看成是测试方法的假设,当假设不成立时,就没有继续执行后续代码的必要
//assumeTrue结果给他false,所以假设不成立,所以输出11111111不执行,代码会被跳过

@Test
@DisplayName("前置条件")
public void testassumptions() {
    Assumptions.assumeTrue(false,"结果不是true");
    System.out.println("11111111");
}

4.5 嵌套测试

//@Nested注解实现嵌套测试,从而可以更好的把相关的测试方法组织在一起

package com.tys.boot;

import org.junit.jupiter.api.*;
import java.util.*;
import java.time.*;
import java.util.concurrent.*;
import static org.junit.jupiter.api.Assertions.*;

@DisplayName("嵌套测试")
public class Junit5Test {
    Stack<Object> stack;

    @Test
    @DisplayName("创建一个栈")
    void isInstantiatedWithNew() {
        new Stack<>();
    }

    @Nested
    @DisplayName("当创建一个栈时,此时栈没有元素")
    class WhenNew {
        @BeforeEach
        void createNewStack() {
            stack = new Stack<>();
        }

        @Test
        @DisplayName("判断此时栈是否为空")
        void isEmpty() {
            assertTrue(stack.isEmpty());
        }

        @Test
        @DisplayName("出栈一个元素,此时栈为空,抛出异常")
        void throwsExceptionWhenPopped() {
            assertThrows(EmptyStackException.class, stack::pop);
        }

        @Test
        @DisplayName("查看栈的第一个元素,此时栈为空,抛出异常")
        void throwsExceptionWhenPeeked() {
            assertThrows(EmptyStackException.class, stack::peek);
        }

        @Nested
        @DisplayName("在放入一个元素后")
        class AfterPushing {
            String anElement = "aaa";

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

            @Test
            @DisplayName("此时栈不为空")
            void isNotEmpty() {
                assertFalse(stack.isEmpty());
            }

            @Test
            @DisplayName("在弹出一个元素后栈是否为空")
            void returnElementWhenPopped() {
                assertEquals(anElement, stack.pop());
                assertTrue(stack.isEmpty());
            }
        }
    }
}

4.6 参数化测试

//都需要加上@ParameterizedTest注解,下面五个选一个加上

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

package com.tys.boot;

import org.junit.jupiter.api.*;
import org.junit.jupiter.params.*;
import org.junit.jupiter.params.provider.*;
import java.util.stream.*;

@DisplayName("参数化测试")
public class Junit5Test {

    //参数化测试 常见用法
    @ParameterizedTest
    @ValueSource(strings = {"one", "two", "three"})
    @DisplayName("常见用法")
    void testdemo1(String str) {
        System.out.println(str);
    }

    //参数化测试 指定方法名
    @ParameterizedTest
    @MethodSource("fruits")
    @DisplayName("指定方法名")
    void testdemo2(String name) {
        System.out.println(name);
    }
    static Stream<String> fruits() {
        return Stream.of("apple", "banana");
    }
}

五、数据访问-环境整合

5.1 整合jdbcTemplate

//pom.xml 复制进去,刷新Maven

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.37</version>
</dependency>
//application.yaml

spring:
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/companydb
    username: root
    password: 123456
//对着/src/test/java==右键新建==软件包==com.tys.boot
//对着com.tys.boot==右键新建==Java类==DAOTest

package com.tys.boot;

import org.junit.jupiter.api.*;
import org.springframework.beans.factory.annotation.*;
import org.springframework.boot.test.context.*;
import org.springframework.jdbc.core.*;

@SpringBootTest
public class DAOTest {

    @Autowired
    JdbcTemplate jdbcTemplate;

    @Test
    void contextLoads() {
        Long aLong = jdbcTemplate.queryForObject("select count(*) from t_jobs", Long.class);
        System.out.println("记录总数:"+aLong);
    }
}

5.2 整合MyBatis

USE companydb;
CREATE TABLE account_tb (
  `id` INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
  `user_id` VARCHAR(255),
  `money` INT(11)
)CHARSET=utf8;

INSERT INTO account_tb(user_id,money) VALUES ('1001',5000);
INSERT INTO account_tb(user_id,money) VALUES ('1002',8000);
 
SELECT * FROM account_tb;

DROP TABLE account_tb;
//pom.xml 复制进去,刷新Maven

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.37</version>
</dependency>
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.1.4</version>
</dependency>
//在/src/main/resources==右键新建==文件==application.yaml

spring:
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/companydb
    username: root
    password: 123456
    
mybatis:
  config-location: classpath:mybatis/sqlMapConfig.xml  
  mapper-locations: classpath:mybatis/mapper/*.xml  
//对着src/main/resources==右键新建==目录==mybatis
//对着mybatis==右键新建==文件==sqlMapConfig.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">

<configuration>

    <settings>
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>
    
</configuration>
//对着mybatis==右键新建==目录==mapper
//对着mapper==右键新建==文件==AccountMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.tys.boot.dao.AccountMapper">

    <select id="getAccount" resultType="com.tys.boot.pojo.Account" parameterType="java.lang.Integer" >
        select * from account_tb where id=#{id}
    </select>
</mapper>
//对着src/main/java==右键新建==软件包==com.tys.boot.pojo
//对着com.tys.boot.pojo==右键新建==Java类==Account

package com.tys.boot.pojo;

public class Account {
    private Integer id;
    private String userId;
    private Integer money;

    public Account() {
    }

    public Account(Integer id, String userId, Integer money) {
        this.id = id;
        this.userId = userId;
        this.money = money;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getUserId() {
        return userId;
    }

    public void setUserId(String userId) {
        this.userId = userId;
    }

    public Integer getMoney() {
        return money;
    }

    public void setMoney(Integer money) {
        this.money = money;
    }

    @Override
    public String toString() {
        return "Account{" +
                "id=" + id +
                ", userId='" + userId + '\'' +
                ", money=" + money +
                '}';
    }
}
//对着src/main/java==右键新建==软件包==com.tys.boot.dao
//对着com.tys.boot.dao==右键新建==Java类==AccountMapper

package com.tys.boot.dao;

import com.tys.boot.pojo.*;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface AccountMapper {
    Account getAccount(Integer id);
}
//对着src/main/java==右键新建==软件包==com.tys.boot.service
//对着com.tys.boot.service==右键新建==Java类==AccountService

package com.tys.boot.service;

import com.tys.boot.dao.*;
import com.tys.boot.pojo.*;
import org.springframework.beans.factory.annotation.*;
import org.springframework.stereotype.*;
@Service
public class AccountService {

    @Autowired
    private AccountMapper accountMapper;

    public Account getAccountByid(Integer id){
        return accountMapper.getAccount(id);
    }
}
//对着src/main/java==右键新建==软件包==com.tys.boot.controller
//对着com.tys.boot.controller==右键新建==Java类==AccountController

package com.tys.boot.controller;

import com.tys.boot.pojo.*;
import com.tys.boot.service.*;
import org.springframework.beans.factory.annotation.*;
import org.springframework.stereotype.*;
import org.springframework.web.bind.annotation.*;

@Controller
public class AccountController {
    @Autowired
    private AccountService accountService;

    @ResponseBody
    @GetMapping("/account/{id}")
    public Account getById(@PathVariable("id") Integer id){
        return accountService.getAccountByid(id);
    }
}

//运行主程序 访问 localhost:8080/account/1  看见json格式就对了

5.3 整合Redis

添加依赖

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

<!--导入jedis-->
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
</dependency>
  • RedisAutoConfiguration自动配置类,RedisProperties 属性类 --> spring.redis.xxx是对redis的配置。
  • 连接工厂LettuceConnectionConfigurationJedisConnectionConfiguration是准备好的。
  • 自动注入了RedisTemplate<Object, Object>xxxTemplate
  • 自动注入了StringRedisTemplate,key,value都是String
  • 底层只要我们使用StringRedisTemplateRedisTemplate就可以操作Redis。

外网Redis环境搭建

  1. 阿里云按量付费Redis,其中选择经典网络
  2. 申请Redis的公网连接地址。
  3. 修改白名单,允许0.0.0.0/0访问。

数据访问-Redis操作与统计小实验

相关Redis配置:

spring:
  redis:
#   url: redis://lfy:Lfy123456@r-bp1nc7reqesxisgxpipd.redis.rds.aliyuncs.com:6379
    host: r-bp1nc7reqesxisgxpipd.redis.rds.aliyuncs.com
    port: 6379
    password: lfy:Lfy123456
    client-type: jedis
    jedis:
      pool:
        max-active: 10
#   lettuce:# 另一个用来连接redis的java框架
#      pool:
#        max-active: 10
#        min-idle: 5

测试Redis连接:

@SpringBootTest
public class Boot05WebAdminApplicationTests {

    @Autowired
    StringRedisTemplate redisTemplate;


    @Autowired
    RedisConnectionFactory redisConnectionFactory;

    @Test
    void testRedis(){
        ValueOperations<String, String> operations = redisTemplate.opsForValue();

        operations.set("hello","world");

        String hello = operations.get("hello");
        System.out.println(hello);

        System.out.println(redisConnectionFactory.getClass());
    }

}

Redis Desktop Manager:可视化Redis管理软件。

URL统计拦截器:

@Component
public class RedisUrlCountInterceptor implements HandlerInterceptor {

    @Autowired
    StringRedisTemplate redisTemplate;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String uri = request.getRequestURI();

        //默认每次访问当前uri就会计数+1
        redisTemplate.opsForValue().increment(uri);

        return true;
    }
}

注册URL统计拦截器:

@Configuration
public class AdminWebConfig implements WebMvcConfigurer{

    @Autowired
    RedisUrlCountInterceptor redisUrlCountInterceptor;


    @Override
    public void addInterceptors(InterceptorRegistry registry) {

        registry.addInterceptor(redisUrlCountInterceptor)
                .addPathPatterns("/**")
                .excludePathPatterns("/","/login","/css/**","/fonts/**","/images/**",
                        "/js/**","/aa/**");
    }
}

Filter、Interceptor 几乎拥有相同的功能?

  • Filter是Servlet定义的原生组件,它的好处是脱离Spring应用也能使用。
  • Interceptor是Spring定义的接口,可以使用Spring的自动装配等功能。

调用Redis内的统计数据:

@Slf4j
@Controller
public class IndexController {

	@Autowired
    StringRedisTemplate redisTemplate;
    
	@GetMapping("/main.html")
    public String mainPage(HttpSession session,Model model){

        log.info("当前方法是:{}","mainPage");

        ValueOperations<String, String> opsForValue =
                redisTemplate.opsForValue();

        String s = opsForValue.get("/main.html");
        String s1 = opsForValue.get("/sql");

        model.addAttribute("mainCount",s);
        model.addAttribute("sqlCount",s1);

        return "main";
    }
}

六、Thymeleaf

6.1 快速入门

//pom.xml 复制进去,刷新Maven

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
//对着com.tys.boot.controller==右键新建==Java类==ViewTestController
//model和Map中的数据会被放在请求域中 request.setAttribute("a",aa)

package com.tys.boot.controller;

import org.springframework.stereotype.*;
import org.springframework.ui.*;
import org.springframework.web.bind.annotation.*;

@Controller
public class ViewTestController {
    @GetMapping("/hello")
    public String hello(Model model){
        model.addAttribute("msg","一定要大力发展工业文化");
        model.addAttribute("link","http://www.baidu.com");
        return "success";
    }
}
//对着src/main/resources==右键新建==目录==templates
//对着templates==右键新建==HTML文件==success

<!DOCTYPE html>
<html lang="zh-CN" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>测试Thymeleaf</title>
</head>
<body>
<h1 th:text="${msg}">nice</h1>
<h2>
    <a href="#" th:href="${link}">去百度$</a>  <br/>
    <a href="#" th:href="@{link}">去百度@</a>
</h2>
</body>
</html>

//启动Tomcat,访问 localhost:8080/hello

6.2 基本语法

1.文本值: 'one text' 
2.数字: 0 34 3.0 
3.布尔值: true false
4.空值: null
5.变量: one two
6.字符串拼接: +
7.变量替换: |The name is ${name}|
8.数学运算符: + -  / %
9.布尔运算符:  and or ! not
10.比较运算符: > < >= <= gt lt ge le == != eq ne
11.条件运算:
If-then: (if) ? (then)
If-then-else: (if) ? (then) : (else)
Default: (value) ?: (defaultvalue)
12.无操作: _
13.设置属性值-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}" />    

迭代

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


条件运算

<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>
       
表达式名字语法用途
变量取值${…}获取请求域、session域、对象等值
选择变量*{…}获取上下文对象值
消息#{…}获取国际化等值
链接@{…}生成链接
片段表达式~{…}jsp:include 作用,引入公共页面片段

七、yaml

7.1 基本语法

1.key: value 冒号后要加空格
2.大小写敏感
3.使用缩进表示层级关系
4.缩进不允许使用tab,只允许空格,但idea会自动把tab转换为空格
5.缩进的空格数不重要,只要相同层级的元素左对齐即可
6.#表示注释
7.字符串无需加引号,如果要加,则单引号表示字符串会被转义,双引号表示不转义

7.2 数据类型

# 单个的不可再分的值。String Boolean Date Integer
k: v
========================================================================================
# 对象:键值对的集合。  Map 例如User对象

#行内写法:  

k: {k1: v1,k2: v2,k3: v3}

#或

k: 
  k1: v1
  k2: v2
  k3: v3
=======================================================================================
# 数组:一组按次序排列的值。 array list Set

#行内写法:  

k: [v1,v2,v3]

#或者

k:
 - v1
 - v2
 - v3

7.3 主程序与controller层

//对着src/main/java==右键新建==软件包==com.tys.boot.controller
//对着com.tys.boot.controller==右键新建==Java类==TestController
    
package com.tys.boot.controller;

import com.tys.boot.pojo.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.*;
import org.springframework.web.bind.annotation.*;

@Controller
@ResponseBody
public class TestController {
    @Autowired
    Person person;

    @RequestMapping("/person")
    public Person person(){
        return person;
    }
}
=======================================================================================
//对着src/main/java==右键新建==Java类==MainApplication

package com.tys.boot;

import org.springframework.boot.*;
import org.springframework.boot.autoconfigure.*;

@SpringBootApplication
public class MainApplication {

    public static void main(String[] args) {
        SpringApplication.run(MainApplication.class, args);
    }
}

7.4 实体类

//对着src/main/java==右键新建==软件包==com.tys.boot.pojo
//对着com.tys.boot.pojo==右键新建==Java类==Person
//添加无参有参,get/set方法,tostring方法   

package com.tys.boot.pojo;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.*;
import java.util.*;
@Component
@ConfigurationProperties(prefix = "person")
public class Person {
    private String userName;
    private Boolean boss;
    private Date birth;
    private Integer age;
    private String[] interests;
    private List<String> animal;
    private Set<Double> salary;
    private Map<String, Object> score;
    private Pet pet;
    private Map<String, List<Pet>> allPets;
}
=========================================================================================
//对着com.tys.boot.pojo==右键新建==Java类==Pet
//添加无参有参,get/set方法,tostring方法    
    
package com.tys.boot.pojo;

public class Pet {
    private String name;
    private Double weight;
}     

7.5 举例

//用yaml表示以上对象,/src/main/resources==右键新建==文件==application.yaml
    
person:
  userName: zhangsan
  boss: false
  birth: 2019/12/12 20:12:33
  age: 18
  interests: [篮球,游泳]
  animal: [jerry,mario]
  salary: [3000.0,4000.0]
  score: {english: 80,math: 90}
  pet:
    name: tomcat
    weight: 30.0
  allPets:
    slick:
      - {name: 阿毛,weight: 30}
      - {name: 阿狗,weight: 40}
    health:
      - {name: 阿花,weight: 50}
=======================================================================================
//MainApplication,运行主程序,点击第7行的小箭头,控制台出现多少秒,打开浏览器
//访问:localhost:8080/person   看见json字符串 代表成功  

八、指标监控

8.1 快速入门

//pom.xml 复制进去,刷新Maven

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
//在/src/main/resources==右键新建==文件==application.yaml

management:
  endpoints:
    enabled-by-default: true #开启所有监控端点信息
    web:
      exposure:
        include: '*'  #以web方式暴露所有监控端点
//运行主程序,浏览器访问以下url

http://localhost:8080/actuator/beans    
http://localhost:8080/actuator/configprops
http://localhost:8080/actuator/metrics
http://localhost:8080/actuator/metrics/jvm.gc.pause
http://localhost:8080/actuator/metrics/endpointName/detailPath

8.2 常使用的端点

ID描述
auditevents暴露当前应用程序的审核事件信息。需要一个AuditEventRepository组件
beans显示应用程序中所有Spring Bean的完整列表。
caches暴露可用的缓存。
conditions显示自动配置的所有条件信息,包括匹配或不匹配的原因。
configprops显示所有@ConfigurationProperties
env暴露Spring的属性ConfigurableEnvironment
flyway显示已应用的所有Flyway数据库迁移。 需要一个或多个Flyway组件。
health显示应用程序运行状况信息。
httptrace显示HTTP跟踪信息(默认情况下,最近100个HTTP请求-响应)。需要一个HttpTraceRepository组件。
info显示应用程序信息。
integrationgraph显示Spring integrationgraph 。需要依赖spring-integration-core
loggers显示和修改应用程序中日志的配置。
liquibase显示已应用的所有Liquibase数据库迁移。需要一个或多个Liquibase组件。
metrics显示当前应用程序的“指标”信息。
mappings显示所有@RequestMapping路径列表。
scheduledtasks显示应用程序中的计划任务。
sessions允许从Spring Session支持的会话存储中检索和删除用户会话。需要使用Spring Session的基于Servlet的Web应用程序。
shutdown使应用程序正常关闭。默认禁用。
startup显示由ApplicationStartup收集的启动步骤数据。需要使用SpringApplication进行配置BufferingApplicationStartup
threaddump执行线程转储。

如果您的应用程序是Web应用程序(Spring MVC,Spring WebFlux或Jersey),则可以使用以下附加端点:

ID描述
heapdump返回hprof堆转储文件。
jolokia通过HTTP暴露JMX bean(需要引入Jolokia,不适用于WebFlux)。需要引入依赖jolokia-core
logfile返回日志文件的内容(如果已设置logging.file.namelogging.file.path属性)。支持使用HTTPRange标头来检索部分日志文件的内容。
prometheus以Prometheus服务器可以抓取的格式公开指标。需要依赖micrometer-registry-prometheus

其中最常用的Endpoint:

  • Health:监控状况
  • Metrics:运行时指标
  • Loggers:日志记录

Health Endpoint

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

重要的几点:

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

Metrics Endpoint

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

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

8.3 开启与禁用Endpoints

  • 默认所有的Endpoint除过shutdown都是开启的。
  • 需要开启或者禁用某个Endpoint。配置模式为management.endpoint.<endpointName>.enabled = true
management:
  endpoint:
    beans:
      enabled: true
  • 或者禁用所有的Endpoint然后手动开启指定的Endpoint。
management:
  endpoints:
    enabled-by-default: false
  endpoint:
    beans:
      enabled: true
    health:
      enabled: true

8.4 暴露Endpoints

支持的暴露方式

  • HTTP:默认只暴露health和info。
  • JMX:默认暴露所有Endpoint。
  • 除过health和info,剩下的Endpoint都应该进行保护访问。如果引入Spring Security,则会默认配置安全访问规则。
IDJMXWeb
auditeventsYesNo
beansYesNo
cachesYesNo
conditionsYesNo
configpropsYesNo
envYesNo
flywayYesNo
healthYesYes
heapdumpN/ANo
httptraceYesNo
infoYesYes
integrationgraphYesNo
jolokiaN/ANo
logfileN/ANo
loggersYesNo
liquibaseYesNo
metricsYesNo
mappingsYesNo
prometheusN/ANo
scheduledtasksYesNo
sessionsYesNo
shutdownYesNo
startupYesNo
threaddumpYesNo

若要更改公开的Endpoint,请配置以下的包含和排除属性:

PropertyDefault
management.endpoints.jmx.exposure.exclude
management.endpoints.jmx.exposure.include*
management.endpoints.web.exposure.exclude
management.endpoints.web.exposure.includeinfo, health

官方文档 - Exposing Endpoints

8.5 指标监控-定制Endpoint

定制 Health 信息

management:
    health:
      enabled: true
      show-details: always #总是显示详细信息。可显示每个模块的状态信息

通过实现HealthIndicator接口,或继承MyComHealthIndicator类。

import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.stereotype.Component;

@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();
    }

}

/*
构建Health
Health build = Health.down()
                .withDetail("msg", "error service")
                .withDetail("code", "500")
                .withException(new RuntimeException())
                .build();
*/
@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);

    }
}

定制info信息

常用两种方式:

  • 编写配置文件
info:
  appName: boot-admin
  version: 2.0.1
  mavenProjectName: @project.artifactId@  #使用@@可以获取maven的pom文件值
  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信息

定制Metrics信息

Spring Boot支持的metrics

增加定制Metrics:

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

定制Endpoint

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

}

场景:

  • 开发ReadinessEndpoint来管理程序是否就绪。
  • 开发LivenessEndpoint来管理程序是否存活。

8.6 可视化指标监控

官方Github

官方文档

可视化指标监控

What is Spring Boot Admin?

codecentric’s Spring Boot Admin is a community project to manage and monitor your Spring Boot ® applications. The applications register with our Spring Boot Admin Client (via HTTP) or are discovered using Spring Cloud ® (e.g. Eureka, Consul). The UI is just a Vue.js application on top of the Spring Boot Actuator endpoints.

开始使用方法

九、高级特性

9.1 Profile环境切换

为了方便多环境适配,Spring Boot简化了profile功能。

  • 默认配置文件application.yaml任何时候都会加载。
  • 指定环境配置文件application-{env}.yamlenv通常替代为test
  • 激活指定环境
    • 配置文件激活:spring.profiles.active=prod
    • 命令行激活:java -jar xxx.jar --spring.profiles.active=prod --person.name=haha(修改配置文件的任意值,命令行优先
  • 默认配置与环境配置同时生效
  • 同名配置项,profile配置优先

@Profile条件装配功能

@Data
@Component
@ConfigurationProperties("person")//在配置文件中配置
public class Person{
    private String name;
    private Integer age;
}

application.properties

person: 
  name: lun
  age: 8

public interface Person {

   String getName();
   Integer getAge();

}

@Profile("test")//加载application-test.yaml里的
@Component
@ConfigurationProperties("person")
@Data
public class Worker implements Person {

    private String name;
    private Integer age;
}

@Profile(value = {"prod","default"})//加载application-prod.yaml里的
@Component
@ConfigurationProperties("person")
@Data
public class Boss implements Person {

    private String name;
    private Integer age;
}

application-test.yaml

person:
  name: test-张三

server:
  port: 7000

application-prod.yaml

person:
  name: prod-张三

server:
  port: 8000

application.properties

# 激活prod配置文件
spring.profiles.active=prod
@Autowired
private Person person;

@GetMapping("/")
public String hello(){
    //激活了prod,则返回Boss;激活了test,则返回Worker
    return person.getClass().toString();
}

@Profile还可以修饰在方法上:

class Color {
}

@Configuration
public class MyConfig {

    @Profile("prod")
    @Bean
    public Color red(){
        return new Color();
    }

    @Profile("test")
    @Bean
    public Color green(){
        return new Color();
    }
}

可以激活一组:

spring.profiles.active=production

spring.profiles.group.production[0]=proddb
spring.profiles.group.production[1]=prodmq

9.2 配置加载优先级

外部化配置

官方文档 - Externalized Configuration

Spring Boot uses a very particular PropertySource order that is designed to allow sensible overriding of values. Properties are considered in the following order (with values from lower items overriding earlier ones)(1优先级最低,14优先级最高):

  1. Default properties (specified by setting SpringApplication.setDefaultProperties).
  2. @PropertySource annotations on your @Configuration classes. Please note that such property sources are not added to the Environment until the application context is being refreshed. This is too late to configure certain properties such as logging.* and spring.main.* which are read before refresh begins.
  3. Config data (such as application.properties files)
  4. A RandomValuePropertySource that has properties only in random.*.
  5. OS environment variables.
  6. Java System properties (System.getProperties()).
  7. JNDI attributes from java:comp/env.
  8. ServletContext init parameters.
  9. ServletConfig init parameters.
  10. Properties from SPRING_APPLICATION_JSON (inline JSON embedded in an environment variable or system property).
  11. Command line arguments.
  12. properties attribute on your tests. Available on @SpringBootTest and the test annotations for testing a particular slice of your application.
  13. @TestPropertySource annotations on your tests.
  14. Devtools global settings properties in the $HOME/.config/spring-boot directory when devtools is active.
import org.springframework.stereotype.*;
import org.springframework.beans.factory.annotation.*;

@Component
public class MyBean {

    @Value("${name}")//以这种方式可以获得配置值
    private String name;

    // ...

}

  • 外部配置源
    • Java属性文件。
    • YAML文件。
    • 环境变量。
    • 命令行参数。
  • 配置文件查找位置
    1. classpath 根路径。
    2. classpath 根路径下config目录。
    3. jar包当前目录。
    4. jar包当前目录的config目录。
    5. /config子目录的直接子目录。
  • 配置文件加载顺序:
    1. 当前jar包内部的application.propertiesapplication.yml
    2. 当前jar包内部的application-{profile}.propertiesapplication-{profile}.yml
    3. 引用的外部jar包的application.propertiesapplication.yml
    4. 引用的外部jar包的application-{profile}.propertiesapplication-{profile}.yml
  • 指定环境优先,外部优先,后面的可以覆盖前面的同名配置项。

9.3 自定义starter细节

starter启动原理

  • starter的pom.xml引入autoconfigure依赖
starter
autoconfigure
spring-boot-starter
  • autoconfigure包中配置使用META-INF/spring.factoriesEnableAutoConfiguration的值,使得项目启动加载指定的自动配置类

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

    • @Configuration
    • @Conditional
    • @EnableConfigurationProperties
    • @Bean
  • 引入starter — xxxAutoConfiguration — 容器中放入组件 ---- 绑定xxxProperties ---- 配置项

自定义starter

  • 目标:创建HelloService的自定义starter。

  • 创建两个工程,分别命名为hello-spring-boot-starter(普通Maven工程),hello-spring-boot-starter-autoconfigure(需用用到Spring Initializr创建的Maven工程)。

  • hello-spring-boot-starter无需编写什么代码,只需让该工程引入hello-spring-boot-starter-autoconfigure依赖:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.lun</groupId>
    <artifactId>hello-spring-boot-starter</artifactId>
    <version>1.0.0-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>com.lun</groupId>
            <artifactId>hello-spring-boot-starter-autoconfigure</artifactId>
            <version>1.0.0-SNAPSHOT</version>
        </dependency>
    </dependencies>

</project>
  • hello-spring-boot-starter-autoconfigure的pom.xml如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.4.2</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.lun</groupId>
	<artifactId>hello-spring-boot-starter-autoconfigure</artifactId>
	<version>1.0.0-SNAPSHOT</version>
	<name>hello-spring-boot-starter-autoconfigure</name>
	<description>Demo project for Spring Boot</description>
	<properties>
		<java.version>1.8</java.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter</artifactId>
		</dependency>
	</dependencies>
</project>
  • 创建4个文件:
    • com/lun/hello/auto/HelloServiceAutoConfiguration
    • com/lun/hello/bean/HelloProperties
    • com/lun/hello/service/HelloService
    • src/main/resources/META-INF/spring.factories
import com.lun.hello.bean.HelloProperties;
import com.lun.hello.service.HelloService;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@ConditionalOnMissingBean(HelloService.class)
@EnableConfigurationProperties(HelloProperties.class)//默认HelloProperties放在容器中
public class HelloServiceAutoConfiguration {

    @Bean
    public HelloService helloService(){
        return new HelloService();
    }

}
import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties("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;
    }
}

import com.lun.hello.bean.HelloProperties;
import org.springframework.beans.factory.annotation.Autowired;


/**
 * 默认不要放在容器中
 */
public class HelloService {

    @Autowired
    private HelloProperties helloProperties;

    public String sayHello(String userName){
        return helloProperties.getPrefix() + ": " + userName + " > " + helloProperties.getSuffix();
    }
}
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.lun.hello.auto.HelloServiceAutoConfiguration
  • 用maven插件,将两工程install到本地。

  • 接下来,测试使用自定义starter,用Spring Initializr创建名为hello-spring-boot-starter-test工程,引入hello-spring-boot-starter依赖,其pom.xml如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.4.2</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.lun</groupId>
    <artifactId>hello-spring-boot-starter-test</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <name>hello-spring-boot-starter-test</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

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

        <!-- 引入`hello-spring-boot-starter`依赖 -->
        <dependency>
            <groupId>com.lun</groupId>
            <artifactId>hello-spring-boot-starter</artifactId>
            <version>1.0.0-SNAPSHOT</version>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

  • 添加配置文件application.properties
hello.prefix=hello
hello.suffix=666
  • 添加单元测试类:
import com.lun.hello.service.HelloService;//来自自定义starter
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class HelloSpringBootStarterTestApplicationTests {

    @Autowired
    private HelloService helloService;

    @Test
    void contextLoads() {
        // System.out.println(helloService.sayHello("lun"));
        Assertions.assertEquals("hello: lun > 666", helloService.sayHello("lun"));
    }

}

版权声明:本文为weixin_48778425原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。