故事會遲到,但他從不會缺席。今天的故事開始了,你準備好了嗎?html
前奏
簡單介紹一下個人老婆:集智慧與美貌於一身的女子——阿狸,一句「咱們心有靈犀,不是嗎?」讓我瞬間「淪陷」。 java
阿Q: 老婆,還記得往年過節的時候我都送過你什麼禮物嗎?面試
阿狸: 固然記得呀:剛過的兒童節送了一排旺仔牛奶和一大包零食;5·20
送了一款我喜歡的香水;女神節給我買了一個超好看的包包......算法
阿Q: 這都是今年的,那去年的還記得嗎?spring
阿狸: 我想一想哈:去年聖誕節買了個聖誕老人的蛋糕還有一雙漂亮的高跟鞋;過生日的時候送了一束鮮花還有一個大紅包;嗯......sql
阿Q: 看看,看看想不起來了吧,我就知道時間久了就記不住了,我來給你說一下吧:巴拉巴拉(露出得意的表情)。數據庫
阿狸: 哇塞,你真厲害,你是怎麼作到的呢?apache
阿Q: 哈哈,這就不得不說一下我用到的日誌了,你可聽好了。api
正題
LogBack簡介
阿Q: 我說的日誌呢就跟我們以前寫過的日記同樣,只不過它是用來記錄操做系統事件的文件的集合。常見的日誌框架呢有如下幾種:數組
- JUL(Java Util Logging)
- Logback
- Log4j
- Log4j2
- JCL(Jakarta Commons Logging)
- Slf4j(Simple Logging Facade For Java)
阿狸: 這麼多框架,該使用哪一個好呢?
阿Q: 我首推Logback
日誌框架:首先它配置比較簡單,比較容易上手;其次配置比較靈活,能知足大部分項目的需求;最後性能比較好,能夠異步存儲日誌。我以爲這也是它在市面上比較流行,項目中使用比較多的緣由吧。
阿狸: 哦哦,那我pick
它。
阿Q: Logback
是經過slf4j
的日誌門面搭建的日誌系統,門面與實現的關係瞭解一下。
接着奉上官網地址,它分爲如下三個模塊:
- logback-core:其它兩個模塊的基礎模塊;
- logback-classic:它是
log4j
的一個改良版本,同時它完整實現了slf4j API
,你能夠很方便地更換成其它日誌框架(如log4j
或者JUL
); - logback-access:訪問模塊與
Servlet
容器集成提供經過Http
來訪問日誌的功能,能夠輕鬆地在logback
核心之上構建本身的模塊。
logback
組件之間的關係 能夠大致瞭解下,實戰篇更容易理解:
Logger
做爲日誌的記錄器,把它關聯到應用的對應的context
上後,主要用於存放日誌對象,也能夠定義日誌類型、級別;Appender
主要用於指定日誌輸出的目的地,能夠是控制檯、文件、遠程套接字服務器、MySQL
、PostreSQL
、Oracle
和其餘數據庫、JMS
和遠程UNIX Syslog
守護進程等;Layout
負責把事件轉換成字符串,格式化的日誌信息的輸出。在logback
中Layout
對象被封裝在encoder
中;Logger Context
:各個logger
都被關聯到一個LoggerContext
,它負責製造logger
,也負責以樹結構排列各logger
。其餘全部logger
也經過org.slf4j.LoggerFactory
類的靜態方法getLogger
取得。
Logger
能夠被分配的級別包括:TRACE
、DEBUG
、INFO
、WARN
和ERROR
,定義於ch.qos.logback.classic.Level
類。若是logger
沒有被分配級別,那麼它將從有被分配級別的最近的祖先那裏繼承級別。root logger
默認級別是DEBUG
。
級別排序爲: TRACE
< DEBUG
< INFO
< WARN
< ERROR
項目實戰
阿狸: 太囉嗦了,快點進入實戰吧。
阿Q: OK
,若是你建立的是普通的maven
項目,你須要引入pom
文件:
<!-- slf4j日誌門面 --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.30</version> </dependency> <!-- logback日誌實現 --> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.2.3</version> </dependency>
logback
會從 classpath
下依次讀取如下類型的配置文件:
- logback.groovy
- logback-test.xml
- logback.xml
若是文件都不存在,logback
用 BasicConfigurator
自動對本身進行配置,這會致使記錄輸出到控制檯。
基本信息配置
代碼測試樣例奉上:
public class TestLogBack { private static final Logger logger = LoggerFactory.getLogger(TestLogBack.class); public static void main(String[] args) { for (int i = 0; i < 10000; i++) { logger.error("error"); logger.warn("warn"); logger.info("info"); logger.debug("debug"); logger.trace("trace"); } } }
首先咱們在resources
下建立一個logback.xml
,而後進行配置
<?xml version="1.0" encoding="UTF-8" ?> <configuration> <!-- 配置集中管理屬性 咱們能夠直接改屬性的 value 值 格式:${name} --> <property name="pattern" value="[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} %c %M %L [%thread] %m%n"/> <!-- 日誌輸出格式: %-5level 日誌輸出級別,佔5位,靠左補全 %d{yyyy-MM-dd HH:mm:ss.SSS} 時間 %c 類的完整名稱 %M method %L 行號 %thread 線程名稱 %m或者%msg 信息 %n 換行 --> <!-- 控制檯日誌輸出的 appender --> <appender name="console" class="ch.qos.logback.core.ConsoleAppender"> <!-- 控制輸出流對象 默認 System.out 咱們爲了測試能夠改成 System.err(項目中使用 System.out ) --> <target>System.err</target> <!-- 日誌消息格式配置 --> <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> <pattern>${pattern}</pattern> </encoder> </appender> <!-- 定義日誌文件保存路徑屬性 --> <property name="log_dir" value="/logs"/> <!-- 日誌文件輸出的 appender --> <appender name="file" class="ch.qos.logback.core.FileAppender"> <!-- 日誌文件保存路徑 --> <file>${log_dir}/logback.log</file> <!-- 日誌消息格式配置 --> <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> <pattern>${pattern}</pattern> </encoder> </appender> <!-- html 格式文件輸出的 appender --> <appender name="htmlFile" class="ch.qos.logback.core.FileAppender"> <!-- 日誌文件保存路徑 --> <file>${log_dir}/logback.html</file> <!-- html消息格式配置 --> <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder"> <layout class="ch.qos.logback.classic.html.HTMLLayout"> <!-- <pattern>${pattern}</pattern>--> <pattern>%-5level%d{yyyy-MM-dd HH:mm:ss.SSS}%c%M%L%thread%m</pattern> </layout> </encoder> </appender> <!-- root logger 配置 --> <root level="ALL"> <appender-ref ref="console"/> <appender-ref ref="file"/> <appender-ref ref="htmlFile"/> </root> </configuration>
運行以後發如今控制檯打印出紅色字體的日誌信息,在/log
文件下有logback.log
和logback.html
兩個日誌文件,在項目中通常都只會使用.log
結尾的日誌的。
阿狸: 奧,你就是經過這個文件找到的吧,那天天都產生這麼多行日誌,找起來不是太費勁了嗎?另外文件太大,打開都很費勁呀。
阿Q: 固然了,請接着往下看
<!-- 日誌拆分和歸檔壓縮的 appender --> <appender name="rollFile" class="ch.qos.logback.core.rolling.RollingFileAppender"> <!-- 日誌文件保存路徑(拆分的話此處能夠省略) --> <file>${log_dir}/roll_logback.log</file> <!-- 日誌消息格式配置--> <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> <pattern>${pattern}</pattern> </encoder> <!-- 指定拆分規則 --> <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"> <!-- 每滿1M或者 每秒 產生一個新文件,%i產生0 或者 1 的文件名 ,gz爲壓縮, 咱們通常設置爲天天產生一個文件%d{yyyy-MM-dd} --> <!-- 按照文件大小拆分 --> <maxFileSize>1MB</maxFileSize> <!-- 按照時間和壓縮格式聲明拆分的文件名 --> <fileNamePattern>${log_dir}/rolling.%d{yyyy-MM-dd-HH-mm-ss}.log%i.gz</fileNamePattern> </rollingPolicy> </appender>
此時咱們對測試程序加上for
循環,循環1w
次,發現每秒或者每超過1M
都會產生新的文件。固然也能夠在appender
下增長過濾器,過濾須要的日誌級別。
<!-- 日誌級別過濾器 --> <filter class="ch.qos.logback.classic.filter.LevelFilter"> <!-- 日誌過濾規則 --> <level>ERROR</level> <!-- 匹配時的操做:接收(記錄) --> <onMatch>ACCEPT</onMatch> <!-- 不匹配時的操做:拒絕(不記錄) --> <onMismatch>DENY</onMismatch> </filter>
阿狸: 這樣就清晰多了,那你上邊提到的那個分文件的策略是怎麼發現的呢?能跟我說一下你的依據嗎?
阿Q: 好的,那就來幾張圖感覺一下吧
- 先打開
RollingFileAppender
,能夠看到他底下有個RollingPolicy
策略 - 點進去發現它是一個接口,而後看一下它的實現類,咱們找到
SizeAndTimeBasedRollingPolicy
策略看一下 3.發現這個類裏邊就有文件大小的屬性maxFileSize
,卻沒有找到按照時間份文件的屬性,咱們進入它的父類TimeBasedRollingPolicy
查看 4.發現裏邊就有該屬性,翻譯一下:必須在使用TimeBasedRollingPolicy
以前設置FileNamePattern
選項
阿狸: 我還有一個問題,就是系統在執行時還要完成打印日誌的工做,它的效率會不會很低呀?
阿Q: 不會的,爲了提升性能,它還支持異步輸出日誌,這樣就能夠大大提升性能了。
<!-- 異步日誌 --> <appender name="async" class="ch.qos.logback.classic.AsyncAppender"> <!-- 指定具體的appender --> <appender-ref ref="rollFile"/> </appender>
除了上邊用到的root
,還支持自定義的logger
呢。
<!-- 自定義logger對象 additivity="false" 自定義的logger 對象是否繼承root logger name 用來指定受此loger約束的某一個包或者具體的某一個類 --> <logger name="com.aq.logback" level="info" additivity="false"> <appender-ref ref="console"/> </logger>
SpringBoot中使用
阿狸: 說到這我想起來了,你說的是普通的maven
項目,那經常使用的SpringBoot
項目也是這樣使用嗎?
阿Q: 若是是SpringBoot
項目的話,它默認使用slf4j
做爲日誌門面,logback
做爲日誌實現來記錄日誌,因此咱們不須要引入任何依賴,默認是info
級別。
咱們還能夠直接使用@Slf4j
的註解來代替上邊的
private static final Logger logger = LoggerFactory.getLogger(TestLogBack.class);
引用是使用log.info("info");
來實現。它的默認加載順序是logback-spring.xml
->logback.xml
咱們能夠在application.properties
中簡單配置
#指定自定義 logger 對象的日誌級別 logging.level.com.itzyq.sblogback=trace #指定控制檯輸出消息格式 logging.pattern.console=[%-5level] %d{yyyy-MM-dd HH:mm:ss} %c [%thread] ===== %m %n #指定存放日誌文件的具體路徑(已經棄用) #logging.file=/logs/springboot.log #指定日誌文件存放的目錄,默認的文件名爲spring.log logging.file.path=/logs/springboot/ #指定日誌文件的消息格式 logging.pattern.file=[%-5level] %d{yyyy-MM-dd HH:mm:ss} %c [%thread] ===== %m %n
由於在properties
中配置功能有限,咱們仍是使用上文中的logback.xml
來配置。
阿狸: 艾,那爲啥不使用logback-spring.xml
呢?
阿Q: SpringBoot
中是推薦使用logback-spring.xml
的,由於上文中是普通的maven
項目,爲了好理解就搞成logback.xml
了。
logback-spring.xml
只有在Spring
應用程序運行的時候才生效,即帶有@SpringBootApplication
註解的類啓動的時候纔會生效。這裏咱們徹底可使用它。
另外它還有個特殊的功能,能夠用來解析日誌的配置。
<?xml version="1.0" encoding="UTF-8"?> <configuration> <property name="pattern" value="[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} %c %M %L [%thread] %m%n"/> <!-- 定義日誌文件保存路徑屬性 --> <property name="log_dir" value="/logs"/> <!-- 日誌文件輸出的 appender --> <appender name="file" class="ch.qos.logback.core.FileAppender"> <!-- 日誌文件保存路徑 --> <file>${log_dir}/logback.log</file> <!-- 日誌消息格式配置--> <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> <springProfile name="dev"> <pattern>${pattern}</pattern> </springProfile> <springProfile name="pro"> <pattern> %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %m%n</pattern> </springProfile> </encoder> </appender> <root level="info"> <appender-ref ref="file"/> </root> </configuration>
此時在application.properties
中引入spring.profiles.active=dev
或者pro
能夠切換測試和正式環境了,是否是很方便呀。
Logback-access
阿狸: 確實是,那既然都說到這了,你能說下上邊提到的logback-access
嗎?
阿Q: 好吧,那我就大致說一下它的配置和使用吧:logback-access
模塊與Servlet
容器(如Tomcat
和jetty
)集成,已提供HTTP
訪問日誌功能。咱們可使用logback-access
模塊來替換tomcat
的訪問日誌;
-
將
logback-access.jar
與logback-core.jar
複製到$TOMCAT_HOME/lib/
(安裝Tomcat
的文件夾)目錄下; -
修改
$TOMCAT_HOME/conf/server.xml
中的Host
元素中添加:
<Value className="ch.qos.logback.access.tomcat.LogbackValue" />
這一行一般嵌套在一個<Engine>
或 <Host>
元素中。
logback
默認會在$TOMCAT_HOME/conf
下查找文件logback-access.xml
,該配置的官方地址:http://logback.qos.ch/access.html#configuration
<configuration> <!-- always a good activate OnConsoleStatusListener --> <statusListener class="ch.qos.logback.core.status.OnConsoleStatusListener" /> <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <file>access.log</file> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <fileNamePattern>access.%d{yyyy-MM-dd}.log.zip</fileNamePattern> </rollingPolicy> <encoder> <!-- 日誌消息表達格式 --> <pattern>%h %l %u [%t] "%r" %s %b "%i{Referer}" 「%i{User-Agent}」</pattern> <pattern>combined</pattern> </encoder> </appender> <appender-ref ref="FILE" /> </configuration>
配置信息補充
阿狸: 講到這就結束了嗎?
阿Q: 由於logback
的配置信息在上文中難以所有展現,特將具體的配置信息列出來,供你們參考學習。
(1)根節點configuration
,包含下面三個屬性:
- scan: 當此屬性設置爲
true
時,配置文件若是發生改變,將會被從新加載,默認值爲true
。 - scanPeriod: 設置監測配置文件是否有修改的時間間隔,若是沒有給出時間單位,默認單位是毫秒。當
scan
爲true
時,此屬性生效。默認的時間間隔爲1分鐘。 - debug: 當此屬性設置爲
true
時,將打印出logback
內部日誌信息,實時查看logback
運行狀態。默認值爲false
。
<configuration scan="true" scanPeriod="60 seconds" debug="false"> </configuration>
(2)contextName
:用來設置上下文名稱,每一個logger
都關聯到logger
上下文,默認上下文名稱爲default
。但可使用contextName
設置成其餘名字,用於區分不一樣應用程序的記錄。一旦設置,不能修改。
<configuration scan="true" scanPeriod="60 seconds" debug="false"> <contextName>myAppName</contextName> </configuration>
(3) property
:用來定義變量值,它有兩個屬性name
和value
,經過property
定義的值會被插入到logger
上下文中,可使「${}」來使用變量。
name
: 變量的名稱value
: 的值時變量定義的值
(4) timestamp
:獲取時間戳字符串,他有兩個屬性key
和datePattern
key
: 標識此timestamp
的名字;datePattern
: 設置將當前時間(解析配置文件的時間)轉換爲字符串的模式,遵循java.txt.SimpleDateFormat
的格式。
<configuration scan="true" scanPeriod="60 seconds" debug="false"> <timestamp key="bySecond" datePattern="yyyyMMdd'T'HHmmss"/> </configuration>
(5)appender
:負責寫日誌的組件,它有兩個必要屬性name
和class
。name
指定appender
名稱,class
指定appender
的全限定名
5.一、ConsoleAppender
把日誌輸出到控制檯,有如下子節點:
encoder
:對日誌進行格式化。target
:字符串System.out
(默認)或者System.err
5.二、FileAppender
:把日誌添加到文件,有如下子節點:
file
:被寫入的文件名,能夠是相對目錄,也能夠是絕對目錄,若是上級目錄不存在會自動建立,沒有默認值。append
:若是是true
,日誌被追加到文件結尾,若是是false
,清空現存文件,默認是true
。encoder
:對記錄事件進行格式化。prudent
:若是是true
,日誌會被安全的寫入文件,即便其餘的FileAppender
也在向此文件作寫入操做,效率低,默認是false
。
5.三、RollingFileAppender
:滾動記錄文件,先將日誌記錄到指定文件,當符合某個條件時,將日誌記錄到其餘文件。有如下子節點:
file
:被寫入的文件名,能夠是相對目錄,也能夠是絕對目錄,若是上級目錄不存在會自動建立,沒有默認值。append
:若是是true
,日誌被追加到文件結尾,若是是false
,清空現存文件,默認是true
。rollingPolicy
:當發生滾動時,決定RollingFileAppender
的行爲,涉及文件移動和重命名。屬性class
定義具體的滾動策略類。
5.四、策略: class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy": 最經常使用的滾動策略,它根據時間來制定滾動策略,既負責滾動也負責觸發滾動。 有如下子節點:
fileNamePattern
:必要節點,包含文件名及「%d」轉換符,「%d」
能夠包含一個java.text.SimpleDateFormat
指定的時間格式,如:%d{yyyy-MM}
。若是直接使用%d
,默認格式是yyyy-MM-dd
。RollingFileAppender
的file
子節點無關緊要,經過設置file
,能夠爲活動文件和歸檔文件指定不一樣位置,當前日誌老是記錄到file
指定的文件(活動文件),活動文件的名字不會改變;若是沒設置file
,活動文件的名字會根據fileNamePattern
的值,每隔一段時間改變一次。「/」或者「\」會被當作目錄分隔符。maxHistory
:可選節點,控制保留的歸檔文件的最大數量,超出數量就刪除舊文件。 假設設置每月滾動,且<maxHistory>是6,則只保存最近6個月的文件,刪除以前的舊文件。注意,刪除舊文件時,那些爲了歸檔而建立的目錄也會被刪除。
class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy": 查看當前活動文件的大小,若是超過指定大小會告知RollingFileAppender
觸發當前活動文件滾動。只有一個節點:
maxFileSize
:這是活動文件的大小,默認值是10MB
。prudent
:當爲true
時,不支持FixedWindowRollingPolicy
。支持TimeBasedRollingPolicy
,可是有兩個限制,1不支持也不容許文件壓縮,2不能設置file
屬性,必須留空。triggeringPolicy
: 告知RollingFileAppender
合適激活滾動。
class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy" 根據固定窗口算法重命名文件的滾動策略。有如下子節點:
minIndex
:窗口索引最小值maxIndex
:窗口索引最大值,當用戶指定的窗口過大時,會自動將窗口設置爲12。fileNamePattern
:必須包含「%i」
例如,假設最小值和最大值分別爲1和2,命名模式爲mylog%i.log
,會產生歸檔文件mylog1.log
和mylog2.log
。還能夠指定文件壓縮選項,例如,mylog%i.log.gz
或者 沒有log%i.log.zip
<configuration> <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <fileNamePattern>logFile.%d{yyyy-MM-dd}.log</fileNamePattern> <maxHistory>30</maxHistory> </rollingPolicy> <encoder> <pattern>%-4relative [%thread] %-5level %logger{35} - %msg%n</pattern> </encoder> </appender> <root level="DEBUG"> <appender-ref ref="FILE" /> </root> </configuration> //上述配置表示天天生成一個日誌文件,保存30天的日誌文件。
<configuration> <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <file>test.log</file> <rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy"> <fileNamePattern>tests.%i.log.zip</fileNamePattern> <minIndex>1</minIndex> <maxIndex>3</maxIndex> </rollingPolicy> <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy"> <maxFileSize>5MB</maxFileSize> </triggeringPolicy> <encoder> <pattern>%-4relative [%thread] %-5level %logger{35} - %msg%n</pattern> </encoder> </appender> <root level="DEBUG"> <appender-ref ref="FILE" /> </root> </configuration> //上述配置表示按照固定窗口模式生成日誌文件,當文件大於5MB時,生成新的日誌文件。窗口大小是1到3,當保存了3個歸檔文件後,將覆蓋最先的日誌。
encoder
:對記錄事件進行格式化。負責兩件事,一是把日誌信息轉換成字節數組,二是把字節數組寫入到輸出流。
PatternLayoutEncoder
是惟一有用的且默認的encoder
,有一個pattern
節點,用來設置日誌的輸入格式。使用「%」
加「轉換符」方式,若是要輸出「%」
,則必須用「\」
對「\%」
進行轉義。
(6)子節點logger
:用來設置某一個包或具體的某一個類的日誌打印級別、以及指定appender
。僅有一個name
屬性,一個可選的level
和一個可選的additivity
(單詞必定要注意寫對,好多地方都寫成了addtivity)屬性。能夠包含零個或多個<appender-ref>元素,標識這個appender
將會添加到這個logger
- name:用來指定受此
logger
約束的某一個包或者具體的某一個類; - level:用來設置打印級別(日誌級別),大小寫無關:
TRACE
,DEBUG
,INFO
,WARN
,ERROR
,ALL
和OFF
,還有一個特殊值INHERITED
或者同義詞NULL
,表明強制執行上級的級別。若是未設置此屬性,那麼當前logger
將會繼承上級的級別。 - additivity:是否向上級
loger
傳遞打印信息。默認是true
。
(7)子節點root
:它也是logger
元素,可是它是根 logger
,是全部logger
的上級。只有一個level
屬性,由於name
已經被命名爲"root"
,且已是最上級了。同logger
同樣,能夠包含零個或多個appender-ref
元素,標識這個appender
將會添加到這個logger
。
level
: 用來設置打印級別,大小寫無關:TRACE
,DEBUG
,INFO
,WARN
,ERROR
,ALL
和OFF
,不能設置爲INHERITED
或者同義詞NULL
。 默認是DEBUG
經常使用logger
配置
<!-- show parameters for hibernate sql 專爲 Hibernate 定製 --> <logger name="org.hibernate.type.descriptor.sql.BasicBinder" level="TRACE" /> <logger name="org.hibernate.type.descriptor.sql.BasicExtractor" level="DEBUG" /> <logger name="org.hibernate.SQL" level="DEBUG" /> <logger name="org.hibernate.engine.QueryParameters" level="DEBUG" /> <logger name="org.hibernate.engine.query.HQLQueryPlan" level="DEBUG" /> <!--myibatis log configure--> <logger name="com.apache.ibatis" level="TRACE"/> <logger name="java.sql.Connection" level="DEBUG"/> <logger name="java.sql.Statement" level="DEBUG"/> <logger name="java.sql.PreparedStatement" level="DEBUG"/>
阿狸: 老公你也太貼心了,我也要用起來,記錄美好的生活!
以上就是今天的全部內容了,若是你有不一樣的意見或者更好的idea,歡迎聯繫阿Q:qingqing-4132,阿Q期待你的到來!
後臺留言領取java乾貨資料:學習筆記與大廠面試題