接上次博客:JavaEE进阶(6)SpringBoot 配置文件(作用、格式、properties配置文件说明、yml配置文件说明、验证码案例)-CSDN博客
目录
日志概述
日志的用途
日志使用
打印日志
在程序中获取日志对象
使用日志对象打印日志
日志框架介绍(了解)
门面模式(外观模式)
门面模式的优点
策略模式
模板模式
定义和介绍
特点
优势
使用场景
策略模式和模板模式的区别?
SLF4J 框架介绍
不引入日志门面
引入日志门面
日志格式的说明
日志级别
日志级别的使用
日志配置
配置日志级别
日志持久化
配置日志文件分割
配置日志格式
更简单的日志输出
添加 lombok 依赖
输出日志
@Slf4j 注解原理解析
日志概述
为什么要学习日志?
日志在我们的编程生涯中并不陌生,甚至在JavaSE的部分,我们就已经开始使用 System.out.print 来打印日志了。通过打印日志,我们能够发现和定位问题,或者根据日志来分析程序的运行过程。在学习Spring框架时,我们经常需要根据控制台的日志来分析和定位问题。
随着项目的复杂度提升,我们对日志的记录也有了更高的需求,不仅仅是用于定位和排查问题。例如,需要记录一些用户的操作记录(一些审计公司可能会要求这样做),也可能需要使用日志来记录用户的一些偏好,将日志持久化,以便后续进行数据分析等。然而,System.out.print 并不能很好地满足我们的需求,因此我们就需要使用一些专业的日志框架,毕竟专业的事情应该交给专业的人去做。
日志的用途
通过前面的学习,我们知道日志主要是为了发现问题、分析问题、定位问题,但除此之外,日志还有许多其他用途。
-
系统监控: 系统监控几乎是一个成熟系统的标配。通过日志记录系统的运行状态、每个方法的响应时间、响应状态等,我们可以对数据进行分析,设置不同的规则,并在超过阈值时进行报警。例如,统计日志中关键字的数量,并在关键字数量达到一定条件时触发报警,这是日志的常见需求之一。
系统监控中日志的应用和常见需求:
运行状态记录:通过日志记录系统的运行状态,我们能够实时了解系统的健康状况。这包括系统启动、关闭、关键服务的启停状态等信息。对于大型系统,通过分析这些日志,我们可以及时发现潜在问题,确保系统一直保持在正常运行状态。
方法响应时间:记录每个方法的响应时间是性能监控的一项重要指标。通过分析日志中的时间戳信息,我们可以了解系统中每个方法的执行时间,识别潜在的性能瓶颈,并进行性能优化。超过预定阈值的响应时间可以触发警报,帮助我们及时处理性能问题。
响应状态监测:日志还记录了每次请求的响应状态,包括成功、失败或异常。通过分析这些状态信息,我们可以快速定位到具体的请求或操作,从而迅速排查问题并采取相应措施。例如,当某个关键操作的成功率下降时,系统可以自动触发警报,通知相关人员进行处理。
数据分析与规则设置:将日志信息进行统计和分析有助于制定规则,用于监测系统的运行情况。通过统计关键字的数量、特定操作的频率等,我们可以建立规则,当某项指标超过设定的阈值时,系统会生成警报,通知相关人员进行进一步的检查和处理。
实时报警机制:系统监控中的重要功能之一是实时报警。通过日志记录的数据,系统可以实现对特定事件或指标的实时监测,一旦超过预定的阈值,系统会自动触发警报,通知相关团队采取紧急措施,确保系统的可用性和稳定性。
系统监控通过日志记录提供了对软件系统运行情况的全面了解,使开发人员和运维人员能够快速响应和处理问题,确保系统在高效、稳定的状态下运行。
-
数据采集: 数据采集涵盖了广泛的领域,采集的数据可以在许多方面发挥作用,如数据统计、推荐排序等。
-
数据统计:通过统计页面的浏览量(PV)、访客量(UV)、点击量等,可以进行数据分析,优化公司的运营策略。
- 浏览量(PV): 通过记录用户访问每个页面的次数,我们可以了解哪些页面受欢迎,哪些不够吸引用户,从而调整页面布局和内容。
- 访客量(UV): 跟踪独立访客的数量,帮助我们了解网站或应用的受众规模,为市场营销和用户定位提供重要信息。
- 点击量: 记录用户在页面上的点击次数,有助于分析用户的兴趣点,改进页面设计,提高用户的互动体验。
- 其他定制化统计: 根据具体业务需求,还可以记录和统计其他关键指标,如注册量、转化率等,以全面了解用户行为和业务运营情况。
数据统计通过日志的记录和分析,为公司提供了有力的数据支持,帮助优化运营策略,提高用户满意度和公司业绩。
-
推荐排序:推荐排序应用在购物、广告、新闻等领域。数据采集是推荐排序工作中必须做的一环,系统通过日志记录用户的浏览历史、停留时长等信息,算法人员通过分析这些数据、训练模型,为用户提供个性化的推荐。
- 用户浏览历史记录: 通过记录用户在系统中的浏览历史,包括浏览的产品、阅读的文章等,系统可以建立用户的兴趣模型。
- 停留时长记录: 记录用户在特定页面停留的时长,有助于判断用户对内容的兴趣程度,进而进行个性化推荐。
- 点击行为记录: 记录用户点击的产品、广告或新闻,为推荐算法提供更准确的输入数据。
- 算法模型训练: 算法人员通过分析这些日志数据,训练推荐算法模型,以更精准地预测用户的喜好和行为,为用户提供个性化的推荐服务。
数据采集为推荐排序系统提供了关键的原始数据,使得推荐系统能够更好地理解用户需求,提高推荐的准确性和用户满意度。
通过合理而有针对性的数据采集,公司可以更好地了解用户行为,调整业务策略,并通过智能推荐系统提供更符合用户兴趣和需求的个性化体验。
下图中的数据源, 其中⼀部分就来自于日志记录的数据:
-
-
日志审计: 随着互联网的发展,许多企业的关键业务越来越多地运行于网络之上。网络安全成为关注焦点,系统安全也成为项目中的一个重要环节。安全审计在这其中扮演着非常重要的角色。国家的政策法规、行业标准等都对日志审计提出了要求。通过系统日志分析,可以判断一些非法攻击、非法调用,以及系统处理过程中的安全隐患。日志审计不仅有助于确保系统的安全性,还符合法规和标准的要求。
2、符合法规和标准:
1、网络安全焦点:
(1)、非法攻击检测: 通过系统日志的分析,可以识别并判断潜在的非法攻击行为,包括网络入侵、恶意代码注入等。对异常行为的及时发现有助于采取相应的防御措施,确保网络安全。
(2)、非法调用追踪: 记录系统中的调用情况,有助于发现非法的系统调用,保护核心业务逻辑不受未授权访问。
(1)、政策法规要求: 随着信息技术的快速发展,政府和行业组织对于信息系统的安全性提出了越来越高的要求。日志审计可以帮助企业遵循相关的政策法规,确保业务运营的合法性和安全性。
(2)、行业标准遵循: 许多行业都有自己的安全标准和规范,如PCI DSS(支付卡行业数据安全标准)等。通过进行日志审计,企业可以持续监测和验证系统是否符合这些行业标准,保障数据和用户信息的安全。
3、安全隐患识别:系统处理过程中的问题: 通过分析日志,可以发现系统在处理过程中可能存在的安全隐患,如权限问题、异常处理不当等。及时发现并解决这些问题可以有效防范潜在的风险。
4、事件溯源与调查:安全事件追踪: 日志审计提供了对系统中发生的事件进行追踪的手段。当发生安全事件时,可以通过日志追溯事件的源头,帮助调查人员迅速了解事件的发生和影响,以便采取适当的反应。
日志审计不仅有助于确保系统的安全性,还是企业遵循法规和行业标准的有效手段。通过对日志的仔细分析,企业可以提前发现潜在的风险和问题,加强对关键业务的安全保障,维护用户和企业信息的完整性和可信度。
日志使用
Spring Boot 项目在启动的时候默认就日志输出,如下图所示:
Spring Boot项目在启动时默认会输出日志信息。这是因为Spring Boot集成了一种默认的日志框架,通常是Logback或者Log4j2,作为默认的日志实现。这样的设计有助于开发者在项目启动过程中获取关键的信息,方便了解应用的状态和问题。
这些日志信息包括了应用的启动时间、启动所用时间、应用的版本信息等。默认的日志级别一般是INFO,但我们也可以在应用的配置文件中修改日志级别,以调整输出的详细程度。
Spring Boot内置了Slf4j(Simple Logging Facade for Java)作为默认的日志框架。Slf4j并不是一个具体的日志实现,而是一个为Java应用提供简单日志门面的框架,它允许我们在应用中使用统一的日志接口,而具体的日志实现则由我们在项目中选择。
也就是说,我们刚刚提到的Logback和Log4j2都是常见的日志框架,它们可以作为Slf4j的实现,因此可以与Spring Boot集成。
Logback:
- Logback是由Log4j的创始人设计的,旨在成为Log4j的继承者。它提供了比Log4j更好的性能和一些新的特性。在Spring Boot中,Logback作为默认的日志实现,因此你不需要额外配置就可以使用它。
Log4j2:
- Log4j2是Log4j的下一代版本,它在性能和灵活性方面进行了改进。Log4j2支持异步日志记录和更灵活的配置。在Spring Boot中,你可以通过添加相应的依赖来使用Log4j2,并通过配置文件进行定制化。
这两个日志框架的选择取决于我们的需求和个人偏好。Spring Boot提供了默认的配置,但我们可以根据项目的需要选择合适的日志框架。无论选择哪个框架,都可以通过Slf4j进行统一的日志接口调用,从而使得应用代码与具体的日志实现解耦。
在Spring Boot项目中,Slf4j一般与Logback搭配使用,作为默认的日志实现。Slf4j的使用方式非常简单,我们可以在代码中通过Slf4j的Logger接口来输出日志。
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class MyService {
private static final Logger logger = LoggerFactory.getLogger(MyService.class);
public void doSomething() {
// ...
logger.info("Doing something...");
// ...
}
}
在上面的例子中,LoggerFactory.getLogger(MyService.class) 返回一个Slf4j的Logger实例,然后我们可以使用这个实例来输出不同级别的日志信息,如info、debug、error等。
使用Logback作为底层的日志实现,并且Spring Boot是默认配置的情况下,通常不需要显式引入Logback的依赖。Spring Boot的起步依赖(starter)已经包含了Logback的依赖,并会在项目启动时自动加载。
使用Slf4j的好处在于,我们能够方便地切换和替换底层的日志实现,而不需要修改应用代码。如果我们希望使用其他日志框架,比如Log4j2或者Java Util Logging,只需添加相应的依赖并进行配置即可,Slf4j会自动与其集成。这种灵活性是Spring Boot设计的一个重要特点。
使用Log4j2:
添加Log4j2的依赖:
<!-- Maven 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
修改MyService类:
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class MyService {
private static final Logger logger = LogManager.getLogger(MyService.class);
public void doSomething() {
// ...
logger.info("Doing something...");
// ...
}
}
使用Java Util Logging:
添加Java Util Logging的依赖:
<!-- Maven 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
修改MyService类:
import java.util.logging.Logger;
public class MyService {
private static final Logger logger = Logger.getLogger(MyService.class.getName());
public void doSomething() {
// ...
logger.info("Doing something...");
// ...
}
}
接下来我们还是使用Logback框架。
打印日志
打印日志的一般步骤:
-
获取日志对象: 在程序中,我们首先需要获取一个日志对象,一般通过日志框架提供的工厂方法获取。在上述例子中,我们使用了Slf4j的LoggerFactory.getLogger(MyService.class)方法来获取日志对象。具体的获取方式会根据使用的日志框架而有所不同。
-
使用日志对象输出内容: 一旦我们获得了日志对象,就可以使用该对象来输出日志信息。通常,日志对象提供了多个级别的日志输出方法,如info()、debug()、warn()、error()等。我们可以根据需要选择合适的日志级别,将相应的信息输出到日志系统中。
在程序中获取日志对象
在程序中获取日志对象是一项重要的任务。为了实现这一目标,我们需要使用日志工厂 LoggerFactory。
// 获取Logger对象,参数是日志的名称,使得我们能够清晰地知道是哪个类输出的日志。
private static final Logger logger = LoggerFactory.getLogger(LoggerController.class);
在上述代码中,LoggerFactory.getLogger 需要传递一个参数,即标识这个日志的名称。这样做的好处是,我们可以更清晰地知道是哪个类输出的日志。当程序出现问题时,通过日志可以更方便直观地定位到问题所在的类。
需要注意的是,Logger对象属于 org.slf4j 包,因此在导入时要确保不要导入错误的包。这样可以避免出现不必要的问题。
使用日志对象打印日志
使用日志对象打印日志是一种常见的做法,可以通过不同级别的方法来输出不同类型的日志。
我们先使用 info() 方法来输出日志:
package com.example.captchademo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RequestMapping("/log")
@RestController
public class LoggerController {
private static final Logger logger = LoggerFactory.getLogger(LoggerController.class);
@RequestMapping("/printLog")
public String printLog(){
System.out.println("打印日志");
logger.info("======我是日志对象打印的日志=====");
return "success";
}
@RequestMapping("/logLevel")
public String logLevel(){
logger.trace("我是trace级别的日志");
logger.debug("我是debug级别的日志");
logger.info("我是info级别的日志");
logger.warn("我是warn级别的日志");
logger.error("我是error级别的日志");
return "success";
}
}
日志框架介绍(了解)
SLF4J(Simple Logging Facade for Java)与其他日志框架有所不同,它并不是一个真正的日志实现,而是一个抽象层。它旨在为各种日志框架提供一种规范、标准和接口,使得开发人员能够以统一的方式处理日志操作。SLF4J并不是一个独立可用的日志框架,而是需要与具体的日志框架配合使用。
通过SLF4J,开发人员可以使用统一的API进行日志记录,而无需关心底层日志框架的具体实现。这种抽象层的设计使得应用程序可以更灵活地切换和配置不同的日志框架,而不需要修改应用程序的源代码。SLF4J的存在有助于解决Java应用程序中日志框架的混乱局面,为开发人员提供了更一致的日志管理体验。
总体而言,SLF4J的作用在于提供了一个通用的日志抽象接口,使得应用程序能够更方便地与不同的日志实现进行集成和交互,从而达到更好的可维护性和可扩展性。
门面模式(外观模式)
门面模式定义
门面模式是一种设计模式,又称为外观模式。它旨在提供一个统一的接口,使得访问子系统中一组接口变得更加简单。这种模式的主要特征是定义了一个高层接口,使得整个子系统更容易使用。
门面模式是一种结构型设计模式,在门面模式中,有一个称为门面(Facade)的类,它包含了客户端可能需要的子系统的功能的简化接口。客户端只需与门面类交互,而无需直接与子系统的各个组件打交道。这有助于降低系统的耦合度,使得系统更容易维护和扩展。
门面模式常常应用在大型系统中,其中存在多个复杂的子系统。通过引入门面,可以将系统的复杂性隐藏起来,提供一个清晰的入口点供客户端使用。这有助于降低系统的理解和使用难度。
原文: Provide a unified interface to a set of interfaces in a subsystem.Facade defines a higher-level interface that makes the subsystem easier to use.
解释:为一个子系统中的一组接口提供统一的接口。门面模式定义了一个更高层次的接口,使得子系统更容易使用。
原文描述了门面模式的两个关键点:
-
提供统一接口: 门面模式的目标是要求子系统的外部与内部的通信通过一个统一的对象进行。这个统一的对象就是门面类,它包含了子系统的一组接口,并提供了一个高层次的接口。
-
高层次接口简化子系统使用: 门面模式定义了一个高层次的接口,这使得整个子系统更容易使用。客户端只需与门面类进行交互,而不需要直接与子系统的多个接口打交道。这种简化的接口有助于提高系统的易用性和降低复杂性。
总之,门面模式通过引入门面类,提供了一个简化的接口,使得客户端能够更轻松地使用子系统功能,同时隐藏了子系统的复杂性。这有助于实现系统的解耦和提高整体的可维护性。
在门面模式中,主要包含两种角色:
-
外观角色(Facade): 也称为门面角色,是系统对外的统一接口。外观角色扮演着整个子系统的入口,它将客户端请求委派给子系统中的相应对象处理。
-
子系统角色(SubSystem): 子系统可以包含一个或多个子系统角色,每个子系统角色都是一个类的集合,而不是单独的类。这些子系统角色并不知道外观角色的存在,对于子系统而言,外观只是另一个客户端,即外观对子系统是透明的。
SLF4J(Simple Logging Facade for Java)是门面模式的一个典型应用,但它并不仅仅使用了门面模式。SLF4J 提供了一个简单的日志门面,使得应用程序能够以统一的方式与不同的日志系统进行交互,而不必关心底层日志系统的具体实现。
门面模式的优势在于简化了复杂系统的接口,提供了一个更加清晰、易用的入口,使得客户端能够更轻松地与整个子系统进行交互,而无需了解底层实现的复杂性。
门面模式的实现
场景:
回到家,我们会开各个屋的灯。离开家时, 会关闭各个屋的灯。如果现在家里设置一个人工家具管家来控制灯的总开关, 我们控制整个屋的灯就会很方便。
使用门面模式的实现:
// 客户端代码
public class FacadePatternDemo {
public static void main(String[] args) {
// 创建灯的门面对象
LightFacade lightFacade = new LightFacade();
// 打开所有灯
lightFacade.lightOn();
// 关闭所有灯
lightFacade.lightOff();
}
}
// 灯的门面类
class LightFacade {
private Light livingRoomLight = new LivingRoomLight();
private Light hallLight = new HallLight();
private Light diningLight = new DiningLight();
// 打开所有灯
public void lightOn() {
livingRoomLight.on();
hallLight.on();
diningLight.on();
}
// 关闭所有灯
public void lightOff() {
livingRoomLight.off();
hallLight.off();
diningLight.off();
}
}
// 灯的接口
interface Light {
void on();
void off();
}
// 客厅灯
class LivingRoomLight implements Light {
@Override
public void on() {
System.out.println("打开客厅灯");
}
@Override
public void off() {
System.out.println("关闭客厅灯");
}
}
// 走廊灯
class HallLight implements Light {
@Override
public void on() {
System.out.println("打开走廊灯");
}
@Override
public void off() {
System.out.println("关闭走廊灯");
}
}
// 餐厅灯
class DiningLight implements Light {
@Override
public void on() {
System.out.println("打开餐厅灯");
}
@Override
public void off() {
System.out.println("关闭餐厅灯");
}
}
门面模式的优点
门面模式的优点包括:
-
减少系统相互依赖: 门面模式能够降低系统内部各组件之间的相互依赖关系。通过引入门面类,客户端与子系统的耦合关系得以实现,这使得子系统的变化不会直接影响到调用它的客户端。这有助于系统更容易维护和扩展。
-
提高灵活性: 门面模式提供了一个简化的接口,从而简化了客户端对子系统的使用。客户端无需了解子系统的具体实现方式,只需要与门面对象进行交互。这提高了系统的灵活性,使得对子系统的使用更加方便。
-
提高安全性: 门面模式允许灵活地设定访问权限。通过在门面对象中控制方法的开通,可以限制客户端对子系统的访问。这有助于提高系统的安全性,确保只有授权的操作可以执行。
总的来说,门面模式通过简化接口、降低耦合性、提高灵活性和安全性,为系统提供了一种有效的设计方式,特别适用于大型系统中存在复杂子系统的情况。
策略模式
策略模式是一种行为设计模式,它允许在运行时选择不同的算法或策略来完成特定任务。在策略模式中,我们定义了一组算法,并将每个算法封装到具有相同接口的独立类中。这样做的目的是使得这些算法可以相互替换,而不会影响到客户端代码的使用。
举个例子,假设有一个排序算法的策略模式。我们可能有多种排序算法,比如冒泡排序、快速排序、插入排序等等。每种排序算法都有自己的实现方式,但它们都实现了同一个排序接口,比如 SortStrategy 接口。然后,客户端可以根据需要选择合适的排序算法,并将其传递给一个排序器,排序器在执行时就可以调用所选择的算法。
这种模式的优势在于它使得算法可以独立于客户端而变化,同时也使得客户端可以更灵活地选择和使用算法,而无需修改现有的代码。
-
定义: 策略模式定义了一系列算法,将每个算法封装到独立的类中,并使它们可以相互替换。客户端可以在运行时选择算法,而不需要修改客户端代码。
-
介绍: 在策略模式中,通常会有一个上下文(Context)类,它持有一个对策略接口的引用,并在运行时选择具体的策略来执行任务。每个具体策略类实现了策略接口,并提供了自己的算法实现。客户端通过上下文类来使用不同的策略,而不需要知道具体的算法实现细节。
-
特点:
- 封装性(Encapsulation):策略模式将算法封装到独立的策略类中,使得每个算法可以独立于其他算法进行修改和扩展。
- 替换性(Replaceability):客户端可以在运行时选择不同的策略来完成相同的任务,从而实现了算法的替换。
- 灵活性(Flexibility):策略模式使得系统可以动态地选择和使用算法,提高了系统的灵活性和可维护性。
- 解耦性(Decoupling):策略模式将算法与客户端解耦,使得客户端不需要了解具体的算法实现,降低了类之间的耦合度。
-
优势:
- 增加扩展性:策略模式使得新增加、修改或替换算法变得更加容易,不会影响到其他部分的代码。
- 简化客户端代码:客户端通过统一的接口与上下文类进行交互,无需关注具体的算法实现细节,使得客户端代码更加简洁。
- 提高代码复用性:不同的算法可以被多个上下文类共享使用,提高了代码的复用性。
总的来说,策略模式适用于需要在运行时根据不同的情况选择不同算法的场景,它提供了一种灵活、可扩展和可维护的解决方案,有助于实现代码的解耦和复用。
策略模式包含以下几个核心角色:
-
环境(Context):环境类负责维护一个对策略对象的引用,并将客户端的请求委派给具体的策略对象来执行。它通过调用具体策略对象的方法来实现所需的功能。环境类可以根据具体的需求选择不同的策略对象,实现了算法的动态切换和灵活性。
-
抽象策略(Abstract Strategy):抽象策略定义了策略对象的公共接口或抽象类,规定了具体策略类必须实现的方法。它为具体策略类提供了统一的接口,使得它们可以被环境类调用。抽象策略类通常包含了策略的抽象算法,但不包含具体的实现。
-
具体策略(Concrete Strategy):具体策略类实现了抽象策略定义的接口或抽象类,包含了具体的算法实现。每个具体策略类代表着一种具体的算法,它们可以根据实际需求来实现不同的算法逻辑。具体策略类之间相互独立,可以在不影响其他策略的情况下进行修改和扩展。
策略模式的优点在于它提供了一种灵活、可扩展、可维护的解决方案,有助于降低代码的耦合度和复杂度。通过将算法与客户端解耦,策略模式使得系统更容易扩展和维护。同时,它也避免了使用多重条件判断带来的复杂性和难以维护性,提高了代码的可读性和可维护性。
需要注意的是,如果系统中的策略较多,可能会导致具体策略类的增加,从而使得类的数量增多。在这种情况下,可以考虑使用混合模式来解决策略类膨胀的问题,进一步优化代码结构。
模板模式
模板模式是一种行为设计模式,它定义了一个算法的骨架,将算法中的某些步骤延迟到子类中实现。模板模式使得子类可以在不改变算法结构的情况下重新定义算法中的某些步骤。
简单来说就是,当涉及模板模式时,有一个抽象类作为模板,其中包含了一个定义好的算法骨架,这个骨架中的某些步骤是抽象的或者是有默认实现的。子类可以通过继承这个抽象类,并覆写其中的某些方法,来提供特定的实现。
举个例子,假设有一个制作饮料的模板,该模板包含了准备饮料的算法骨架,包括了一些共同的步骤,比如煮水、冲泡、倒入杯子等。这些步骤在模板中可能已经被实现了,但也可能是抽象的,需要由子类来实现。然后,具体的饮料(比如咖啡、茶)可以通过继承这个模板类,并提供自己特定的实现来制作不同的饮料。
这样,模板模式使得算法的整体结构得以保留,但具体步骤的实现可以由子类自行决定。
定义和介绍
模板模式通过定义一个模板方法来封装算法的骨架,该模板方法由一系列的抽象方法和具体方法组成。抽象方法由子类实现,而具体方法则在模板类中已经实现好了。子类可以根据需要选择性地覆盖模板方法中的抽象方法,以实现特定的行为。
特点
- 封装算法骨架:模板模式封装了算法的骨架,提供了一个稳定的算法结构,而将具体的实现延迟到子类中。
- 具体方法和抽象方法:模板类中的具体方法实现了算法中的通用部分,而抽象方法留给子类来实现具体的步骤。
- 继承和多态:模板模式通过继承和多态来实现算法的定制化,子类可以根据需要来实现特定的步骤,同时还可以继承父类中已经实现好的具体方法。
- 代码复用:模板模式提供了一种代码复用的机制,将相同的算法逻辑封装在模板方法中,避免了重复编写相同的代码。
优势
- 代码复用:模板模式提供了一种代码复用的机制,可以将相同的算法逻辑封装在模板方法中,避免了重复编写相同的代码。
- 灵活性:模板模式通过子类实现抽象方法的方式,使得算法的具体实现可以灵活地定制和扩展,而不会影响到算法的整体结构。
- 适用范围广:模板模式适用于多个具有相似算法结构但具体实现细节不同的场景,可以有效地提高代码的可维护性和扩展性。
使用场景
- 当多个子类拥有相同的算法骨架,但各自的实现细节不同时,可以考虑使用模板模式。
- 当需要在不同的情况下灵活地选择特定步骤时,可以使用模板模式来封装算法的结构。
- 当希望通过继承和多态来实现算法的定制化时,模板模式也是一个很好的选择。
总的来说,模板模式是一种简单而有效的设计模式,它通过封装算法骨架和延迟具体实现来提高代码的复用性、可维护性和扩展性。
策略模式和模板模式的区别?
策略模式(Strategy Pattern)和模板模式(Template Pattern)是两种常用的设计模式,它们都属于行为型设计模式,但它们解决的问题和实现方式有所不同。
-
目的:
- 策略模式:解决的是在运行时选择不同的算法或策略来完成特定任务的问题,允许算法独立于客户端而变化,同时使得客户端可以更灵活地选择和使用算法。
- 模板模式:解决的是定义一个算法的骨架,将算法中的某些步骤延迟到子类中实现,以使子类可以在不改变算法结构的情况下重新定义算法中的某些步骤。
-
实现方式:
- 策略模式:通过将每个算法封装到具有统一接口的独立类中,并允许客户端在运行时选择算法。
- 模板模式:通过定义一个模板类,该模板类包含一个算法的框架,但某些具体步骤由子类实现。
-
灵活性:
- 策略模式:更加灵活,允许在运行时动态选择和切换算法。
- 模板模式:相对较少的灵活性,因为算法的骨架已经在模板类中定义好,子类只能重写某些特定的步骤。
-
侧重点:
- 策略模式:侧重于在不同的算法之间进行选择和切换。
- 模板模式:侧重于定义算法的框架,而将具体实现留给子类。
总的来说,策略模式更加灵活,适用于运行时需要动态选择不同算法的情况,而模板模式更适合于定义一个算法的固定框架,只允许在特定步骤进行定制化的情况。
还有很多设计模式,具体可以参考:设计模式 | 菜鸟教程
SLF4J 框架介绍
SLF4J(Simple Logging Facade for Java)是一种日志门面,充当其他日志框架的接口。SLF4J可以被理解为提供日志服务的统一API接口,而不涉及具体的日志逻辑实现。
不引入日志门面
常见的日志框架如log4J、logback等,如果一个项目已经使用了log4j,而你依赖的另一个类库,比如Apache Active MQ,它依赖于另外一个日志框架logback,那么你就需要将logback也加载进去。
也就是说,如果一个项目已经使用了log4j而另一个类库(比如Apache Active MQ)依赖于另外一个日志框架,如logback,就需要在项目中同时引入这两种日志框架。
这可能导致一些问题:
-
不同日志框架的冲突: log4j和logback可能有不同的API接口和配置文件,因此需要在项目中同时维护这两套配置,可能导致冲突和混淆。
-
代码冲突: 由于不同的日志框架可能存在相同的类或方法名,可能导致在项目中的代码冲突,使得编译或运行时发生错误。
-
维护复杂性: 需要同时处理多个日志框架的配置和问题,增加了项目的维护复杂性。
-
多套配置文件的维护难度: 不同日志框架使用不同的API接口和配置文件,如果多个日志框架同时存在于项目中,就需要维护多套不同的配置文件。这会增加配置的复杂性,可能导致配置文件之间的不一致性,使得日志的管理变得困难。
-
日志框架更换的困难性: 如果需要更换项目中使用的日志框架,应用程序将不得不修改代码以适应新的框架。这个过程可能涉及到大量的代码更改,而且在修改的过程中可能会发生一些代码冲突,增加了维护的难度和风险。
-
引入第三方框架的配置问题: 当引入第三方框架时,如果该框架使用了另外一套日志框架,就不得不维护多套配置。这可能导致与第三方框架的集成变得复杂,同时增加了配置的混乱程度。
综上所述,不引入日志门面可能会使项目在日志管理方面面临一系列挑战,包括配置文件的复杂性、日志框架更换的困难性以及与第三方框架的配置问题。为了解决这些问题,引入一个日志门面框架(如SLF4J)可以帮助统一日志的接口,使得项目中的代码只需与日志门面交互,而不直接依赖于具体的日志框架。这样,即使底层的日志框架发生变化,项目中的代码无需修改,提高了项目的灵活性和可维护性。
引入日志门面
引入日志门面框架后,应用程序和日志框架之间有了统一的API接口(门面日志框架实现)。这意味着应用程序只需要与日志门面进行交互,而不直接依赖于具体的日志框架。SLF4J作为一种日志门面,实现了这样的设计理念。
具体表现在以下方面:
-
统一的API接口: 应用程序通过SLF4J提供的统一API接口进行日志记录,而不需要直接调用具体日志框架的API。这使得应用程序的日志记录操作更为简单和一致。
-
维护一套日志文件配置: 引入日志门面后,应用程序只需维护一套日志文件配置。无论底层实现框架如何变化,这套配置都可以适配不同的日志框架,减少了配置的复杂性。
-
底层实现框架的变化不影响应用程序代码: 当底层实现框架(如log4j、logback)发生变化时,应用程序无需修改代码。SLF4J的存在使得日志框架的更换变得简单,不会影响到应用程序的业务逻辑。
总的来说,SLF4J使得应用程序的代码独立于具体的日志实现,提供了一种良好的抽象,使开发者能够更轻松地管理日志框架的依赖关系,简化配置,降低维护成本,提高代码的可维护性和灵活性。这是一种有益于整个软件开发生态系统的设计思想。
日志格式的说明
打印的日志分别代表什么呢?
日志输出内容通常包含以下具体元素:
-
时间日期: 记录日志的时间和日期,通常精确到毫秒级别,以便准确追踪日志发生的时间。
-
日志级别: 指示日志的严重程度,常见的级别包括ERROR(错误)、WARN(警告)、INFO(信息)、DEBUG(调试)或TRACE(跟踪)。不同级别用于表示日志信息的重要性。
-
进程ID: 标识生成日志的进程的唯一标识符。可以帮助在多个进程同时运行时区分日志来源。
-
线程名: 记录生成日志的线程的名称。在多线程环境中,可以通过线程名区分不同线程的日志输出。
-
Logger名: 通常使用源代码的类名作为Logger的名称。这有助于确定日志的来源,即哪个类生成了相应的日志。
-
日志内容: 包含具体的日志信息,描述了发生的事件、错误、警告或其他相关信息。这是日志记录的核心内容。
通过这些元素,我们可以更全面地了解日志的上下文和内容,从而更方便地进行日志分析、故障排查和系统监控。不同的日志框架和输出格式可能会略有差异,但通常都包含这些基本元素。
日志级别
日志级别是用来表示日志信息对应问题严重性的指标,它帮助开发者更快速地筛选和定位目标信息。可以将日志级别类比为组织架构中的层级关系,不同级别的日志反映了不同严重程度的问题,从而实现信息的逐级筛选和管理。
比如,在一个大公司的组织结构中,领导层通常不会关心每个员工的日常琐碎信息,因此会有组织架构的分级设置,如部门、组等。通过这样的分级结构,信息可以逐级向上汇报,例如,组员向组长报告,组长向研发组报告,研发组向Java研发报告,以此类推。
日志级别的分类
日志的级别从高到低依次为: FATAL、ERROR、WARN、INFO、DEBUG、TRACE,级别越高,收到的消息越少。
-
FATAL(致命):
- 描述:表示致命信息,表明系统遇到了严重的问题,需要立即被处理。
- 影响:这种级别的日志通常指示了系统无法继续运行的严重错误,需要立即采取措施处理。
-
ERROR(错误):
- 描述:表示错误信息,是较高级别的错误日志信息,但仍然不影响系统的继续运行。
- 影响:这类日志通常涉及到系统的错误情况,需要开发者关注并及时处理。
-
WARN(警告):
- 描述:表示警告信息,提醒开发者注意某些可能会引起问题的情况。
- 影响:虽然不会影响系统的使用,但需要开发者关注这些信息,可能需要采取一些预防性的措施。
-
INFO(信息):
- 描述:表示普通信息,用于记录应用程序正常运行时的一些信息。
- 影响:这类信息通常是描述系统运行状态的,例如系统启动完成、请求处理完成等。
-
DEBUG(调试):
- 描述:表示调试信息,提供关键的调试时所需的信息。
- 影响:通常在开发和测试阶段使用,用于追踪代码执行流程,定位问题。
-
TRACE(追踪):
- 描述:表示更细粒度的追踪信息,比DEBUG更详细。
- 影响:通常用于追踪代码的执行路径,提供最详细的信息。一般情况下,建议使用DEBUG级别替代TRACE,除非有特殊用意。
通过这种级别分类,开发者可以根据需要选择性地记录和关注不同严重程度的日志信息,以更有效地进行系统调试、问题排查和性能优化。
日志级别通常与测试人员的Bug级别没有直接关系。日志级别是由开发人员设置的,主要用于给开发人员查看和分析系统运行时的信息。正确设置日志级别与开发人员的工作经验和对项目的了解有关。
如果开发人员将ERROR级别的日志设置成了INFO,可能会影响开发人员对项目运行情况的判断。例如,将严重错误的日志级别设置得太低,可能导致开发人员未能及时发现潜在的问题。
出现ERROR级别的日志信息较多时,并不一定意味着项目存在严重问题。这可能是开发人员故意设置的,用于记录某些异常情况,而这些异常并不一定会导致系统崩溃或功能失效。因此,测试人员通常更多地依据Bug的现象、复现步骤以及影响范围等方面来判断Bug的级别,而不是仅仅依赖于日志级别的设置。
总的来说,日志级别的正确设置是开发人员根据经验和项目需求进行的,而测试人员在Bug级别的判断中更侧重于现象和影响的综合考虑。这两者在软件开发和测试中各自有着不同的角色和关注点。
日志级别的使用
日志级别在软件开发中扮演着至关重要的角色,它由开发人员自己设置,根据其对信息的理解和重要性来判断。在应用中,开发人员可以使用Logger对象的不同⽅法来记录不同级别的日志,这有助于在系统运行时更清晰地了解各种事件和状态。
比如我们刚刚写的:
@RequestMapping("/logLevel")
public String logLevel(){
logger.trace("我是trace级别的日志");
logger.debug("我是debug级别的日志");
logger.info("我是info级别的日志");
logger.warn("我是warn级别的日志");
logger.error("我是error级别的日志");
return "success";
}
在这个例子中,开发人员通过Logger对象记录了包括TRACE、DEBUG、INFO、WARN和ERROR在内的不同级别的日志信息。这样的日志记录可帮助开发人员在系统运行时了解各个环节的详细情况。
在Spring Boot中,默认使用Logback作为日志框架,需要注意的是Logback没有单独的FATAL级别,而是将其映射到ERROR级别。FATAL日志通常表示服务已经出现了某种程度的不可用,需要系统管理员紧急介入处理。一般情况下,⼀个进程生命周期中应该最多只有一次FATAL记录。
观察实际打印的日志结果时,我们发现只打印了INFO、WARN和ERROR级别的日志。
这是因为Logback的输出级别默认是INFO级别,只会打印大于等于此级别的日志。这种配置方式有助于在系统运行时聚焦关注我们自己认为重要的信息,减少不必要的输出,提高日志的可读性和可维护性。
日志配置
上述我们了解的是日志的使用,日志框架支持我们更灵活的输出日志,包括内容,格式等。
配置日志级别
日志级别配置只需要在配置文件中设置“logging.level"配置项即可。
Properties配置
logging.level.root: debug
yml配置
logging:
level:
root: debug
我们现在把日志配置成了Debug级别,所以以前很多没有显示的Debug日志现在全部都显示出来了。
运行刚刚的代码会发现,此时Debug级别的配置文件就可以正常显示了。
日志持久化
以上的日志都是输出在控制台上的,然而在线上环境中,我们需要把日志保存下来,以便出现问题之后追溯问题。
把日志保存下来就叫持久化。
日志持久化有两种方式:
- 配置日志文件名:通过配置日志框架,可以指定日志输出到特定的文件中。这可以通过在日志框架的配置文件中设置相关参数来实现。
- 配置日志的存储目录:另一种方式是配置日志的存储目录。这允许日志信息按照一定的目录结构进行存储,使得在后续的日志管理和检索中更加方便。
通过这两种方式的配置,可以将日志信息保存在指定的文件中,使得在生产环境中更容易管理和分析日志数据。
配置日志文件的路径和文件名:
Properties配置
logging.file.name: logger/springboot.log
yml配置
# 设置⽇志⽂件的⽂件名
logging:
file:
name: logger/springboot.log
后面可以跟绝对路径或者相对路径。
运行结果显示,日志内容保存在了对应的目录下。
运行后刷新:
刷新网页:http://127.0.0.1:8080/log/logLevel
那么我们自然而然地会提出疑问,当path和name同时配置时,会出现什么结果?
为了探寻这个结果,我们把刚刚生成的日志文件都删除:
看来同时配置时,path配置不生效,以name配置为准。
配置日志文件的保存路径
Properties配置
logging.file.path: E:/temp
yml配置
# 设置⽇志⽂件的⽬录
logging:
file:
path: E:/temp
这种方式只能设置日志的路径,文件名为固定的spring.log 运行程序,该路径下多出一个日志文件: spring.log。
注意: logging.file.name 和 logging.file.path 两个都配置的情况下, 只生效其一,以 logging.file.name 为准。
配置日志文件分割
如果我们的日志都放在一个文件中, 随着项目运行,日志文件会越来越大,需要我们对日志文件进行分割。
对于日志文件的自动分割,常见的做法是通过日志框架的配置来实现。一般来说,日志框架会提供相应的配置选项,使得当日志文件大小达到一定阈值时自动进行分割。
在常见的日志框架中,比如Log4j、Logback等,都有对日志文件大小进行判断的功能,并提供了配置参数来设置日志文件的最大大小以及分割方式。如果不进行特殊配置,这些框架通常会有默认的行为,比如当日志文件大小超过一定阈值时自动进行分割。
默认日志文件超过10M就进行分割,这通常可以在日志框架的配置文件中设置。例如,在Logback中,可以通过配置<maxFileSize>来设置日志文件的最大大小,一旦超过这个大小,日志框架会自动进行分割。
具体的配置方式会因使用的日志框架而有所不同,但一般都会提供类似的功能,即在配置文件中设置日志文件大小的阈值,以及指定分割后的日志文件的命名规则。
关于配置日志文件分割的属性说明和默认值:
logging.logback.rollingpolicy.file-name-pattern:日志分割后的文件名格式。默认情况下,采用${LOG_FILE}.%d{yyyy-MM-dd}.%i.gz的格式,其中${LOG_FILE}表示原始日志文件名,%d{yyyy-MM-dd}表示日期格式,%i表示索引(用于区分同一天内的不同文件),.gz表示压缩文件格式。
logging.logback.rollingpolicy.max-file-size:日志文件超过这个大小(默认为10MB)就自动分割。默认值为10MB,表示当日志文件大小达到10MB时就会触发分割操作。
除了 logging.logback.rollingpolicy.file-name-pattern 和 logging.logback.rollingpolicy.max-file-size 这两个属性之外,还有一些其他常用的日志文件分割相关的属性可以配置,包括:
logging.logback.rollingpolicy.max-history:指定保留历史日志文件的最大数量,默认为7。当日志文件达到最大数量后,旧的日志文件将被删除。
logging.logback.rollingpolicy.clean-history-on-start:指定是否在应用启动时清理历史日志文件,默认为false。如果设置为true,则在应用启动时会删除过期的历史日志文件。
logging.logback.rollingpolicy.total-size-cap:指定日志文件总大小的上限。当所有日志文件的总大小超过该上限时,会删除最旧的日志文件以确保总大小不超过限制。
logging.logback.rollingpolicy.clean-history-on-startup-days:指定应用启动后多少天内的历史日志文件会被保留,默认为-1,表示不清理历史日志文件。
logging.logback.rollingpolicy.clean-history-on-startup-total-size:指定应用启动后保留的历史日志文件总大小,默认为-1,表示不限制总大小。
这些属性可以根据项目的需求进行配置,以满足对日志文件管理的不同要求。
还有很多其他的配置项,都可以在SpringBoot配置文件中找到:常见的 Application Properties (springdoc.cn)
接下来我们来配置一下日志分割后文件名格式和大小:
yml配置:
file:
path: logger/ #配置日志路径
name: logger/ioc.log #配置日志名称
logback:
rollingpolicy:
file-name-pattern: ${LOG_FILE}.%d{yyyy-MM-dd}.%i #日志分割格式
max-file-size: 2KB #日志分割条件
Properties配置:
logging.logback.rollingpolicy.file-name-pattern=${LOG_FILE}.%d{yyyy-MM-dd}.%i
logging.logback.rollingpolicy.max-file-size=1KB
工作中日志分割大小通常为200M或500M。
打开文件夹观察:
上面存储的就是我们新产生的文件:
配置日志格式
⽬前日志打印的格式是默认的:
打印日志的格式也是支持配置的。
在上述配置中,有两个关键配置项用于分别设置控制台和日志文件的日志格式:
-
logging.pattern.console:这个配置项定义了控制台日志的格式。默认情况下,控制台日志格式包括日期、日志级别、进程ID、线程名、日志记录器名称以及日志消息。这些元素用不同的颜色和格式进行了处理,以增强可读性。最后的%m%n表示输出日志消息和换行符。
这段配置使用了Logback作为日志系统,并设置了默认的日志输出格式。让我们逐步分析:
%clr(%d{${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd'T'HH:mm:ss.SSSXXX}}){faint}:这部分定义了日期格式化,并使用了颜色(clr)来渲染日期部分。${LOG_DATEFORMAT_PATTERN}表示日期格式的变量,如果未指定,则使用默认格式yyyy-MM-dd'T'HH:mm:ss.SSSXXX。{faint}表示渲染为淡色。
%clr(${LOG_LEVEL_PATTERN:-%5p}):这部分定义了日志级别格式,并使用颜色来渲染。${LOG_LEVEL_PATTERN}表示日志级别格式的变量,如果未指定,则使用默认格式%5p,其中%5p表示将日志级别按照5个字符的长度输出。
%clr(${PID:- }){magenta}:这部分定义了进程ID的格式,并使用颜色来渲染。${PID}表示进程ID的变量,如果未指定,则显示空格。{magenta}表示渲染为洋红色。
%clr(---){faint}:这部分定义了分隔符,并使用颜色渲染。{faint}表示渲染为淡色。
%clr([%15.15t]){faint}:这部分定义了线程名的格式,并使用颜色渲染。[%15.15t]表示线程名,限制在15个字符的长度内,{faint}表示渲染为淡色。
%clr(%-40.40logger{39}){cyan}:这部分定义了日志记录器名称的格式,并使用颜色渲染。%-40.40logger{39}表示日志记录器名称,限制在40个字符的长度内,{cyan}表示渲染为青色。
%clr(:){faint}:这部分定义了日志消息与其他信息的分隔符,并使用颜色渲染。{faint}表示渲染为淡色。
%m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}:这部分定义了日志消息的格式,并添加了异常信息。%m%n表示输出日志消息和换行符,${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}表示异常信息的格式,如果未指定,则使用默认格式%wEx。
-
logging.pattern.file:这个配置项定义了日志文件的格式。与控制台日志类似,日志文件的格式也包括日期、日志级别、进程ID、线程名、日志记录器名称以及日志消息。但与控制台日志不同的是,日志文件没有使用颜色和特殊格式化。最后的%m%n同样表示输出日志消息和换行符。
%d{${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd'T'HH:mm:ss.SSSXXX}}:这部分定义了日期格式。${LOG_DATEFORMAT_PATTERN}表示日期格式的变量,如果未指定,则使用默认格式yyyy-MM-dd'T'HH:mm:ss.SSSXXX。%d表示输出日期。
${LOG_LEVEL_PATTERN:-%5p}:这部分定义了日志级别格式。${LOG_LEVEL_PATTERN}表示日志级别格式的变量,如果未指定,则使用默认格式%5p,其中%5p表示将日志级别按照5个字符的长度输出。
${PID:- }:这部分定义了进程ID。${PID}表示进程ID的变量,如果未指定,则显示空格。
---:这部分是日志条目的分隔符。
[%t]:这部分定义了线程名。[%t]表示输出线程名。
%-40.40logger{39}:这部分定义了日志记录器名称的格式。%-40.40logger{39}表示日志记录器名称,限制在40个字符的长度内。
::这部分是日志消息与其他信息的分隔符。
%m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}:这部分定义了日志消息的格式,并添加了异常信息。%m%n表示输出日志消息和换行符,${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}表示异常信息的格式,如果未指定,则使用默认格式%wEx。
通过这两个配置项,可以灵活地定义控制台和日志文件的日志格式,以满足不同的需求和偏好。例如,可以根据实际情况调整日志格式中的元素顺序、添加额外的信息或者改变输出的样式,以使日志更易于阅读和理解。
总的来说:Logback中常用的格式化字符及其含义:
-
%clr(表达式){颜色}:这部分设置输入日志的颜色,支持多种颜色,如蓝色、青色、绿色等。
-
%d{${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd'T'HH:mm:ss.SSSXXX}}:这部分定义了日期和时间的格式,精确到毫秒。${LOG_DATEFORMAT_PATTERN}用于获取系统属性LOG_DATEFORMAT_PATTERN,如果该属性不存在,则使用默认格式yyyy-MM-dd'T'HH:mm:ss.SSSXXX。
-
%5p:这部分表示显示日志级别,如ERROR、WARN、INFO、DEBUG、TRACE。
-
%t:这部分表示线程名。
-
%c:这部分表示类的全限定名。
-
%M:这部分表示方法名。
-
%L:这部分表示行号。
-
%thread:这部分表示线程名称。
-
%m或%msg:这部分表示输出消息。
-
%n:这部分表示换行符。
-
%5、%-5、%.15、%15.15:这部分是格式化字符长度的控制符,用于对字符长度进行填充、截取等操作。
想要了解更多关于配置项说明的话可以通过链接:Chapter 6: Layouts
如果你发现自己设置了颜色,却没有生效?
- 打开启动配置,这通常可以在IDEA的编辑器顶部找到,也可以在菜单栏中找到。在启动配置中找到对应的项目配置。
- 添加VM options,即虚拟机参数。在启动配置的VM options中添加-Dspring.output.ansi.enabled=ALWAYS。
- 重新启动程序。重新运行你的项目,然后查看控制台输出,就会发现控制台支持颜色了。
这样设置后,IDEA会解析控制台输出的ANSI颜色码,并将其渲染成相应的颜色,从而实现控制台颜色显示的效果。
接下来我们来配置一下文件的格式:
Properties配置:
logging.pattern.console='%d{yyyy-MM-dd HH:mm:ss.SSS} %c %M %L [%thread] %m%n'
yml配置:
logging:
pattern:
console: '%d{yyyy-MM-dd HH:mm:ss.SSS} %c %M %L [%thread] %m%n'
file: '%d{yyyy-MM-dd HH:mm:ss.SSS} %c %M %L [%thread] %m%n'
项目运行,观察日志变化:
但是文件中仍然同原先一样,说明日志文件与日志格式是分开独立设置的:
更简单的日志输出
你会发现我们的定义中存在大量可重复利用的东西,当我们想要新创建一个Logger的时候只需要修改后面的名字.class。
使用LoggerFactory.getLogger(xxx.class)这种方式每次都需要手动添加,显得繁琐。为了简化日志的添加过程,以及提高代码的可读性,Lombok为我们提供了一种更简单的方式。
-
添加Lombok框架支持:首先,在项目中引入Lombok框架的依赖,并确保IDEA或Eclipse等开发工具对Lombok的支持已经正确配置。
-
使用@Slf4j注解输出日志:在需要输出日志的类上使用@Slf4j注解即可,无需再手动添加Logger对象。通过在类上添加@Slf4j注解,Lombok会自动在编译时为该类生成一个名为log的Logger对象,可以直接在类中使用,大大简化了日志输出的代码编写过程。
添加 lombok 依赖
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
输出日志
添加注解后,它会自动帮我们生成一个log日志对象:
日志成功打印:
自定义注解 @Slf4注解具有以下特征:
-
@Retention(RetentionPolicy.SOURCE):指定了注解的保留策略为 SOURCE,这意味着该注解仅存在于源代码中,在编译后不会保留在编译后的字节码文件中,也不会在运行时被读取。这样的注解通常用于编译时的静态检查或代码生成等场景。
-
@Target({ElementType.TYPE}):指定了该注解可以应用于类、接口(包括注解类型)或枚举声明。这意味着 @Slf4j 注解可以标注在类、接口或枚举的声明处。
-
public @interface Slf4j:这行代码定义了一个名为 Slf4j 的注解类型,它是一个注解声明。该注解声明的元素名称为 Slf4j,并且没有定义任何属性或方法。
-
String topic() default "";:在注解声明中定义了一个名为 topic 的属性,类型为 String,并指定了默认值为 ""。这意味着在使用 @Slf4j 注解时,可以选择性地指定 topic 属性的值,如果不指定,则默认为空字符串。
注解的生命周期(Retention Policy)定义了注解在 Java 程序中的存活时间,它决定了注解在源代码、编译期间和运行时期间的可见性和生命周期。Java 中的注解有三种生命周期,分别是:
-
SOURCE(源代码级别):在源代码中声明的注解只会存在于源代码中,在编译过程中会被编译器忽略,不会保留在编译后的字节码文件中,更不会被加载到 JVM 中。这意味着源代码级别的注解仅在编写代码和进行编译时对代码进行静态检查时有效,对于运行时期间完全不存在。通常用于提供给编译器进行静态检查或者在编译期间生成辅助代码等场景。
-
CLASS(类级别):在编译过程中,注解会被编译器保留在编译后的字节码文件(.class 文件)中,但在运行时期间不会被加载到 JVM 中。这意味着类级别的注解仅在编译期间有效,在运行时期间不会产生任何影响。通常用于生成辅助代码、框架的注解处理器等场景。
-
RUNTIME(运行时级别):在编译过程中,注解会被编译器保留在编译后的字节码文件中,并且在运行时期间会被加载到 JVM 中,并且可以通过反射机制读取。这意味着运行时级别的注解在编译和运行时期间都有效,并且可以通过反射在运行时期间读取注解信息。通常用于框架的配置、依赖注入、AOP 等场景。
总之,生命周期为 SOURCE 的注解仅存在于源代码中,生命周期为 CLASS 的注解存在于编译后的字节码文件中但不会加载到 JVM 中,生命周期为 RUNTIME 的注解则会在运行时期间加载到 JVM 中并且可以通过反射机制读取。
@Slf4j 注解原理解析
@Slf4j 注解是 Lombok 框架提供的一个注解,用于在 Java 类中自动添加日志记录器(Logger)。当我们在一个类上添加了 @Slf4j 注解后,在编译时,Lombok 会自动生成一个名为 log 的静态成员变量,类型为 org.slf4j.Logger,并且在类的静态初始化块中初始化这个 log 变量。这样,我们就可以在类中直接使用 log 变量进行日志记录,而不需要显式地创建 Logger 对象。
@Slf4j 注解的基本工作原理:
- Lombok 编译时处理器: 当我们在代码中使用了 @Slf4j 注解时,Lombok 的编译时处理器会扫描源代码,在编译阶段自动生成对应的日志记录器代码。
- 生成日志记录器: Lombok 根据 @Slf4j 注解生成一个名为 log 的静态 Logger 类型的成员变量,并在类的静态初始化块中初始化这个 log 变量。
- 替代手动创建 Logger: 生成的 log 变量可以直接在类中使用,而不需要手动创建 Logger 对象。例如,我们可以使用 log.info("message") 而不是 LoggerFactory.getLogger(MyClass.class).info("message")。
例如:
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class MyClass {
public void myMethod() {
log.info("Logging with Lombok @Slf4j annotation");
}
}
反编译后的代码可能会显示类似以下内容:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class MyClass {
private static final Logger log = LoggerFactory.getLogger(MyClass.class);
public void myMethod() {
log.info("Logging with Lombok @Slf4j annotation");
}
}
可以看到,@Slf4j 注解生成了一个静态的 Logger 类型的成员变量 log,并在类的静态初始化块中初始化了这个变量,使得我们可以直接在类中使用 log 进行日志记录。