Spring Boot 使用简介

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>

通过这一个启动器,导入的依赖:

image-20230517085327362

方式二:使用 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 等包并列的位置:

image-20230517090610880

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 函数:

image-20230517091042584

BOOM!项目搭建完成!

注意:倒数第二行日志提示:

Tomcat started on port(s): 8080 (http) with context path ''

context path 为空串,所以访问时不需要加入上下文路径,在本项目中,访问:http://localhost:8080/hello:

image-20230517092122624

还能更简单吗?

IDEA 给我们提供了更简单的创建 Spring Boot 项目的方式:

  1. 在创建 module 时,选择 Spring Initializer,进行相关配置:

    image-20230517092738888
  2. 勾选 Web 中的 Spring Web,这会帮我们导入 spring web 相关的 starter:

    image-20230517093814664

    注意:如果使用的 JDK 版本低于 17,请使用 2.X 的 Spring Boot 版本。

通过简单的两步,一个 Spring Boot 项目就创建成功了!

Spring Boot 项目配置

Spring Boot 项目配置方式

Spring Boot 不需要进行繁琐的 xml 配置,虽然它已经提供了很多默认的配置,但是为了自由度,还是允许配置一些参数,对于这些参数,默认是通过读取以 application 开头的 yml、yaml、properties 文件进行配置的:

image-20230517100809614

Spring Boot 支持的配置列表可以参见:

Common Application Properties (spring.io)

例如:

如果我们希望更换服务器绑定的端口号,可以通过「各种手段」获取到它对应的 properties:

image-20230517101653279

并在 yml、yaml 或 properties 中进行配置:

server.port=8090
image-20230517101816862

各种手段」包括但不限于:使用 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]

配置文件的位置

配置文件有四个位置可以存放,它们执行的优先级如下:

  1. 根目录下的 config 子目录下
  2. 当前项目的根目录下
  3. resources 下的 config 子目录下
  4. 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 会将部分异常信息直接打印到客户端:

image-20230519152153615

但这样的提示说不上友好,我们可以通过设置 templates 文件夹下的 error 文件夹,来配置提示页面:

image-20230519152409666

此外,还可以支持 40x.html,5xx.html 这样的模糊查询。查询的优先级是:

  1. 完全匹配错误码的错误页面
  2. 匹配模糊的错误页面
  3. err.html
  4. 默认的错误页面

当然,通过 SpringMVC 配置异常处理也是一种可行的方法!

项目打包发布

通过 Maven 实现 Spring Boot 项目的打包发布,可以打包称为 jar、war、pom,其中,pom 类型的打包是 Maven 项目发布时进行的,可供他人继承,这里不重点讨论。

jar 文件打包

jar 文件的打包比较简单:

  1. 设置打包格式为 jar

        <packaging>jar</packaging>
    
  2. 使用 maven 打包

    image-20230519145826695

war 文件打包

war 文件的打包相对繁琐,因为我们不希望打包的 war 文件仍然包含 tomcat 或者 Servlet API 环境,这些在服务器容器已经提供了。而 spring-boot-starter-web的依赖中包含了 tomcat 环境的依赖,所以:

  1. 需要在 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>
    
  2. 将 tomcat 依赖分离出来,然后再以 provided 的形式导入,告知编译器不要将它打包发布。

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-tomcat</artifactId>
        <scope>provided</scope>
    </dependency>
    
  3. 由于,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);
        }
    
    }
    
  4. 完成上述步骤,就可以使用 maven 进行打包发布了。