- SpringBoot 是整合 Spring 技术栈的一站式框架,是简化 Spring 技术栈的快速开发脚手架
- 约定大于配置
文章目录
- 一 概述
- 1 第一个 SpringBoot 项目
- 2 SpringBoot 特性:依赖管理
- 3 SpringBoot 特性:自动配置
- 二 SpringBoot 的 IOC容器
- 1 组件添加:@Configuration
- 2 组件添加:@Import
- 3 组件添加:@Conditional
- 4 引入原生配置文件:@ImportResource
- 5 配置绑定:@ConfigurationProperties
- 6 Lombok 的使用
- 三 yaml 配置文件
- 1 基本语法
- 2 使用示例
- 3 添加配置提示(自动补全)
- 四 实例:后端管理系统
- 1 静态资源的配置与访问
- 2 配置控制器
- 3 Thymeleaf 抽取页面的相同元素
- 4 配置拦截器
- 5 文件上传
- 6 错误处理
- 五 注入原生 Web 组件(Servlet,Filter,Listener)
- 1 使用 Servlet API(推荐)
- 2 使用 RegistrationBean
- 六 数据访问
- 1 导入 JDBC 场景
- 2 切换 Druid 数据源
- 3 整合 MyBatis
- 七 单元测试
- 1 JUnit5
- 2 常用注解
- 3 断言 assert
- 4 假设 assumption
- 5 嵌套测试
- 八 指标监控
- 1 开启方法
- 2 常用端点
一 概述
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容器中注入对象,默认情况下的对象名为 jerrymousepublic 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 {@Beanpublic 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 {// 配置拦截器@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new LoginInterceptor()).addPathPatterns("/**") // 静态请求也会被拦截.excludePathPatterns("/", "/login", "/css/**", "/fonts/**", "/images/**", "/js/**"); // 放行不登陆就能访问的页面,和静态资源}
}
- 拦截器的实现:
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {@Overridepublic 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;}}@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {//...HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);}@Overridepublic 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 {@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {//...}
}
@WebFilter(urlPatterns = {"/**"})
public class MyFilter implements Filter {@Overridepublic void init(FilterConfig filterConfig) throws ServletException {System.out.println("过滤器初始化");}@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {System.out.println("执行过滤操作");filterChain.doFilter(servletRequest, servletResponse);}@Overridepublic void destroy() {System.out.println("过滤器销毁");}
}
@WebListener
public class MyListener implements ServletContextListener {@Overridepublic void contextInitialized(ServletContextEvent sce) {System.out.println("监听到项目初始化");}@Overridepublic void contextDestroyed(ServletContextEvent sce) {System.out.println("监听到项目销毁");}
}
2 使用 RegistrationBean
- 无需注解
@Configuration
public class MyRegistConfig {@Beanpublic ServletRegistrationBean myServlet(){MyServlet myServlet = new MyServlet();return new ServletRegistrationBean(myServlet,"/my","/my02");}@Beanpublic FilterRegistrationBean myFilter(){MyFilter myFilter = new MyFilter();FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(myFilter);filterRegistrationBean.setUrlPatterns(Arrays.asList("/my","/css/*"));return filterRegistrationBean;}@Beanpublic 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.Driverusername: rootpassword: 123url: 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.Driverusername: rootpassword: 123url: jdbc:mysql://localhost:3306/testdruid:aop-patterns: com.atguigu.admin.* #监控SpringBeanfilters: stat,wall # 底层开启功能,stat(sql监控),wall(防火墙)stat-view-servlet: # 配置监控页功能enabled: truelogin-username: adminlogin-password: adminresetEnable: falseweb-stat-filter: # 监控webenabled: trueurlPattern: /*exclusions: '*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*'filter: # 配置开启的filterstat: # 对上面filters里面的stat的详细配置slow-sql-millis: 1000logSlowSql: trueenabled: truewall:enabled: trueconfig: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";@Testpublic void simpleAssume() {assumeTrue(Objects.equals(this.environment, "DEV"));assumeFalse(() -> Objects.equals(this.environment, "PROD"));}@Testpublic void assumeThenDo() {assumingThat(Objects.equals(this.environment, "DEV"),() -> System.out.println("In DEV"));}
}
5 嵌套测试
- 目的是将测试过程结构化,把相关的测试方法组织在一起
- 对测试类及其内部类使用
@Nested
注解,实现嵌套测试
@DisplayName("A stack")
class TestingAStackDemo {Stack<Object> stack;// 最外层测试方法@Testvoid isInstantiatedWithNew() {new Stack<>();}// 嵌套第一层测试类@Nestedclass WhenNew {@BeforeEachvoid createNewStack() {stack = new Stack<>();}@Testvoid isEmpty() {assertTrue(stack.isEmpty());}// 嵌套第二层测试类@Nested@DisplayName("after pushing an element")class AfterPushing {String anElement = "an element";@BeforeEachvoid 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:日志记录