- SpringBoot 是整合 Spring 技术栈的一站式框架,是简化 Spring 技术栈的快速开发脚手架
- 约定大于配置
文章目录
一 概述
1 第一个 SpringBoot 项目
- 导入 maven 依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.3</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
- 创建主程序类,作为启动的入口,注意:启动类的目录一定要在 controller 等目录的至少上一级,或者在
@SpringBootApplication
注解中添加scanBasePackages
属性
@SpringBootApplication(scanBasePackages = "controller")
public class HelloApplication {
public static void main(String[] args) {
SpringApplication.run(HelloApplication.class, args);
}
}
- 创建控制器类以及控制器方法,
@RestController = @ResponseBody + @Controller
,用于标注控制器类,该控制器的 所有方法 向浏览器返回 控制器方法的返回值
@RestController // @RestController = @ResponseBody + @Controller,向浏览器返回 该方法的返回值
public class HelloController {
@RequestMapping("/hello")
public String hello() {
return "hello springboot";
}
}
-
运行主程序类的 main 方法即可,无需更多配置
-
如果要修改某些配置,在
resources/application.properties
中修改即可,如:server.port=8888
-
如果要导出为 jar 包,需要在 maven 配置文件中添加:
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
并执行 maven 的 clean + package 操作
2 SpringBoot 特性:依赖管理
- 自定义的 SpringBoot 项目的父项目
spring-boot-starter-parent
,实现了依赖管理功能 - 父项目的父项目
spring-boot-dependencies
的<properties>
标签声明了几乎所有开发中常用的依赖的版本号,实现自动版本仲裁机制(即:引入依赖默认可以不写版本,但是引入非版本仲裁的 jar,要写版本号) - 如果想使用依赖的指定版本,需要在当前项目的 maven 配置文件中的
<properties>
标签声明
1、查看spring-boot-dependencies里面规定当前依赖的版本 用的 key。
2、在当前项目里面重写配置
<properties>
<mysql.version>5.1.43</mysql.version>
</properties>
- 场景启动器:
spring-boot-starter-*
,只要引入 starter,这个场景的所有常规需要的依赖都进行自动引入;所有的启动器底层都依赖 SpringBoot 核心依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>2.6.3</version>
<scope>compile</scope>
</dependency>
3 SpringBoot 特性:自动配置
- 主程序所在包及其下面的所有子包里面的组件(需要
@Component、@Controller
… 注解才能称为组件,不加注解无法扫描)都会被默认扫描进来,不用显式地配置组件扫描 - 各种配置拥有默认值,并按需加载所有自动配置项
- 主程序类的注解
@SpringBootApplication = @SpringBootConfiguration + @EnableAutoConfiguration + @ComponentScan(主程序类所在包)
- 自动配置流程:
(Ⅰ) SpringBoot 加载所有的自动配置类(xxxxxAutoConfiguration
类,而非组件)
(Ⅱ) 每个自动配置类按照条件生效,默认绑定配置文件指定的值(xxxxProperties
类)
(Ⅲ) 生效的配置类就会给容器中装配很多组件
(Ⅳ) 如果要自定义配置,可以选择:创建 @Bean 替换底层组件,或修改组件获取的配置文件(这些文件最终都映射到application.properties
)
二 SpringBoot 的 IOC容器
1 组件添加:@Configuration
@Configuration
注解的类,相当于原来的 xml 配置文件- 配置类本身也是组件
- 向 IOC 容器中注入 Bean,对
@Configuration
类中的方法使用@Bean
注解,将方法的返回对象注入容器 - 默认情况下,注入容器的对象名为被注解的方法名,如果要修改则向
@Bean
中传递参数
@Configuration
class MyConfig {
@Bean // 根据方法的返回值,向IOC容器中注入对象,默认情况下的对象名为 jerrymouse
public Pet jerrymouse() {
return new Pet("Jerry", 3);
}
}
- 代理 Bean 方法:
@Configuration(proxyBeanMethod = true)
时(Full 模式),对于@Bean
注解的方法,如果直接被调用,方法返回的对象是单例的;如果是@Configuration(proxyBeanMethod = false)
(Lite 模式)则非单例 - Full / Lite 模式与组件依赖问题:
配置的组件之间无依赖关系,用 Lite 模式,加速容器启动过程,减少判断
配置类组件之间有依赖关系,用 Full 模式,组件单实例,保证依赖成立
2 组件添加:@Import
- 对类进行的注解
- 在 IOC 容器中创建组件,名字为类的全类名
@Import({User.class, Pet.class})
@Configuration(proxyBeanMethods = true)
public class MyConfig {
}
3 组件添加:@Conditional
- 条件装配:满足指定的条件后进行组件装配,可以注解类和方法
- 具有一系列的子注解,对应不同的情况
@ConditionalOnBean(name="...")
:IOC容器具有指定 Bean 时执行当前组件的装配
@ConditionalOnMissingBean(name="...")
:IOC容器失去指定 Bean 时执行当前组件的装配
…
4 引入原生配置文件:@ImportResource
- 用于注解配置类,导入 Spring 的 xml 配置文件
- 传递参数为配置文件的路径:
@ImportResource("classpath:myspringconfig.xml")
5 配置绑定:@ConfigurationProperties
- 目的是,使得 Java 读取到 properties 文件中的内容,并且把它封装到 JavaBean 中,以供随时修改并使用,即:JavaBean 和配置文件的绑定
@ConfigurationProperties
注解需要填入prefix
属性,以在配置文件中指定其对象的属性值
@Component
@ConfigurationProperties(prefix = "jjjerry")
public class Pet {
private String name;
private int age;
...
}
// 在 application.properties 配置中:
// jjjerry.name="杰瑞"
// jjjerry.age=10
- 如果是自定义类,在自定义类上注解
@ConfigurationProperties
即可;如果非自定义类,需要在配置类上额外注解@EnableConfigurationProperties(Pet.class)
,它的作用是开启 Pet 的配置绑定功能,并将 Pet 组件自动注册到容器中
6 Lombok 的使用
- 简化 Bean 的开发:
@Data // 创建 getter、setter
@AllArgsConstructor // 有参构造器
@NoArgsConstructor // 无参构造器
@ToString // ...
@EqualsAndHashCode // ...
public class Person {
private String name;
private String address;
}
@Slf4j
简化日志记录,为注解的类注入了 log 实例:
@Slf4j
@SpringBootApplication
public class Application {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(Application.class, args);
log.info(String.valueOf(context.getBean("wubai").toString()));
}
}
@Configuration
class MyConfiguration {
@Bean
public Person wubai() {
return new Person("wubai", "taipei");
}
}
三 yaml 配置文件
- 是一种面向数据的配置文件,推荐在配置组件属性时使用
1 基本语法
- 大小写敏感
- 使用缩进表示层级关系
- 缩进不允许使用tab,只允许空格
- 缩进的空格数不重要,只要相同层级的元素左对齐即可
- '#'表示注释
- 使用 key: value 的形式,注意其中的空格
- 字符串无需使用引号标注,使用引号时,单引号的转义字符不起作用,双引号起作用
- 详细的语法规则
2 使用示例
创建名为 wubai_wujunlin
的组件,并注入 IOC 容器:
@Component(value = "wubai_wujunlin") // 组件在 IOC 容器中的名字
@ConfigurationProperties(prefix = "wujunlin") // 组件在配置文件中的前缀
@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
@EqualsAndHashCode
public class Person {
private String name;
private String address;
private List<String> bandMates;
}
依赖的 application.yaml
:
wujunlin:
name: 吴俊霖
address: 台北
bandMates: [小朱, 大猫, Dino]
3 添加配置提示(自动补全)
在 maven 配置文件添加:
<!--添加自动补全功能-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<!--打包时不包括自动补全-->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
四 实例:后端管理系统
1 静态资源的配置与访问
- 默认静态资源在 resources 目录下的 static 、public… 文件夹内
- 访问时使用的路径:当前项目根路径/ + 静态资源名
- 自定义访问静态资源使用的路径:
spring:
mvc:
static-path-pattern: "/custom_static_url/**"
# 访问时: http://localhost:8080/custom_static_url/bf1.png
- 欢迎页默认为静态资源路径下的
index.html
- 输入地址
http://localhost:8080
即可访问欢迎页 - 在静态资源目录下的
favicon.ico
同样会被自动解析
2 配置控制器
- 为了避免刷新页面导致表单的重复提交,首次成功登陆后,在控制器方法中返回
"redirect:/main.html"
重定向到新的页面
@Controller
public class IndexController {
@GetMapping(value = {"/login", "/"})
public String loginPage() {
return "login";
}
@PostMapping("/login")
public String processLogin(User user, HttpSession session, Model model) {
if (!user.getUsername().isEmpty() && !user.getPassword().isEmpty()) { // 省略判断逻辑
session.setAttribute("loginUser", user);
return "redirect:/main.html"; // 使用重定向,防止表单的重复提交
} else {
model.addAttribute("msg", "账号密码错误");
return "login";
}
}
@GetMapping("/main.html") // 直接访问 http://localhost:8080/main.html 仍然需要经过视图解析器才能获取main.html,而不能直接读取静态资源
public String mainPage(HttpSession session, Model model) {
if (session.getAttribute("loginUser") != null) {
return "main";
} else {
model.addAttribute("msg", "未登录");
return "login";
}
}
}
3 Thymeleaf 抽取页面的相同元素
官方文档对于 th:insert, th:replace, th:include
的示例
<body>
<div th:insert="footer :: copy"></div>
<div th:replace="footer :: copy"></div>
<div th:include="footer :: copy"></div>
</body>
…will result in:
<body>
<div>
<footer>
© 2011 The Good Thymes Virtual Grocery
</footer>
</div>
<footer>
© 2011 The Good Thymes Virtual Grocery
</footer>
<div>
© 2011 The Good Thymes Virtual Grocery
</div>
</body>
实现步骤
- 引入命名空间
<html lang="en" xmlns:th="http://www.thymeleaf.org">
- 创建公共部分的 html 页面,使用
th:fragment
或id
为公共部分命名
1 使用 fragment 属性命名
<head th:fragment="commonheader">
<meta charset="UTF-8">
<title>表格页面的公共部分</title>
...
</head>
2 使用 id 属性命名
<div id="leftmenu" class="left-side sticky-left-side">
...
</div>
- 导入公共部分:
1 对于 th:fragment 命名的标签,添加属性 th:xx(replace/insert/include)="公共文件名 :: fragment属性"
<div th:replace="common :: commonheader"></div>
2 对于 id 命名的标签,添加属性 th:xx(replace/insert/include)="公共文件名 :: #id属性"
<div th:replace="common :: #leftmenu"></div>
4 配置拦截器
- 拦截路径为 /** 时,静态资源的访问也会被拦截,需要为静态资源路径添加放行
- 配置类:
@Configuration
public class MyConfig implements WebMvcConfigurer {
// 配置拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor())
.addPathPatterns("/**") // 静态请求也会被拦截
.excludePathPatterns("/", "/login", "/css/**", "/fonts/**", "/images/**", "/js/**"); // 放行不登陆就能访问的页面,和静态资源
}
}
- 拦截器的实现:
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
Object user = request.getAttribute("loginUser");
if (user != null) {
return true;
} else {
// 未登录,跳转到登录页
// response.sendRedirect("/"); 或:
request.getRequestDispatcher("/").forward(request, response);
log.warn("未登录!!");
return false;
}
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
//...
HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
//...
HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
}
}
5 文件上传
- html 的 from 标签
<form role="form" th:action="@{/upload}" method="post" enctype="multipart/form-data">
<div class="form-group">
<label for="exampleInputFile">上传单个文件</label>
<input type="file" name="singleImage" id="exampleInputFile1">
</div>
<div class="form-group">
<label for="exampleInputFile">上传多个文件,添加 multiple 属性</label>
<input type="file" name="multiImage" id="exampleInputFile2" multiple>
</div>
</form>
- 控制器方法
使用MultipartFile
参数类型获取上传文件,其方法transferTo(...)
用于保存文件到服务器
@PostMapping("/upload")
public String upload(@RequestPart("headerImage") MultipartFile headerImg,
@RequestPart("lifeImage") MultipartFile[] lifeImg) throws IOException {
// 保存上传文件
if (!headerImg.isEmpty()) {
String fileName = headerImg.getOriginalFilename();
headerImg.transferTo(new File("E:\\" + fileName));
}
// 多个文件用 for 循环上传
// ...
return "redirect:/main.html";
}
6 错误处理
- SpringBoot 默认的错误处理机制:error/下的4xx,5xx页面会被自动解析,发生错误时,有精确的错误状态码页面就匹配精确,没有就找 4xx / 5xx;如果都没有就触发白页
五 注入原生 Web 组件(Servlet,Filter,Listener)
1 使用 Servlet API(推荐)
- 使用
@WebServlet,@WebFilter,@WebListener
注解对应的类,并在启动类注解@ServletComponentScan
传入 Web 组件位置
@WebServlet(urlPatterns = "/")
public class MyServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//...
}
}
@WebFilter(urlPatterns = {"/**"})
public class MyFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("过滤器初始化");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("执行过滤操作");
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void destroy() {
System.out.println("过滤器销毁");
}
}
@WebListener
public class MyListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
System.out.println("监听到项目初始化");
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
System.out.println("监听到项目销毁");
}
}
2 使用 RegistrationBean
- 无需注解
@Configuration
public class MyRegistConfig {
@Bean
public ServletRegistrationBean myServlet(){
MyServlet myServlet = new MyServlet();
return new ServletRegistrationBean(myServlet,"/my","/my02");
}
@Bean
public FilterRegistrationBean myFilter(){
MyFilter myFilter = new MyFilter();
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(myFilter);
filterRegistrationBean.setUrlPatterns(Arrays.asList("/my","/css/*"));
return filterRegistrationBean;
}
@Bean
public ServletListenerRegistrationBean myListener(){
MySwervletContextListener mySwervletContextListener = new MySwervletContextListener();
return new ServletListenerRegistrationBean(mySwervletContextListener);
}
}
六 数据访问
1 导入 JDBC 场景
- 创建项目时,在 Spring Initializer 中勾选 JDBC 即可
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
- 默认的数据源是
HikariDataSource
- 常规配置
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
username: root
password: 123
url: jdbc:mysql://localhost:3306/test
2 切换 Druid 数据源
- 引入依赖
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.8</version>
</dependency>
- 配置示例
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
username: root
password: 123
url: jdbc:mysql://localhost:3306/test
druid:
aop-patterns: com.atguigu.admin.* #监控SpringBean
filters: stat,wall # 底层开启功能,stat(sql监控),wall(防火墙)
stat-view-servlet: # 配置监控页功能
enabled: true
login-username: admin
login-password: admin
resetEnable: false
web-stat-filter: # 监控web
enabled: true
urlPattern: /*
exclusions: '*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*'
filter: # 配置开启的filter
stat: # 对上面filters里面的stat的详细配置
slow-sql-millis: 1000
logSlowSql: true
enabled: true
wall:
enabled: true
config:
drop-table-allow: false
3 整合 MyBatis
…
七 单元测试
1 JUnit5
- JUnit 5 = JUnit Platform(基础框架) + JUnit Jupiter(核心) + JUnit Vintage(向下兼容)
- JUnit Platform: 在JVM上启动测试框架的基础,不仅支持Junit自制的测试引擎,其他测试引擎也都可以接入
- JUnit Jupiter: JUnit Jupiter 提供了 JUnit5 的新的编程模型,是 JUnit5 新特性的核心。内部 包含了一个测试引擎,用于在Junit Platform上运行
- JUnit Vintage: JUnit Vintage提供了兼容JUnit4.x,Junit3.x的测试引擎
2 常用注解
@Test
:标注方法是测试方法@ParameterizedTest
:表示方法是参数化测试@RepeatedTest
:重复执行测试方法@DisplayName
:为测试类或者测试方法设置展示名称@BeforeEach
:在每个单元测试之前执行@AfterEach
:在每个单元测试之后执行@BeforeAll
:在所有单元测试之前执行,方法需要 static 修饰@AfterAll
:在所有单元测试之后执行,方法需要 static 修饰@Tag
:表示单元测试类别@Disabled
:不执行测试类或测试方法@Timeout
:测试方法运行如果超过了指定时间,将会返回错误@ExtendWith
:为测试类或测试方法提供扩展类引用
3 断言 assert
- 简单断言
方法 | 说明 |
---|---|
assertEquals | 判断两个对象或两个原始类型是否相等 |
assertEquals | 判断两个对象或两个原始类型是否相等 |
assertNotEquals | 判断两个对象或两个原始类型是否不相等 |
assertSame | 判断两个对象引用是否指向同一个对象 |
assertNotSame | 判断两个对象引用是否指向不同的对象 |
assertTrue | 判断给定的布尔值是否为 true |
assertFalse | 判断给定的布尔值是否为 false |
assertNull | 判断给定的对象引用是否为 null |
assertNotNull | 判断给定的对象引用是否不为 null |
- 数组断言
- 通过
assertArrayEquals(...)
方法来判断两个对象或原始类型的数组是否相等 - 对于对象类型数组,比较的方式是逻辑等于,即调用
equals
方法
@Test
@DisplayName("array assertion")
public void array() {
assertArrayEquals(new int[]{1, 2}, new int[] {1, 2});
}
- 组合断言
- 要求一系列断言同时满足
- 使用 lambda 表达式提供方法实参(lambda 表达式对应函数式编程)
@Test
@DisplayName("assert all")
public void all() {
assertAll("Math",
() -> assertEquals(2, 1 + 1),
() -> assertTrue(1 > 0)
);
}
- 异常断言
assertThrows
方法需要的参数:异常类型的 class 属性,需要抛出异常的语句的 lambda 表达式
@Test
@DisplayName("异常测试")
public void exceptionTest() {
Assertions.assertThrows(ArithmeticException.class, () -> System.out.println(1 % 0));
}
- 超时断言
@Test
@DisplayName("超时测试")
public void timeoutTest() {
Assertions.assertTimeout(Duration.ofMillis(1000), () -> Thread.sleep(500));
}
- 快速失败
@Test
@DisplayName("fail")
public void shouldFail() {
fail("This should fail");
}
4 假设 assumption
- 假设作为单元测试的前提条件,如果前提条件不满足则没有进行测试的必要
- 不满足的断言会使得测试方法失败;不满足的前置条件只会使得测试方法的执行终止,而不会引起测试失败
assumeTrue
和assumFalse
确保给定的条件为 true 或 false,不满足条件会使得测试执行终止assumingThat
的参数是表示条件的布尔值和对应的Executable
接口的实现对象,只有条件满足时,Executable 对象才会被执行;当条件不满足时,测试执行并不会终止
@DisplayName("前置条件")
public class AssumptionsTest {
private final String environment = "DEV";
@Test
public void simpleAssume() {
assumeTrue(Objects.equals(this.environment, "DEV"));
assumeFalse(() -> Objects.equals(this.environment, "PROD"));
}
@Test
public void assumeThenDo() {
assumingThat(
Objects.equals(this.environment, "DEV"),
() -> System.out.println("In DEV")
);
}
}
5 嵌套测试
- 目的是将测试过程结构化,把相关的测试方法组织在一起
- 对测试类及其内部类使用
@Nested
注解,实现嵌套测试
@DisplayName("A stack")
class TestingAStackDemo {
Stack<Object> stack;
// 最外层测试方法
@Test
void isInstantiatedWithNew() {
new Stack<>();
}
// 嵌套第一层测试类
@Nested
class WhenNew {
@BeforeEach
void createNewStack() {
stack = new Stack<>();
}
@Test
void isEmpty() {
assertTrue(stack.isEmpty());
}
// 嵌套第二层测试类
@Nested
@DisplayName("after pushing an element")
class AfterPushing {
String anElement = "an element";
@BeforeEach
void pushAnElement() {
stack.push(anElement);
}
@Test
@DisplayName("it is no longer empty")
void isNotEmpty() {
assertFalse(stack.isEmpty());
}
}
}
}
八 指标监控
- 微服务在云上部署以后,都需要对其进行监控、追踪、审计、控制等。SpringBoot 抽取了Actuator 场景,使得每个微服务可获得生产级别的应用监控、审计等功能
1 开启方法
- 引入场景依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
- 配置暴露端点(endpoint)及暴露方式
management:
endpoints:
enabled-by-default: true #暴露所有端点信息
web:
exposure:
include: '*' #以web方式暴露
- 访问
http://localhost:8080/actuator/...
监控对应指标
2 常用端点
- Health:健康状况
- Metrics:运行时指标
- Loggers:日志记录