`

读logback源码系列文章(六)——ContextInitializer

阅读更多
放了一个长假回来啦,继续写本系列博客

这篇博客我们接着上一篇的主题,来介绍一下logback是怎么读取配置文件并初始化整个框架的。还是老规矩,先上总览图



从图中可以看到,logback框架的初始化是由ContextInitializer类来负责完成的,而实际进行配置的是GenericConfigurator类,它调用SaxEventRecorder类来负责读取logback.xml文件,然后由Interpreter类来进行解析,而最后真正的初始化工作,是由一系列Action组件来完成。接下来就实际看看代码
try {
        new ContextInitializer(defaultLoggerContext).autoConfig();
      } catch (JoranException je) {
        Util.report("Failed to auto configure default logger context", je);
      }

以上代码来自StaticLoggerBinder类,它在提供LoggerContext之前,就对LoggerContext进行初始化,初始化的入口方法,即是ContextInitializer类的autoConfig()方法
public void autoConfig() throws JoranException {
    StatusListenerConfigHelper.installIfAsked(loggerContext);
    URL url = findURLOfDefaultConfigurationFile(true);
    if (url != null) {
      configureByResource(url);
    } else {
      BasicConfigurator.configure(loggerContext);
    }
  }

这里首先调用findURLOfDefaultConfigurationFile()方法,来寻找一个配置文件,一般就是logback.xml文件,如果没有找到,则用BasicConfigurator来进行默认配置,否则就调用configureByResource()方法,根据配置文件进行配置
public void configureByResource(URL url) throws JoranException {
    if (url == null) {
      throw new IllegalArgumentException("URL argument cannot be null");
    }
    if (url.toString().endsWith("groovy")) {
      if (EnvUtil.isGroovyAvailable()) {
        // avoid directly referring to GafferConfigurator so as to avoid
        // loading  groovy.lang.GroovyObject . See also http://jira.qos.ch/browse/LBCLASSIC-214
        GafferUtil.runGafferConfiguratorOn(loggerContext, this, url);
      } else {
        StatusManager sm = loggerContext.getStatusManager();
        sm.add(new ErrorStatus("Groovy classes are not available on the class path. ABORTING INITIALIZATION.",
                loggerContext));
      }
    }
    if (url.toString().endsWith("xml")) {
      JoranConfigurator configurator = new JoranConfigurator();
      configurator.setContext(loggerContext);
      configurator.doConfigure(url);
    }
  }

这里如果配置文件是以.groovy结尾,就调用另一种方法进行配置,我没有跟进去看;如果是常规以.xml文件结尾,那么就用JoranConfigurator类来进行配置
final public void doConfigure(URL url) throws JoranException {
    try {
      informContextOfURLUsedForConfiguration(url);
      URLConnection urlConnection = url.openConnection();
      // per http://jira.qos.ch/browse/LBCORE-105
      // per http://jira.qos.ch/browse/LBCORE-127
      urlConnection.setUseCaches(false);
      
      InputStream in = urlConnection.getInputStream();
      doConfigure(in);
      in.close();
    } catch (IOException ioe) {
      String errMsg = "Could not open URL [" + url + "].";
      addError(errMsg, ioe);
      throw new JoranException(errMsg, ioe);
    }
  }

这个方法则是打开配置文件的URL,转化成InputStream,然后继续调用
// this is the most inner form of doConfigure whereto other doConfigure
  // methods ultimately delegate
  final public void doConfigure(final InputSource inputSource)
      throws JoranException {

    if(!ConfigurationWatchListUtil.wasConfigurationWatchListReset(context)) {
      informContextOfURLUsedForConfiguration(null);
    }
    SaxEventRecorder recorder = new SaxEventRecorder();
    recorder.setContext(context);
    recorder.recordEvents(inputSource);
    buildInterpreter();
    // disallow simultaneous configurations of the same context
    synchronized (context.getConfigurationLock()) {
      interpreter.play(recorder.saxEventList);
    }
  }

上面的WatchList,一般是走不进这个分支的,我也就没深入地看。接下来创建一个SaxEventRecorder类,由它负责读取XML文件,并解析成流,创建SaxEvent,这部分功能是JDK提供的。然后是一个核心方法buildInterpreter()
protected void buildInterpreter() {
    RuleStore rs = new SimpleRuleStore(context);
    addInstanceRules(rs);
    this.interpreter = new Interpreter(context, rs, initialPattern());
    InterpretationContext ec = interpreter.getInterpretationContext();
    ec.setContext(context);
    addImplicitRules(interpreter);
    addDefaultNestedComponentRegistryRules(ec.getDefaultNestedComponentRegistry());
  }

这里的核心是初始化了interpreter字段,这种设计其实我觉得是不太好的,因为把interpreter字段的初始化隐藏得很深,不容易看到

Interpreter在框架的配置中是一个很关键的类,logback是依赖这个类来进行初始化工作的。这里用到了RuleStore接口,RuleStore只有一个唯一的实现类SimpleRuleStore,这个类的作用是,把Pattern匹配到Action上,因为所有的配置工作,最终是由Action来完成的。作者对这个类的设计是十分精巧的,个人认为logback框架的初始化设计思路,是非常值得学习的,这部分内容也是我阅读logback源码感觉收获最大的地方

buildInterpreter()方法调用之后,调用play(recorder.saxEventList)方法,开始进行配置。注意,这个时候logback.xml文件已经被解析成了SAX流,并且保存在SaxEventRecorder的saxEventList字段里了。接下来就要重点分析一下Interpreter这个核心类的字段和方法,一些不重要和一目了然的字段和方法,这里就忽略了
final private RuleStore ruleStore;
  final private InterpretationContext interpretationContext;
private Pattern pattern;
Stack<List> actionListStack;

以上的字段中,ruleStore正如前面提到的,是用来根据Sax节点,来匹配对应的Action。ruleStore是在Interpreter创建时初始化的,而且只初始化一次,因此logback.xml中的每个元素,用哪个Action来处理,都已经规定好了。如果在解析logback.xml的过程中,遇到了无法识别的元素,就会抛出异常

interpretationContext也非常关键,在Action组件实际处理过程中,大部分对象的出栈和入栈,都依赖这个类来实现

pattern存放了当前正在处理的元素

actionListStack则是对负责处理某元素的Action进行出栈、进栈的操作

介绍完了关键的字段,接下来看方法
public void play(List<SaxEvent> eventList) {
    player.play(eventList);
  }

这是入口方法,委托EventPlayer来处理SAX流
public void play(List<SaxEvent> seList) {
    eventList = seList;
    SaxEvent se;
    for(currentIndex = 0; currentIndex < eventList.size(); currentIndex++) {
      se = eventList.get(currentIndex);
      
      if(se instanceof StartEvent) {
        interpreter.startElement((StartEvent) se);
        // invoke fireInPlay after startElement processing
        interpreter.getInterpretationContext().fireInPlay(se);
      }
      if(se instanceof BodyEvent) {
        // invoke fireInPlay before  characters processing
        interpreter.getInterpretationContext().fireInPlay(se);
        interpreter.characters((BodyEvent) se);
      }
      if(se instanceof EndEvent) {
        // invoke fireInPlay before endElement processing
        interpreter.getInterpretationContext().fireInPlay(se);
        interpreter.endElement((EndEvent) se);
      }
    
    }
  }

这个方法是对整个logback.xml配置文件的元素来循环遍历,并根据SaxEvent的类型,还给Interpreter类来处理,SaxEvent有3种,基本大同小异,我们用StartEvent来举例
public void startElement(StartEvent se) {
    setDocumentLocator(se.getLocator());
    startElement(se.namespaceURI, se.localName, se.qName, se.attributes);
  }

这里设置了一下文档的Locator,然后继续调用
private void startElement(String namespaceURI, String localName,
      String qName, Attributes atts) {

    String tagName = getTagName(localName, qName);
    pattern.push(tagName);

    if (skip != null) {
      // every startElement pushes an action list
      pushEmptyActionList();
      return;
    }

    List applicableActionList = getApplicableActionList(pattern, atts);
    if (applicableActionList != null) {
      actionListStack.add(applicableActionList);
      callBeginAction(applicableActionList, tagName, atts);
    } else {
      // every startElement pushes an action list
      pushEmptyActionList();
      String errMsg = "no applicable action for [" + tagName
          + "], current pattern is [" + pattern + "]";
      cai.addError(errMsg);
    }
  }

这个方法先得到元素的TagName,然后通过RuleStore来匹配一下,得到处理的Action列表,然后调用callBeginAction()方法来处理,如果SaxEvent类型是EndEvent,则这里是调用callEndAction()方法
void callBeginAction(List applicableActionList, String tagName,
      Attributes atts) {
    if (applicableActionList == null) {
      return;
    }

    Iterator i = applicableActionList.iterator();
    while (i.hasNext()) {
      Action action = (Action) i.next();
      // now let us invoke the action. We catch and report any eventual
      // exceptions
      try {
        action.begin(interpretationContext, tagName, atts);
      } catch (ActionException e) {
        skip = (Pattern) pattern.clone();
        cai.addError("ActionException in Action for tag [" + tagName + "]", e);
      } catch (RuntimeException e) {
        skip = (Pattern) pattern.clone();
        cai.addError("RuntimeException in Action for tag [" + tagName + "]", e);
      }
    }
  }

这里就简单啦,循环遍历负责处理的Action,依次调用其begin()方法。类似的callEndAction(),则会循环遍历负责处理的Action,依次调用其end()方法

总结一下整个配置流程:
1、首先读取配置文件,如果没有找到配置文件,则用默认配置
2、如果找到配置文件logback.xml,则调用SAX解析器来解析该配置文件
3、初始化一个Interpreter对象,调用其play()方法
4、Interpreter对象委托EventPlayer对象,循环遍历XML文件中的所有节点
5、根据节点的类型(<element>、</element>、content),调用Interpreter中的startElement()、characters()、endElement()方法
6、Interpreter委托RuleStore来匹配元素名,选择合适的Action
7、委托Action组件来进行实际的配置

本篇博客的内容就结束了,把logback框架是怎么配置的讲了一半,但Action组件是怎么进行实际配置的,暂时没有涉及,下一篇博客就介绍这个方面的内容
  • 大小: 203.5 KB
4
1
分享到:
评论

相关推荐

    springboot、logback源码解读

    springboot、logback源码解读,对logback从初始化到,配置文件加载到日志打印,所有步骤的源码分析

    logback-1.1.2源码包

    包含所有的jar包和源代码 logback-core logback-classic logback-access pom.xml

    logback源代码

    rar包中包括:logback源代码,以及struts2的基础的页面跳转,logback虽然是开源的,但它依赖的jar包找全不也不容易,当然你也可以用Maven去下载是最好的喽。 你可以访问:...

    扩展logback将日志输出到Kafka实例源码

    扩展logback将日志输出到Kafka实例源码,详情请参见博文:http://blog.csdn.net/l1028386804/article/details/79135948

    SpringBoot中自定义日志配置logback-spring.xml示例源码

    SpringBoot中自定义日志配置logback-spring.xml示例源码

    logback+slf4j的JAR包和源码

    只要里面的logback-classic-1.1.7,logback-core-1.1.7,slf4j-api-1.7.21的JAR就可以打印出日志信息,而带有source表示对应的JAR包的源代码。可以要也可以不要

    Android代码-logback

    Spring Security源码分析六:Spring Social 社交登录源码解析 Spring Security源码分析七:Spring Security 记住我 Spring Security源码分析八:Spring Security 退出 Spring Security源码分析九:Spring Security ...

    Slf4j+logback实现logback测试

    Slf4j+logback实现logback测试,Slf4j+logback实现logback测试

    spring boot logback demo

    源码可见:https://github.com/kent124454731/spring-boot-logback。 logback的一些说明可参见文章:http://blog.csdn.net/u011794238/article/details/50770557 如有乱码问题可参见:...

    扩展logback将日志输出到Kafka实例扩展源码

    扩展logback将日志输出到Kafka实例扩展源码,详情参见博文:http://blog.csdn.net/l1028386804/article/details/79136841

    Logback类库含logback.xml配置文件

    该压缩包包含 logback类库所包含的jar包以及logback.xml配置文件(放到 src 目录),用于开发学习使用。

    logback下载 日志文件jar包

    内置三个jar包 一个配置文件 logback.txt logback-classic-1.2.3.jar logback-core-1.2.3.jar slf4j-api-1.7.26.jar

    logback.的jar包

    此zip包含logback-access-1.2.3和logback-classic-1.2.3和logback-core-1.2.3

    logback-core-1.2.10-API文档-中文版.zip

    赠送jar包:logback-core-1.2.10.jar; 赠送原API文档:logback-core-1.2.10-javadoc.jar; 赠送源代码:logback-core-1.2.10-sources.jar; 赠送Maven依赖信息文件:logback-core-1.2.10.pom; 包含翻译后的API文档...

    logback.jar包

    日志组件logback jar包,logback-access-1.1.3.jar logback-classic-1.1.3.jar logback-core-1.1.3.jar slf4j-api-1.7.12.jar

    logback-0.9.18.zip

    logback-0.9.18.zip 官网下载

    logback-1.0.1

    logback当前分成三个模块:logback-core,logback- classic和logback-access。logback-core是其它两个模块的基础模块。logback-classic是log4j的一个 改良版本。此外logback-classic完整实现SLF4J API使你可以很方便...

    logback jar包和logback.xml配置文件打包下载 小白新手学Java

    slf4j-api-1.7.26.jar logback-core-1.2.3.jar logback-classic-1.2.3.jar logback.xml

    LogBack 中文开发手册

    Logback 中文手册,清晰版. 简单地说,Logback 是一个 Java 领域的日志框架。它被认为是 Log4J 的继承人。 Logback 主要由三个模块组成: logback-core logback-classic logback-access

    LogBack配置文件

    LogBack配置文件,主要包括LOGBack的配置文件内容

Global site tag (gtag.js) - Google Analytics