前言:本篇文章主要用来记录springboot的一些常见操作,以及常用框架的整合。且本文并非是从零基础开始,所以需要读者有一定的开发经验。
1. 新建一个SpringBoot项目
1.1 Spring 官网新建项目
网站地址 : start.spring.io
1.2* IDEA 方式新建项目
(下面的各种配置和整合都是以此为根基)
File->New->Project->Spring Initializr
这里注意 : Default和Custom两个选项
Default(默认) : start.spring.io -> 有的时候Spring官网会有一些卡顿
Custom(自定义) : start.aliyun.com -> 阿里云的镜像地址
新建之后,首先将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说明没什么问题了。
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 单数据源
项目目录 : 代码 :
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 多数据源
代码目录:
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 单数据源
项目目录 :
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") 注解来实现。
项目目录 :
数据准备
-
数据库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>
然后在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
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
效果 :
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());
}
}
这里要注意各个异常方法的返回值参数
没有被封装会显示 :
被封装显示 :
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实现的自定义日志系统。只需要两个类就能实现:
-
注解类:设置自定义属性属性
-
切面类:用于横切注解,获取注解属性值,保存日志
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
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
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窗口,可以看到出现了对应的配置,此处可以动态切换。
10. 日志输出
本节全文参考 :
zhuanlan.zhihu.com/p/555185411
日志级别从低到高分为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中配置如下内容,即可实现默认文件日志输出 :
- logging.file,设置文件,可以是绝对路径(d://tm.log),也可以是相对路径(tm.log)。
- 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
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>
用来定义变量值的标签, 有两个属性,name
和value
;其中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文件。
但是这里问题就出现了,我们本意是想生成的文件名称类似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>
生成文件效果为 :
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">
<!– 每天日志归档路径以及格式 –>
<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>
<!–日志文件保留天数–>
<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
。是按照日来生成的文件
按照日生成文件,文件名为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。效果如下:
即生成了新文件,后面序号+1,新日志将会存储到新文件里。我们继续多次调用,当该级别类型总文件数超过22k大小的时候,发现并没有删除旧的日志
经过查询 totalSizeCap
在Logback 1.1.8-SNAPSHOT中工作。这个也没用上
目前测试出的效果为22k貌似不准确,用info类型的日志举例
可以看到确实是删除了旧的文件,但是该级别日志总文件大小远大于22k了,继续调用发现应该是新文件都到11k(最大限制)的时候才会清除
继续调用发现应该是这个情况,但是我配置的是单文件10k,但是为什么单文件是11k才会生成新的文件,这个目前还没理解
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
随着日志的增多,可以看到对应归档到各自的文件夹中,
随着日志越来越多,超过了22k,效果如上,会删除掉旧日志。