文章目录
一、基础入门
SpringBoot 是整合 Spring 技术栈的一站式框架
SpringBoot 是简化 Spring 技术栈的快速开发脚手架
- 核心特性
- Create stand-alone Spring applications
- Embed Tomcat, Jetty or Undertow directly (no need to deploy WAR files)
- Provide opinionated ‘starter’ dependencies to simplify your build configuration
- Automatically configure Spring and 3rd party libraries whenever possible
- Provide production-ready features such as metrics, health checks, and externalized configuration
- Absolutely no code generation and no requirement for XML configuration
1.1 时代背景
- 微服务
- 微服务是一种架构风格
- 一个应用拆分为一组小型服务
- 每个服务运行在自己的进程内,也就是可独立部署和升级
- 服务之间使用轻量级 HTTP 交互
- 服务围绕业务功能拆分
- 可以由全自动部署机制独立部署
- 去中心化,服务自治,服务可以使用不同的语言、不同的存储技术
- 分布式
- 挑战:远程调用、服务发现、负载均衡、服务容错、配置管理、服务监控、链路追踪、日志管理、任务调度;
- 解决:SpringBoot + SpringCloud。
- 云原生
- 挑战:服务自愈、弹性伸缩、服务隔离、自动化部署、灰度发布、流量治理;
- 解决: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
- 创建项目并配置依赖
<?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>
- 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);
}
}
- 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";
}
}
- 运行并验证
- 简化配置
<!-- src/main/resources/application.properties -->
server.port=8888
- 简化部署
<!-- 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)
自动重启应用。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
二、核心功能
2.1 配置文件
YAML:YAML Ain’t Markup Language 的递归缩写,该语言非常适合来做以数据为中心的配置文件。
- 基本语法
- 空格表示缩进,不允许使用 tab
- ‘#’ 表示注释
- 字符串不需要加引号,若加则 ‘’ 和 “” 分别表示内容 转义/不转义(与shell相反)
- 数据类型
- 字面量:
key: value
- 对象:
k: {k1:v1, k2:v2, k3:v3}
k:
k1: v1
k2: v2
k3: v3
- 数组:
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 静态资源
- 静态资源访问
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
数据源,用于获取连接操纵数据库。
- 导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
- 确认数据库版本
在spring-boot-dependencies-2.4.1.pom中确认默认配置的mysql版本号,对应覆盖修改。
<properties>
<java.version>15</java.version>
<!--<mysql.version>8.0.22</mysql.version>-->
</properties>
- 配置数据源
spring:
datasource:
url: jdbc:mysql://simwor.com:33060/simwor
username: simwor
password: abcd1234..
driver-class-name: com.mysql.jdbc.Driver
- 编写测试
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
- 引入依赖
<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>
- 准备 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;
}
- 准备 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);
}
- 准备 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);
}
}
- 准备 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;
}
}
- 测试
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
- 引入依赖
<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>
- 准备 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;
}
- 准备 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> {
}
- 准备 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 {
}
- 准备 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));
}
}
- 测试
注意观察:这里把所有的数据都返回了,并且
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
}
- 分页
- 配置
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;
}
}
- 测试
{
"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
作为底层的连接池。
- 准备数据源
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
redis.conf
databases 16
requirepass abcd1234..
- 基本使用
- 添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
- 配置数据源
spring:
redis:
host: simwor.com
port: 6379
password: abcd1234..
- 测试
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");
}
}
- 验证
[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>
==> 拦截器场景(统计页面访问次数)
- 创建拦截器
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;
}
}
- 配置拦截器
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("/**");
}
}
- 验证
连续访问:
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
三个不同子项目组成。
- Junit Platform 是在JVM上启动测试框架的基础,不仅支持Junit自制的测试引擎,其它测试引擎也都可以接入;
- JUnit Jupiter 提供了新的编程模型,是JUnit 5新特性的核心,其内部包含了一个测试引擎,用于在Junit Platform 上运行;
- 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
- 简单断言
方法 | 说明 |
---|---|
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 指标监控
2.6.1 原生功能
- 基本查看
- 引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
- 启动应用验证
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
- 开启
# actuator 的所有配置都在 management 下
management:
endpoints:
enabled-by-default: true # 默认开启所有监控端点
web:
exposure:
include: '*' # 以 web 方式暴露所有监控端点
- 端点查看示例
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'"
]
}
]
}
- 常用端点
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);
}
}
- 新增配置
management:
endpoint:
health:
show-details: always # 开启详细信息展示
- 验证
http://localhost:8080/actuator/health
- Metrics
- 插入统计代码
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!";
}
}
- 验证
先访问: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
- 创建新的应用(仅勾选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>
- 开启可视化服务
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);
}
}
- 查看前端页面
server:
port: 8888
http://localhost:8888/applications(默认8080端口已被上一个应用占用)
- 在需要被监控的应用引入客户端依赖及添加配置文件
<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的访问地址
- 启动客户端应用,在服务端查看
2.7 环境配置
默认配置文件
application.yml
永远都会被加载,用来配置公共参数;可以在默认配置文件中通过spring.profiles.active
参数指定依赖于环境的配置文件,如spring.profiles.active=dev
则application-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