Spring Boot 是一个 Spring 项目的快速启动器,可以让使用者进行较少的配置实现需要的功能,具备很好的扩展性和生态。
SpringBoot 简介
Spring 的缺点:
明明是「轻量级」框架,配置却太过「重量级」,存在大量的配置,其中不少还是重复的配置。
SpringBoot:
Spring Boot 是 Spring 开发公司的另一个顶级项目,和 Spring Framework 一个级别。实际上 Spring Boot 就是利用 Spring Framework 的自动配置特性完成的,编写项目时不需要编写 xml 文件。现在,各项技术都已经提供了 Spring Boot 的启动器,也不需要编写复杂的 pom.xml 文件了。
- 内嵌 Tomcat、Jetty、Undertow,无需配置部署
- 通过提供自己的启动器依赖,简化项目构建配置
- 可创建独立的 Spring 程序
- 不需要 XML 配置文件
启动器(Starter):把某个类型的项目的所有依赖打包成一个依赖,导入这个依赖就可以完成项目配置。spring 开发的启动器一般命名为 spring-boot-starter-XXX,第三方开发的启动器一般命名为 XXX-spring-boot-starter.
Spring Boot 项目搭建
Step1:导入依赖
方式一:继承父项目
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.5</version>
</parent>
导入 web 项目的启动器:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.4.5</version>
</dependency>
通过这一个启动器,导入的依赖:
方式二:使用 dependencyManagement 单独导入依赖
这种形式的导入适合于某些已经设定了继承父类的情况,如果此时还想导入 Spring Boot,可以通过这种方式实现。
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.4.5</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.4.5</version>
</dependency>
</dependencies>
Step2:定义启动类
启动类的作用是启动 Spring Boot 项目,Spring Boot 在启动类的 main 方法中实现启动。一般命名为 XXXXApplication,XXXX与项目名保持一致。
@SpringBootApplication
public class SpringbootHelloApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootHelloApplication.class, args);
}
}
这里需要用启动类传递字节码参数的原因是:Spring Boot 需要获得启动类的具体位置,以配置包扫描。由于 Spring Boot 默认扫描的是启动类所在的包和它的子包的内容,所以启动类应该放在和 controller、mapper、service 等包并列的位置:
Step3:写业务代码
这里在 controller 包创建一个 HelloController:
@Controller
public class HelloController {
@ResponseBody
@RequestMapping("hello")
public String hello() {
String hello = "Hello Spring Boot!";
System.out.println(hello);
return hello;
}
}
Step4:启动
运行启动类的 main 函数:
BOOM!项目搭建完成!
注意:倒数第二行日志提示:
Tomcat started on port(s): 8080 (http) with context path ''
context path 为空串,所以访问时不需要加入上下文路径,在本项目中,访问:http://localhost:8080/hello:
还能更简单吗?
IDEA 给我们提供了更简单的创建 Spring Boot 项目的方式:
-
在创建 module 时,选择 Spring Initializer,进行相关配置:
-
勾选 Web 中的 Spring Web,这会帮我们导入 spring web 相关的 starter:
注意:如果使用的 JDK 版本低于 17,请使用 2.X 的 Spring Boot 版本。
通过简单的两步,一个 Spring Boot 项目就创建成功了!
Spring Boot 项目配置
Spring Boot 项目配置方式
Spring Boot 不需要进行繁琐的 xml 配置,虽然它已经提供了很多默认的配置,但是为了自由度,还是允许配置一些参数,对于这些参数,默认是通过读取以 application 开头的 yml、yaml、properties 文件进行配置的:
Spring Boot 支持的配置列表可以参见:
Common Application Properties (spring.io)
例如:
如果我们希望更换服务器绑定的端口号,可以通过「各种手段」获取到它对应的 properties:
并在 yml、yaml 或 properties 中进行配置:
server.port=8090
「各种手段」包括但不限于:使用 IDEA 自带的联想功能、使用搜索引擎、使用大模型、查阅官方文档、询问有经验者等。
yml 配置
Spring Boot 配置
在实际项目中,使用 yml 文件配置的情况比较多。yml 文件的格式也很简单,如下:
server:
port: 8090
servlet:
context-path: /springboot
相当于 properties 的:
server.port=8090
server.servlet.context-path=/springboot
如果同时出现多个 yml、properties 配置文件,它们都会生效,冲突项目的优先级:properties > yml。
配置其他上下文信息
Spring Boot 还允许我们配置各种其他的上下文信息。
配置对象
# 定义对象
person:
name: zhangsan
age: 18
# 也支持写成 json 格式
person2: {name: zhangsan, age: 18}
配置数组
# 定义数组
countries:
- China
- USA
- Japan
# 或者 json 格式
countries2: [China, USA, Japan]
配置文件的位置
配置文件有四个位置可以存放,它们执行的优先级如下:
- 根目录下的 config 子目录下
- 当前项目的根目录下
- resources 下的 config 子目录下
- resources 根目录下
一般来说,我们直接放在「resources 根目录下」即可。
整合主流开发框架
Spring Boot 的整合十分简单,如果要添加新的框架,只需要导入启动器即可;创建模块时,只需要勾选响应的启动器即可。
整合测试框架
导入依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
测试类(Spring 2.2 以前):
@SpringBootTest
@RunWith(SpringRunner.class)
public class MapperTest() {
@Test
public void test(){
...
}
}
测试类(Spring 2.2 以后):
@SpringBootTest
public class MapperTest() {
@Test
public void test(){
...
}
}
注意,在测试时,如果不进行任何路径配置,必须将 SpringBoot 启动类与测试类在同一路径下运行,否则需要配置:
@SpringBootTest(classes = MapperApplication.class)
整合 MyBatis
导入依赖:
<!--mybatis 启动器-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.3.0</version>
</dependency>
<!--mysql 驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.32</version>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.26</version>
</dependency>
配置相关连接参数:
spring:
datasource:
url: jdbc:mysql://localhost:3306/springboot?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: root
mybatis:
type-aliases-package: site.penghao.springboothello3.pojo
mapper-locations: classpath:mybatis/*.xml
在启动类中配置 MapperScan:
@SpringBootApplication
@MapperScan("site.penghao.mapper")
public class MapperApplication {
public static void main(String[] args) {
SpringApplication.run(MapperApplication.class, args);
}
}
整合 logback
logback 是 Spring Boot 默认使用的日志框架,它是由 log4j 创始人的另一日志框架。使用时,和 log4j 配置的方式类似,只需要在 resources 下配置 logback.xml 即可完成配置:
<?xml version="1.0" encoding="UTF-8"?>
<!-- 默认为 <configuration scan="true" scanPeriod="60 seconds" debug="false"> -->
<!-- scan 当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true -->
<!-- scanPeriod 设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。当scan为true时,此属性生效。默认的时间间隔为1分钟 -->
<!-- debug 当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false -->
<configuration>
<contextName>myAppName</contextName>
<springProperty name="basePath" source="log.path" defaultValue=""/>
<!-- 彩色日志依赖的渲染类 -->
<conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter"/>
<conversionRule conversionWord="wex"
converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter"/>
<conversionRule conversionWord="wEx"
converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter"/>
<property name="file_pattern"
value="%date [%thread] %-5level [%logger{50}] %file:%line - %msg%n"/>
<property name="console_pattern"
value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>
<!-- appender是指输出的形式或位置,name和class是两个必备属性 -->
<appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
<!-- encoder负责两件事,一是把日志信息转换成字节数组,二是把字节数组写入到输出流。 -->
<encoder>
<pattern>${console_pattern}</pattern>
<charset>UTF-8</charset>
</encoder>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>DEBUG</level>
</filter>
</appender>
<!-- Log file debug output -->
<appender name="debug" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${basePath}/debug.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${basePath}/%d{yyyy-MM, aux}/debug.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
<maxFileSize>50MB</maxFileSize>
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder>
<pattern>${file_pattern}</pattern>
</encoder>
</appender>
<!-- Log file info output -->
<appender name="info" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${basePath}/info.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${basePath}/%d{yyyy-MM, aux}/info.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
<maxFileSize>50MB</maxFileSize>
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder>
<pattern>${file_pattern}</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>INFO</level>
</filter>
</appender>
<!-- Log file error output -->
<appender name="error" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${basePath}/error.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${basePath}/%d{yyyy-MM}/error.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
<maxFileSize>50MB</maxFileSize>
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder>
<pattern>${file_pattern}</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>ERROR</level>
</filter>
</appender>
<root level="info">
<appender-ref ref="stdout"/>
<appender-ref ref="debug"/>
<appender-ref ref="info"/>
<appender-ref ref="error"/>
</root>
<logger name="site.penghao.springboothello3.mapper" level="DEBUG"/>
</configuration>
整合 PageHelper
导入依赖:
<!--PageHelper启动器-->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.4.6</version>
</dependency>
使用方式:
// RestFul 风格的 url
@RequestMapping("selectByPage/{pageNum}/{pageSize}")
@ResponseBody
public PageInfo<User> selectByPage(@PathVariable Integer pageNum, @PathVariable Integer pageSize) {
return userService.selectByPage(pageNum, pageSize);
}
public PageInfo<User> selectByPage(Integer pageNum, Integer pageSize) {
PageHelper.startPage(pageNum, pageSize);
userMapper.selectAllUser();
PageInfo<User> pageInfo = new PageInfo<>();
return pageInfo;
}
分页插件使用了静态的 ThreadLocal 参数,分页参数和线程是绑定的,通过 ThreadLocal 设置分页参数后,执行查询的时候通过 MyBatis 的拦截器在 SQL 语句中添加分页参数,查询结束后在 finally 语句中清除 ThreadLocal 中的参数。分页插件的更多用法可以参见:MyBatis(十):逆向工程和分页插件 | 一芜蓬蒿 (penghao.site)
整合 Druid
阿里巴巴推出的数据库连接池,包含控制台,方便实现对 SQL 执行的监控。
导入依赖:
<!--Druid启动器-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.15</version>
</dependency>
设置参数:
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/springboot?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
username: root
password: root
druid:
# 初始化大小、最小、最大
initial-size: 5
min-idle: 5
max-active: 20
# 等待超时
max-wait: 60000
# 检测间隔
time-between-eviction-runs-millis: 60000
# 最小生存时间
min-evictable-idle-time-millis: 300000
validation-query: SELECT 1
test-while-idle: true
test-on-borrow: false
test-on-return: false
# PSCache
pool-prepared-statements: true
max-pool-prepared-statement-per-connection-size: 20
# filters
filters: stat,wall,slf4j
# mergeSQL;慢 SQL 记录
connection-properties: druid.stat.mergeSql\=true;druid.stat.slowSqlMillis\=5000
# DruidStatFilter
web-stat-filter:
enabled: true
url-pattern: "/*"
exclusions: "*.js,*.gis,*.jpg,*.bmp,*.pnh,*.css,*.ico,/druid/*"
# DruidStatViewServlet
stat-view-servlet:
enabled: true
url-pattern: "/druid/*"
# 黑名单
deny: 192.168.80.130
# 白名单
allow: 127.0.0.1
reset-enable: false
login-username: admin
login-password: 123456
整合 FreeMarker
FreeMarker 和 Thymeleaf 是模板引擎技术,模板引擎就是通过模板进行页面的生成,它们是 jsp 技术的替代,效率更高。Spring Boot 默认支持的模板引擎是 Thymeleaf,SpringMVC 默认支持 FreeMarker,但是现在开发中已经较少使用 FreeMarker。
导入依赖:
<!--FreeMarker启动器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
<version>3.0.2</version>
</dependency>
与 JSP 不同,FreeMarker 的页面文件(或者说用于生成页面的脚本),存放在 resources/template
下,默认后缀名为 .ftlh/.ftl(早期版本)
,其语法与 JSP 类似,使用 ${}
形式获取变量。
FreeMarker 的语法参见文档:Overall structure - Apache FreeMarker Manual
整合 Thymeleaf
Thymeleaf 是当下主流的模板引擎,它的官网是 https://www.thymeleaf.org/,与 JSP 不同,Thymeleaf 是原生的,不依赖标签库,并且没有与 Servlet 耦合。Thymeleaf 在 Spring Boot 的默认位置在 resources/templates
中,这个文件夹的作用类似于 WEB-INF
,只能通过转发访问,静态资源应该放在 resources/static
目录,有意思的是,Thymeleaf 的文件后缀名就是 .html
。
导入依赖:
<!--Thymeleaf启动器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
<version>2.7.11</version>
</dependency>
注意这里的 Thymeleaf 启动器版本要和 Spring Boot 版本相匹配。
Thymeleaf 的语法可以看另一篇笔记。
开发者工具
通过 Spring Boot 提供的开发者工具,可以简化开发。
<!--开发者工具-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<version>2.7.11</version>
<optional>true</optional>
</dependency>
异常处理
默认情况下,如果访问页面时出现异常,Spring Boot 会将部分异常信息直接打印到客户端:
但这样的提示说不上友好,我们可以通过设置 templates 文件夹下的 error 文件夹,来配置提示页面:
此外,还可以支持 40x.html,5xx.html 这样的模糊查询。查询的优先级是:
- 完全匹配错误码的错误页面
- 匹配模糊的错误页面
- err.html
- 默认的错误页面
当然,通过 SpringMVC 配置异常处理也是一种可行的方法!
项目打包发布
通过 Maven 实现 Spring Boot 项目的打包发布,可以打包称为 jar、war、pom,其中,pom 类型的打包是 Maven 项目发布时进行的,可供他人继承,这里不重点讨论。
jar 文件打包
jar 文件的打包比较简单:
-
设置打包格式为 jar
<packaging>jar</packaging>
-
使用 maven 打包
war 文件打包
war 文件的打包相对繁琐,因为我们不希望打包的 war 文件仍然包含 tomcat 或者 Servlet API 环境,这些在服务器容器已经提供了。而 spring-boot-starter-web的依赖中包含了 tomcat 环境的依赖,所以:
-
需要在 spring-boot-starter-web 中设置:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> </exclusion> </exclusions> </dependency>
-
将 tomcat 依赖分离出来,然后再以 provided 的形式导入,告知编译器不要将它打包发布。
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> <scope>provided</scope> </dependency>
-
由于,war 文件的运行并不是从 main 函数开始的,所以还需要按下面的方式配置启动类继承并重写 configure 方法:
@SpringBootApplication public class SpringbootWebApplication extends SpringBootServletInitializer { @Override protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) { return builder.sources(SpringbootWebApplication.class); } public static void main(String[] args) { SpringApplication.run(SpringbootWebApplication.class, args); } }
-
完成上述步骤,就可以使用 maven 进行打包发布了。