SpringBoot(2)学习内容

一、SpringBoot关于静态资源的处理

1.1 介绍

springboot启动时会根据配置文件自动配置相应场景的组件xxxAutoConfiguration,web项目启动时会初始化WebMvcAutoConfiguration组件处理请求相关的操作.

其中有默认处理静态资源的方法:

注:this.resourceProperties.getStaticLocations()获取初始化指定放在静态资源的默认位置

1.2 静态资源存放的位置

由于我们在springboot项目中加入了spring-boot-starter-web,也就是web场景启动器;springboot项目启动后,会自动加载web场景启动器,web场景启动器会进行自动化初始配置,设置静态资源的默认存放位置为:

{"classpath:/META-INF/resources/", "classpath:/resources/", "classpath:/static/", "classpath:/public/"}

从该方法中可以发现:

1、默认情况下:

(1)匹配/webjars/** 的请求,都去 classpath:/META-INF/resources/webjars/ 找资源;

(2)匹配 "/**" 的请求,都去(静态资源的文件夹)找映射,静态资源文件夹路径如下:

"classpath:/META‐INF/resources/"

"classpath:/resources/"

"classpath:/static/"

"classpath:/public/"

"/":当前项目的根路径

2、自定义配置静态资源路径:

spring.web.resources.static-locations=自定义路径

(1)设置自定义静态资源路径,默认静态资源位置不生效。

(2)自定义:此时寻找静态资源就会从/pages 去寻找

spring.web.resources.static-locations=classpath:/pages/

注意:自定义静态资源路径后,默认静态资源路径失效!

1.3 案例实现

访问测试:

二、SpringBoot的自动配置

Springboot使用注解对一些常规的配置项做默认配置,减少或不使用xml配置,让你的项目快速运行起来;

Springboot还为大量的开发常用框架封装了starter,如今引入框架只要引入一个starter,你就可以使用这个框架,只需少量的配置甚至是不需要任何配置,这些自动配置的东西都有它自己的默认值,以端口号为例:springboot设置tomcat默认端口号 server.port的默认值为8080。文件上传默认最大值为1M(spring.servlet.multipart.max-file-size=1MB)。访问静态资源的默认位置等等,都是springboot自动配置的东西;

以前,比如spring springMvc整合的时候,需要指定很多包扫描之类的一大堆东西,springboot给我们自动配置了默认的注解扫描规则, 只要是主程序所在的包,或者下面的子包,里面的所有组件都能扫描的到。

2.2 springboot的自动配置加载流程

自动装配流程可以参考: https://developer.aliyun.com/article/1681308

2.2.1 配置加载流程

我们看到,Day02Springboot01Application作为入口类,入口类中有一个main方法,这个方法其实就是一个标准的Java应用的入口方法,一般在main方法中使用SpringApplication.run()来启动整个应用。值得注意的是,这个入口类要使用@SpringBootApplication注解声明,它是SpringBoot的核心注解。

1. @SpringBootConfiguration:标记当前类为配置类

根据其源码可以知道,@SpringBootConfiguration注解包含@Configuration,所以其拥有@Configuration注解相似的功能,而@Configuration注解又包含@Companent注解,所以配置类也存在于IOC容器中。

2. @ComponentScan(excludeFilters = {@Filter(type = FilterType.CUSTOM,classes = {TypeExcludeFilter.class}):是注解扫描器,扫描程序入口所在包及下面所有子包里面的所有组件扫描到Spring容器;

3. 这个注解里面,最主要的就是@EnableAutoConfiguration,作用开启自动配置, 它会根据类路径下的jar包、bean定义和各种属性来自动配置和注册bean。

根据其源码得出其主要由@AutoConfigurationPackages注解和@Import注解组成;

点进去@EnableAutoConfiguration的源码, 它通过使用@Import(AutoConfigurationImportSelector.class)来导入配置,这个选择器负责从META-INF/spring.factories文件中读取自动配置的类名,并根据条件(如类路径上是否有某个库)来决定哪些自动配置类应该被加载。

/*
// 第一步:
可以看到,在@EnableAutoConfiguration注解内使用到了@import注解来完成导入配置的功能,
我们可以查看这个类selectImports()方法的内容,他返回了一个 autoConfigurationEntry , 
来自  this.getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata); 
这个方法。我们继续跟踪;
*/
public String[] selectImports(AnnotationMetadata annotationMetadata) {
    if (!this.isEnabled(annotationMetadata)) {
        return NO_IMPORTS;
    } else {
//获取自动配置的内容
    AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry =     this.getAutoConfigurationEntry(annotationMetadata);
    return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
    }
}

/*
// 第二步:
这个方法中有一个值 : List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes); 
叫做获取候选的配置 , 我们点击去继续跟踪;
*/
protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
    if (!this.isEnabled(annotationMetadata)) {
        return EMPTY_ENTRY;
    } else {
        AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
        // 获取配置类信息
        List<String> configurations =
 this.getCandidateConfigurations(annotationMetadata, attributes);
        // 根据情况,自动配置需要的配置类和不需要的配置了
        configurations = this.removeDuplicates(configurations);
        Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
        this.checkExcludedClasses(configurations, exclusions);
        configurations.removeAll(exclusions);
        configurations = this.getConfigurationClassFilter().filter(configurations);
        this.fireAutoConfigurationImportEvents(configurations, exclusions);
        // 返回最终需要的配置
        return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
    }
}

/*
// 第三步:
这里面有一个 SpringFactoriesLoader.loadFactoryNames() ,
我们继续进去看 , 它又调用了  loadSpringFactories 方法;继续跟踪。
发现它去获得了一个资源文件:"META-INF/spring.factories"
*/
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
	// 主要就是为了获取META-INF/spring.factories 文件下EnableAutoConfiguration对应的value值,并返回该值  
    List<String> configurations =
SpringFactoriesLoader.loadFactoryNames(
this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
    Assert.notEmpty(configurations,
 "No auto configuration classes found in META-INF/spring.factories. 
If you are using a custom packaging, make sure that file is correct.");
    return configurations;
}

//第四步
/*
我们继续进去看 , 它又调用了 loadSpringFactories 方法;继续跟踪。
发现它去获得了一个资源文件:"META-INF/spring.factories"
*/
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
    ClassLoader classLoaderToUse = classLoader;
    if (classLoader == null) {
        classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
    }
      String factoryTypeName = factoryType.getName();
    //获取要自动注入的组件信息
    return (List)loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}

//第五步
private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
    Map<String, List<String>> result = (Map)cache.get(classLoader);
    if (result != null) {
        return result;
    } else {
        HashMap result = new HashMap();

        try {
		   //它将读取到的资源封装在url中,然后遍历url , 将这些url文件封装在Properties文件中;最后返回封装好的结果;获取spring.factories文件中要自动注入的组件信息
           Enumeration urls = classLoader.getResources("META-INF/spring.factories");

            while(urls.hasMoreElements()) {
                URL url = (URL)urls.nextElement();
                UrlResource resource = new UrlResource(url);
                Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                Iterator var6 = properties.entrySet().iterator();

                while(var6.hasNext()) {
                    Entry<?, ?> entry = (Entry)var6.next();
                    String factoryTypeName = ((String)entry.getKey()).trim();
                    String[] factoryImplementationNames =
 StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
                    String[] var10 = factoryImplementationNames;
                    int var11 = factoryImplementationNames.length;

                    for(int var12 = 0; var12 < var11; ++var12) {
                        String factoryImplementationName = var10[var12];
                        ((List)result.computeIfAbsent(factoryTypeName, (key) -> {
                            return new ArrayList();
                        })).add(factoryImplementationName.trim());
                    }
                }
            }
            result.replaceAll((factoryType, implementations) -> {
                return (List)implementations.stream().distinct().
collect(Collectors.collectingAndThen(Collectors.toList(), 
Collections::unmodifiableList));
            });
            cache.put(classLoader, result);
            return result;
        } catch (IOException var14) {
            throw new IllegalArgumentException
("Unable to load factories from location [META-INF/spring.factories]", var14);
        }
    }
}

说明了这个逻辑就是 从properties中获取到EnableAutoConfiguration.class类(类名)对应的值,然后把他们添加在容器中。

总结一句话就是:将类路径下 META-INF/spring.factories 里面配置的所有EnableAutoConfiguration的值加入到了容器中;我们从源码中拿过来。

下面是2.6.13.RELEASE实现源码:

总结:在@SpringBootApplication中有一个注解@EnableAutoConfiguration,这个注解也是一个派生注解,其中的关键功能由@Import提供,其导入的AutoConfigurationImportSelector的selectImports()方法通过SpringFactoriesLoader.loadFactoryNames()扫描所有具有META-INF/spring.factories的jar包。spring-boot-autoconfigure-x.x.x.x.jar里就有一个这样的spring.factories文件。

这个spring.factories文件也是一组一组的key=value的形式,其中一个key是EnableAutoConfiguration类的全类名,而它的value是一个xxxxAutoConfiguration(启动器)的类名的列表;

在SpringApplication.run(...)的内部就会执行selectImports()方法,找到所有JavaConfig自动配置类的全限定名对应的class,然后将所有自动配置类加载到Spring容器中。

2.2.2 配置生效

springboot所有自动配置都是在启动的时候扫描并加载:spring.factories所有的自动配置类都在这里面,但是不一定生效,要判断条件是否成立(@ConditionalOnXXX注解判断条件),只要导入了对应的start,就有对应的启动器了,有了启动器,我们自动装配就会生效,然后就配置成功。

例如我们要让ServletWebServerFactoryAutoConfiguration自动配置生效,当前项目中必须加入web启动器:

每一个XxxxAutoConfiguration自动配置类都是在某些条件之下才会生效的,这些条件的限制在Spring Boot中以注解的形式体现,常见的条件注解有如下几项:

以ServletWebServerFactoryAutoConfiguration配置类为例,解释一下全局配置文件中的属性如何生效,比如:server.port=8081,是如何生效的(当然不配置也会有默认值,这个默认值来自于org.apache.catalina.startup.Tomcat)

在ServletWebServerFactoryAutoConfiguration类上,有一个@EnableConfigurationProperties注解:开启配置属性,而它后面的参数是一个ServerProperties类,这就是习惯优于配置的最终落地点。

在这个类上,我们看到了一个非常熟悉的注解:@ConfigurationProperties,它的作用就是从配置文件中绑定属性到对应的bean上,而@EnableConfigurationProperties负责导入这个已经绑定了属性的bean到spring容器中(见上面源码)。

至此,我们大致可以了解。在全局配置的属性如:server.port等,通过@ConfigurationProperties注解,绑定到对应的XxxxProperties配置实体类上封装为一个bean,然后再通过@EnableConfigurationProperties注解导入到Spring容器中。

2.2.3 获取springboot自动配置到ioc容器中的实例

三、Lombok插件

3.1 Lombok插件简介

Lombok是一个插件,用途是使用注解给你类里面的字段,自动的加上属性,构造器,ToString方法,Equals方法等等,比较方便的一点是,你在更改字段的时候,Lombok会立即发生改变以保持和你代码的一致性。

3.2 常用的 lombok 注解介绍

1. @Getter 加在类上,可以自动生成参数的getter方法

2. @Setter 加在类上,可以自动生成参数的setter方法

3. @ToString 加在类上,调用toString()方法,可以输出实体类中所有属性的值

4. @RequiredArgsConstructor会生成一个包含常量,和标识了NotNull的变量的构造方法。生成的构造方法是私有的private。这个我用的很少

5. @EqualsAndHashCode

它会生成equals和hashCode方法

默认使用非静态的属性

可以通过exclude参数排除不需要生成的属性

可以通过of参数来指定需要生成的属性

它默认不调用父类的方法,只使用本类定义的属性进行操作,可以使用callSuper=true来解决,会在@Data中进行讲解。

@Data这个是非常常用的注解,这个注解其实是五个注解的合体:整合了Getter、Setter、ToString、EqualsAndHashCode、RequiredArgsConstructor注解。

@NoArgsConstructor生成一个无参数的构造方法。

@AllArgsConstructor生成一个包含所有变量的构造方法。

3.3 IDEA安装Lombok插件

首先我们需要安装IntelliJ IDEA中的Lombok插件,打开IntelliJ IDEA后点击菜单栏中的File-->Settings,或者使用快捷键Ctrl+Alt+S进入到设置页面

我们点击设置中的Plugins进行插件的安装,然后在搜索页面输入Lombok变可以查询到下方的Lombok,鼠标点击Lombok可在右侧看到Install按钮,点击该按钮便可安装。

安装完成之后重启idea即可。

3.4 Lombok插件的使用

3.4.1 使用Lombok需要引入Lombok的依赖

四、SpringBoot整合Junit

4.1 Junit5 介绍

Spring Boot 2.2.0 版本开始引入 Junit 5 作为单元测试默认库

作为最新版本的Junit框架,Junit5与之前版本的Junit框架有很大的不同。由三个不同子项目的几个不同模块组成。

Junit 5 = Junit Platform + Junit Jupiter + Junit Vintage

Junit Platform: Junit Platform是在JVM上启动测试框架的基础,不仅支持Junit自制的测试引擎,其他测试引擎也都可以接入。

Junit Jupiter: Junit Jupiter提供了JUnit5的新的编程模型,是Junit5新特性的核心。内部 包含了一个测试引擎,用于在Junit Platform上运行。

Junit Vintage: 由于Junit已经发展多年,为了照顾老的项目,Junit Vintage提供了兼容Junit4.x,Junit3.x的测试引擎。

4.2 SpringBoot整合Junit

第一步: 构建工程添加依赖

第二步: 创建User类进行测试

application.yml

第三步: 创建测试类

第四步: 测试类上添加注解

@SpringBootTest

第五步: 测试类注入测试对象

加入@SpringBootTest注解后台,表名当前类是一个测试类,可以自动装配容器中的对象:

五、SpringBoot整合MyBatis

5.1 名词学习

在前后端分离的开发架构中,VO、BO、DTO 是用于数据传递和封装的重要对象,它们各自有明确的应用场景和职责边界,合理使用可以提高代码的可读性、可维护性和安全性。

以下是三者的详细区别:

5.1.1 VO(View Object,视图对象)
  • 定义:用于封装前端页面(视图层)需要展示的数据,是后端返回给前端的最终数据格式。
  • 核心作用
    • 按需暴露数据,避免将数据库字段或内部业务数据直接返回给前端(如隐藏敏感字段:密码、手机号等)。
    • 对数据进行格式化或组合(如将日期转为字符串、拼接姓名和昵称等),方便前端直接使用。
  • 使用场景
    • 控制器(Controller)接口返回给前端的数据对象。
    • 前端表单提交时,若仅需部分字段,也可使用 VO 接收(但更推荐用 DTO 接收提交数据)。
  • 示例
    用户详情页面需要展示 “用户名、昵称、注册时间(格式化)”,则 VO 可能包含:
public class UserVO {
    private Long id;          // 用户ID
    private String username;  // 用户名
    private String nickname;  // 昵称
    private String registerTime; // 注册时间(格式化后,如"2023-10-01 12:00:00")
}
5.1.2 DTO(Data Transfer Object,数据传输对象)
  • 定义:用于前后端之间或服务之间的数据传输,封装接口调用时的请求参数或响应数据(但响应给前端的最终数据通常是 VO,DTO 更多用于请求参数)。
  • 核心作用
    • 规范接口的输入输出格式,明确接口需要接收或返回的数据字段。
    • 减少接口调用的次数(如合并多个参数为一个 DTO,避免多次请求)。
  • 使用场景
    • 前端提交表单数据(如新增 / 修改用户时,前端传递的参数封装为 DTO)。
    • 服务间调用时传递的数据(如微服务架构中,A 服务调用 B 服务时传递的参数)。
  • 示例
    新增用户时,前端需要传递 “用户名、密码、昵称”,则 DTO 可能包含:
public class UserDTO {
    private String username;  // 用户名(必填)
    private String password;  // 密码(必填,后端会加密处理)
    private String nickname;  // 昵称(可选)
}
5.1.3 BO(Business Object,业务对象)
  • 定义:用于封装业务逻辑处理过程中的数据,是服务层(Service)内部使用的对象,承载业务计算、规则验证等逻辑。
  • 核心作用
    • 封装业务数据和业务行为,使服务层专注于业务逻辑处理。
    • 可以组合多个数据源的数据(如合并用户表和订单表的数据进行业务计算)。
  • 使用场景
    • 服务层(Service)内部处理业务时使用(如从数据库查询到 PO 后,转换为 BO 进行业务处理)。
    • 业务逻辑的载体(如包含计算用户积分、验证用户权限等方法)。
  • 示例
    处理用户登录业务时,BO 可能包含用户的原始数据和业务处理结果:
public class UserBO {
    private Long id;
    private String username;
    private String password; // 数据库中的加密密码
    private Integer status;  // 账号状态(1-正常,0-禁用)
    
    // 业务方法:验证密码是否匹配
    public boolean checkPassword(String inputPassword) {
        // 实际逻辑:加密输入密码后与数据库密码对比
        return encrypt(inputPassword).equals(this.password);
    }
}

三者的核心区别与联系:

维度

VO(视图对象)

DTO(数据传输对象)

BO(业务对象)

使用范围

前端视图层(后端返回给前端)

前后端 / 服务间数据传输

后端服务层内部业务处理

核心职责

展示数据(按需、格式化)

规范传输数据格式

承载业务逻辑和数据处理

数据来源

通常由 BO 或 DTO 转换而来

前端输入或服务间传递的数据

通常由数据库实体(PO)转换而来

是否含逻辑

无业务逻辑,仅数据封装

无业务逻辑,仅数据封装

包含业务逻辑方法

数据流转流程(以 “新增用户” 为例)

  1. 前端提交表单数据 → 后端控制器用UserDTO接收参数。
  2. 控制器调用服务层,将UserDTO通过BeanUtils转换为UserBO
  3. 服务层对UserBO进行业务处理(如密码加密、参数校验),再转换为数据库实体(PO)存入数据库。
  4. 服务层返回处理结果(如新增的用户 ID),控制器将结果封装为 UserVO(如包含用户 ID、用户名、注册时间)返回给前端。
     

通过明确区分 VO、DTO、BO 的职责,可以使代码分层更清晰,降低各层之间的耦合度,尤其在复杂业务系统中作用显著。

5.2 整合MyBatis

本案例实现一个简化的用户管理系统,采用前后端分离架构,重点展示各层对象 (VO、DTO、BO) 的规范使用及配置的集中管理。

  • 后端技术栈:Spring Boot 3.5.13 + MyBatis + Druid 连接池 + 分页插件
  • 前端技术栈:Vue 3 (组合式 API) + ElementPlus + Axios + Vue Router

效果图:

查询所有并分页包含条件查询:

新增用户:

修改用户:

删除用户:

5.2.1 数据库设计

5.2.2 后端实现
5.2.2.1 项目结构

查询用户信息分析:

新增用户信息分析:

编辑信息:

分两步来实现:

1. 回显用户信息

2. 修改用户信息

5.2.2.2 Maven 配置 (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>3.5.13</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.sy</groupId>
    <artifactId>day02_springboot_mybatis</artifactId>
    <version>0.0.1-SNAPSHOT</version>

    <!-- 版本配置 -->
    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <mybatis.version>3.5.15</mybatis.version>
        <mybatis-spring.version>3.0.3</mybatis-spring.version>
        <pagehelper.version>1.4.7</pagehelper.version>
        <druid.version>1.2.20</druid.version>
        <spring-boot.version>3.5.4</spring-boot.version>
        <bcrypt.version>0.4</bcrypt.version>
    </properties>

    <dependencies>
        <!-- Spring Boot Web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>${spring-boot.version}</version>
        </dependency>

        <!-- Spring Boot 事务支持 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
            <version>${spring-boot.version}</version>
        </dependency>

        <!-- MyBatis 核心包 -->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>${mybatis.version}</version>
        </dependency>

        <!-- MyBatis 与 Spring 整合 -->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>${mybatis-spring.version}</version>
        </dependency>

        <!-- MySQL 驱动 -->
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <scope>runtime</scope>
        </dependency>

        <!-- Druid 连接池 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-3-starter</artifactId>
            <version>${druid.version}</version>
        </dependency>

        <!-- MyBatis 分页插件 -->
        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper-spring-boot-starter</artifactId>
            <version>${pagehelper.version}</version>
        </dependency>

        <!-- Lombok 简化代码 -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <!-- Spring Boot Validation 参数校验 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
            <version>${spring-boot.version}</version>
        </dependency>

        <!-- 测试 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <version>${spring-boot.version}</version>
            <scope>test</scope>
        </dependency>

        <!--密码加密-->
        <dependency>
            <groupId>org.mindrot</groupId>
            <artifactId>jbcrypt</artifactId>
            <version>${bcrypt.version}</version>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <!-- Spring Boot 打包插件 -->
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>${spring-boot.version}</version>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>
5.2.2.3 配置文件 (application.yml)
# 服务器配置
server:
  port: 8080

# Spring 配置
spring:
  # 数据源配置
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/sy_db?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true
    username: root
    password: root

    # Druid 连接池配置
    druid:
      initial-size: 5         # 初始化连接数
      min-idle: 5             # 最小空闲连接数
      max-active: 20          # 最大活跃连接数
      max-wait: 60000         # 获取连接时的最大等待时间(毫秒)
      time-between-eviction-runs-millis: 60000  # 检测空闲连接的间隔时间
      min-evictable-idle-time-millis: 300000    # 连接最小生存时间

  # 事务配置
  transaction:
    default-timeout: 60      # 事务默认超时时间(秒)
    rollback-on-commit-failure: true  # 提交失败时回滚

# MyBatis 配置
mybatis:
  type-aliases-package: com.sy.pojo  # 实体类别名扫描包
  mapper-locations: classpath*:mapper/*.xml  # mapper.xml 路径
  configuration:
    map-underscore-to-camel-case: true  # 驼峰命名转换
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl  # 日志实现
    default-statement-timeout: 30       # 超时时间

# 分页插件配置
pagehelper:
  helper-dialect: mysql          # 数据库方言
  reasonable: true               # 合理化分页
  support-methods-arguments: true # 支持方法参数分页
  params: count=countSql         # 计数参数

# 日志配置
logging:
  level:
    com.sy.mapper: debug  # 打印 mapper 日志
    org.springframework.jdbc.datasource.DataSourceTransactionManager: debug  # 打印事务日志
5.2.2.4 启动类

5.2.2.5 配置类

跨域配置

package com.sy.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;

/**
 * 跨域配置,解决前后端分离架构中的跨域问题
 */
@Configuration
public class WebConfig {
    
    @Bean
    public CorsFilter corsFilter() {
        // 1.创建CORS配置对象
        CorsConfiguration config = new CorsConfiguration();
        // 允许所有源访问
        config.addAllowedOriginPattern("*");
        // 允许所有请求头
        config.addAllowedHeader("*");
        // 允许所有请求方法
        config.addAllowedMethod("*");
        // 允许携带cookie
        config.setAllowCredentials(true);
        // 预检请求的有效期,单位秒
        config.setMaxAge(3600L);
        
        // 2.添加映射路径
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", config);
        
        // 3.返回新的CorsFilter
        return new CorsFilter(source);
    }
}
5.2.2.6 工具类

响应状态码

package com.sy.utils;

/**
 * 响应状态码枚举
 */
public enum ResultCode {
    SUCCESS(200, "成功"),
    ERROR(500, "失败"),
    PARAM_ERROR(400, "参数错误"),
    NOT_FOUND(404, "资源不存在");

    private final int code;
    private final String msg;

    ResultCode(int code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    public int getCode() {
        return code;
    }

    public String getMsg() {
        return msg;
    }
}

统一响应结果

密码加密

package com.sy.utils;

import org.mindrot.jbcrypt.BCrypt;

/**
 * 密码加密工具类
 */
public class PasswordUtils {

    //1.对密码进行加密处理
    public static String encryptPassword(String plainPassword){
        //判断密码是否为空
        if (plainPassword == null || plainPassword == "" || plainPassword.isEmpty()){
            throw new  IllegalCallerException("密码不能为空");
        }
        /**
         * plainPassword:明文密码
         * BCrypt.gensalt():生成盐(固定的字符串)  12:迭代次数(加载因子)
         * BCrypt加密方式:  不可逆    加密的次数范围4-31(值越大, 安全性越高, 加密越慢 耗费内存和性能)  取值为:21
         * 如何加密的: 明文密码 + 盐(固定字符串) + 加载因子(加密的次数)
         */
        try {
            return BCrypt.hashpw(plainPassword, BCrypt.gensalt(12));
        } catch (Exception e) {
            throw new IllegalCallerException("密码加载失败");
        }
    }

    /**
     * 2.校验密码
     * @param plainPassword       明文密码
     * @param encryptedPassword   加密后的密码
     * @return 验证结果   true:表示匹配 ,false:表示不匹配
     */
    public static boolean checkPassword(String plainPassword,String encryptedPassword){
        //判断密码是否为空
        if (
                plainPassword == null || plainPassword == "" || plainPassword.isEmpty()
                || encryptedPassword == null || encryptedPassword == "" || encryptedPassword.isEmpty()
        ){
            return false;
        }
        try {
            //验证密码
            return BCrypt.checkpw(plainPassword,encryptedPassword);
        } catch (Exception e) {
            return false;
        }
    }

}

分页结果 DTO

5.2.2.7 实体类 (pojo)
package com.sy.pojo;

import lombok.Data;
import java.time.LocalDateTime;

/**
 * 用户实体类,与数据库表结构一一对应
 */
@Data
public class User {
    /**
     * 用户ID
     */
    private Long id;
    
    /**
     * 用户名
     */
    private String username;
    
    /**
     * 密码
     */
    private String password;
    
    /**
     * 真实姓名
     */
    private String realName;
    
    /**
     * 手机号
     */
    private String phone;
    
    /**
     * 状态:0-禁用,1-正常
     */
    private Integer status;
    
    /**
     * 创建时间
     */
    private LocalDateTime createTime;
}
5.2.2.8 业务对象 (BO)
package com.sy.bo;

import lombok.Data;
import java.time.LocalDateTime;

/**
 * 用户业务对象,用于服务层内部业务处理
 * 包含业务处理所需的字段和方法
 */
@Data
public class UserBO {
    /**
     * 用户ID
     */
    private Long id;
    
    /**
     * 用户名
     */
    private String username;
    
    /**
     * 密码
     */
    private String password;
    
    /**
     * 真实姓名
     */
    private String realName;
    
    /**
     * 手机号
     */
    private String phone;
    
    /**
     * 状态:0-禁用,1-正常
     */
    private Integer status;
    
    /**
     * 状态名称,用于业务展示
     */
    private String statusName;
    
    /**
     * 创建时间
     */
    private LocalDateTime createTime;
    
    /**
     * 转换状态码为状态名称
     */
    public void convertStatus() {
        if (this.status != null) {
            this.statusName = this.status == 1 ? "正常" : "禁用";
        }
    }
}
5.2.2.9 数据传输对象 (DTO)

查询请求 DTO

package com.sy.dto.request;

import lombok.Data;

/**
 * 用户查询请求数据传输对象
 * 用于接收前端传递的查询参数
 */
@Data
public class UserQueryRequest {
    /**
     * 用户名(模糊查询)
     */
    private String username;
    
    /**
     * 真实姓名(模糊查询)
     */
    private String realName;
    
    /**
     * 状态:0-禁用,1-正常
     */
    private Integer status;
    
    /**
     * 当前页码
     */
    private Integer pageNum = 1;
    
    /**
     * 每页条数
     */
    private Integer pageSize = 10;
}

添加请求 DTO

更新请求 DTO

5.2.2.10 视图对象 (VO)

5.2.2.11 Mapper 接口和 XML

Mapper 接口

package com.sy.mapper;


import com.sy.dto.request.UserQueryRequest;

import com.sy.pojo.User;
import org.apache.ibatis.annotations.Mapper;

import java.util.List;

/**
 * 用户Mapper接口,定义数据库操作方法
 */
@Mapper
public interface UserMapper {
    /**
     * 根据ID查询用户
     * @param id 用户ID
     * @return 用户实体
     */
    User selectById(Long id);
    
    /**
     * 根据用户名查询用户
     * @param username 用户名
     * @return 用户实体
     */
    User selectByUsername(String username);
    
    /**
     * 分页查询用户列表
     * @param queryRequest 查询条件
     * @return 用户列表
     */
    List<User> getUserPage(UserQueryRequest queryRequest);
    
    /**
     * 新增用户
     * @param user 用户实体
     * @return 影响行数
     */
    int insert(User user);
    
    /**
     * 更新用户
     * @param user 用户实体
     * @return 影响行数
     */
    int update(User user);
    
    /**
     * 根据ID删除用户
     * @param id 用户ID
     * @return 影响行数
     */
    int deleteById(Long id);
}

UserMapper.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接口 -->
<mapper namespace="com.sy.mapper.UserMapper">

    <!-- 通用查询结果列 -->
    <sql id="Base_Column_List">
        id, username, password, real_name, phone, status, create_time
    </sql>

    <!-- 根据ID查询 -->
    <select id="selectById" parameterType="java.lang.Long" resultType="com.sy.pojo.User">
        SELECT
        <include refid="Base_Column_List"/>
        FROM user
        WHERE id = #{id}
    </select>

    <!-- 根据用户名查询 -->
    <select id="selectByUsername" parameterType="java.lang.String" resultType="com.sy.pojo.User">
        SELECT
        <include refid="Base_Column_List"/>
        FROM user
        WHERE username = #{username}
    </select>

    <!-- 分页查询 -->
    <select id="selectByPage" parameterType="com.sy.dto.request.UserQueryRequest" resultType="com.sy.pojo.User">
        SELECT
        <include refid="Base_Column_List"/>
        FROM user
        <where>
            <if test="username != null and username != ''">
                AND username LIKE CONCAT('%', #{username}, '%')
            </if>
            <if test="realName != null and realName != ''">
                AND real_name LIKE CONCAT('%', #{realName}, '%')
            </if>
            <if test="status != null">
                AND status = #{status}
            </if>
        </where>
        ORDER BY create_time DESC
    </select>

    <!-- 新增用户 -->
    <insert id="insert" parameterType="com.sy.pojo.User" useGeneratedKeys="true" keyProperty="id">
        INSERT INTO user (
            username, password, real_name, phone, status, create_time
        ) VALUES (
                     #{username}, #{password}, #{realName}, #{phone}, #{status}, NOW()
                 )
    </insert>

    <!-- 更新用户 -->
    <update id="update" parameterType="com.sy.pojo.User">
        UPDATE user
        <set>
            <if test="realName != null">real_name = #{realName},</if>
            <if test="phone != null">phone = #{phone},</if>
            <if test="status != null">status = #{status},</if>
        </set>
        WHERE id = #{id}
    </update>

    <!-- 删除用户 -->
    <delete id="deleteById" parameterType="java.lang.Long">
        DELETE FROM user WHERE id = #{id}
    </delete>

</mapper>
5.2.2.12 服务层 (Service)

服务接口

package com.sy.service;

import com.sy.vo.UserVO;
import com.sy.dto.request.UserQueryRequest;
import com.sy.dto.request.UserAddRequest;
import com.sy.dto.request.UserUpdateRequest;
import com.sy.dto.response.PageResult;

/**
 * 用户服务接口,定义业务逻辑方法
 */
public interface UserService {
    /**
     * 根据ID查询用户
     * @param id 用户ID
     * @return 用户视图对象
     */
    UserVO getUserById(Long id);
    
    /**
     * 分页查询用户列表
     * @param queryRequest 查询条件
     * @return 分页结果
     */
    PageResult<UserVO> getUserPage(UserQueryRequest queryRequest);
    
    /**
     * 新增用户
     * @param addRequest 新增用户参数
     * @return 新增的用户ID
     */
    Long addUser(UserAddRequest addRequest);
    
    /**
     * 更新用户
     * @param updateRequest 更新用户参数
     * @return 是否更新成功
     */
    boolean updateUser(UserUpdateRequest updateRequest);
    
    /**
     * 根据ID删除用户
     * @param id 用户ID
     * @return 是否删除成功
     */
    boolean deleteUser(Long id);
}

服务实现类

package com.sy.service.impl;

import com.sy.bo.UserBO;
import com.sy.pojo.User;
import com.sy.vo.UserVO;
import com.sy.mapper.UserMapper;
import com.sy.service.UserService;
import com.sy.dto.request.UserQueryRequest;
import com.sy.dto.request.UserAddRequest;
import com.sy.dto.request.UserUpdateRequest;
import com.sy.dto.response.PageResult;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.DigestUtils;
import org.springframework.beans.factory.annotation.Autowired;

import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;

/**
 * 用户服务实现类,实现具体业务逻辑
 */
@Service
public class UserServiceImpl implements UserService {

    // 使用@Autowired注入Mapper
    @Autowired
    private UserMapper userMapper;

    /**
     * 根据ID查询用户
     */
    @Override
    public UserVO getUserById(Long id) {
        // 查询数据库
        User user = userMapper.selectById(id);
        if (user == null) {
            return null;
        }
        
        // 转换为业务对象并处理
        UserBO userBO = new UserBO();
        BeanUtils.copyProperties(user, userBO);
        userBO.convertStatus();
        
        // 转换为视图对象返回
        UserVO userVO = new UserVO();
        BeanUtils.copyProperties(userBO, userVO);
        return userVO;
    }

    /**
     * 分页查询用户列表
     */
    @Override
    public PageResult<UserVO> getUserPage(UserQueryRequest queryRequest) {
        // 启用分页插件
        PageHelper.startPage(queryRequest.getPageNum(), queryRequest.getPageSize());
        
        // 查询数据
        List<User> userList = userMapper.selectByPage(queryRequest);
        PageInfo<User> pageInfo = new PageInfo<>(userList);
        
        // 转换为业务对象并处理
        List<UserVO> userVOList = new ArrayList<>();
        for (User user : userList) {
            UserBO userBO = new UserBO();
            BeanUtils.copyProperties(user, userBO);
            userBO.convertStatus();
            
            UserVO userVO = new UserVO();
            BeanUtils.copyProperties(userBO, userVO);
            userVOList.add(userVO);
        }
        
        // 封装分页结果
        return new PageResult<>(
                pageInfo.getTotal(),
                pageInfo.getPages(),
                pageInfo.getPageNum(),
                pageInfo.getPageSize(),
                userVOList
        );
    }

    /**
     * 新增用户 - 带事务
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long addUser(UserAddRequest addRequest) {
        // 检查用户名是否已存在
        User existUser = userMapper.selectByUsername(addRequest.getUsername());
        if (existUser != null) {
            throw new RuntimeException("用户名已存在");
        }
        
        // 转换为实体类
        User user = new User();
        BeanUtils.copyProperties(addRequest, user);
        
        // 密码加密处理(MD5)
        String encryptedPassword = DigestUtils.md5DigestAsHex(
                addRequest.getPassword().getBytes(StandardCharsets.UTF_8));
        user.setPassword(encryptedPassword);
        
        // 插入数据库
        userMapper.insert(user);
        return user.getId();
    }

    /**
     * 更新用户 - 带事务
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean updateUser(UserUpdateRequest updateRequest) {
        // 检查用户是否存在
        User existUser = userMapper.selectById(updateRequest.getId());
        if (existUser == null) {
            throw new RuntimeException("用户不存在");
        }
        
        // 转换为实体类
        User user = new User();
        BeanUtils.copyProperties(updateRequest, user);
        
        // 更新数据库
        int rows = userMapper.update(user);
        return rows > 0;
    }

    /**
     * 删除用户 - 带事务
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean deleteUser(Long id) {
        // 检查用户是否存在
        User existUser = userMapper.selectById(id);
        if (existUser == null) {
            throw new RuntimeException("用户不存在");
        }
        
        // 删除用户
        int rows = userMapper.deleteById(id);
        return rows > 0;
    }
}
5.2.2.13 控制层 (Controller)
package com.sy.controller;

import com.sy.vo.UserVO;
import com.sy.service.UserService;
import com.sy.utils.Result;
import com.sy.dto.request.UserQueryRequest;
import com.sy.dto.request.UserAddRequest;
import com.sy.dto.request.UserUpdateRequest;
import com.sy.dto.response.PageResult;
import jakarta.validation.Valid;
import org.springframework.web.bind.annotation.*;
import org.springframework.beans.factory.annotation.Autowired;

/**
 * 用户控制器,处理前端请求
 */
@RestController
@RequestMapping("/user")
public class UserController {

    // 使用@Autowired注入服务
    @Autowired
    private UserService userService;

    /**
     * 根据ID查询用户
     */
    @GetMapping("/{id}")
    public Result<UserVO> getUserById(@PathVariable Long id) {
        UserVO userVO = userService.getUserById(id);
        return Result.success(userVO);
    }

    /**
     * 分页查询用户列表
     */
    @GetMapping("/page")
    public Result<PageResult<UserVO>> getUserPage(UserQueryRequest queryRequest) {
        PageResult<UserVO> pageResult = userService.getUserPage(queryRequest);
        return Result.success(pageResult);
    }

    /**
     * 新增用户
     */
    @PostMapping
    public Result<Long> addUser(@Valid @RequestBody UserAddRequest addRequest) {
        Long userId = userService.addUser(addRequest);
        return Result.success(userId);
    }

    /**
     * 更新用户
     */
    @PutMapping
    public Result<Boolean> updateUser(@Valid @RequestBody UserUpdateRequest updateRequest) {
        boolean success = userService.updateUser(updateRequest);
        return Result.success(success);
    }

    /**
     * 删除用户
     */
    @DeleteMapping("/{id}")
    public Result<Boolean> deleteUser(@PathVariable Long id) {
        boolean success = userService.deleteUser(id);
        return Result.success(success);
    }
}

六、SpringBoot使用logback日志框架

6.1 前言

项目中日志系统是必不可少的,目前比较流行的日志框架有 log4j、logback 等,可能大家还不知道,这两个框架的作者是同一个人,Logback 旨在作为流行的 log4j 项目的后续版本,从而恢复 log4j 离开的位置。

另外 slf4j(Simple Logging Facade for Java) 则是一个日志门面框架,提供了日志系统中常用的接口,logback 和 log4j 则对slf4j 进行了实现。

我们本文将讲述如何在 SpringBoot 中应用 logback+slf4j 实现日志的记录。

6.2 为什么使用logback

- Logback 是log4j 框架的作者开发的新一代日志框架,它效率更高、能够适应诸多的运行环境,同时天然支持 SLF4J。

- Logback 的定制性更加灵活,同时也是 SpringBoot 的内置日志框架。

6.3 开始使用

6.3.1 添加依赖

实际开发中我们直接引入spring-boot-starter-web依赖即可,因为spring-boot-starter-web包含了spring-boot-starter。而spring-boot-starter包含了spring-boot-starter-logging,所以我们只需要引入 web 组件即可。

6.3.2 logback-spring.xml详解

SpringBoot 官方推荐优先使用带有-spring的文件名作为你的日志配置(如使用logback-spring.xml,而不是logback.xml),命名为logback-spring.xml的日志配置文件,将 xml 放至src/main/resource下面。

也可以使用自定义的名称,比如logback-config.xml,只需要在 application.properties 文件中使用logging.config=classpath:logback-config.xml指定即可。

在讲解 logback-spring.xml之前我们先来了解三个单词:

  • Logger(记录器)
  • Appenders(附加器)
  • Layouts(布局)
6.3.3 详细的logback-spring.xml示例
<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="true">

  <!-- appender是configuration的子节点,是负责写日志的组件。 -->
  <!-- ConsoleAppender:把日志输出到控制台 -->
  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <!--配置日志输出的格式-->
    <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
      <!--%c.%M:类名和方法名-->
      <!-- %15.15():如果记录的线程字符长度小于15(第一个)则用空格在左侧补齐,如果字符长度大于15(第二个),则从开头开始截断多余的字符 -->
      <!-- %msg:日志打印详情 -->
      <!-- %n:换行符 -->
      <!-- %highlight():转换说明符以粗体红色显示其级别为ERROR的事件,红色为WARN,BLUE;为INFO以及其他级别的默认颜色。 -->
      <!-- %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %highlight(%-5level) %msg %cyan(%logger{5}).%M\(%F:%L\)%n-->
      <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %highlight(%-5level) --- [%15.15(%thread)] %cyan(%(%logger{40})).%M\(%F:%L\) : %msg%n</pattern>
      <!-- 控制台也要使用UTF-8,不要使用GBK,否则会中文乱码 -->
      <charset>UTF-8</charset>
    </encoder>
  </appender>

  <!-- 设置日志输出文件的话,格式的配置-->
  <!-- RollingFileAppender:日志记录到文件 -->
  <!-- 以下的大概意思是:
  1.先按日期存日志,日期变了,将前一天的日志文件名重命名为XXX%日期%索引,新的日志仍然是
  project_data.log
  2.如果日期没有发生变化,但是当前日志的文件大小超过10MB时,对当前日志进行分割 重命名-->
  <appender name="file" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <!--日志文件路径和名称-->
    <File>F:\logs/project_data.log</File>
    <!--是否追加到文件末尾,默认为true-->
    <append>true</append>

    <!--rollingPolicy是RollingFileAppender交互的重要子组件,负责执行翻转所需的操作。-->
    <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
      <!-- 日志文件的名字会根据fileNamePattern的值,每隔一段时间改变一次 -->
      <!-- 文件名:logs/project_info.2023-12-05.0.log -->
      <!-- 注意:SizeAndTimeBasedRollingPolicy中 %i和%d都是强制性的,必须存在,要不会报错 -->
      <fileNamePattern>F:\logs/project_data.%d.%i.log</fileNamePattern>
      <!-- 每产生一个日志文件,该日志文件的保存期限为30天;
      ps:maxHistory的单位是根据fileNamePattern中的翻转策略自动推算出来的,
      例如上面选用了yyyy-MM-dd,则单位为天
      如果上面选用了yyyy-MM,则单位为月,另外上面的单位默认为yyyy-MM-dd-->
      <maxHistory>30</maxHistory>
      <!-- 每个日志文件到10mb的时候开始切分,最多保留30天,但最大到20GB(该文件夹最大存放20GB的文件),
      哪怕没到30天也要删除多余的日志 -->
      <totalSizeCap>20GB</totalSizeCap>
      <!-- maxFileSize:这是活动文件的大小,默认值是10MB,测试时可改成5KB看效果 -->
      <maxFileSize>10MB</maxFileSize>
    </rollingPolicy>
    <!--编码器-->
    <encoder>
      <!-- pattern节点,用来设置日志的输入格式
      ps:日志文件中没有设置颜色,否则颜色部分会有ESC[0:39em等乱码-->
      <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level --- [%15.15(%thread)] %(%logger{40}).%M\(%F:%L\) : %msg%n</pattern>
      <!-- 记录日志的编码:此处设置字符集 - -->
      <charset>UTF-8</charset>
    </encoder>
  </appender>


  <!-- configuration中最多允许一个root,别的logger如果没有设置级别则从父级别root继承;
  例如name=STDOUT没有设置日志的级别,那么默认使用root中声明的INFO级别-->
  <!--使用name=STDOUT的配置输出info以及info以上级别的日志;
  级别依次为【从高到低】:FATAL > ERROR > WARN > INFO > DEBUG > TRACE -->
  <!--level="INFO":如果name=STDOUT和name=file没有设置日志级别会使用priority的info级别;
  如果name=STDOUT或者name=file设置了日志级别为error,那么以error为准;
  如果name=STDOUT或者name=file设置了日志级别为了debug,那么以info为
  准 -->
    <root level="INFO">
        <appender-ref ref="STDOUT" />
        <appender-ref ref="file" />
    </root>

    <!-- 指定项目中某个包,当有日志操作行为时的日志记录级别;
     当com.sy.service出现WARN级别及以上级别的日志的时候使用name=file这个配置记录日志到文件;
    当前配置的优先级高于全局配置,全局配置日志级别为info,如果sy.servic出现info级别的日志,也不会输出
    ,只有sy.service包中出现warn及其以上级别的日志才会使用name=file配置输出日志;
    为防止日志重复输出,我们需要把additivity设置为false,如果sy.service出现了error级别的日志,只会
   使用name=file配置输出日志,将不再使用全局配置输出日志
     -->
    <logger name="com.sy.service" level="WARN" additivity="false">
        <appender-ref ref="file" />
    </logger>
</configuration>
6.3.3 使用案例

使用slf4j的API很简单。使用LoggerFactory初始化一个Logger实例,然后调用Logger对应的打印等级函数就行了。

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class App {
    private static final Logger log = LoggerFactory.getLogger(App.class);
    public static void main(String[] args) {
        String msg = "print log, current level: ";
        //级别依次为【从高到低】:FATAL > ERROR > WARN > INFO > DEBUG > TRACE
        log.error(msg + "error");

        log.warn(msg + "warn");

        log.info(msg + "info");

        log.debug(msg + "debug");

        log.trace(msg + "trace");
    }
}

注解@Slf4j的使用

声明:如果不想每次都写private final Logger logger = LoggerFactory.getLogger(当前类名.class); 可以用注解@Slf4j;

1、使用idea在pom文件加入lombok的依赖

pom.xml

<dependency>

        <groupId>org.projectlombok</groupId>

        <artifactId>lombok</artifactId>

</dependency>

2、类上面添加@Sl4j注解,然后使用log打印日志;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值