文章目录
一、前言
上一篇介绍了logback模块解析logback.mxl文件的入口, 我们可以手动指定logback.xml文件的位置, 也可以使用其它的名字, 本节我们继续讨论logback是如何解析logback.xml文件的。
二、源码解析
拿出我们上一节的继承图
其中ContextAware 和ContextAwareBase是有关日志上下文LoggerContext注入与打印启动日志的, 我们不介绍。
看到这个Aware结尾的有的同学可能会感觉到很有亲切感, 没错, spring中有很多这种Aware结尾的类, 例如ApplicationContextAware
、EnvironmentAware
等xxxAware, 它们都会提供一个setXxx的方法, 用来在框架启动的时候注入一个xxx对象。
GenericXMLConfigurator
java
public abstract class GenericXMLConfigurator extends ContextAwareBase {
/**
* SaxEvent解析器
*/
protected SaxEventInterpreter saxEventInterpreter;
/**
* model解析器的上下文
*/
protected ModelInterpretationContext modelInterpretationContext;
/**
* 配置文件节点路径和action的映射
* 默认是SimpleRuleStore
*/
private RuleStore ruleStore;
public final void doConfigure(URL url) throws JoranException {
InputStream in = null;
try {
// 1.给Context设置ConfigurationWatchList; 用于配置文件热更新
informContextOfURLUsedForConfiguration(getContext(), url);
URLConnection urlConnection = url.openConnection();
// per http://jira.qos.ch/browse/LOGBACK-117 LBCORE-105
// per http://jira.qos.ch/browse/LOGBACK-163 LBCORE-127
urlConnection.setUseCaches(false);
in = urlConnection.getInputStream();
// 2.解析配置; url.toExternalForm():返回url表示的绝对路径的字符串形式
doConfigure(in, url.toExternalForm());
} catch (IOException ioe) {
String errMsg = "Could not open URL [" + url + "].";
// 打印解析异常日志
addError(errMsg, ioe);
throw new JoranException(errMsg, ioe);
} finally {
if (in != null) {
try {
// 3.关闭流
in.close();
} catch (IOException ioe) {
String errMsg = "Could not close input stream";
addError(errMsg, ioe);
throw new JoranException(errMsg, ioe);
}
}
}
}
}
这个方法就是开始解析logback.xml文件真正的入口了public final void doConfigure(URL url) throws JoranException
- informContextOfURLUsedForConfiguration方法用来设置动态热加载的配置文件, 也就是我们
<configuration debug="true" scan="true" scanPeriod="10 second">
这里动态刷新的默认文件 - doConfigure: 进一步解析
doConfigure(final InputSource inputSource)
直接从doConfigure跳过来看这个方法即可
java
public final void doConfigure(final InputSource inputSource) throws JoranException {
// 发布配置开始事件
context.fireConfigurationEvent(newConfigurationStartedEvent(this));
long threshold = System.currentTimeMillis();
// 1.解析日志配置文件; 例如logback.xml
SaxEventRecorder recorder = populateSaxEventRecorder(inputSource);
// 获取解析节点的结果; 每个节点都会解析成StartEvent, BodyEvent, EndEvent
List<SaxEvent> saxEvents = recorder.getSaxEventList();
if (saxEvents.isEmpty()) {
addWarn("Empty sax event list");
return;
}
// 2.根据xml节点的解析生成对应的model对象, top默认是configuration的model
Model top = buildModelFromSaxEventList(recorder.getSaxEventList());
if (top == null) {
addError(ErrorCodes.EMPTY_MODEL_STACK);
return;
}
// 3.语法检查
sanityCheck(top);
// 4.解析model节点(核心)
processModel(top);
// no exceptions at this level
StatusUtil statusUtil = new StatusUtil(context);
// 5.发布配置解析结束事件
if (statusUtil.noXMLParsingErrorsOccurred(threshold)) {
// xml解析无异常
addInfo("Registering current configuration as safe fallback point");
registerSafeConfiguration(top);
context.fireConfigurationEvent(newConfigurationEndedSuccessfullyEvent(this));
} else {
// xml解析发生异常
context.fireConfigurationEvent(newConfigurationEndedWithXMLParsingErrorsEvent(this));
}
}
方法小结
这里就是解析logback.xml的整个流程了, 编排了5个点
- populateSaxEventRecorder方法用来解析文件, 然后返回解析对象
- 根据解析logback.xml的结果生成对应的model
- 检查语法(不介绍)
- 解析model节点(核心)
- 发布解析完成事件(成功/失败)
logback.xml解析
populateSaxEventRecorder
java
public SaxEventRecorder populateSaxEventRecorder(final InputSource inputSource) throws JoranException {
SaxEventRecorder recorder = new SaxEventRecorder(context);
// sax解析配置文件, 每一个节点都会解析成SaxEvent, 分为StartEvent, BodyEvent, EndEvent
recorder.recordEvents(inputSource);
return recorder;
}
SaxEventRecorder
java
public class SaxEventRecorder extends DefaultHandler implements ContextAware {
// 节点路径
final ElementPath elementPath;
// 解析节点得到的结果对象
List<SaxEvent> saxEventList = new ArrayList<SaxEvent>();
public void recordEvents(InputSource inputSource) throws JoranException {
// 创建sax解析器
SAXParser saxParser = buildSaxParser();
try {
// sax解析; 当前类SaxEventRecorder也是个DefaultHandler
saxParser.parse(inputSource, this);
return;
} catch (xxxException ie) {
// ...异常处理
}
throw new IllegalStateException("This point can never be reached");
}
// 解析节点的起始标签
public void startElement(String namespaceURI, String localName, String qName, Attributes atts) {
// ...
}
// 解析标签内容部分
public void characters(char[] ch, int start, int length) {
// ...
}
// 解析到标签结尾部分时触发
public void endElement(String namespaceURI, String localName, String qName) {
// ...
}
}
方法小结
- SaxEventRecorder对象用来封装解析logback.xml的逻辑, 同时它也是一个
DefaultHandler
对象, 负责处理每个节点的具体解析逻辑 - 使用sax解析logback.xml文件
- 每个节点解析结果存放在实例变量saxEventList中
关于常见的xml解析框架的对比如下
特性 | DOM4J | SAX | JSOUP |
---|---|---|---|
解析方式 | 基于树模型,加载整个文档到内存 | 基于事件驱动,逐行解析 | 类似 DOM 树模型,专注于 HTML/XML |
性能 | 性能较高,但文件过大时内存消耗明显 | 性能最高,适合大文件解析 | 性能适中,适合中小型 XML 或 HTML 文档 |
内存占用 | 较高,依赖于内存加载整个文档 | 最低,仅在解析时占用较少内存 | 较高,但通常适合处理网页等较小文件 |
功能支持 | 强大的 XPath 支持,支持修改和生成 XML | 只支持读取,不能修改文档 | 支持解析和修改文档,HTML/XML 均适用 |
易用性 | 较高,API 友好 | 较低,需要手动处理事件逻辑 | 非常高,简洁的 API,类似 jQuery 操作 |
修改能力 | 支持动态修改 | 不支持修改 | 支持动态修改,灵活度高 |
适用场景 | 适合处理中小型 XML 文件 | 适合处理超大文件或流式读取 | 适合处理 HTML/XML 文件,尤其是网页解析 |
依赖性 | 需要引入额外依赖(如 dom4j jar 包) | 无需额外依赖,Java 内置支持 | 需要引入 jsoup jar 包 |
-
DOM4J:功能全面,支持 XPath,适合需要频繁修改 XML 的场景,但处理超大文件时会占用大量内存。
-
SAX:性能最佳,占用内存最少,适合超大文件的解析,但使用复杂且无法修改文档内容。
-
JSOUP:偏向网页内容解析,API 简单易用,支持 XML 和 HTML 的解析和修改,适合中小型文件处理。
通过SaxEvent构建节点model
buildModelFromSaxEventList
java
// 通过节点的saxEvent构建节点的model
public Model buildModelFromSaxEventList(List<SaxEvent> saxEvents) throws JoranException {
// 构建saxEvent解析器
buildSaxEventInterpreter(saxEvents);
// 解析节点, 解析节点的顺序是StartEvent, BodyEvent, EndEvent, 最终使用
playSaxEvents();
Model top = saxEventInterpreter.getSaxEventInterpretationContext().peekModel();
return top;
}
// 构建saxEvent解析器, 并添加标签路径对应的action
protected void buildSaxEventInterpreter(List<SaxEvent> saxEvents) {
// 1.创建ruleStore, 默认是SimpleRuleStore
RuleStore rs = getRuleStore();
// 2.将路径和对应的解析对象action绑定
addElementSelectorAndActionAssociations(rs);
// 3.构建saxEvent解析器
this.saxEventInterpreter = new SaxEventInterpreter(context, rs, initialElementPath(), saxEvents);
// 给saxEvent解析器上下文添加context
SaxEventInterpretationContext interpretationContext = saxEventInterpreter.getSaxEventInterpretationContext();
interpretationContext.setContext(context);
// 4.给没有指定action的标签路径添加默认action; 这里是ImplicitModelAction, 用来解析ruleStore规则之外的标签, 给父标签对象添加属性用
setImplicitRuleSupplier(saxEventInterpreter);
}
方法小结
- 创建ruleStore, 默认是SimpleRuleStore
- 将路径和对应的解析对象action绑定
- 构建saxEvent解析器
- 给没有指定action的标签路径添加默认action; 这里是ImplicitModelAction, 用来解析ruleStore规则之外的标签, 给父标签对象添加属性用
具体的解析逻辑在saxEvent解析器SaxEventInterpreter
中, saxEvent经过action处理之后会得到对应标签节点的model对象
解析model节点
java
public void processModel(Model model) {
// 1.创建ModelInterpretationContext并添加默认对象; 当一个标签没有指定class时, 会从这里尝试获取
buildModelInterpretationContext();
// configuration节点
this.modelInterpretationContext.setTopModel(model);
modelInterpretationContext.setConfiguratorHint(this);
// 2.创建解析model的核心类
DefaultProcessor defaultProcessor = new DefaultProcessor(context, this.modelInterpretationContext);
// 3.将model与modelHandler和Analyser关联
addModelHandlerAssociations(defaultProcessor);
// disallow simultaneous configurations of the same context
ReentrantLock configurationLock = context.getConfigurationLock();
try {
configurationLock.lock();
// 开始解析model
defaultProcessor.process(model);
} finally {
configurationLock.unlock();
}
}
方法小结
- 构建model解析时的上下文ModelInterpretationContext, 并添加默认标签的class类(如下面的表格)
- 创建解析model的核心类DefaultProcessor
- 将model和对应的处理类(modelHandler)关联起来
- 使用DefaultProcessor解析model
默认标签的属性对应的类
通过buildModelInterpretationContext方法添加在ModelInterpretationContext.DefaultNestedComponentRegistry属性中
类 | 属性 | 默认值 |
---|---|---|
AppenderBase | layout | PatternLayout.class |
UnsynchronizedAppenderBase | layout | PatternLayout.class |
AppenderBase | encoder | PatternLayoutEncoder |
UnsynchronizedAppenderBase | encoder | PatternLayoutEncoder |
SSLComponent | ssl | SSLConfiguration |
SSLConfiguration | parameters | SSLParametersConfiguration |
SSLConfiguration | keyStore | KeyStoreFactoryBean |
SSLConfiguration | trustStore | KeyStoreFactoryBean |
SSLConfiguration | keyManagerFactory | KeyManagerFactoryFactoryBean |
SSLConfiguration | trustManagerFactory | TrustManagerFactoryFactoryBean |
SSLConfiguration | secureRandom | SecureRandomFactoryBean |
例如下面的配置, 由于FileAppender是UnsynchronizedAppenderBase
的子类, 并且encoder节点没有指定class, 而encoder是UnsynchronizedAppenderBase
的一个属性, 所以这里取默认值PatternLayoutEncoder
xml
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>logs/app.log</file>
<!-- encoder不指定class的时候, 默认使用的是PatternLayoutEncoder -->
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} %-5level [%thread] %logger{36} - %msg%n</pattern>
</encoder>
</appender>
我们常用的也就appender标签下的这两个layout和encoder属性
各标签节点路径以及其对应的model和modelHandler如下表格
一般情况下我们看节点路径和action以及handler就行, 这个handler就是用来处理logback.xml中各个标签的类。
这里的节点路径就是我们logback.xml文件中所有可以定义的节点了。
标签节点路径 | 解析路径节点的Action | action解析之后生成的model | 解析model的Handler |
---|---|---|---|
configuration | ConfigurationAction | ConfigurationModel | ConfigurationModelHandlerFull |
configuration/contextName | ContextNameAction | ContextNameModel | ContextNameModelHandler |
configuration/contextListener | LoggerContextListenerAction | LoggerContextListenerModel | LoggerContextListenerModelHandler |
configuration/insertFromJNDI | InsertFromJNDIAction | InsertFromJNDIModel | InsertFromJNDIModelHandler |
configuration/logger | LoggerAction | LoggerModel | LoggerModelHandler |
configuration/logger/level | LevelAction | LevelModel | LevelModelHandler |
configuration/root | RootLoggerAction | RootLoggerModel | RootLoggerModelHandler |
configuration/root/level | RootLoggerAction | RootLoggerModel | RootLoggerModelHandler |
configuration/logger/appender-ref | AppenderRefAction | AppenderRefModel | AppenderRefModelHandler |
configuration/root/appender-ref | AppenderRefAction | AppenderRefModel | AppenderRefModelHandler |
configuration/include | IncludeAction | IncludeModel | IncludeModelHandler |
configuration/propertiesConfigurator | PropertiesConfiguratorAction | PropertiesConfiguratorModel | PropertiesConfiguratorModelHandler |
configuration/consolePlugin | ConsolePluginAction | 无 | 无 |
configuration/receiver | ReceiverAction | ReceiverModel | ReceiverModelHandler |
*/variable | PropertyAction | PropertyModel | PropertyModelHandler |
*/property | PropertyAction | PropertyModel | PropertyModelHandler |
configuration/import | ImportAction | ImportModel | ImportModelHandler |
configuration/timestamp | TimestampAction | TimestampModel | TimestampModelHandler |
configuration/shutdownHook | ShutdownHookAction | ShutdownHookModel | ShutdownHookModelHandler |
configuration/sequenceNumberGenerator | SequenceNumberGeneratorAction | SequenceNumberGeneratorModel | SequenceNumberGeneratorModelHandler |
configuration/serializeModel | SerializeModelAction | SerializeModelModel | SerializeModelModelHandler |
configuration/define | DefinePropertyAction | DefineModel | DefineModelHandler |
configuration/evaluator | EventEvaluatorAction | EventEvaluatorModel | EventEvaluatorModelHandler |
configuration/conversionRule | ConversionRuleAction | ConversionRuleModel | ConversionRuleModelHandler |
configuration/statusListener | StatusListenerAction | StatusListenerModel | StatusListenerModelHandler |
*/appender | AppenderAction | AppenderModel | AppenderModelHandler |
configuration/appender/appender-ref | AppenderRefAction | AppenderRefModel | AppenderRefModelHandler |
configuration/newRule | NewRuleAction | 无 | 无 |
*/param | ParamAction | ParamModel | ParamModelHandler |
*/if | IfAction | IfModel | IfModelHandler |
*/if/then | ThenAction | ThenModel | ThenModelHandler |
*/if/else | ElseAction | ElseModel | ElseModelHandler |
*/appender/sift | SiftAction | SiftModel | SiftModelHandler |
其它属性标签 | ImplicitModelAction | ImplicitModel | ImplicitModelHandler |
DefaultProcessor解析model
java
public void process(Model model) {
// 根节点为空, 直接异常
if (model == null) {
addError("Expecting non null model to process");
return;
}
// 1.将LoggerContext添加到ModelInterpretationContext中, 这是第一个也是最底层的
initialObjectPush();
// 2.使用第一阶段过滤器过滤model, 将满足条件的model使用handler处理
mainTraverse(model, getPhaseOneFilter());
// 3.处理依赖
analyseDependencies(model);
// 4.使用第二阶段过滤器过滤model, 将满足条件的model使用handler处理
traversalLoop(this::secondPhaseTraverse, model, getPhaseTwoFilter(), "phase 2");
// 配置解析完成
addInfo("End of configuration.");
// 5.将LoggerContext从ModelInterpretationContext中弹出
finalObjectPop();
}
方法小结
- 将日志上下文loggerContext放到model解析器中
- 使用第一阶段过滤器过滤model, 将满足条件的model使用handler处理, 不满足第一阶段过滤器的model有下面四个, 为什么这四个model这么特殊呢?? 因为它们需要依赖别的model(appender-ref标签)
- LoggerModel
- RootLoggerModel
- AppenderModel
- AppenderRefModel
- 确定依赖顺序
- 使用第二阶段过滤器过滤model, 将满足条件的model使用handler处理; 上面四个model将会在这里处理
- 弹出loggerContext节点
具体的model节点解析将会在下节挑出几个重点来介绍, 到这里, logback解析logback.xml文件的整体流程就介绍完了
关于一些细节以及JoranConfigurator和JoranConfiguratorBase中相关的内容没有详细介绍, 读者想要了解的更多建议看下源码
三、总结
- JoranConfigurator 是logback框架用来解析配置文件的核心类(logback.xml配置文件)
- logback.xml文件中每个节点都会被解析成一个saxEvent, 解析过程中借助解析器上下文SaxEventInterpretationContext保存相关信息
- RuleStore中映射了每个节点路径对应的action, action会创建对应节点的model
- DefaultProcessor中记录了每个model和modelHandler的映射关系
- DefaultProcessor借助上下文对象ModelInterpretationContext将model分为两个阶段使用modelHandler进行处理
- 最后将处理结果都放到了日志上下文LoggerContext中(这是我们打印日志的重要对象)
- 流程就是(eg. configuration/appender/encoder -> saxEvent -> action -> model -> modelHandler -> loggerContext)