Spring 生态笔记 -- Spring Boot

一、基础入门

SpringBoot 是整合 Spring 技术栈的一站式框架

SpringBoot 是简化 Spring 技术栈的快速开发脚手架

  • 核心特性
  1. Create stand-alone Spring applications
  2. Embed Tomcat, Jetty or Undertow directly (no need to deploy WAR files)
  3. Provide opinionated ‘starter’ dependencies to simplify your build configuration
  4. Automatically configure Spring and 3rd party libraries whenever possible
  5. Provide production-ready features such as metrics, health checks, and externalized configuration
  6. Absolutely no code generation and no requirement for XML configuration

1.1 时代背景

  • 微服务
  1. 微服务是一种架构风格
  2. 一个应用拆分为一组小型服务
  3. 每个服务运行在自己的进程内,也就是可独立部署和升级
  4. 服务之间使用轻量级 HTTP 交互
  5. 服务围绕业务功能拆分
  6. 可以由全自动部署机制独立部署
  7. 去中心化,服务自治,服务可以使用不同的语言、不同的存储技术
  • 分布式
  1. 挑战:远程调用、服务发现、负载均衡、服务容错、配置管理、服务监控、链路追踪、日志管理、任务调度;
  2. 解决:SpringBoot + SpringCloud。
  • 云原生
  1. 挑战:服务自愈、弹性伸缩、服务隔离、自动化部署、灰度发布、流量治理;
  2. 解决:k8s、DevOps、Service Mesh/Serverless

1.2 开发环境准备

  • JDK 1.8+

在这里插入图片描述

  • idea 2019.1 +
    在这里插入图片描述

  • Maven 3.3+

在这里插入图片描述

这里就简单配置了 Maven 的国内阿里镜像源:

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

<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">

  <pluginGroups>
  </pluginGroups>
  <proxies>
  </proxies>
  <servers>
  </servers>

  <mirrors>
    <mirror>
      <id>nexus-aliyun</id>
      <mirrorOf>central</mirrorOf>
      <name>Nexus aliyun</name>
      <url>http://maven.aliyun.com/nexus/content/groups/public</url>
    </mirror>
  </mirrors>

  <profiles>
  </profiles>
</settings>

1.3 Hello World

需求:浏览器发送 /hello 请求,响应 Hello, SpringBoot 2

参考文档:https://docs.spring.io/spring-boot/docs/current/reference/html/getting-started.html#getting-started-first-application

  1. 创建项目并配置依赖
<?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.simwor</groupId>
    <artifactId>01-helloworld</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>15</maven.compiler.source>
        <maven.compiler.target>15</maven.compiler.target>
    </properties>

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

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>
</project>
  1. MainApplication.java
package com.simwor.boot;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class MainApplication {
    public static void main(String[] args) {
        SpringApplication.run(MainApplication.class, args);
    }
}
  1. HelloController.java
package com.simwor.boot.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {

    @RequestMapping("/hello")
    public String handleHello() {
        return "Hello, Spring Boot 2";
    }
}
  1. 运行并验证

在这里插入图片描述

  1. 简化配置

完整配置项参考:https://docs.spring.io/spring-boot/docs/current/reference/html/appendix-application-properties.html#common-application-properties

<!-- src/main/resources/application.properties -->
server.port=8888
  1. 简化部署
<!-- pom.xml -->
<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

在这里插入图片描述

在这里插入图片描述

  • 修改默认依赖包版本
<properties>
	<!-- 2.覆盖默认的版本(8.0.22) -->
	<mysql.version>6.0.4</mysql.version>
</properties>

<dependencies>
	<!-- 1.引入依赖 -->
	<dependency>
		<groupId>mysql</groupId>
		<artifactId>mysql-connector-java</artifactId>
	</dependency>
</dependencies>

1.4 底层注解

  • @Configuration - 注册组件

另:@Bean @Component @Controller @Service @Repository @ComponentScan 也可注册组件

package com.simwor.boot.config;

import com.simwor.boot.bean.Person;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

//1.告诉SpringBoot这是一个配置类 == 配置文件
//2.配置类本身也是组件
//3.proxyBeanMethods = true(Full模式) :默认,容器每次返回同一个实例对象
//  proxyBeanMethods = false(Lite模式):容器每次返回一个新的实例对象
@Configuration(proxyBeanMethods = true)
public class MyConfig {

    //给容器中添加组件
    @Bean
    public Person person() {
        return new Person("Tom",18);
    }
}
package com.simwor.boot;

import com.simwor.boot.bean.Person;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;

@SpringBootApplication
public class MainApplication {
    public static void main(String[] args) {
        //1. 返回IOC容器
        ConfigurableApplicationContext context = SpringApplication.run(MainApplication.class, args);
        //2. 获取容器中的所有组件
        String[] names = context.getBeanDefinitionNames();
        //3. 从容器中获取组件
        Person p = context.getBean("person", Person.class);
    }
}
  • @Import - 注册组件
package com.simwor.boot;

import com.simwor.boot.bean.Person;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Import;

//此方式注册的组件名为全类名
@Import({Person.class})
@SpringBootApplication
public class MainApplication {
    public static void main(String[] args) {
        //1. 返回IOC容器
        ConfigurableApplicationContext context = SpringApplication.run(MainApplication.class, args);
        //2. 获取组件
        String[] personBeanNames = context.getBeanNamesForType(Person.class);
        for(String personBeanName : personBeanNames)
            System.out.println(personBeanName);
        //person
        //com.simwor.boot.bean.Person
    }
}
  • @Conditional - 条件装配

条件装配:满足指定的条件,则进行组件注入。

package com.simwor.boot.config;

import com.simwor.boot.bean.Person;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MyConfig {

    //当容器中存在‘money’组件时,才注册‘person’组件
    @ConditionalOnBean(name = "money")
    @Bean
    public Person person() {
        return new Person("Tom",18);
    }
}
  • @ConfigurationProperties - 配置绑定
package com.simwor.boot.bean;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

//配合@Component使用,因为只有在容器中的组件,才拥有SpringBoot提供的众多功能
@Component
@ConfigurationProperties(prefix = "mycar")
public class Car {
    private String brand;
    private float price;

    public String getBrand() {
        return brand;
    }

    public void setBrand(String brand) {
        this.brand = brand;
    }

    public float getPrice() {
        return price;
    }

    public void setPrice(float price) {
        this.price = price;
    }

    @Override
    public String toString() {
        return "Car{" +
                "brand='" + brand + '\'' +
                ", price=" + price +
                '}';
    }
}
<!-- resources/appication.properties -->
mycar.brand=tesla
mycar.price=330000
package com.simwor.boot.controller;

import com.simwor.boot.bean.Car;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class CarController {

    @Autowired
    private Car car;

    @RequestMapping("/car")
    public String getCar() {
        return car.toString();
    }
    //浏览器输出:Car{brand='tesla', price=330000.0}
}
  • @EnableConfigurationProperties - 配置绑定

如果想让第三方非组件类注册容器中时,就没办法直接在类上标注 @Component 了。

package com.simwor.boot.config;

import com.simwor.boot.bean.Car;
import com.simwor.boot.bean.Person;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
//1.开启Car的配置绑定功能
//2.将Car自动注册到容器中
@EnableConfigurationProperties(Car.class)
public class MyConfig {

    @Bean
    public Person person() {
        return new Person("Tom",18);
    }
}

1.5 最佳实践

  • Lombok
package com.simwor.boot.bean;

import lombok.*;
import lombok.extern.slf4j.Slf4j;

//getter & setter & toString
@Data
@ToString
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode
//自动注入一个log变量
@Slf4j
public class Car {
    private String brand;
    private float price;
}
  • Developer Tools

应用发生变化时,按 Ctrl+F9(Build Project) 自动重启应用。

参考文档:https://docs.spring.io/spring-boot/docs/current/reference/html/using-spring-boot.html#using-boot-devtools

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <optional>true</optional>
</dependency>

二、核心功能

2.1 配置文件

YAML:YAML Ain’t Markup Language 的递归缩写,该语言非常适合来做以数据为中心的配置文件。

  • 基本语法
  1. 空格表示缩进,不允许使用 tab
  2. ‘#’ 表示注释
  3. 字符串不需要加引号,若加则 ‘’ 和 “” 分别表示内容 转义/不转义(与shell相反)
  • 数据类型
  1. 字面量:key: value
  2. 对象:
k: {k1:v1, k2:v2, k3:v3}
k:
  k1: v1
  k2: v2
  k3: v3
  1. 数组:
k: [v1,v2,v3]
k:
  - v1
  - v2
  - v3
  • 最佳示例
package com.atguigu.boot.bean;

import lombok.Data;
import lombok.ToString;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;

@ConfigurationProperties(prefix = "person")
@Component
@ToString
@Data
public class Person {
    private String userName;
    private Boolean boss;
    private Date birth;
    private Integer age;
    private Pet pet;
    private String[] interests;
    private List<String> animal;
    private Map<String, Object> score;
    private Set<Double> salarys;
    private Map<String, List<Pet>> allPets;
}
package com.atguigu.boot.bean;

import lombok.Data;
import lombok.ToString;

@ToString
@Data
public class Pet {
    private String name;
    private Double weight;
}
# application.yaml
person:
  user-name: zhangsan
  boss: true
  birth: 2019/12/9
  age: 18
#  interests: [篮球,足球]
  interests:
    - 篮球
    - 足球
  animal: [阿猫,阿狗]
#  score:
#    english: 80
#    math: 90
  score: {english:80,math:90}
  salarys:
    - 9999.98
    - 9999.99
  pet:
    name: 阿狗
    weight: 99.99
  allPets:
    sick:
      - {name: 阿狗,weight: 99.99}
      - name: 阿猫
        weight: 88.88
      - name: 阿虫
        weight: 77.77
    health:
      - {name: 阿花,weight: 199.99}
      - {name: 阿明,weight: 199.99}

2.2 Web开发

  • Spring Assistant

简化创建Spring项目的流程,通过界面组件勾选的方式添加依赖。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

<?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.1</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.simwor</groupId>
	<artifactId>05-web-01</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>05-web-01</name>
	<description>Demo project for Spring Boot</description>

	<properties>
		<java.version>15</java.version>
	</properties>

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

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-devtools</artifactId>
			<scope>runtime</scope>
			<optional>true</optional>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-configuration-processor</artifactId>
			<optional>true</optional>
		</dependency>
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<optional>true</optional>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
				<configuration>
					<excludes>
						<exclude>
							<groupId>org.projectlombok</groupId>
							<artifactId>lombok</artifactId>
						</exclude>
					</excludes>
				</configuration>
			</plugin>
		</plugins>
	</build>

</project>

2.2.1 静态资源

  • 静态资源访问

参考文档:https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-features-spring-mvc-static-content

By default, Spring Boot serves static content from a directory called /static (or /public or /resources or /META-INF/resources) in the classpath or from the root of the ServletContext.

只要静态资源放在这几个路径下,就可以通过 根路径 + 静态资源名 访问。

在这里插入图片描述

  • 修改静态资源访问路径

application.yml 配置文件中修改后,静态资源的访问路径变成了 根路径 + static + 静态资源名

spring:
  mvc:
    static-path-pattern: /static/**
  • 修改静态资源存放目录

application.yml 配置文件中修改后,只有放置在该些目录的静态资源才能被访问到。

spring:
  resources:
    static-locations:
      - classpath:/static
      - classpath:/public

2.2.2 欢迎页

Spring Boot supports both static and templated welcome pages. It first looks for an index.html file in the configured static content locations.

#  欢迎页放置在任何一个静态资源路径下即可,但若配置了欢迎页则不可以再修改静态资源访问前缀。
spring:
#  mvc:
#    static-path-pattern: /static/**
  resources:
    static-locations:
      - classpath:/static
      - classpath:/public

2.2.3 请求参数

  • param / header / query
package com.simwor.boot.controller;

import org.springframework.boot.web.servlet.server.Session;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.Cookie;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@RestController
public class ParamController {

    @GetMapping("/car/{id}/owner/{name}")
    public Map<String,Object> getCar(@PathVariable Integer id,
                                     @PathVariable String name,
                                     @PathVariable Map<String,String> pathVars,
                                     @RequestHeader("User-Agent") String userAgent,
                                     @RequestHeader Map<String,String> headers,
                                     @RequestParam("age") Integer age,
                                     @RequestParam("hobbies") List<String>hobbies,
                                     @RequestParam Map<String,String> queries) {
        Map<String,Object> map = new HashMap<>();

        map.put("id",id);
        map.put("name",name);
        map.put("pathVars",pathVars);
        map.put("userAgent",userAgent);
        map.put("headers",headers);
        map.put("age",age);
        map.put("hobbies",hobbies);
        map.put("queries",queries);

        return map;
    }

}

http://localhost:8080/car/1997/owner/rayslee?age=18&hobbies=jog&hobbies=swim

{
    "headers": {
        "host": "localhost:8080",
        "connection": "keep-alive",
        "cache-control": "max-age=0",
        "upgrade-insecure-requests": "1",
        "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36",
        "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
        "sec-fetch-site": "none",
        "sec-fetch-mode": "navigate",
        "sec-fetch-user": "?1",
        "sec-fetch-dest": "document",
        "accept-encoding": "gzip, deflate, br",
        "accept-language": "zh-CN,zh;q=0.9"
    },
    "pathVars": {
        "id": "1997",
        "name": "rayslee"
    },
    "hobbies": [
        "jog",
        "swim"
    ],
    "name": "rayslee",
    "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36",
    "id": 1997,
    "queries": {
        "age": "18",
        "hobbies": "jog"
    },
    "age": 18
}
  • body
package com.simwor.boot.controller;

import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;

@RestController
public class ParamController {

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

在这里插入图片描述

  • request

取出请求域中参数的值。

package com.simwor.boot.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestAttribute;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;

@Controller
public class RequestController {

    @GetMapping("goto")
    public String goToPage(HttpServletRequest request) {
        request.setAttribute("code",200);
        request.setAttribute("msg","Congratulations");

        return "forward:/success";
    }

    @ResponseBody
    @GetMapping("/success")
    public Map<String,String> successPage(@RequestAttribute("code") Integer annotationCode,
                                          @RequestAttribute("msg") String annotationMsg,
                                          HttpServletRequest request) {
        Map<String,String> map = new HashMap<>();

        map.put("annotationCode", annotationCode.toString());
        map.put("annotationMsg", annotationMsg);
        map.put("requestCode", request.getAttribute("code").toString());
        map.put("requestMsg", request.getAttribute("msg").toString());

        return map;
    }
}

在这里插入图片描述

2.3 数据访问 - MySQL

2.3.1 JDBC

spring-boot-starter-data-jdbc 默认自动配置 HikariDataSource 数据源,用于获取连接操纵数据库。

  1. 导入依赖
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
<dependency>
	<groupId>mysql</groupId>
	<artifactId>mysql-connector-java</artifactId>
</dependency>
  1. 确认数据库版本

在spring-boot-dependencies-2.4.1.pom中确认默认配置的mysql版本号,对应覆盖修改。

<properties>
	<java.version>15</java.version>
<!--<mysql.version>8.0.22</mysql.version>-->
</properties>

在这里插入图片描述

  1. 配置数据源
spring:
  datasource:
    url: jdbc:mysql://simwor.com:33060/simwor
    username: simwor
    password: abcd1234..
    driver-class-name: com.mysql.jdbc.Driver
  1. 编写测试
package com.simwor.boot;

import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.jdbc.core.JdbcTemplate;

import java.util.List;
import java.util.Map;

import static org.junit.jupiter.api.Assertions.assertEquals;

@Slf4j
@SpringBootTest
public class MysqlTest {
    @Autowired
    JdbcTemplate jdbcTemplate;

    @Test
    public void testMySQLConnection() {
        String sql = "SELECT * FROM user";
        
        List<Map<String, Object>> result = jdbcTemplate.queryForList(sql);

        assertEquals(result.toString(), "[{id=1, name=rayslee}, {id=2, name=joshua}]");
    }

}

2.3.2 MyBatis

  1. 引入依赖
<properties>
	<mybatis.version>2.1.4</mybatis.version>
</properties>

<dependencies>
	<dependency>
		<groupId>org.mybatis.spring.boot</groupId>
		<artifactId>mybatis-spring-boot-starter</artifactId>
		<version>${mybatis.version}</version>
	</dependency>
</dependencies>
  1. 准备 Table <-> Bean
package com.simwor.boot.bean;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
    private Long id;
    private String name;
}
  1. 准备 Mapper
package com.simwor.boot.mapper;

import com.simwor.boot.bean.User;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Options;
import org.apache.ibatis.annotations.Select;

@Mapper
public interface UserMapper {

    @Select("SELECT * FROM user WHERE id=#{id}")
    public User getUserById(Long id);

    @Insert("INSERT INTO user(`name`) VALUES(#{name})")
    //插入数据后,取回数据库为该条数据返回的key,设置到user的id属性上。
    @Options(useGeneratedKeys = true, keyProperty = "id")
    public void saveUser(User user);
}
  1. 准备 Service
package com.simwor.boot.service;

import com.simwor.boot.bean.User;
import com.simwor.boot.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class UserService {
    @Autowired
    UserMapper userMapper;

    public User getUserById(Long id) {
        return userMapper.getUserById(id);
    }

    public void saveUser(User user) {
        userMapper.saveUser(user);
    }
}
  1. 准备 Controller
package com.simwor.boot.controller;

import com.simwor.boot.bean.User;
import com.simwor.boot.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

@RestController
public class UserController {

    @Autowired
    UserService userService;

    @GetMapping("/get-user")
    public User getUserById(@RequestParam("id") Long id) {
        return userService.getUserById(id);
    }

    @PostMapping("/save-user")
    public User saveUser(User user) {
        userService.saveUser(user);
        return user;
    }
}
  1. 测试

在这里插入图片描述
在这里插入图片描述

2.3.3 MyBatis Plus

  • 安装插件

插件使用文档:https://baomidou.com/guide/mybatisx-idea-plugin.html#%E5%8A%9F%E8%83%BD

在这里插入图片描述

  • 快速开始

参考文档:https://baomidou.com/guide/quick-start.html#%E6%B7%BB%E5%8A%A0%E4%BE%9D%E8%B5%96

  1. 引入依赖
<properties>
	<mybatis-plus.version>3.4.1</mybatis-plus.version>
</properties>

<dependencies>
	<dependency>
		<groupId>com.baomidou</groupId>
		<artifactId>mybatis-plus-boot-starter</artifactId>
		<version>${mybatis-plus.version}</version>
	</dependency>
</dependencies>
  1. 准备 Table <-> Bean
package com.simwor.boot.bean;

import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
//默认 类名<-->表名,若确实不匹配才需用到此注解。
@TableName("user")
public class User {
    private Long id;
    private String name;
    //告诉 MyBatis 该字段在数据库中并不存在
    @TableField(exist = false)
    private String hobby;
}
  1. 准备 Mapper

到目前为止,此接口可以直接进行注入使用。

package com.simwor.boot.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.simwor.boot.bean.User;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface UserMapper extends BaseMapper<User> {
}
  1. 准备 Service

直接继承Mybatis提供的泛型,里面实现了很多常用的方法,无需自己手动实现。

package com.simwor.boot.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.simwor.boot.bean.User;

public interface UserService extends IService<User> {
}
package com.simwor.boot.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.simwor.boot.bean.User;
import com.simwor.boot.mapper.UserMapper;
import com.simwor.boot.service.UserService;
import org.springframework.stereotype.Service;

@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
}
  1. 准备 Controller
package com.simwor.boot.controller;

import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.simwor.boot.bean.User;
import com.simwor.boot.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class UserController {

    @Autowired
    UserService userService;

    @GetMapping("/get-users")
    public Page<User> getUsers(@RequestParam(value = "page", defaultValue = "1") Integer page) {
        //分页查询,默认返回第一页,每页展示3条数据
        return userService.page(new Page<>(page, 3));
    }
}
  1. 测试

注意观察:这里把所有的数据都返回了,并且 total、pages 属性并没有获取到,下一小节会解决这个问题。

{
    "records": [
        {
            "id": 1,
            "name": "rayslee",
            "hobby": null
        },
        {
            "id": 2,
            "name": "joshua",
            "hobby": null
        },
        {
            "id": 12,
            "name": "chandler",
            "hobby": null
        },
        {
            "id": 13,
            "name": "lily",
            "hobby": null
        }
    ],
    "total": 0,
    "size": 3,
    "current": 1,
    "orders": [],
    "optimizeCountSql": true,
    "hitCount": false,
    "countId": null,
    "maxLimit": null,
    "searchCount": true,
    "pages": 0
}
  • 分页
  1. 配置
package com.simwor.boot.config;

import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MybatisPlusConfig {
    
    @Bean
    public MybatisPlusInterceptor paginationInterceptor() {
        MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
        // 设置请求的页面大于最大页后操作, true调回到首页,false 继续请求  默认false
        // mybatisPlusInterceptor.setOverflow(false);
        // 设置最大单页限制数量,默认 500 条,-1 不受限制
        // mybatisPlusInterceptor.setLimit(500);
        //分页拦截器
        PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor();
        mybatisPlusInterceptor.addInnerInterceptor(paginationInnerInterceptor);
        return mybatisPlusInterceptor;
    }
}
  1. 测试
{
    "records": [
        {
            "id": 1,
            "name": "rayslee",
            "hobby": null
        },
        {
            "id": 2,
            "name": "joshua",
            "hobby": null
        },
        {
            "id": 12,
            "name": "chandler",
            "hobby": null
        }
    ],
    "total": 4,
    "size": 3,
    "current": 1,
    "orders": [],
    "optimizeCountSql": true,
    "hitCount": false,
    "countId": null,
    "maxLimit": null,
    "searchCount": true,
    "pages": 2
}

2.4 数据访问 - Redis

注意:spring-boot-starter-data-redis 默认使用 lettuce-core 而不是 jedis 作为底层的连接池。

  • 准备数据源
  1. docker-compose
version: "3.8"
services:
  simwor-redis:
    image: redis
    container_name: simwor-redis
    ports:
      - 6379:6379
    volumes:
      - /opt/docker/redis/conf:/usr/local/etc/redis
    command: redis-server /usr/local/etc/redis/redis.conf
  1. redis.conf
databases 16
requirepass abcd1234..
  • 基本使用
  1. 添加依赖
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
  1. 配置数据源
spring:
  redis:
    host: simwor.com
    port: 6379
    password: abcd1234..
  1. 测试
package com.simwor.boot;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;

@SpringBootTest
public class RedisTest {
    @Autowired
    StringRedisTemplate stringRedisTemplate;

    @Test
    public void testRedis() {
        stringRedisTemplate.opsForValue().increment("hit");
    }
}
  1. 验证
[root@simwor compose]# docker container exec -it simwor-redis redis-cli
127.0.0.1:6379> auth abcd1234..
OK
127.0.0.1:6379> get hit
"1"
127.0.0.1:6379> get hit
"3"
127.0.0.1:6379> 

==> 拦截器场景(统计页面访问次数)

  1. 创建拦截器
package com.simwor.boot.interceptor;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Component
public class UrlCountRedisInterceptor implements HandlerInterceptor {

    @Autowired
    StringRedisTemplate redisTemplate;

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

        redisTemplate.opsForValue().increment(uri);

        return true;
    }
}
  1. 配置拦截器
package com.simwor.boot.config;

import com.simwor.boot.interceptor.UrlCountRedisInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Autowired
    UrlCountRedisInterceptor redisInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(redisInterceptor)
                .addPathPatterns("/**");
    }
}
  1. 验证

连续访问:localhost:8080/car/1997/owner/rayslee?age=18&hobbies=jog&hobbies=swim,同时查看 Redis

[root@simwor compose]# docker container exec -it simwor-redis redis-cli
127.0.0.1:6379> auth abcd1234..
OK
127.0.0.1:6379> get /car/1997/owner/rayslee
"3"
127.0.0.1:6379> 

2.5 单元测试

  • JUnit 5

JUnit 5 由 JUnit Platform + JUnit Jupiter + JUnit Vintage 三个不同子项目组成。

  1. Junit Platform 是在JVM上启动测试框架的基础,不仅支持Junit自制的测试引擎,其它测试引擎也都可以接入;
  2. JUnit Jupiter 提供了新的编程模型,是JUnit 5新特性的核心,其内部包含了一个测试引擎,用于在Junit Platform 上运行;
  3. Junit Vintage 是为了照顾老的项目,提供了兼容JUnit 4.x,JUnit 3.x 的测试引擎(已在SpringBoot 2.4.x 中移除)。
  • 常用注解

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

注解说明
@Test声明方法是测试方法,职责非常单一不能声名任何属性
@ParameterizedTest声明方法是参数化测试
@RepeatedTest可重复执行
@DisplayName为测试类或者测试方法设置展示名称
@BeforeEach在每个单元测试之前执行
@AfterEach… 之后 …
@BeforeAll在所有单元测试之前执行
@AfterAll… 之后 …
@Tag表示单元测试的类别
@Disabled表示测试类或测试方法不执行
@Timeout表示测试方法运行如果超过了指定时间将会返回错误
@ExtendWith为测试类或测试方法提供扩展类应用
@Transactional数据库测试完成后会自动回滚

2.5.1 常用注解

package com.simwor.boot;

import org.junit.jupiter.api.*;

import java.util.concurrent.TimeUnit;

public class Junit5Test {

    @Test
    @DisplayName("Just a name")
    void testDisplayName() {
        System.out.println("test1");
    }

    @Disabled
    @Test
    @Timeout(value = 5,unit = TimeUnit.MILLISECONDS)
    void testTimeout() throws InterruptedException {
        Thread.sleep(500);
    }

    @RepeatedTest(3)
    public void testRepeatedTest() {
        System.out.println("testRepeatedTest");
    }

    @BeforeEach
    void testBeforeEach() {
        System.out.println("testBeforeEach");
    }

    @AfterEach
    void testAfterEach() {
        System.out.println("testAfterEach\n");
    }

    @BeforeAll
    static void testBeforeAll() {
        System.out.println("testBeforeAll");
    }

    @AfterAll
    static void testAfterAll() {
        System.out.println("testAfterAll");
    }
}

在这里插入图片描述

2.5.2 断言机制

断言方法类:org.junit.jupiter.api.Assertions

  1. 简单断言
方法说明
assertEquals判断两个对象或两个原始类型是否相等
assertNotEquals判断两个对象或两个原始类型是否不相等
assertSame判断两个对象引用是否指向同一个对象
assertNotSame判断两个对象引用是否指向不同的对象
assertTrue判断给定的布尔值是否为 true
assertFalse判断给定的布尔值是否为 false
assertNull判断给定的对象引用是否为 null
assertNotNull判断给定的对象引用是否不为 null
package com.simwor.boot;

import com.simwor.boot.bean.Car;
import org.junit.jupiter.api.Test;

import java.time.Duration;

import static org.junit.jupiter.api.Assertions.*;

public class Junit5Test {

   @Test
    void testSimpleAssertions() {
       //assertEquals 是属性级别的比较,assertSame 是对象级别的比较。
       assertEquals(new Car("tesla", 3000), new Car("tesla", 3000));
       assertNotSame(new Car("tesla", 3000), new Car("tesla", 3000));
   }

   @Test
    void testArrayAssertions() {
       assertArrayEquals(new int[]{1,2}, new int[]{1,2});
   }

   @Test
    void testAllAssertions() {
        //组合断言,全通过则通过。
       assertAll(()->assertTrue(true),
                 ()->assertFalse(false));
   }

   @Test
    void testExceptionAssertions() {
       //断言将会抛出异常
       assertThrows(ArithmeticException.class, ()->{int i=10/0;});
   }

   @Test
    void testTimeoutAssertions() {
       //断言将会超时
        assertTimeout(Duration.ofMillis(1000), ()->Thread.sleep(1000));
    }

    @Test
    void testQuickFailAssertions() {
       assertTrue(true);
       assertFalse(false);
        //如果满足一定条件,快速失败,不再往进行。
       if("boring".equals("boring"))
           fail();
       assertTrue(true);
    }
}

在这里插入图片描述

2.5.3 前置条件

当前置条件为真时,才继续往下执行,否则该测试被忽略。

package com.simwor.boot;

import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.Test;

public class Junit5Test {

    @Test
    void testAssumptions() {
        Assumptions.assumeTrue("myMoney".equals("1000w"));
        System.out.println("Good job!");
    }

}

2.5.4 参数化测试

使用参数多测运行同一个测试,支持八大基础类入参、null入参、枚举入参、CSV/YML/JSON文件入参和方法返回值入参。

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

import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import org.junit.jupiter.params.provider.ValueSource;

import java.util.stream.Stream;

public class Junit5Test {

    //read parameters from annotation
    @ParameterizedTest
    @ValueSource(ints = {1,2,3})
    void testParameterizedTestFromValueSource(int i) {
        System.out.println(i);
    }

    //read parameters from method
    @ParameterizedTest
    @MethodSource("stringProvider")
    void testParameterizedTestMethod(String s) {
        System.out.println(s);
    }

    //must be static
    static Stream<String> stringProvider() {
        return Stream.of("apple", "orange", "banana");
    }

}

在这里插入图片描述

2.6 指标监控

https://docs.spring.io/spring-boot/docs/current/reference/html/production-ready-features.html#production-ready-enabling

2.6.1 原生功能

  • 基本查看
  1. 引入依赖
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
  1. 启动应用验证

http://localhost:8080/actuator

{
    "_links": {
        "self": {
            "href": "http://localhost:8080/actuator",
            "templated": false
        },
        "health": {
            "href": "http://localhost:8080/actuator/health",
            "templated": false
        },
        "health-path": {
            "href": "http://localhost:8080/actuator/health/{*path}",
            "templated": true
        },
        "info": {
            "href": "http://localhost:8080/actuator/info",
            "templated": false
        }
    }
}
  • 管理 Endpoints
  1. 开启
# actuator 的所有配置都在 management 下
management:
  endpoints:
    enabled-by-default: true # 默认开启所有监控端点
    web:
      exposure:
        include: '*' # 以 web 方式暴露所有监控端点
  1. 端点查看示例

http://localhost:8080/actuator/metrics/jvm.memory.max

{
    "name": "jvm.memory.max",
    "description": "The maximum amount of memory in bytes that can be used for memory management",
    "baseUnit": "bytes",
    "measurements": [
        {
            "statistic": "VALUE",
            "value": 5591007229
        }
    ],
    "availableTags": [
        {
            "tag": "area",
            "values": [
                "heap",
                "nonheap"
            ]
        },
        {
            "tag": "id",
            "values": [
                "CodeHeap 'profiled nmethods'",
                "G1 Old Gen",
                "CodeHeap 'non-profiled nmethods'",
                "G1 Survivor Space",
                "Compressed Class Space",
                "Metaspace",
                "G1 Eden Space",
                "CodeHeap 'non-nmethods'"
            ]
        }
    ]
}
  1. 常用端点
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执行线程转储。

2.6.2 定制功能

  • Health
package com.simwor.boot.health;

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

import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;

@Component
public class MyAppHealthIndicator extends AbstractHealthIndicator {

    @Override
    protected void doHealthCheck(Health.Builder builder) throws Exception {
        Map<String,Object> checkResults = new HashMap<>();

        if(true) {
            builder.up();
            checkResults.put("code", 200);
            checkResults.put("msg", "everything is fine");
        }
        else {
            builder.down();
            checkResults.put("code", 400);
            checkResults.put("msg", "something wrong");
        }

        builder.withDetail("time", LocalDateTime.now())
                .withDetails(checkResults);
    }
}
  1. 新增配置
management:
  endpoint:
    health:
      show-details: always # 开启详细信息展示
  1. 验证

http://localhost:8080/actuator/health

在这里插入图片描述

  • Metrics
  1. 插入统计代码
package com.simwor.boot.controller;

import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.MeterRegistry;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {

    private Counter counter;

    public HelloController(MeterRegistry meterRegistry) {
        counter = meterRegistry.counter("hello.invocation.count");
    }

    @GetMapping("/hello")
    public String getHelloInfo() {
        counter.increment();
        return "Hello, I will remember you!";
    }
}
  1. 验证

先访问:http://localhost:8080/hello

再观察:http://localhost:8080/actuator/metrics/hello.invocation.count

{
    "name": "hello.invocation.count",
    "description": null,
    "baseUnit": null,
    "measurements": [
        {
            "statistic": "COUNT",
            "value": 4
        }
    ],
    "availableTags": []
}

2.6.3 可视化

可视化工具 spring-boot-admin-starter-server 参考文档:https://codecentric.github.io/spring-boot-admin/2.3.1/#getting-started

  1. 创建新的应用(仅勾选web即可)并引入依赖
<properties>
	<adminserver.version>2.3.1</adminserver.version>
</properties>

<dependencies>
	<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>${adminserver.version}</version>
	</dependency>
</dependencies>
  1. 开启可视化服务
package com.simwor.adminserver;

import de.codecentric.boot.admin.server.config.EnableAdminServer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

//开启可视化服务
@EnableAdminServer
@SpringBootApplication
public class DemoApplication {

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

}
  1. 查看前端页面
server:
  port: 8888

http://localhost:8888/applications(默认8080端口已被上一个应用占用)

在这里插入图片描述

  1. 在需要被监控的应用引入客户端依赖及添加配置文件
<properties>
	<adminclient.version>2.3.1</adminclient.version>
</properties>

<dependencies>
	<dependency>
		<groupId>de.codecentric</groupId>
		<artifactId>spring-boot-admin-starter-client</artifactId>
		<version>${adminclient.version}</version>
	</dependency>
</dependencies>
# 监控项的权限开大一点,方便监控
management:
  endpoints:
    enabled-by-default: true # 默认开启所有监控端点
    web:
      exposure:
        include: '*' # 以 web 方式暴露所有监控端点
# 配置以注册 admin-server
spring:
  application:
    name: spring-boot-admin-client # 想以什么名字注册
  boot:
    admin:
      client:
        url:
          - http://localhost:8888 # admin-server的访问地址
  1. 启动客户端应用,在服务端查看

在这里插入图片描述

2.7 环境配置

默认配置文件 application.yml 永远都会被加载,用来配置公共参数;可以在默认配置文件中通过 spring.profiles.active 参数指定依赖于环境的配置文件,如 spring.profiles.active=devapplication-dev.yml 配置文件也会生效,且优先级大于默认配置文件。

package com.simwor.boot.controller;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ProfileController {

    @Value("${env.name:default}")
    private String env;

    @GetMapping("/profile")
    public String getEnvInfo() {
        //Now the environment is development
        return "Now the environment is " + env;
    }
}

//application.yml
//spring:
//  profiles:
//    active: dev

//application-dev.yml
//env:
//  name: development

//application-pro.yml
//env:
//  name: production
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值