SpringBoot总结(一)

80 阅读22分钟

前言:本篇文章主要用来记录springboot的一些常见操作,以及常用框架的整合。且本文并非是从零基础开始,所以需要读者有一定的开发经验。

1. 新建一个SpringBoot项目

1.1 Spring 官网新建项目

网站地址 : start.spring.io

image.png

1.2* IDEA 方式新建项目

(下面的各种配置和整合都是以此为根基)

File->New->Project->Spring Initializr

这里注意 : Default和Custom两个选项

Default(默认) : start.spring.io -> 有的时候Spring官网会有一些卡顿

Custom(自定义) : start.aliyun.com -> 阿里云的镜像地址

image.png

image.png

image.png

image.png

新建之后,首先将application.properties文件改成application.yml并在里面添加服务端口号

server:
  port: 8080

然后创建TestController,内容为

@RestController
@RequestMapping("/tm")
public class TestController {
    @GetMapping("/getMsg")
    public String getMsg(){
        return "123";
    }
}

pom.xml内容为:

<?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.7.6</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.learn</groupId>
    <artifactId>tm</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>tianmeng</name>
    <description>Spring Boot Learn</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </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>

启动项目,浏览器访问地址:http://localhost:8080/tm/getMsg 得到字符串123说明没什么问题了。

image.png

2. SpringBoot 整合 jdbcTemplate

2.1 单数据源

首先准备数据库learn,表user,一条数据,mysql数据库版本为5.7.x

CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `name` varchar(255) DEFAULT NULL COMMENT '姓名',
  `age` int(11) DEFAULT NULL COMMENT '年龄',
  `tel` varchar(255) DEFAULT NULL COMMENT '电话',
  `sex` int(11) DEFAULT NULL COMMENT '性别 0:女 1:男',
  `address` varchar(255) DEFAULT NULL COMMENT '地址',
  `email` varchar(255) DEFAULT NULL COMMENT '邮箱',
  `birthday` date DEFAULT NULL COMMENT '出生日期',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

INSERT INTO `learn`.`user`(`id`, `name`, `age`, `tel`, `sex`, `address`, `email`, `birthday`) VALUES (1, '张三', 11, '13712121212', 1, '吉林省长春市', '136117182@qq.com', '2022-12-12');

pom.xml文件内容添加jdbc和mysql的驱动

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.47</version>
    <scope>runtime</scope>
</dependency>

application.yml文件添加数据库相关配置

spring:
  datasource:
    url: jdbc:mysql://192.168.198.100:3306/learn?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true
    driver-class-name: com.mysql.jdbc.Driver
    username: root
    password: 123456

新增用户表对应实体类

@Data
public class User {
    private Long id;
    private String name;
    private Long age;
    private String tel;
    private Long sex;
    private String address;
    private String email;
    private Date birthday;
}

TestController新增访问数据库方法,写完上述内容后,重启服务,访问下面地址:

访问地址:http://localhost:8080/tm/getJdbcTemplateMsg

结果 : [User(id=1, name=张三, age=11, tel=13712121212, sex=1, address=吉林省长春市, email=136117182@qq.com, birthday=2022-12-12 00:00:00.0)]

@GetMapping("/getJdbcTemplateMsg")
public String getJdbcTemplateMsg(){
    List<User> users = jdbcTemplate.query("select id,name,age,tel,sex,address,email,birthday from user", new BeanPropertyRowMapper<>(User.class));
    return users.toString();
}

2.2 多数据源

使用docker新启动一个mysql,端口号为3307,版本5.7.x,新建数据库learn2,新建表user2,与上面单数据源一致,数据为

INSERT INTO `learn2`.`user2`(`id`, `name`, `age`, `tel`, `sex`, `address`, `email`, `birthday`) VALUES (2, '李四', 22, '13712121213', 0, '辽宁省大连市', '136122182@qq.com', '2021-12-11');

pom.xml配置如下

由于这里一会需要开发者自己配置DataSoruce,所以这里必须要使用 druid-spring-boot-starter 依赖,而不是传统的那个 druid 依赖,因为 druid-spring-boot-starter 依赖提供了DruidDataSourceBuilder 类,这个可以用来构建一个 DataSource 实例,而传统的Druid 则没有该类。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.47</version>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
    <version>1.1.10</version>
</dependency>

application.yml配置如下

在 application.properties 中配置数据源,不同于上文,这里的数据源需要配置两个。

这里通过 one 和 two 对数据源进行了区分,但是加了 one 和 two 之后,这里的配置就没法被 SpringBoot 自动加载了(因为前面的 key 变了),需要我们自己去加载DataSource 了,此时,需要 自己配置一个 DataSourceConfig,用 来提供两 个DataSource Bean

spring:
  datasource:
    one:
      url: jdbc:mysql://192.168.198.100:3306/learn?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true
      type: com.alibaba.druid.pool.DruidDataSource
      driver-class-name: com.mysql.jdbc.Driver
      username: root
      password: 123456
    two:
      url: jdbc:mysql://192.168.198.100:3307/learn2?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true
      type: com.alibaba.druid.pool.DruidDataSource
      driver-class-name: com.mysql.jdbc.Driver
      username: root
      password: 123456

新增DataSourceConfig配置类

这里提供了两个 Bean,其中 @ConfigurationProperties 是 Spring Boot 提供的类型安全的属性绑定,以第一个 Bean 为例, @ConfigurationProperties(prefix ="spring.datasource.one") 表示使用spring.datasource.one 前缀的数据库配置去创建一个 DataSource,这样配置之后,我们就有了两个不同的DataSource,接下来再用这两个不同的 DataSource 去创建两个不同的 JdbcTemplate。

import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;

@Configuration
public class DataSourceConfig {
    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.one")
    DataSource dsOne() {
        return DruidDataSourceBuilder.create().build();
    }
    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.two")
    DataSource dsTwo() {
        return DruidDataSourceBuilder.create().build();
    }
}

新增配置类JdbcTemplateConfig

创建 JdbcTemplateConfig 类,用来提供两个不同的 JdbcTemplate 实例 每一个 JdbcTemplate 的创建都需要一个 DataSource,由于 Spring 容器中现在存在两个 DataSource,默认使用类型查找,会报错,因此加上 @Qualifier 注解,表示按照名称查找。这里创建了两个 JdbcTemplate 实例,分别对应了两个 DataSource。接下来直接去使用这个 JdbcTemplate 就可以了。

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import javax.sql.DataSource;

@Configuration
public class JdbcTemplateConfig {
    @Bean
    JdbcTemplate jdbcTemplateOne(@Qualifier("dsOne") DataSource dsOne) {
        return new JdbcTemplate(dsOne);
    }
    @Bean
    JdbcTemplate jdbcTemplateTwo(@Qualifier("dsTwo") DataSource dsTwo) {
        return new JdbcTemplate(dsTwo);
    }
}

测试类TestController

和 DataSource 一样,Spring 容器中的 JdbcTemplate 也是有两个,因此不能通过byType 的方式注入进来,这里给大伙提供了两种注入思路,一种是使用 @Resource注解,直接通过 byName 的方式注入进来,另外一种就是 @Autowired 注解加上@Qualifier 注解,两者联合起来,实际上也是 byName。将 JdbcTemplate 注入进来之后,jdbcTemplateOne 和 jdbcTemplateTwo 此时就代表操作不同的数据源,使用不同的 JdbcTemplate 操作不同的数据源,实现了多数据源配置。

@RestController
@RequestMapping("/tm")
public class TestController {
    @Autowired
    @Qualifier("jdbcTemplateOne")
    private JdbcTemplate jdbcTemplateOne;

    @Resource(name = "jdbcTemplateTwo")
    private JdbcTemplate jdbcTemplateTwo;

    @GetMapping("/getJdbcTemplateOneMsg")
    public String getJdbcTemplateOneMsg(){
        List<User> users = jdbcTemplateOne.query("select * from user", new BeanPropertyRowMapper<>(User.class));
        return users.toString();
    }

    @GetMapping("/getJdbcTemplateTwoMsg")
    public String getJdbcTemplateTwoMsg(){
        List<User2> users = jdbcTemplateTwo.query("select * from user2", new BeanPropertyRowMapper<>(User2.class));
        return users.toString();
    }
}

3. SpringBoot 整合 mybatis

3.1 单数据源

项目目录 : image.png 代码 : image.png

image.png

3.1.1 pom.xml引入依赖

这里新增了mybatis的相关依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.47</version>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
    <version>1.1.10</version>
</dependency>
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.0.0</version>
</dependency>

3.1.2 application.yml配置

这里需要添加mybatis对应的xml文件扫描的路径配置,是因为我们的xml文件没有跟mapper接口放到一个文件夹下。

spring:
  datasource:
    url: jdbc:mysql://192.168.198.100:3306/learn?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.jdbc.Driver
    username: root
    password: 123456
mybatis:
  mapper-locations: classpath:mapper/*.xml
  #此配置可以使得控制台打印出执行的sql
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

这里要注意

1.xml文件放到resource下,不会被扫描到
需要在application.yml配置mapper-locations: classpath:mapper/*.xml来指定扫描位置

2.xml文件放到java目录下,比如跟mapper接口同目录,是会被扫描到的,不需要配置扫描位置。
但是有另外一个 Maven 带来的问题, 就是 java 目录下的 xml 资源在项目打包时会被忽略掉。
所以,,需要在 pom.xml 文件中再添加如下配置,避免打包时 java 目录下的 XML 文件被自动忽略掉
<build>
  <resources>
    <resource>
      <directory>src/main/java</directory>
    	<includes>
    	  <include>**/*.xml</include>
    	</includes>
    </resource>
    <resource>
      <directory>src/main/resources</directory>
    </resource>
  </resources>
</build>

3.1.3 新增mapper接口

public interface UserMapper {
    List<User> getAllUser();
}

3.1.4 resource下新增mapper文件夹,里面添加mapper.xml文件

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.learn.tm.mapper.UserMapper">
    <select id="getAllUser" resultType="com.learn.tm.entity.User">
        select * from user
    </select>
</mapper>

3.1.5 启动类上新增@MapperScan扫描注解

这里配置上mapper扫描的路径,如果不配置,则需要在所有的mapper接口上添加@Mapper注解,遗漏任何一个就会报错,所以建议启动类上添加扫描注解

@SpringBootApplication
@MapperScan(basePackages = "com.learn.tm.mapper")
public class TianmengApplication {
    public static void main(String[] args) {
        SpringApplication.run(TianmengApplication.class, args);
    }
}

3.1.6 测试controller里添加

@RestController
@RequestMapping("/tm")
public class TestController {
    @Autowired
    private UserMapper userMapper;

    @GetMapping("getMybatisUserMsg")
    public List<User> getMybatisUserMsg(){
        return userMapper.getAllUser();
    }
}

浏览器访问 : http://localhost:8080/tm/getMybatisUserMsg

3.2 多数据源

代码目录:

image.png

image.png

image.png

image.png

image.png

3.2.1 数据源配置

# --------------------------1.DataSourceConfig--------------------------
@Configuration
public class DataSourceConfig {
    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.one")
    DataSource dsOne() {
        return DruidDataSourceBuilder.create().build();
    }
    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.two")
    DataSource dsTwo() {
        return DruidDataSourceBuilder.create().build();
    }
}
# --------------------------2.MybatisConfigOne--------------------------
@Configuration
@MapperScan(basePackages = "com.learn.tm.mapper1",
        sqlSessionFactoryRef = "sqlSessionFactory1",
        sqlSessionTemplateRef = "sqlSessionTemplate1")
public class MybatisConfigOne {

    @Resource(name = "dsOne")
    DataSource dsOne;

    @Bean
    SqlSessionFactory sqlSessionFactory1() {
        SqlSessionFactory sessionFactory = null;
        try {
            SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
            bean.setDataSource(dsOne);
            bean.setMapperLocations(new PathMatchingResourcePatternResolver()
                    .getResources("classpath*:mapper1/*.xml"));
            sessionFactory = bean.getObject();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return sessionFactory;
    }
    @Bean
    SqlSessionTemplate sqlSessionTemplate1() {
        return new SqlSessionTemplate(sqlSessionFactory1());
    }
}
# --------------------------3.MybatisConfigTwo--------------------------
@Configuration
@MapperScan(basePackages = "com.learn.tm.mapper2",
        sqlSessionFactoryRef = "sqlSessionFactory2",
        sqlSessionTemplateRef = "sqlSessionTemplate2")
public class MybatisConfigTwo {

    @Resource(name = "dsTwo")
    DataSource dsTwo;

    @Bean
    SqlSessionFactory sqlSessionFactory2() {
        SqlSessionFactory sessionFactory = null;
        try {
            SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
            bean.setDataSource(dsTwo);
            bean.setMapperLocations(new PathMatchingResourcePatternResolver()
                    .getResources("classpath*:mapper2/*.xml"));
            sessionFactory = bean.getObject();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return sessionFactory;
    }
    @Bean
    SqlSessionTemplate sqlSessionTemplate2() {
        return new SqlSessionTemplate(sqlSessionFactory2());
    }
}

3.2.2 接口类

# --------------------------1.UserMapperOne--------------------------
public interface UserMapperOne {
    List<User> getAllUser();
}
# --------------------------2.UserMapperTwo--------------------------
public interface UserMapperTwo {
    List<User> getAllUser();
}
# --------------------------3.UserMapperOne.xml--------------------------
<mapper namespace="com.learn.tm.mapper1.UserMapperOne">
    <select id="getAllUser" resultType="com.learn.tm.entity.User">
        select * from user
    </select>
</mapper>
# --------------------------3.UserMapperTwo.xml--------------------------
<mapper namespace="com.learn.tm.mapper2.UserMapperTwo">
    <select id="getAllUser" resultType="com.learn.tm.entity.User2">
        select * from user2
    </select>
</mapper>

3.2.3 配置文件

# --------------------------1.application.yml--------------------------
spring:
  datasource:
    one:
      url: jdbc:mysql://192.168.198.100:3306/learn?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true
      type: com.alibaba.druid.pool.DruidDataSource
      driver-class-name: com.mysql.jdbc.Driver
      username: root
      password: 123456
    two:
      url: jdbc:mysql://192.168.198.100:3307/learn2?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true
      type: com.alibaba.druid.pool.DruidDataSource
      driver-class-name: com.mysql.jdbc.Driver
      username: root
      password: 123456
mybatis:
  mapper-locations: classpath:mapper1/*.xml,classpath:mapper2/*.xml
  #此配置可以使得控制台打印出执行的sql
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    
# --------------------------2.pom.xml--------------------------
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.47</version>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
    <version>1.1.10</version>
</dependency>
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.0.0</version>
</dependency>

3.2.4 测试类

@RestController
@RequestMapping("/tm")
public class TestController {
    @Autowired
    private UserMapperOne userMapperOne;

    @Autowired
    private UserMapperTwo userMapperTwo;

    @GetMapping("getMybatisOne")
    public List<User> getMybatisOne(){
        return userMapperOne.getAllUser();
    }

    @GetMapping("getMybatisTwo")
    public List<User2> getMybatisTwo(){
        return userMapperTwo.getAllUser();
    }
}

浏览器访问 :

http://localhost:8080/tm/getMybatisOne

http://localhost:8080/tm/getMybatisTwo

4. SpringBoot 整合 mybatis-plus

4.1 单数据源

项目目录 :

image.png

image.png

image.png

4.1.1 配置内容

# --------------------------1.application.yml--------------------------
server:
  port: 8080
spring:
  datasource:
    url: jdbc:mysql://192.168.198.100:3306/learn?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.jdbc.Driver
    username: root
    password: 123456
mybatis-plus:
  mapper-locations: classpath:mapper/*.xml
  configuration:
    map-underscore-to-camel-case: true
    #此配置可以使得控制台打印出执行的sql
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  type-aliases-package: com.learn.tm.entity
  
# --------------------------2.pom.xml--------------------------
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.47</version>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
    <version>1.1.10</version>
</dependency>
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.4.0</version>
</dependency>

# --------------------------3.启动类--------------------------
@SpringBootApplication
@MapperScan(basePackages = "com.learn.tm.mapper")
public class TianmengApplication {
    public static void main(String[] args) {
        SpringApplication.run(TianmengApplication.class, args);
    }
}

4.1.2 业务代码

@RestController
@RequestMapping("/tm")
public class TestController {

    @Autowired
    private UserMapper userMapper;

    @GetMapping("getMybatisPlusMsg")
    public List<User> getMybatisPlusMsg(){
        return userMapper.selectList(new QueryWrapper<>());
    }

}


@Data
@TableName("user")
public class User {
    @TableId(value = "id",type = IdType.AUTO)
    private Long id;
    @TableField("name")
    private String name;
    private Long age;
    private String tel;
    private Long sex;
    private String address;
    private String email;
    private Date birthday;
}


@Mapper
public interface UserMapper extends BaseMapper<User> {
}

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.learn.tm.mapper.UserMapper">
</mapper>

4.2 多数据源

主要通过在service的实现类上使用 @DS("xxx") 注解来实现。

项目目录 :

image.png

image.png

image.png

image.png

数据准备

  • 数据库1 : 数据库名learn 表user 1条数据

  • 数据库2 : 数据库名learn2 表product 1条数据

CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `name` varchar(255) DEFAULT NULL COMMENT '姓名',
  `age` int(11) DEFAULT NULL COMMENT '年龄',
  `tel` varchar(255) DEFAULT NULL COMMENT '电话',
  `sex` int(11) DEFAULT NULL COMMENT '性别 0:女 1:男',
  `address` varchar(255) DEFAULT NULL COMMENT '地址',
  `email` varchar(255) DEFAULT NULL COMMENT '邮箱',
  `birthday` date DEFAULT NULL COMMENT '出生日期',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;

CREATE TABLE `product` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) DEFAULT NULL,
  `price` int(10) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;

4.2.1 配置内容

# --------------------------1.application.yml--------------------------
server:
  port: 8080
spring:
  datasource:
    dynamic:
      # 设置默认的数据源或者数据源组,默认值即为master
      primary: master
      # 严格匹配数据源,默认false.true未匹配到指定数据源时抛异常,false使用默认数据源
      strict: false
      datasource:
        master:
          url: jdbc:mysql://192.168.198.100:3306/learn?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true
          type: com.alibaba.druid.pool.DruidDataSource
          driver-class-name: com.mysql.jdbc.Driver
          username: root
          password: 123456
        slave_1:
          url: jdbc:mysql://192.168.198.100:3307/learn2?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true
          type: com.alibaba.druid.pool.DruidDataSource
          driver-class-name: com.mysql.jdbc.Driver
          username: root
          password: 123456

mybatis-plus:
  mapper-locations: classpath:mapper/*.xml
  configuration:
    map-underscore-to-camel-case: true
    #此配置可以使得控制台打印出执行的sql
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  type-aliases-package: com.learn.tm.entity
  
# --------------------------2.pom.xml--------------------------

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.47</version>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
    <version>1.1.10</version>
</dependency>
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.4.0</version>
</dependency>
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
    <version>3.5.0</version>
</dependency>

# --------------------------3.启动类--------------------------
@SpringBootApplication
@MapperScan(basePackages = "com.learn.tm.mapper")
public class TianmengApplication {
    public static void main(String[] args) {
        SpringApplication.run(TianmengApplication.class, args);
    }
}

4.2.2 业务代码

@RestController
@RequestMapping("/tm")
public class TestController {
    @Autowired
    private UserService userService;
    @Autowired
    private ProductService productService;
    @GetMapping("getMasterMsg")
    public List<User> getMasterMsg(){
        return userService.list();
    }
    @GetMapping("getSlaveMsg")
    public List<Product> getSlaveMsg(){
        return productService.list();
    }
}

@Data
@TableName("product")
public class Product {
    @TableId(value = "id",type = IdType.AUTO)
    private Long id;
    @TableField("name")
    private String name;
    private Integer price;
}

@Data
@TableName("user")
public class User {
    @TableId(value = "id",type = IdType.AUTO)
    private Long id;
    @TableField("name")
    private String name;
    private Long age;
    private String tel;
    private Long sex;
    private String address;
    private String email;
    private Date birthday;
}


public interface UserService extends IService<User> {
}

public interface ProductService extends IService<Product> {
}


@Service
@DS("master")
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
}

@Service
@DS("slave_1")
public class ProductServiceImpl extends ServiceImpl<ProductMapper, Product> implements ProductService {
}

public interface UserMapper extends BaseMapper<User> {
}

public interface ProductMapper extends BaseMapper<Product> {
}

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.learn.tm.mapper.UserMapper"></mapper>

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.learn.tm.mapper.ProductMapper"></mapper>

浏览器访问 :

5. SpringBoot 配置https

5.1 https证书生成

方法1 : 可以在各个云服务提供商那里申请一个免费的证书。有效期一年,可以申请20个。

方法2 : 可以直接借助 Java 自带的 JDK 管理工具 keytool 来生成一个免费的 https 证书。

本文采用第二种方式,首先进入到 %JAVVA_HOME%\bin 目录下,执行如下命令生成一个数字证书:

keytool -genkey -alias tomcathttps -keyalg RSA -keysize 2048 -keystore D:\javatm.p12 -validity 365

命令含义如下: 
# genkey 表示要创建一个新的密钥。 
# alias 表示 keystore 的别名。 
# keyalg 表示使用的加密算法是 RSA ,一种非对称加密算法。 
# keysize 表示密钥的长度。 
# keystore 表示生成的密钥存放位置。 
# validity 表示密钥的有效时间,单位为天。

* 这里要注意alias要记住需要和application.yml里的配置(server.ssl.key-alias)相对应

具体步骤如下

D:\soft\jdk\bin>keytool -genkey -alias tomcathttps -keyalg RSA -keysize 2048 -keystore D:\javatm.p12 -validity 365
输入密钥库口令: # 这里口令输入为123456,这个口令要记住 需要与application.yml中的server.ssl.key-store-password 配置对应
再次输入新口令:
您的名字与姓氏是什么?
  [Unknown]:  1
您的组织单位名称是什么?
  [Unknown]:  1
您的组织名称是什么?
  [Unknown]:  1
您所在的城市或区域名称是什么?
  [Unknown]:  1
您所在的省/市/自治区名称是什么?
  [Unknown]:  1
该单位的双字母国家/地区代码是什么?
  [Unknown]:  1
CN=1, OU=1, O=1, L=1, ST=1, C=1是否正确?
  [否]:  y

输入 <tomcathttps> 的密钥口令
        (如果和密钥库口令相同, 按回车): # 这里直接回车

Warning:
JKS 密钥库使用专用格式。建议使用 "keytool -importkeystore -srckeystore D:\javatm.p12 -destkeystore D:\javatm.p12 -deststoretype pkcs12" 迁移到行业标准格式 PKCS12。

D:\soft\jdk\bin>

image.png

然后在D盘下就可以看到名字为javatm.p12的文件了

5.2 项目配置

将刚生成的文件(javatm.p12)放入项目的resource下。然后在application.yml中配置如下内容:

server:
  ssl:
    key-alias: tomcathttps
    key-store: classpath:javatm.p12
    key-store-password: 123456
    

# key-store 表示密钥文件名。 -> 与生成的密钥文件名要一致
# key-alias 表示密钥别名。 -> 与刚才执行的命令中的别名要一致
# key-store-password 就是在 cmd 命令执行过程中输入的密码。123456

重启服务,随便调用一个接口 如果访问url是http会提示

http://localhost:8080/tm/getMsg

Bad Request
This combination of host and port requires TLS.

改成https,效果如下,点击高级即可成功调用接口

这是因为我们自己生成的 https 证书不被浏览器认可,不过没关系,我们直接点击继续访问就可以了(实际项目中只需要更换一个被浏览器认可的 https 证书即可)

https://localhost:8080/tm/getMsg

image.png

5.3 配置允许http访问

因为Spring Boot 不支持同时启动 HTTP 和 HTTPS ,为了解决这个问题,我们这 里可以配置一个请求转发,当用户发起 HTTP 调用时,自动转发到 HTTPS 上。

新增配置类 :

在这里,我们配置了 Http 的请求端口为 8081,所有来自 8081 的请求,将被自动重定 向到 8080 这个 https 的端口上。 如此之后,我们再去访问 http 请求,就会自动重定向到 https。

@Configuration
public class TomcatConfig {
    @Bean
    TomcatServletWebServerFactory
    tomcatServletWebServerFactory() {
        TomcatServletWebServerFactory factory = new
                TomcatServletWebServerFactory(){
                    @Override
                    protected void postProcessContext(Context context) {
                        SecurityConstraint constraint = new
                                SecurityConstraint();
                        constraint.setUserConstraint("CONFIDENTIAL");
                        SecurityCollection collection = new SecurityCollection();
                        collection.addPattern("/*");
                        constraint.addCollection(collection);
                        context.addConstraint(constraint);
                    }
                };

        factory.addAdditionalTomcatConnectors(createTomcatConnector());
        return factory;
    }
    private Connector createTomcatConnector() {
        Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol");
        connector.setScheme("http");
        connector.setPort(8081);
        connector.setSecure(false);
        connector.setRedirectPort(8080);
        return connector;
    }
}

浏览器访问 : http://localhost:8081/tm/getMsg 即可看到效果

6. SpringBoot 返回结果封装

6.1 Result

public class Result<T> implements Serializable {
    private static final long serialVersionUID = 1L;
    /**
     * 编码:0表示成功,其他值表示失败
     */
    private int code = 0;
    /**
     * 消息内容
     */
    private String msg = "操作成功";
    /**
     * 响应数据
     */
    private T data;

    public Result<T> ok() {
        return this;
    }

    public Result<T> ok(T data) {
        this.setData(data);
        return this;
    }

    public Result<T> ok(T data,String msg) {
        this.setData(data);
        this.setMsg(msg);
        return this;
    }

    public Result<T> error() {
        this.code = ErrorCode.INTERNAL_SERVER_ERROR;
        this.msg = MessageUtils.getMessage(this.code);
        return this;
    }

    public Result<T> error(int code) {
        this.code = code;
        this.msg = MessageUtils.getMessage(this.code);
        return this;
    }

    public Result<T> error(String msg) {
        this.code = ErrorCode.INTERNAL_SERVER_ERROR;
        this.msg = msg;
        return this;
    }

    public Result<T> error(int code, String msg) {
        this.code = code;
        this.msg = msg;
        return this;
    }

    public Result<T> error(T data,String msg) {
        this.code = ErrorCode.INTERNAL_SERVER_ERROR;
        this.setData(data);
        this.setMsg(msg);
        return this;
    }

    public Result<T> error(int code,T data,String msg) {
        this.code = ErrorCode.INTERNAL_SERVER_ERROR;
        this.setData(data);
        this.setMsg(msg);
        return this;
    }

    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }
}

6.2 ErrorCode

public interface ErrorCode {
    int INTERNAL_SERVER_ERROR = 500;
    int UNAUTHORIZED = 401;
    int FORBIDDEN = 403;
    int NOT_NULL = 10001;
    int DB_RECORD_EXISTS = 10002;
    int PARAMS_GET_ERROR = 10003;
    int ACCOUNT_PASSWORD_ERROR = 10004;
    int ACCOUNT_DISABLE = 10005;
    int IDENTIFIER_NOT_NULL = 10006;
    int CAPTCHA_ERROR = 10007;
    int SUB_MENU_EXIST = 10008;
    int PASSWORD_ERROR = 10009;
    int ACCOUNT_NOT_EXIST = 10010;
    int SUPERIOR_DEPT_ERROR = 10011;
    int SUPERIOR_MENU_ERROR = 10012;
    int DATA_SCOPE_PARAMS_ERROR = 10013;
    int DEPT_SUB_DELETE_ERROR = 10014;
    int DEPT_USER_DELETE_ERROR = 10015;
    int ACT_DEPLOY_ERROR = 10016;
    int ACT_MODEL_IMG_ERROR = 10017;
    int ACT_MODEL_EXPORT_ERROR = 10018;
    int UPLOAD_FILE_EMPTY = 10019;
    int TOKEN_NOT_EMPTY = 10020;
    int TOKEN_INVALID = 10021;
    int ACCOUNT_LOCK = 10022;
    int TENANT_NOEXIST=100221;
    int TENANT_EXPIRSE=100222;
    int ACT_DEPLOY_FORMAT_ERROR = 10023;
    int OSS_UPLOAD_FILE_ERROR = 10024;
    int SEND_SMS_ERROR = 10025;
    int MAIL_TEMPLATE_NOT_EXISTS = 10026;
    int REDIS_ERROR = 10027;
    int JOB_ERROR = 10028;
    int INVALID_SYMBOL = 10029;
    int JSON_FORMAT_ERROR = 10030;
    int SMS_CONFIG = 10031;
    int TASK_CLIME_FAIL = 10032;
    int NONE_EXIST_PROCESS = 10033;
    int SUPERIOR_NOT_EXIST = 10034;
    int REJECT_MESSAGE = 10035;
    int ROLLBACK_MESSAGE = 10036;
    int UNCLAIM_ERROR_MESSAGE = 10037;
    int SUPERIOR_REGION_ERROR = 10038;
    int REGION_SUB_DELETE_ERROR = 10039;
    int PROCESS_START_ERROR = 10040;
    int REJECT_PROCESS_PARALLEL_ERROR = 10041;
    int REJECT_PROCESS_HANDLEING_ERROR = 10042;
    int END_PROCESS_PARALLEL_ERROR = 10043;
    int END_PROCESS_HANDLEING_ERROR = 10044;
    int END_PROCESS_MESSAGE = 10045;
    int BACK_PROCESS_PARALLEL_ERROR = 10046;
    int BACK_PROCESS_HANDLEING_ERROR = 10047;
}

6.3 MessageUtils

public class MessageUtils {
    private static MessageSource messageSource;
    static {
        messageSource = (MessageSource)SpringContextUtils.getBean("messageSource");
    }

    public static String getMessage(int code){
        return getMessage(code, new String[0]);
    }

    public static String getMessage(int code, String... params){
        return messageSource.getMessage(code+"", params, LocaleContextHolder.getLocale());
    }
}

6.4 SpringContextUtils

@Component
public class SpringContextUtils implements ApplicationContextAware {
    public static ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext)
            throws BeansException {
        SpringContextUtils.applicationContext = applicationContext;
    }

    public static Object getBean(String name) {
        return applicationContext.getBean(name);
    }

    public static <T> T getBean(Class<T> requiredType) {
        return applicationContext.getBean(requiredType);
    }

    public static <T> T getBean(String name, Class<T> requiredType) {
        return applicationContext.getBean(name, requiredType);
    }

    public static boolean containsBean(String name) {
        return applicationContext.containsBean(name);
    }

    public static boolean isSingleton(String name) {
        return applicationContext.isSingleton(name);
    }

    public static Class<? extends Object> getType(String name) {
        return applicationContext.getType(name);
    }

}

6.5 测试类

@RestController
@RequestMapping("/tm")
public class TestController {

    @Autowired
    private UserService userService;

    @GetMapping("getMsg")
    public Result<List<User>> getMsg(){
        return new Result<List<User>>().ok(userService.list());
    }

}

浏览器访问 : http://localhost:8080/tm/getMsg

效果 :

image.png

7. SpringBoot 全局异常处理

此处异常处理要借助上面返回结果封装中的Result、ErrorCode类,实际开发中,可以换成自己公司框架里的公共返回类

7.1 TmException

public class TmException extends RuntimeException {

    private static final long serialVersionUID = 1L;

    private int code;
    private String msg;

    public TmException(int code) {
        this.code = code;
        this.msg = MessageUtils.getMessage(code);
    }

    public TmException(int code, String... params) {
        this.code = code;
        this.msg = MessageUtils.getMessage(code, params);
    }

    public TmException(int code, Throwable e) {
        super(e);
        this.code = code;
        this.msg = MessageUtils.getMessage(code);
    }

    public TmException(int code, Throwable e, String... params) {
        super(e);
        this.code = code;
        this.msg = MessageUtils.getMessage(code, params);
    }

    public TmException(String msg) {
        super(msg);
        this.code = ErrorCode.INTERNAL_SERVER_ERROR;
        this.msg = msg;
    }

    public TmException(String msg, Throwable e) {
        super(msg, e);
        this.code = ErrorCode.INTERNAL_SERVER_ERROR;
        this.msg = msg;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }
}

7.2 TmExceptionHandler

这里要注意 :

(1) 代码里通过 throw new TmException("自定义异常"); 这种方式抛出异常的,会执行 handleTmException 方法。

(2) 代码里 int i = 1/0; 这种方式没有在TmExceptionHandler类中定义捕获的异常,会统一进入到 handleException 方法中 。

(3) 代码里例如报空指针类的错,在TmExceptionHandler类中被定义,会直接进入到对应异常的方法 handleNullPointerException 中 。

@Slf4j
@RestControllerAdvice
public class TmExceptionHandler {

    /**
     * 处理自定义异常
     */
    @ExceptionHandler(TmException.class)
    public Result handleTmException(TmException ex){
        Result result = new Result();
        result.error(ex.getCode(), ex.getMsg());
        return result;
    }

    @ExceptionHandler(DuplicateKeyException.class)
    public Result handleDuplicateKeyException(DuplicateKeyException ex){
        Result result = new Result();
        result.error(ErrorCode.DB_RECORD_EXISTS);
        return result;
    }

    @ExceptionHandler(NullPointerException.class)
    public Result handleNullPointerException(NullPointerException ex){
        Result result = new Result();
        result.error(ErrorCode.NOT_NULL,ex.getMessage());
        return result;
    }

    @ExceptionHandler(Exception.class)
    public Result handleException(Exception ex){
        log.error(ex.getMessage(),ex);
        Result result = new Result();

        //Whitelabel Error Page
        //This application has no explicit mapping for /error, so you are seeing this as a fallback.
        //Mon Dec 26 14:09:00 CST 2022
        //There was an unexpected error (type=Internal Server Error, status=500).
        
        //(没被封装)
        //return result.error();

        //(会被封装)页面显示 : {"code":500,"msg":"/ by zero","data":null}
        return result.error(ex.getMessage());
    }

}

这里要注意各个异常方法的返回值参数

没有被封装会显示 :

image.png

被封装显示 :

image.png

7.3 测试类

@GetMapping("getMsg")
    public Result<List<User>> getMsg(){
        //测试1
        int i = 1/0;

        //测试2
        List list = null;
        list.contains("2");

        //测试3
        throw new TmException("自定义异常");
    }

8. 基于注解的日志处理

如果在执行方法的方法体中每次都写上saveLog()的方法,代码不美观,可以使用基于注解的方式来记录日志。

基于注解和AOP实现的自定义日志系统。只需要两个类就能实现:

  1. 注解类:设置自定义属性属性

  2. 切面类:用于横切注解,获取注解属性值,保存日志

8.1 依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

8.2 注解类

@Target({ElementType.TYPE, ElementType.METHOD})//目标是方法
@Retention(RetentionPolicy.RUNTIME)//注解会在class中存在,运行时可通过反射获取
//@Inherited
@Documented
public @interface SysLog {

    /**
     * 描述
     */
    String description() default "";

    /**
     * 行为类型
     * 1.违规行为;2.异常行为;3 一般行为
     */
    String behaviourType() default "3";

    /**
     * 日志风险级别
     * 1紧急、2重要、3一般、4信息
     */
    String level() default "4";
}

8.3 切面类

import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Aspect//声明这是一个事务切面
@Slf4j
@Component//让其被spring管理
public class SysLogAspect {

    //声明切点
    @Pointcut("@annotation(com.learn.tm.log.SysLog)")
    public void logPointCut(){}

    @Around("@annotation(sysLog)")
    @SneakyThrows
    public void around(ProceedingJoinPoint point, SysLog sysLog) {
        //-----------环绕通知开始-----------

        //保存日志的逻辑

        Object obj = point.proceed();

        //-----------环绕通知结束-----------

        //根据obj结果更新日志逻辑

    }

    //此外还可以加上前置通知 后置通知等
    @Before("logPointCut()")
    public void doBefore(JoinPoint point){
        //可通过point获取方法名和类名
        String className = point.getTarget().getClass().getName();
        String methodName = point.getSignature().getName();
    }

    @After("logPointCut()")
    public void doAfter(JoinPoint point){

    }
}

8.4 测试类

@GetMapping("getMsg")
@SysLog(description = "接口日志记录")
public Result<List<User>> getMsg(){
    return new Result<List<User>>().ok(userService.list());
}

9. 多环境切换 profile

9.1 方式1

9.1.1 创建多环境文件

application.yml、application-dev.yml、application-prod.yml、application-test.yml

在application-dev.yml、application-prod.yml、application-test.yml文件中分别添加一样的配置,但是值不同

# application-dev.yml
txtName: dev

# application-prod.yml
txtName: prod

# application-test.yml
txtName: test

image.png

9.1.2 application.yml配置

即默认使用的是dev环境下的配置

spring:
  profiles:
    active: dev

9.1.3 测试类

结果是 : 切换成dev时,输出字符串为dev。如果切换成prod,输出字符串为prod。以此类推

@RestController
@RequestMapping("/tm")
public class TestController {
    @Value("${txtName}")
    private String txtName;

    @GetMapping("getMsg")
    public Result<String> getMsg(){
        System.out.println(txtName);
        return new Result<String>().ok(txtName);
    }
}

9.2 方式2

9.2.1 创建多环境文件

application.yml、application-dev.yml、application-prod.yml、application-test.yml

在application-dev.yml、application-prod.yml、application-test.yml文件中分别添加一样的配置,但是值不同

# application-dev.yml
txtName: dev

# application-prod.yml
txtName: prod

# application-test.yml
txtName: test

image.png

9.2.2 application.yml配置

spring:
  profiles:
    active: @profile.active@

9.2.3 pom.xml配置

这里注意 ,如果不配置resources,可能会导致启动报错。此处默认使用dev环境

<profiles>
    <profile>
        <id>dev</id>
        <activation>
            <activeByDefault>true</activeByDefault>
        </activation>
        <properties>
            <profile.active>dev</profile.active>
        </properties>
    </profile>
    <profile>
        <id>prod</id>
        <properties>
            <profile.active>prod</profile.active>
        </properties>
    </profile>
    <profile>
        <id>test</id>
        <properties>
            <profile.active>test</profile.active>
        </properties>
    </profile>
</profiles>

<build>
    <resources>
        <resource>
            <directory>src/main/resources</directory>
            <!--可以在此配置过滤文件  -->
            <includes>
                <include>**/*.yml</include>
            </includes>
            <!--开启filtering功能  -->
            <filtering>true</filtering>
        </resource>
    </resources>
</build>

配置完成后刷新右侧maven窗口,可以看到出现了对应的配置,此处可以动态切换。

image.png

10. 日志输出

本节全文参考 :

juejin.cn/post/684490…

zhuanlan.zhihu.com/p/555185411

blog.csdn.net/hansome_hon…

日志级别从低到高分为TRACE < DEBUG < INFO < WARN < ERROR < FATAL,如果设置为WARN,则低于WARN的信息都不会输出。

SpringBoot 配置文件的加载顺序

 logback.xml—>application.properties—>logback-spring.xml

如果同时存在logback.xml和logback-spring.xml,或者同时存在logback.xml和自定义的配置文件,则会先加载logback.xml,再根据application配置加载指定配置文件,或加载logback-spring,xml。如果这两个配置文件的contextName不同,就会报错

10.1 默认输出配置

只需要在application.yml中配置如下内容,即可实现默认文件日志输出 :

  1. logging.file,设置文件,可以是绝对路径(d://tm.log),也可以是相对路径(tm.log)。
  2. logging.path,设置目录,会在该目录下创建spring.log文件,并写入日志内容,如:logging.path=/var/log

如果只配置 logging.file,会在项目的当前路径下生成一个 xxx.log 日志文件。 如果只配置 logging.path,在 /var/log文件夹生成一个日志文件为 spring.log。

注:二者不能同时使用,如若同时使用,则只有logging.file生效

logging:
  file:
    # 或 d://tm.log
    name: tm.log
    path: d://log

测试类如下 :

@Slf4j
@RestController
@RequestMapping("/tm")
public class TestController {
    @GetMapping("getMsg")
    public Result<String> getMsg(){
        log.trace("---------trace");
        log.debug("---------debug");
        log.info("---------info");
        log.warn("---------warn");
        log.error("---------error");
        return new Result<String>().ok();
    }
}

结果: 可以看到默认输出的是三个级别的日志info、warn、error

image.png

2022-12-28 09:04:23.295  INFO 7752 --- [main] com.learn.tm.TianmengApplication         : Starting TianmengApplication using Java 1.8.0_181 on DESKTOP-27LQCSL with PID 7752 (D:\code\learn\SpringBoot-Learn\target\classes started by tianmeng in D:\code\learn\SpringBoot-Learn)
2022-12-28 09:04:23.298  INFO 7752 --- [main] com.learn.tm.TianmengApplication         : The following 1 profile is active: "dev"
2022-12-28 09:04:24.429  INFO 7752 --- [main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2022-12-28 09:04:24.438  INFO 7752 --- [main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2022-12-28 09:04:24.439  INFO 7752 --- [main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.69]
2022-12-28 09:04:24.690  INFO 7752 --- [main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2022-12-28 09:04:24.690  INFO 7752 --- [main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1336 ms
2022-12-28 09:04:24.856  INFO 7752 --- [main] c.a.d.s.b.a.DruidDataSourceAutoConfigure : Init DruidDataSource
2022-12-28 09:04:25.059  INFO 7752 --- [main] com.alibaba.druid.pool.DruidDataSource   : {dataSource-1} inited
2022-12-28 09:04:26.012  INFO 7752 --- [main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2022-12-28 09:04:26.021  INFO 7752 --- [main] com.learn.tm.TianmengApplication         : Started TianmengApplication in 3.329 seconds (JVM running for 4.33)
2022-12-28 09:04:32.903  INFO 7752 --- [http-nio-8080-exec-3] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring DispatcherServlet 'dispatcherServlet'
2022-12-28 09:04:32.903  INFO 7752 --- [http-nio-8080-exec-3] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
2022-12-28 09:04:32.904  INFO 7752 --- [http-nio-8080-exec-3] o.s.web.servlet.DispatcherServlet        : Completed initialization in 1 ms
2022-12-28 09:04:32.924  INFO 7752 --- [http-nio-8080-exec-3] com.learn.tm.controller.TestController   : ---------info
2022-12-28 09:04:32.924  WARN 7752 --- [http-nio-8080-exec-3] com.learn.tm.controller.TestController   : ---------warn
2022-12-28 09:04:32.924 ERROR 7752 --- [http-nio-8080-exec-3] com.learn.tm.controller.TestController   : ---------error

10.2 自定义输出级别

只需要在application.yml中配置如下内容,即可实现文件日志输出级别控制 :

即com.learn.tm包下所有类的日志输出级别均为warn及以上

logging:
  level:
    com.learn.tm: warn

使用上面测试类继续测试,可以看到结果为只有warn和error输出了。

2022-12-28 09:25:46.667  WARN 22368 --- [nio-8080-exec-1] com.learn.tm.controller.TestController   : ---------warn
2022-12-28 09:25:46.667 ERROR 22368 --- [nio-8080-exec-1] com.learn.tm.controller.TestController   : ---------error

如果配置如下,即不指定具体包,可能会导致启动报错

logging:
  level:warn

10.3 自定义日志配置

由于日志服务一般都在ApplicationContext创建前就初始化了,它并不是必须通过Spring的配置文件控制。因此通过系统属性和传统的Spring Boot外部配置文件依然可以很好的支持日志控制和管理。 根据不同的日志系统,你可以按如下规则组织配置文件名,就能被正确加载:

# Logback
logback-spring.xml, logback-spring.groovy, logback.xml, logback.groovy
# Log4j
log4j-spring.properties, log4j-spring.xml, log4j.properties, log4j.xml
# Log4j2
log4j2-spring.xml, log4j2.xml
# JDK (Java Util Logging)
logging.properties

Spring Boot官方推荐优先使用带有-spring的文件名作为你的日志配置(如使用logback-spring.xml,而不是logback.xml),命名为logback-spring.xml的日志配置文件,spring boot可以为它添加一些spring boot特有的配置项(下面会提到)。

上面是默认的命名规则,并且放在src/main/resources下面即可。

如果你即想完全掌控日志配置,但又不想用logback.xml作为Logback配置的名字,可以通过logging.config属性指定自定义的名字:

logging.config=classpath:logging-config.xml

下面我们来看看一个普通的logback-spring.xml例子

注意logback-spring.xml文件要放到resources目录下

这里有几个配置

(1) application.yml

logging:
  config: classpath:logback-spring.xml

(2) pom.xml

<resources>
    <resource>
        <directory>src/main/resources</directory>
        <!--可以在此配置过滤文件  -->
        <includes>
            <include>logback-spring.xml</include>
        </includes>
        <!--开启filtering功能  -->
        <filtering>true</filtering>
    </resource>
</resources>

经过测试发现,如果只配置了application.yml,会报错,报错如下:

Logging system failed to initialize using configuration from 'classpath:logback-spring.xml'
java.io.FileNotFoundException: class path resource [logback-spring.xml] cannot be resolved to URL because it does not exist
	at org.springframework.util.ResourceUtils.getURL(ResourceUtils.java:137)
	at org.springframework.boot.logging.logback.LogbackLoggingSystem.loadConfiguration(LogbackLoggingSystem.java:165)
	at org.springframework.boot.logging.AbstractLoggingSystem.initializeWithSpecificConfig(AbstractLoggingSystem.java:66)
	at org.springframework.boot.logging.AbstractLoggingSystem.initialize(AbstractLoggingSystem.java:57)
	at org.springframework.boot.logging.logback.LogbackLoggingSystem.initialize(LogbackLoggingSystem.java:132)

如果只配置pom.xml或者两个文件都配置了,那么会有效果,启动也不会报错。所以这里我们先只配置pom.xml文件。

<?xml version="1.0" encoding="UTF-8"?>
<configuration  scan="true" scanPeriod="60 seconds" debug="false">
    <contextName>logback</contextName>
    <property name="log.path" value="D:\tmLog\logback.log" />
    <!--输出到控制台-->
    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
       <!-- <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>ERROR</level>
        </filter>-->
        <encoder>
            <pattern>%d{HH:mm:ss.SSS} %contextName [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>

    <!--输出到文件-->
    <appender name="file" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${log.path}</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>logback.%d{yyyy-MM-dd}.log</fileNamePattern>
        </rollingPolicy>
        <encoder>
            <pattern>%d{HH:mm:ss.SSS} %contextName [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>

    <root level="warn">
        <appender-ref ref="console" />
        <appender-ref ref="file" />
    </root>

    <!-- logback为java中的包 -->
    <logger name="com.learn.tm.controller"/>
    <!--logback.LogbackDemo:类的全路径 -->
    <logger name="com.learn.tm.controller.LearnController" level="WARN" additivity="false">
        <appender-ref ref="console"/>
    </logger>
</configuration>

节点属性解释 : 根节点<configuration>包含的属性

# scan
当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true# scanPeriod
设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。当scan为true时,此属性生效。默认的时间间隔为1分钟。

# debug
当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false

根节点<configuration> 下面一共有2个属性,3个子节点,分别是:

10.3.1 属性一:设置上下文名称<contextName>

每个logger都关联到logger上下文,默认上下文名称为default。但可以使用设置成其他名字,用于区分不同应用程序的记录。一旦设置,不能修改,可以通过%contextName来打印日志上下文名称。

<contextName>logback</contextName>

10.3.2 属性二:设置变量<property>

用来定义变量值的标签, 有两个属性,namevalue;其中name的值是变量的名称,value的值时变量定义的值。通过定义的值会被插入到logger上下文中。定义变量后,可以使${}来使用变量。

<property name="log.path" value="D:\tmLog\logback.log" />

10.3.3 子节点一:<appender>

appender用来格式化日志输出节点,有俩个属性name和class,class用来指定哪种输出策略,常用就是控制台输出策略和文件输出策略。

10.3.3.1 控制台输出ConsoleAppender

 <!--输出到控制台-->
 <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
     <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
         <level>ERROR</level>
     </filter>
     <encoder>
         <pattern>%d{HH:mm:ss.SSS} %contextName [%thread] %-5level %logger{36} - %msg%n</pattern>
     </encoder>
 </appender>

ThresholdFilter为系统定义的拦截器,例如我们用ThresholdFilter来过滤掉ERROR级别以下的日志不输出到文件中。如果不用记得注释掉,不然你控制台会发现没日志~

<encoder>表示对日志进行编码:

  • %d{HH: mm:ss.SSS}——日志输出时间
  • %thread——输出日志的进程名字,这在Web应用以及异步任务处理中很有用
  • %-5level——日志级别,并且使用5个字符靠左对齐
  • %logger{36}——日志输出者的名字
  • %msg——日志消息
  • %n——平台的换行符

10.3.3.2 输出到文件RollingFileAppender

另一种常见的日志输出到文件,随着应用的运行时间越来越长,日志也会增长的越来越多,将他们输出到同一个文件并非一个好办法。RollingFileAppender用于切分文件日志:

<!--输出到文件-->
<appender name="file" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <file>${log.path}</file>
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
        <fileNamePattern>logback.%d{yyyy-MM-dd}.log</fileNamePattern>
        <maxHistory>30</maxHistory>
        <totalSizeCap>1GB</totalSizeCap>
    </rollingPolicy>
    <encoder>
        <pattern>%d{HH:mm:ss.SSS} %contextName [%thread] %-5level %logger{36} - %msg%n</pattern>
    </encoder>
</appender>

其中重要的是rollingPolicy的定义

上例中<fileNamePattern>logback.%d{yyyy-MM-dd}.log</fileNamePattern>定义了日志的切分方式——把每一天的日志归档到一个文件中,<maxHistory>30</maxHistory>表示只保留最近30天的日志,以防止日志填满整个磁盘空间。

同理,可以使用%d{yyyy-MM-dd_HH-mm}来定义精确到分的日志切分方式。

<totalSizeCap>1GB</totalSizeCap>用来指定日志文件的上限大小,例如设置为1GB的话,那么到了这个值,就会删除旧的日志。

10.3.4 子节点二: <root>

root节点是必选节点,用来指定最基础的日志输出级别,只有一个level属性。

level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,不能设置为INHERITED或者同义词NULL。默认是DEBUG。可以包含零个或多个元素,标识这个appender将会添加到这个logger。

<root level="warn">
  <appender-ref ref="console" />
  <appender-ref ref="file" />
</root>

10.3.5 子节点三: <logger>

<loger>用来设置某一个包或者具体的某一个类的日志打印级别、以及指定<appender><loger>仅有一个name属性,一个可选的level和一个可选的addtivity属性。

  • name:用来指定受此loger约束的某一个包或者具体的某一个类。
  • level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,还有一个特俗值INHERITED或者同义词NULL,代表强制执行上级的级别。如果未设置此属性,那么当前loger将会继承上级的级别。
  • addtivity:是否向上级loger传递打印信息。默认是true。

10.3.5.1 使用情况1

带有logger的配置,不指定级别,不指定appender

<logger name="com.learn.tm.controller"/>

此配置将控制com.learn.tm.controller下所有类的日志的打印,但是因为

(1) 没有设置打印级别,所以继承它的上级的日志级别,即warn

(2) 没有设置addtivity,默认为true,将此loger的打印信息向上级传递;

(3) 没有设置appender,此logger本身不打印任何信息。

<root level="warn">将root的打印级别设置为“warn”,指定了名字为“console”的appender。

所以当执行到上面测试类中的方法的时候,因为在com.learn.tm.controller下,所以会执行<logger name="com.learn.tm.controller"/>,将级别info及大于warn的日志信息传递给root,本身并不打印。

root接到下级传递的信息,交给已经配置好的名为“console”的appender处理,“console”appender将信息打印到控制台。此时我们将项目启动起来,调用测试类的接口。效果如下 : 即控制台只能输出warn及以上的级别的日志。

13:26:00.100 logback [http-nio-8080-exec-2] WARN  c.learn.tm.controller.TestController - ---------warn
13:26:00.102 logback [http-nio-8080-exec-2] ERROR c.learn.tm.controller.TestController - ---------error

这里要注意 : 如果出现找不到logback-spring.xml的报错,需要在pom.xml中配置一下

<resources>
    <resource>
        <directory>src/main/resources</directory>
        <!--可以在此配置过滤文件  -->
        <includes>
            <include>logback-spring.xml</include>
        </includes>
        <!--开启filtering功能  -->
        <filtering>true</filtering>
    </resource>
</resources>

D盘下也出现了tmLog文件夹和对应的logback.log文件。

image.png

但是这里问题就出现了,我们本意是想生成的文件名称类似logback.2022-12-28.log这种的,但是实际生成的文件名并没有时间戳。

需要修改一下logback-spring.xml文件

# 修改1 : 
    # 修改前:
    <property name="log.path" value="D:\\tmLog\\logback.log" />
    # 修改后:
    <property name="log.path" value="D:\\tmLog" />
    
# 修改2 : 
    # 修改前:有file配置
    <appender name="file" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <file>${log.path}</file>
    # 修改后:去掉file配置
    <appender name="file" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <!--<file>${log.path}</file>-->
    
# 修改3 : 
    # 修改前:
    <fileNamePattern>logback.%d{yyyy-MM-dd}.log</fileNamePattern>
    # 修改后:
    <fileNamePattern>${log.path}/logback.%d{yyyy-MM-dd}.log</fileNamePattern>  

最后logback-spring.xml文件内容为 :

<?xml version="1.0" encoding="UTF-8"?>
<configuration  scan="true" scanPeriod="60 seconds" debug="false">
    <contextName>logback</contextName>
    <property name="log.path" value="D:\tmLog" />
    <!--输出到控制台-->
    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{HH:mm:ss.SSS} %contextName [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>

    <!--输出到文件-->
    <appender name="file" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${log.path}/logback.%d{yyyy-MM-dd}.log</fileNamePattern>
        </rollingPolicy>
        <encoder>
            <pattern>%d{HH:mm:ss.SSS} %contextName [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>

    <root level="warn">
        <appender-ref ref="console" />
        <appender-ref ref="file" />
    </root>

    <!-- logback为java中的包 -->
    <logger name="com.learn.tm.controller"/>
</configuration>

生成文件效果为 :

image.png

10.3.5.2 使用情况2

带有多个logger的配置,指定级别,指定appender

将logback-spring.xml中新增配置

<logger name="com.learn.tm.controller.TestController" level="info" additivity="false">
    <appender-ref ref="console"/>
</logger>

name="com.learn.tm.controller.TestController"表示控制TestController类的日志打印

level="info"表示打印级别为info

additivity="false"表示此logger的打印信息不再向上级传递。如果改成true,那么日志会被打印两次

<appender-ref ref="console"/>表示指定了名字为console的appender。

这里没有配置<appender-ref ref="file"/>所以文件是不会记录这些日志的。

最后效果为 :

14:23:18.436 logback [http-nio-8080-exec-1] INFO  c.learn.tm.controller.TestController - ---------info
14:23:18.437 logback [http-nio-8080-exec-1] WARN  c.learn.tm.controller.TestController - ---------warn
14:23:18.437 logback [http-nio-8080-exec-1] ERROR c.learn.tm.controller.TestController - ---------error

10.4 多环境日志输出

即根据不同环境(prod:生产环境,test:测试环境,dev:开发环境)来定义不同的日志输出,在 logback-spring.xml 中使用 springProfile 节点来定义,方法如下:

<!-- 测试环境+开发环境. 多个使用逗号隔开. -->
<springProfile name="test,dev">
    <logger name="com.learn.tm.controller" level="info" />
</springProfile>
<!-- 生产环境. -->
<springProfile name="prod">
    <logger name="com.learn.tm.controller" level="ERROR" />
</springProfile>

最终效果为,如果切换成dev或test环境,输出日志为info及以上级别,如果切换成prod,输出日志为error及以上级别。

文件全部内容为:

<?xml version="1.0" encoding="UTF-8"?>
<configuration  scan="true" scanPeriod="60 seconds" debug="false">
    <contextName>logback</contextName>
    <property name="log.path" value="D:\tmLog" />
    <!--输出到控制台-->
    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <!-- <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
             <level>ERROR</level>
         </filter>-->
        <encoder>
            <pattern>%d{HH:mm:ss.SSS} %contextName [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>

    <!--输出到文件-->
    <appender name="file" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!--        <file>${log.path}</file>-->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${log.path}/logback.%d{yyyy-MM-dd}.log</fileNamePattern>
        </rollingPolicy>
        <encoder>
            <pattern>%d{HH:mm:ss.SSS} %contextName [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>

    <!-- 测试环境+开发环境. 多个使用逗号隔开. -->
    <springProfile name="test,dev">
        <logger name="com.learn.tm.controller" level="info" />
    </springProfile>
    <!-- 生产环境. -->
    <springProfile name="prod">
        <logger name="com.learn.tm.controller" level="ERROR" />
    </springProfile>

    <root level="warn">
        <appender-ref ref="console" />
        <appender-ref ref="file" />
    </root>
</configuration>

10.5 演示

10.5.1 logback-spring.xml内容

<?xml version="1.0" encoding="UTF-8"?>
<!-- 日志级别从低到高分为TRACE < DEBUG < INFO < WARN < ERROR < FATAL,如果设置为WARN,则低于WARN的信息都不会输出 -->
<!-- scan:当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true -->
<!-- scanPeriod:设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。当scan为true时,此属性生效。默认的时间间隔为1分钟。 -->
<!-- debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。 -->
<configuration  scan="true" scanPeriod="10 seconds">

    <contextName>logback</contextName>
    <!-- name的值是变量的名称,value的值时变量定义的值。通过定义的值会被插入到logger上下文中。定义变量后,可以使“${}”来使用变量-->
    <property name="log.path" value="D:/logs" />

    <property name="CONSOLE_LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg %n"/>

    <!--输出到控制台-->
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <!--此日志appender是为开发使用,只配置最底级别,控制台输出的日志级别是大于或等于此级别的日志信息-->
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>debug</level>
        </filter>
        <encoder>
            <Pattern>${CONSOLE_LOG_PATTERN}</Pattern>
            <!-- 设置字符集 -->
            <charset>UTF-8</charset>
        </encoder>
    </appender>

    <!--输出到文件-->
    <!-- 时间滚动输出 level为 DEBUG 日志 -->
    <appender name="DEBUG_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 正在记录的日志文件的路径及文件名 -->
        <!--先将今天的日志保存在这个文件中-->
<!--        <file>${log.path}/log_debug.log</file>-->
        <!--日志文件输出格式   %-d{yyyy-MM-dd HH:mm:ss}  [ %t:%r ] - [ %p ]  %m%n
            %d{HH: mm:ss.SSS}——日志输出时间
            %thread——输出日志的进程名字,这在Web应用以及异步任务处理中很有用
            %-5level——日志级别,并且使用5个字符靠左对齐
            %logger{36}——日志输出者的名字
            %msg——日志消息
            %n——平台的换行符
           -->
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
            <charset>UTF-8</charset> <!-- 设置字符集 -->
        </encoder>
        <!-- 日志记录器的滚动策略,按日期,按大小记录 -->

        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <!-- rollover daily -->
            <!--如果第二天输出日志,会将当天的日志记录在<file>${log.path}/log_debug.log</file>,然后将昨天的日志归档到下面的文件中-->
            <!--以分钟切分  %d{yyyy-MM-dd_HH-mm}-->
            <fileNamePattern>${log.path}/debug/log-debug-%d{yyyy-MM-dd_HH-mm}.%i.log</fileNamePattern>
            <!-- each file should be at most 100MB, keep 60 days worth of history, but at most 20GB -->
            <!--单个日志文件最大10KB,到了这个值,就会再创建一个日志文件,日志文件的名字最后+1-->
            <maxFileSize>10KB</maxFileSize>
            <!--日志文件保留天数-->
            <maxHistory>30</maxHistory>
            <!--所有的日志文件最大22KB,超过就会删除旧的日志-->
            <totalSizeCap>22KB</totalSizeCap>
        </rollingPolicy>
        <!--
            此日志文件只记录debug级别的
            onMatch和onMismatch都有三个属性值,分别为Accept、DENY和NEUTRAL
            onMatch="ACCEPT" 表示匹配该级别及以上
            onMatch="DENY" 表示不匹配该级别及以上
            onMatch="NEUTRAL" 表示该级别及以上的,由下一个filter处理,如果当前是最后一个,则表示匹配该级别及以上
            onMismatch="ACCEPT" 表示匹配该级别以下
            onMismatch="NEUTRAL" 表示该级别及以下的,由下一个filter处理,如果当前是最后一个,则不匹配该级别以下的
            onMismatch="DENY" 表示不匹配该级别以下的
        -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>debug</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <!-- 时间滚动输出 level为 INFO 日志 -->
    <appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 正在记录的日志文件的路径及文件名 -->
<!--        <file>${log.path}/log_info.log</file>-->
        <!--日志文件输出格式-->
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
            <charset>UTF-8</charset>
        </encoder>
        <!-- 日志记录器的滚动策略,按日期,按大小记录 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <!-- rollover daily -->
            <!--如果第二天输出日志,会将当天的日志记录在<file>${log.path}/log_debug.log</file>,然后将昨天的日志归档到下面的文件中-->
            <!--以分钟切分  %d{yyyy-MM-dd_HH-mm}-->
            <fileNamePattern>${log.path}/info/log-info-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <!-- each file should be at most 100MB, keep 60 days worth of history, but at most 20GB -->
            <!--单个日志文件最大10KB,到了这个值,就会再创建一个日志文件,日志文件的名字最后+1-->
            <maxFileSize>10KB</maxFileSize>
            <!--日志文件保留天数-->
            <maxHistory>30</maxHistory>
            <!--所有的日志文件最大22KB,超过就会删除旧的日志-->
            <totalSizeCap>22KB</totalSizeCap>
        </rollingPolicy>
        <!--SizeAndTimeBasedRollingPolicy配置更灵活,所以改用SizeAndTimeBasedRollingPolicy-->
        <!--<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            &lt;!&ndash; 每天日志归档路径以及格式 &ndash;&gt;
            <fileNamePattern>${log.path}/info/log-info-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            &lt;!&ndash;日志文件保留天数&ndash;&gt;
            <maxHistory>15</maxHistory>
        </rollingPolicy>-->
        <!-- 此日志文件只记录info级别的 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>info</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <!-- 时间滚动输出 level为 WARN 日志 -->
    <appender name="WARN_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 正在记录的日志文件的路径及文件名 -->
<!--        <file>${log.path}/log_warn.log</file>-->
        <!--日志文件输出格式-->
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
            <charset>UTF-8</charset> <!-- 此处设置字符集 -->
        </encoder>
        <!-- 日志记录器的滚动策略,按日期,按大小记录 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <!-- rollover daily -->
            <!--如果第二天输出日志,会将当天的日志记录在<file>${log.path}/log_debug.log</file>,然后将昨天的日志归档到下面的文件中-->
            <!--以分钟切分  %d{yyyy-MM-dd_HH-mm}-->
            <fileNamePattern>${log.path}/warn/log-warn-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <!-- each file should be at most 100MB, keep 60 days worth of history, but at most 20GB -->
            <!--单个日志文件最大10KB,到了这个值,就会再创建一个日志文件,日志文件的名字最后+1-->
            <maxFileSize>10KB</maxFileSize>
            <!--日志文件保留天数-->
            <maxHistory>30</maxHistory>
            <!--所有的日志文件最大22KB,超过就会删除旧的日志-->
            <totalSizeCap>22KB</totalSizeCap>
        </rollingPolicy>
        <!-- 此日志文件只记录warn级别的 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>warn</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>


    <!-- 时间滚动输出 level为 ERROR 日志 -->
    <appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 正在记录的日志文件的路径及文件名 -->
<!--        <file>${log.path}/log_error.log</file>-->
        <!--日志文件输出格式-->
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
            <charset>UTF-8</charset> <!-- 此处设置字符集 -->
        </encoder>
        <!-- 日志记录器的滚动策略,按日期,按大小记录 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <!-- rollover daily -->
            <!--如果第二天输出日志,会将当天的日志记录在<file>${log.path}/log_debug.log</file>,然后将昨天的日志归档到下面的文件中-->
            <!--以分钟切分  %d{yyyy-MM-dd_HH-mm}-->
            <fileNamePattern>${log.path}/error/log-error-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <!-- each file should be at most 100MB, keep 60 days worth of history, but at most 20GB -->
            <!--单个日志文件最大10KB,到了这个值,就会再创建一个日志文件,日志文件的名字最后+1-->
            <maxFileSize>10KB</maxFileSize>
            <!--日志文件保留天数-->
            <maxHistory>30</maxHistory>
            <!--所有的日志文件最大22KB,超过就会删除旧的日志-->
            <totalSizeCap>22KB</totalSizeCap>
        </rollingPolicy>
        <!-- 此日志文件只记录ERROR级别的 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>ERROR</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <!--root配置必须在appender下边-->
    <!--root节点是对所有appender的管理,添加哪个appender就会打印哪个appender的日志-->
    <!--root节点的level是总的日志级别控制,如果appender的日志级别设定比root的高,会按照appender的日志级别打印日志,-->
    <!--如果appender的日志级别比root的低,会按照root设定的日志级别进行打印日志-->
    <!--也就是说root设定的日志级别是最低限制,如果root设定级别为最高ERROR,那么所有appender只能打印最高级别的日志-->
    <root level="INFO">
        <appender-ref ref="STDOUT" />
        <appender-ref ref="DEBUG_FILE" />
        <appender-ref ref="INFO_FILE" />
        <appender-ref ref="WARN_FILE" />
        <appender-ref ref="ERROR_FILE" />
    </root>


    <!--name:用来指定受此loger约束的某一个包或者具体的某一个类。-->
    <!--addtivity:是否向上级loger传递打印信息。默认是true。-->
    <logger name="com.pikaiqu.logbackdemo.LogbackdemoApplicationTests" level="debug" additivity="false">
        <appender-ref ref="STDOUT" />
        <appender-ref ref="INFO_FILE" />
    </logger>

    <!--配置多环境日志输出  可以在application.properties中配置选择哪个profiles : spring.profiles.active=dev-->
    <!--生产环境:输出到文件-->
    <!--<springProfile name="pro">
        <root level="info">
            <appender-ref ref="DEBUG_FILE" />
            <appender-ref ref="INFO_FILE" />
            <appender-ref ref="ERROR_FILE" />
            <appender-ref ref="WARN_FILE" />
        </root>
    </springProfile>-->
    <!--开发环境:打印控制台-->
    <!--<springProfile name="dev">
        <root level="debug">
            <appender-ref ref="STDOUT" />
        </root>
    </springProfile>-->

</configuration>

10.5.2 测试类

@GetMapping("getMsg")
public Result<String> getMsg(){
    System.out.println(txtName);
    log.trace("---------trace---------trace---------trace---------trace---------trace");
    log.debug("---------debug---------debug---------debug---------debug---------debug");
    log.info("---------info---------info---------info---------info---------info");
    log.warn("---------warn---------warn---------warn---------warn---------warn");
    log.error("---------error---------error---------error---------error---------error");
    return new Result<String>().ok(txtName);
}

10.5.3 屏蔽<file>标签效果

D://logs目录下生成四个文件夹,分别对应配置里的四种类型的日志,每种类型文件格式为log-日志级别-时间戳.序号.log。是按照日来生成的文件

image.png

按照日生成文件,文件名为log_debug.log、log_warn.log、log_info.log、log_error.log。每个文件大小控制为不大于10KB,超过10KB之后会新建一个文件,文件名为最后的序号+1递增,日志文件保留30天。所有文件总大小不能超过22K。超过了就删除旧的日志。多环境没有打开,打开之后可以根据application.yml文件里配置的profiles来对应级别日志的输出。

调用一次测试类,每个文件内容如下:

# log-debug-2022-12-29_08-43.0.log# log-error-2022-12-29.0.log
2022-12-29 09:00:16.310 [http-nio-8080-exec-2] ERROR com.learn.tm.controller.TestController - ---------error---------error---------error---------error---------error

# log-info-2022-12-29.0.log
2022-12-29 08:43:31.306 [main] INFO  com.learn.tm.TianmengApplication - Starting TianmengApplication using Java 1.8.0_181 on DESKTOP-27LQCSL with PID 17528 (D:\code\learn\SpringBoot-Learn\target\classes started by tianmeng in D:\code\learn\SpringBoot-Learn)
2022-12-29 08:43:31.310 [main] INFO  com.learn.tm.TianmengApplication - The following 1 profile is active: "dev"
2022-12-29 08:43:32.345 [main] INFO  o.s.boot.web.embedded.tomcat.TomcatWebServer - Tomcat initialized with port(s): 8080 (http)
2022-12-29 08:43:32.352 [main] INFO  org.apache.coyote.http11.Http11NioProtocol - Initializing ProtocolHandler ["http-nio-8080"]
2022-12-29 08:43:32.352 [main] INFO  org.apache.catalina.core.StandardService - Starting service [Tomcat]
2022-12-29 08:43:32.353 [main] INFO  org.apache.catalina.core.StandardEngine - Starting Servlet engine: [Apache Tomcat/9.0.69]
2022-12-29 08:43:32.572 [main] INFO  o.a.c.core.ContainerBase.[Tomcat].[localhost].[/] - Initializing Spring embedded WebApplicationContext
2022-12-29 08:43:32.572 [main] INFO  o.s.b.w.s.c.ServletWebServerApplicationContext - Root WebApplicationContext: initialization completed in 1208 ms
2022-12-29 08:43:32.724 [main] INFO  c.a.d.s.b.a.DruidDataSourceAutoConfigure - Init DruidDataSource
2022-12-29 08:43:32.919 [main] INFO  com.alibaba.druid.pool.DruidDataSource - {dataSource-1} inited
2022-12-29 08:43:33.743 [main] INFO  org.apache.coyote.http11.Http11NioProtocol - Starting ProtocolHandler ["http-nio-8080"]
2022-12-29 08:43:33.758 [main] INFO  o.s.boot.web.embedded.tomcat.TomcatWebServer - Tomcat started on port(s): 8080 (http) with context path ''
2022-12-29 08:43:33.765 [main] INFO  com.learn.tm.TianmengApplication - Started TianmengApplication in 3.09 seconds (JVM running for 4.259)
2022-12-29 09:00:16.278 [http-nio-8080-exec-2] INFO  o.a.c.core.ContainerBase.[Tomcat].[localhost].[/] - Initializing Spring DispatcherServlet 'dispatcherServlet'
2022-12-29 09:00:16.278 [http-nio-8080-exec-2] INFO  org.springframework.web.servlet.DispatcherServlet - Initializing Servlet 'dispatcherServlet'
2022-12-29 09:00:16.279 [http-nio-8080-exec-2] INFO  org.springframework.web.servlet.DispatcherServlet - Completed initialization in 1 ms
2022-12-29 09:00:16.310 [http-nio-8080-exec-2] INFO  com.learn.tm.controller.TestController - ---------info---------info---------info---------info---------info

# log-warn-2022-12-29.0.log
2022-12-29 09:00:16.310 [http-nio-8080-exec-2] WARN  com.learn.tm.controller.TestController - ---------warn---------warn---------warn---------warn---------warn

控制台输出为:

2022-12-29 09:07:51.927 [http-nio-8080-exec-4] INFO  com.learn.tm.controller.TestController - ---------info---------info---------info---------info---------info 
2022-12-29 09:07:51.927 [http-nio-8080-exec-4] WARN  com.learn.tm.controller.TestController - ---------warn---------warn---------warn---------warn---------warn 
2022-12-29 09:07:51.928 [http-nio-8080-exec-4] ERROR com.learn.tm.controller.TestController - ---------error---------error---------error---------error---------error

多次调用后,存在单个文件大于10KB。效果如下:

image.png

即生成了新文件,后面序号+1,新日志将会存储到新文件里。我们继续多次调用,当该级别类型总文件数超过22k大小的时候,发现并没有删除旧的日志

image.png

经过查询 totalSizeCap 在Logback 1.1.8-SNAPSHOT中工作。这个也没用上

目前测试出的效果为22k貌似不准确,用info类型的日志举例

image.png

可以看到确实是删除了旧的文件,但是该级别日志总文件大小远大于22k了,继续调用发现应该是新文件都到11k(最大限制)的时候才会清除

image.png

继续调用发现应该是这个情况,但是我配置的是单文件10k,但是为什么单文件是11k才会生成新的文件,这个目前还没理解

image.png

10.5.4 放开<file>标签效果

<file>${log.path}/log_debug.log</file>
<file>${log.path}/log_info.log</file>
<file>${log.path}/log_warn.log</file>
<file>${log.path}/log_error.log</file>

效果如下:

即直接在D://logs下生成四个文件log_debug.log、log_info.log、log_warn.log、log_error.log

image.png

随着日志的增多,可以看到对应归档到各自的文件夹中,

image.png

随着日志越来越多,超过了22k,效果如上,会删除掉旧日志。

image.png