一 日志概述
日志文件
日志文件是用于记录系统操作事件的文件集合,分为调试日志和系统日志
日志框架
-
日志门面
JCL、slf4j
-
日志实现
JUL、logback、log4j、log4j2
二 JUL
Java Util Logging,Java原生日志框架使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19import java.util.logging.*; public class JULTest { @Test public void test() throws Exception { //获取日志记录器对象 Logger logger=Logger.getLogger("com.web02.JULTest"); //日志记录输出 logger.info("hello jul"); //通用方法记录日志 logger.log(Level.INFO,"info msg"); //占位符方式输出变量值 String name="hhf"; Integer age=21; logger.log(Level.INFO,"info msg: {0},{1}",new Object[]{name,age}); } }
日志级别
- SEVERE:严重错误
- WARNING:警告
- INFO:普通信息
- CONFIG:配置信息
- FINE、FINER、FINEST:用于debug记录
- ALL:启用所有消息的日志记录
- OFF:关闭日志记录
控制台默认输出前三个级别的日志
自定义配置日志级别
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29@Test public void testLogConfig() throws Exception { Logger logger=Logger.getLogger("com.web02.JULTest"); //关闭系统默认配置 logger.setUseParentHandlers(false); //创建ConsoleHandler,控制台输出 ConsoleHandler consoleHandler=new ConsoleHandler(); //创建简单格式转换对象 SimpleFormatter simpleFormatter=new SimpleFormatter(); //进行关联 consoleHandler.setFormatter(simpleFormatter); logger.addHandler(consoleHandler); //自定义配置日志级别 logger.setLevel(Level.ALL); consoleHandler.setLevel(Level.ALL); //创建FileHandler,文件输出 FileHandler fileHandler=new FileHandler("jul.log"); fileHandler.setFormatter(simpleFormatter); fileHandler.setLevel(Level.ALL); logger.addHandler(fileHandler); logger.config("config"); }
Logger对象父子关系
用getParent()获取Logger父对象
所有日志记录器的顶级父元素是LogManager$RootLogger
自定义配置文件
resources下新建logging.properties
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29#RootLogger默认处理器 handlers=java.util.logging.ConsoleHandler,java.util.logging.FileHandler #RootLogger默认日志级别 .level=ALL #自定义Logger com.web02.JULTest.handlers=java.util.logging.ConsoleHandler com.web02.JULTest.level=CONFIG com.web02.JULTest.useParentHandlers=false #日志文件路径 java.util.logging.FileHandler.pattern=my.log #日志文件内容大小 java.util.logging.FileHandler.limit=50000 #日志文件数量 java.util.logging.FileHandler.count=1 java.util.logging.FileHandler.formatter=java.util.logging.XMLFormatter #指定追加方式 java.util.logging.FileHandler.append=true #控制台处理器的日志级别 java.util.logging.ConsoleHandler.level=ALL #控制台处理器的消息格式对象 java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter #控制台处理器的字符集 java.util.logging.ConsoleHandler.encoding=UTF-8 #消息格式 java.util.logging.SimpleFormatter.format=%4$s: %5$s [%1$tc]%n
1
2
3
4
5
6
7
8
9
10
11
12
13@Test public void testLogProperties() throws Exception { //通过类加载器读取配置文件 InputStream ins=JULTest.class.getClassLoader().getResourceAsStream("logging.properties"); //创建LogManager LogManager logManager=LogManager.getLogManager(); //加载配置文件 logManager.readConfiguration(ins); Logger logger=Logger.getLogger("com.web02.JULTest"); logger.config("config"); }
原理
三 Log4j
简单使用
1
2
3
4
5
6<dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.12</version> </dependency>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22import org.apache.log4j.Logger; import org.apache.log4j.helpers.LogLog; import org.junit.Test; public class Log4jTest { @Test public void test() throws Exception { //初始化配置信息 BasicConfigurator.configure(); //获取日志记录器对象 Logger logger=Logger.getLogger(Log4jTest.class); //日志级别 logger.fatal("fatal");//严重错误 logger.error("error");//错误信息 logger.warn("warn");//警告信息 logger.info("msg");//运行信息 logger.debug("debug");//调试信息 logger.trace("trace");//追踪信息 } }
组件
Loggers
日志记录器,负责收集处理日志记录
Appenders
指定日志输出的位置
分为以下:
1. ConsoleAppender:控制台输出;
2. FileAppender:文件输出;
3. DailyRollingFileAppender:将日志输出到一个日志文件,且每天输出到一个新的文件;
4. RollingFileAppender:将日志输出到一个日志文件,并指定文件大小,当到达文件大小时,会自动改名,同时产生一个新的文件;
5.JDBCAppender:将日志信息保存到数据库中。
Layout
控制日志输出的格式
分为HTMLLayout, SimpleLayout(简单日志格式), PatternLayout(自定义日志格式)
配置文件
resources下新建log4j.properties
1
2
3
4
5
6
7
8
9#指定日志级别,使用的appender log4j.rootLogger=trace,console #指定控制台输出的appender log4j.appender.console=org.apache.log4j.ConsoleAppender #指定消息格式layout log4j.appender.console.layout=org.apache.log4j.PatternLayout #指定消息格式内容 log4j.appender.console.layout.conversionPattern=%r [%t] %p %c %x - %m%n
参考这个格式内容,可以直接定位到日志输出位置
1log4j.appender.console.layout.conversionPattern=[%p]%r %l %d{yyyy-MM-dd HH:mm:ss.SSS} %m%n
开启log4j内置日志记录
1
2LogLog.setInternalDebugging(true);
FileAppender配置
1
2
3
4
5
6log4j.appender.file=org.apache.log4j.FileAppender log4j.appender.file.layout=org.apache.log4j.PatternLayout log4j.appender.file.layout.conversionPattern=%r [%t] %p %c %x - %m%n log4j.appender.file.file=log4j.log log4j.appender.file.encoding=UTF-8
RollingFileAppender配置
1
2
3
4
5
6
7
8
9#按照文件大小拆分 log4j.appender.rollingFile=org.apache.log4j.RollingFileAppender log4j.appender.rollingFile.layout=org.apache.log4j.PatternLayout log4j.appender.rollingFile.layout.conversionPattern=%r [%t] %p %c %x - %m%n log4j.appender.rollingFile.file=log4j.log log4j.appender.rollingFile.encoding=UTF-8 log4j.appender.rollingFile.maxFileSize=1MB log4j.appender.rollingFile.maxBackupIndex=10
DailyRollingFileAppender配置
1
2
3
4
5
6
7log4j.appender.dailyFile=org.apache.log4j.DailyRollingFileAppender log4j.appender.dailyFile.layout=org.apache.log4j.PatternLayout log4j.appender.dailyFile.layout.conversionPattern=%r [%t] %p %c %x - %m%n log4j.appender.dailyFile.file=log4j.log log4j.appender.dailyFile.encoding=UTF-8 log4j.appender.dailyFile.datePattern='.'yyyy-MM-dd-HH-mm-ss
JDBCAppender配置
1
2
3
4
5
6
7
8log4j.appender.logDB=org.apache.log4j.jdbc.JDBCAppender log4j.appender.logDB.layout=org.apache.log4j.PatternLayout log4j.appender.logDB.Driver=com.mysql.jdbc.Driver log4j.appender.logDB.URL=jdbc:mysql://localhost:3306/test log4j.appender.logDB.User=xxx log4j.appender.logDB.Password=xxx log4j.appender.logDB.Sql=xxx
自定义logger设置
1
2log4j.logger.com.web02=info,file
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23Logger root = Logger.getRootLogger(); root.addAppender(new ConsoleAppender(new PatternLayout("%d{yyyy-MM-dd HH:mm:ss.SSS} [%p]%r %l - %m%n"))); FileAppender fileAppender = new FileAppender(new PatternLayout("%d{yyyy-MM-dd HH:mm:ss.SSS} [%p]%r %l - %m%n"),"log4j.log",true); fileAppender.setEncoding("utf-8"); 与配置文件 log4j.properties log4j.rootLogger=debug, console, file log4j.appender.console=org.apache.log4j.ConsoleAppender log4j.appender.console.layout=org.apache.log4j.PatternLayout log4j.appender.console.layout.conversionPattern=%d{yyyy-MM-dd HH:mm:ss.SSS} [%p]%r %l - %m%n log4j.appender.file=org.apache.log4j.FileAppender log4j.appender.file.layout=org.apache.log4j.PatternLayout log4j.appender.file.layout.conversionPattern=%d{yyyy-MM-dd HH:mm:ss.SSS} [%p]%r %l - %m%n log4j.appender.file.file=log4j.log log4j.appender.file.encoding=UTF-8 等价
四 JCL
Jakarta Commons Logging,Apache提供的一个通用日志API,为所有Java日志实现提供一个统一接口
使用
1
2
3
4
5
6<dependency> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> <version>1.2</version> </dependency>
1
2
3
4
5
6
7
8
9
10
11
12import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.junit.Test; public class JCLTest { @Test public void test() { Log log= LogFactory.getLog(JCLTest.class); log.info("info"); } }
优势
- 面向接口开发,减少代码耦合
- 可灵活切换日志框架
- 统一API,方便学习使用
- 统一配置
原理
- 通过LogFactory动态加载Log实现类
- 查找符合条件的日志实现,包括Log4JLogger, Jdk14Logger, Jdk13LumberjackLogger, SimpleLog
- 获取具体的日志实现
五 Slf4j
简单使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14<!--slf4j日志门面--> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.28</version> </dependency> <!--slf4j内置实现--> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-simple</artifactId> <version>1.7.21</version> </dependency>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class Slf4jTest { public static final Logger LOGGER= LoggerFactory.getLogger(Slf4jTest.class); @Test public void test() { LOGGER.error("error"); LOGGER.warn("warn"); LOGGER.info("info");//默认级别 LOGGER.debug("debug"); LOGGER.trace("trace"); String name="log"; LOGGER.info("{}",name); try { int a=1/0; } catch (Exception ex) { LOGGER.error("error",ex); } } }
日志绑定
只能绑定一个日志实现,如果出现多个默认使用第一个
绑定logback
1
2
3
4
5
6<dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.2.3</version> </dependency>
绑定slf4j-nop
1
2
3
4
5
6
7<!--日志开关--> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-nop</artifactId> <version>1.7.25</version> </dependency>
绑定log4j
1
2
3
4
5
6
7
8
9
10
11
12<!--适配器--> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.7.12</version> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.12</version> </dependency>
绑定JUL
1
2
3
4
5
6
7<!--适配器--> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-jdk14</artifactId> <version>1.5.6</version> </dependency>
日志桥接器
举例:项目由log4j切换为slf4j+logback
1
2
3
4
5
6
7<!--配置log4j桥接器--> <dependency> <groupId>org.slf4j</groupId> <artifactId>log4j-over-slf4j</artifactId> <version>1.7.25</version> </dependency>
桥接器和适配器不能同时存在,否则会出现栈溢出异常
六 Logback
分为三个模块
- logback-core
- logback-classic
- logback-access
1
2
3
4
5
6
7
8
9
10
11
12<!--slf4j日志门面--> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.28</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.2.3</version> </dependency>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class LogbackTest { public static final Logger LOGGER= LoggerFactory.getLogger(LogbackTest.class); @Test public void test() { LOGGER.error("error"); LOGGER.warn("warn"); LOGGER.info("info"); LOGGER.debug("debug");//默认级别 LOGGER.trace("trace"); } }
配置文件
logback会依次读取以下配置文件,如果都不存在会使用默认配置
- logback.groovy
- logback-test.xml
- logback.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21<?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> <!--控制台输出appender--> <appender name="console" class="ch.qos.logback.core.ConsoleAppender"> <!--控制输出流对象--> <target>System.err</target> <!--消息格式配置--> <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> <pattern>${pattern}</pattern> </encoder> </appender> <!--root logger配置--> <root level="ALL"> <appender-ref ref="console"></appender-ref> </root> </configuration>
FileAppender配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31<property name="pattern" value="%-5level %d{yyyy-MM-dd HH:mm:ss.SSS} %c %M %L %thread %m%n"></property> <property name="log_dir" value="/logs"></property> <!--文件输出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> <!--消息格式配置--> <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder"> <layout class="ch.qos.logback.classic.html.HTMLLayout"> <pattern>${pattern}</pattern> </layout> </encoder> </appender> <root level="ALL"> <appender-ref ref="file"></appender-ref> <appender-ref ref="htmlfile"></appender-ref> </root>
拆分Appender配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22<property name="pattern" value="%-5level %d{yyyy-MM-dd HH:mm:ss.SSS} %c %M %L %thread %m%n"></property> <property name="log_dir" value="/logs"></property> <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"> <!--按照时间,压缩格式用log.gz--> <fileNamePattern>${log_dir}/roll.%d{yyyy-MM-dd-HH-mm-ss}.log%i</fileNamePattern> <!--按照文件大小--> <maxFileSize>1MB</maxFileSize> </rollingPolicy> </appender> <root level="ALL"> <appender-ref ref="rollfile"></appender-ref> </root>
过滤器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24<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"> <!--按照时间,压缩格式用log.gz--> <fileNamePattern>${log_dir}/roll.%d{yyyy-MM-dd-HH-mm-ss}.log%i</fileNamePattern> <!--按照文件大小--> <maxFileSize>1MB</maxFileSize> </rollingPolicy> <!--日志级别过滤器--> <filter class="ch.qos.logback.classic.filter.LevelFilter"> <!--过滤规则--> <level>ERROR</level> <onMatch>ACCEPT</onMatch> <onMismatch>DENY</onMismatch> </filter> </appender>
异步日志
1
2
3
4
5
6
7
8
9<!--异步日志,提升性能--> <appender name="async" class="ch.qos.logback.classic.AsyncAppender"> <!--指定某个具体appender--> <appender-ref ref="rollfile"></appender-ref> </appender> <root level="ALL"> <appender-ref ref="async"></appender-ref> </root>
自定义logger
1
2
3
4
5<!--自定义logger additivity:自定义logger是否继承rootlogger--> <logger name="com.web02" level="info" additivity="false"> <appender-ref ref="console"></appender-ref> </logger>
log4j.properties->logback.xml
http://logback.qos.ch/translator/
logback-access
与Servlet容器(Tomcat、Jetty)集成,提供HTTP访问日志功能
七 Log4j2
slf4j+log4j2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28<!--slf4j日志门面--> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.28</version> </dependency> <!--log4j2日志门面--> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-api</artifactId> <version>2.11.2</version> </dependency> <!--log4j2日志实现--> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-core</artifactId> <version>2.10.0</version> </dependency> <!--log4j2适配器--> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-slf4j-impl</artifactId> <version>2.9.1</version> </dependency>
配置文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44<?xml version="1.0" encoding="utf-8" ?> <!--status:输出的日志级别,monitorInternal:自动加载配置文件的间隔时间--> <Configuration status="debug" monitorInternal="5"> <!--配置集中管理属性--> <properties> <property name="log_home">/logs</property> </properties> <!--控制台输出appender--> <Appenders> <Console name="Console" target="SYSTEM_ERR"> <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS}"></PatternLayout> </Console> <File name="file" fileName="${log_home}/myfile.log"> <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS}"></PatternLayout> </File> <RandomAccessFile name="accessfile" fileName="${log_home}/myfile.log"> <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS}"></PatternLayout> </RandomAccessFile> <RollingFile name="rollingfile" fileName="${log_home}/myfile.log" filePattern="/logs/$${date:yyyy-MM-dd}/rolllog-%d{yyyy-MM-dd-HH-mm}-%i.log"> <ThresholdFilter level="debug" onMatch="ACCEPT" onMismatch="DENY"></ThresholdFilter> <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS}"></PatternLayout> <Policies> <OnStartupTriggeringPolicy></OnStartupTriggeringPolicy> <SizeBasedTriggeringPolicy size="1 MB"></SizeBasedTriggeringPolicy> <TimeBasedTriggeringPolicy></TimeBasedTriggeringPolicy> </Policies> <DefaultRolloverStrategy max="30"></DefaultRolloverStrategy> </RollingFile> </Appenders> <Loggers> <Root level="trace"> <AppenderRef ref="Console"></AppenderRef> </Root> </Loggers> </Configuration>
异步日志
1
2
3
4
5
6
7<!--异步日志依赖--> <dependency> <groupId>com.lmax</groupId> <artifactId>disruptor</artifactId> <version>3.3.4</version> </dependency>
配置异步Appender:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16<Appenders> <File name="file" fileName="${log_home}/myfile.log"> <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS}"></PatternLayout> </File> <Async name="Async"> <AppenderRef ref="file"></AppenderRef> </Async> </Appenders> <Loggers> <Root level="trace"> <AppenderRef ref="Async"></AppenderRef> </Root> </Loggers>
配置异步Logger:
全局配置
log4j2.component.properties
1
2Log4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector
混合配置
1
2
3
4
5<!--自定义异步Logger,includeLocation关闭行号信息,additivity不继承rootlogger--> <AsyncLogger name="com.web02" level="trace" includeLocation="false" additivity="false"> <AppenderRef ref="file"></AppenderRef> </AsyncLogger>
使用异步日志,AsyncAppender, AsyncLogger, 全局日志不能同时出现
无垃圾模式
使用重用对象、缓冲区、尽可能不分配临时对象的手段,提高性能
八 SpringBoot日志
依赖
1
2
3
4
5<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-logging</artifactId> </dependency>
springboot默认使用slf4j作为日志门面,使用logback作为日志实现
配置
logback-spring.xml:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26<?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> <!--控制台输出appender--> <appender name="console" class="ch.qos.logback.core.ConsoleAppender"> <!--控制输出流对象--> <target>System.err</target> <!--消息格式配置--> <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}</pattern> </springProfile> </encoder> </appender> <!--root logger配置--> <root level="ALL"> <appender-ref ref="console"></appender-ref> </root> </configuration>
1
2
3#指定环境,使用不同的日志格式 spring.profiles.active=dev
切换为log4j2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <!--排除logback--> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-logging</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-log4j2</artifactId> </dependency>
最后
以上就是烂漫玫瑰最近收集整理的关于Java日志框架学习笔记的全部内容,更多相关Java日志框架学习笔记内容请搜索靠谱客的其他文章。
发表评论 取消回复