009-Joran配置框架
1. Joran配置
1.1.?Logback依賴Joran。Joran是個成熟、靈活和強大的配置框架。Logback模塊的許多能力都只能在Joran幫助下實現。
1.2. Joran實際上是個通用的配置系統, 可以在記錄系統之外使用。
1.3. logback的設置可以在配置文件里指定, 用XML格式。Joran使用模式匹配規則處理XML文件, 對匹配了指定模式的內容應用規則(rule)。Rule類通常很小很專業。在Joran里, rule由一個pattern 和一個action組成。當模式產生匹配時, 就調用action。Patten與action的關系是Joran的核心。毫不夸張地說, 用簡單的模式就能處理相當復雜的需求, 精確匹配和通配符匹配可以進行精確地處理。
2. SAX還是DOM?
2.1. 由于SAX API的基于事件的架構, 基于SAX的工具不能輕易地處理向前的引用, 即引用在當前元素之后定義的元素。有循環引用的元素也是個問題。更一般地說, DOM API允
許用戶在所有元素里進行搜索和往前跳。
2.2. 這種額外的靈活性最初讓我們選擇了DOM API作為Joran的底層解析API。經過一些試驗, 很快表明, 當解釋規則是用pattern和action表達時, 在解析DOM樹的過程中跳到
遠處的元素沒有意義。Joran得到的元素只需要按照XML文件里連續的、深度優先的順序。
2.3. Joran先是用DOM實現, 然而, 作者換成了SAX, 以便得到只有SAX API才提供的元素位置信息。位置信息讓Joran能在錯誤發生時顯示準確的行號和列號, 這對分析問題非常
有用。
2.4. 由于其高度動態的本性, Joran API沒有打算用于分析包含數以千計的元素的巨大的XML文檔。
3. 模式(Pattern)
3.1. Joran的模式本質上是字符串。有兩種模式: 精確模式和通配符模式。
3.2. 模式"a/b"可以匹配嵌套在頂層元素<a>里的<b>元素, 不會匹配其他模式。這就是精確模式。
3.3. 通配符模式用來匹配前綴和后綴。例如, "*/a"模式匹配任何后綴是"a"的元素, 也就是XML文檔里的任意<a>元素, 而不是嵌套在<a>里的元素。"a/*"模式匹配任何前綴是
<a>的元素, 也就是嵌套在<a>里的任何元素。
4. 動作(Action)
4.1. 動作繼承Action類, 包含下面兩個抽象方法, 略去了其他方法。
?
4.2. 因此, 每個動作必須實現begin()方法和end()方法。是否實現body()方法是可選的, 因為Action不提供任何操作。
5. RuleStore
5.1. 之前提到過, 按照匹配的模式而調用動作是Joran的一個核心概念。一個規則(rule)就是一個與模式的關聯關系加一個動作。規則存放在RuleStore。
5.2. 上面說過, Joran構建于SAX API之上。當解析XML文檔時, 每個元素的開始、體(body)和結束都產生對應的事件。當Joran配置器接收到這些事件時, 就會在rule store里查找與當前模式所對應的動作。例如, 對嵌套在頂級元素"A"里的B元素, 它的開始事件、體事件和結束事件的當前模式是"A/B"。當前模式是一種數據結構, 由Joran在接收和處理SAX事件時自動維護。
5.3. 當一些規則匹配了當前模式時, 精確匹配會覆蓋后綴匹配, 后綴匹配覆蓋前綴匹配。詳情請參考SimpleRuleStore類(ch.qos.logback.core.joran.spi.SimpleRuleStore)。
6. 解釋上下文(Interpretation context)
6.1. 為了讓多個動作合作, begin()和end()方法的第一個參數是解釋上下文。解釋上下文包括一個對象堆棧、對象map、屬性列表和一個對調用了動作的Joran解析器的引用。
7. HelloWorld例子
7.1. HelloWorld例子演示了使用Joran所需要的最基本的代碼。每當HelloWorldAction的begin()方法被調用時就向控制臺打印"Hello World"。
7.2. XML文件的解析工作由一個配置器完成。我們開發了一個非常簡單的配置器"SimpleConfigurator"。
7.3. HelloWorld程序把各部分組合到了一起:
創建包含規則的ruleMap和一個context;
把模式"helloWorld"與對應的HelloWorldAction實例相關聯, 從而創建一個解析規則;
創建一個 SimpleConfigutator, 參數是前面的ruleMap;
調用配置器的doConfigure方法, 把指定的XML文件作為參數;
最后一步, 打印context里的狀態消息;
7.4. helloWorld.xml文件包含一個<hello-world>元素, 沒有嵌套任何其他元素。
7.5. 新建一個名為JoranHelloWorld的Java工廠, 同時拷入相關jar包
7.6. 在工程李創建一個helloWorld.xml
7.7. 編寫SimpleConfigurator.java
package chapters.onJoran;import java.util.List; import java.util.Map; import ch.qos.logback.core.joran.GenericConfigurator; import ch.qos.logback.core.joran.action.Action; import ch.qos.logback.core.joran.action.ImplicitAction; import ch.qos.logback.core.joran.spi.ElementSelector; import ch.qos.logback.core.joran.spi.Interpreter; import ch.qos.logback.core.joran.spi.RuleStore;public class SimpleConfigurator extends GenericConfigurator {final Map<ElementSelector, Action> ruleMap;final List<ImplicitAction> iaList;public SimpleConfigurator(Map<ElementSelector, Action> ruleMap) {this(ruleMap, null);}public SimpleConfigurator(Map<ElementSelector, Action> ruleMap, List<ImplicitAction> iaList) {this.ruleMap = ruleMap;this.iaList = iaList;}@Overrideprotected void addInstanceRules(RuleStore rs) {for (ElementSelector elementSelector : ruleMap.keySet()) {Action action = ruleMap.get(elementSelector);rs.addRule(elementSelector, action);}}@Overrideprotected void addImplicitRules(Interpreter interpreter) {if (iaList == null) {return;}for (ImplicitAction ia : iaList) {interpreter.addImplicitAction(ia);}} }7.8. 編寫HelloWorldAction.java
package chapters.onJoran.helloWorld;import org.xml.sax.Attributes; import ch.qos.logback.core.joran.action.Action; import ch.qos.logback.core.joran.spi.InterpretationContext;public class HelloWorldAction extends Action {public void begin(InterpretationContext ec, String name, Attributes attributes) {System.out.println("Hello World");}public void end(InterpretationContext ec, String name) {} }7.9. 編寫HelloWorld.java
package chapters.onJoran.helloWorld;import java.util.HashMap; import java.util.Map; import ch.qos.logback.core.Context; import ch.qos.logback.core.ContextBase; import ch.qos.logback.core.joran.action.Action; import ch.qos.logback.core.joran.spi.ElementSelector; import ch.qos.logback.core.util.StatusPrinter; import chapters.onJoran.SimpleConfigurator;public class HelloWorld {public static void main(String[] args) throws Exception {Map<ElementSelector, Action> ruleMap = new HashMap<ElementSelector, Action>();// 把"helloWorld"模式和HelloWorldAction關聯ruleMap.put(new ElementSelector("helloWorld"), new HelloWorldAction());SimpleConfigurator simpleConfigurator = new SimpleConfigurator(ruleMap);// Joran內部需要一個上下文Context context = new ContextBase();// 設置上下文simpleConfigurator.setContext(context);simpleConfigurator.doConfigure("helloWorld.xml");StatusPrinter.print(context);} }7.10. 運行項目
8. 合作動作(計算器例子)
8.1. 新建一個名為JoranCalculator的Java項目, 同時添加相關jar包
8.2. 文件calculator1.xml包擴一個computation元素, 其中嵌套了一個literal元素, 內容如下:
8.3. Calculator1.java
package chapters.onJoran.calculator1;import java.util.HashMap; import java.util.Map; import ch.qos.logback.core.Context; import ch.qos.logback.core.ContextBase; import ch.qos.logback.core.joran.action.Action; import ch.qos.logback.core.joran.spi.ElementSelector; import ch.qos.logback.core.util.StatusPrinter; import chapters.onJoran.SimpleConfigurator; import chapters.onJoran.calculator.action.ComputationAction1; import chapters.onJoran.calculator.action.LiteralAction;public class Calculator1 {public static void main(String[] args) throws Exception {Map<ElementSelector, Action> ruleMap = new HashMap<ElementSelector, Action>();ruleMap.put(new ElementSelector("computation"), new ComputationAction1());// 通配符模式, computation里的任何元素, 實際上我們的computation里面只有一個literal元素ruleMap.put(new ElementSelector("computation/*"), new LiteralAction());SimpleConfigurator simpleConfigurator = new SimpleConfigurator(ruleMap);Context context = new ContextBase();simpleConfigurator.setContext(context);simpleConfigurator.doConfigure("calculator1.xml");StatusPrinter.print(context);} }8.4. Calculator1.java程序里, 模式"computation"關聯于"ComputationAction1"實例, 所以調用ComputationAction1實例的begin()和end()方法。
package chapters.onJoran.calculator.action;import org.xml.sax.Attributes; import ch.qos.logback.core.joran.action.Action; import ch.qos.logback.core.joran.spi.InterpretationContext; import ch.qos.logback.core.util.OptionHelper;public class ComputationAction1 extends Action {public static final String NAME_ATR = "name";String nameStr;public void begin(InterpretationContext ec, String name, Attributes attributes) {nameStr = attributes.getValue(NAME_ATR);}public void end(InterpretationContext ec, String name) {if (OptionHelper.isEmpty(nameStr)) {} else {Integer i = (Integer) ec.peekObject();System.out.println("The computation named [" + nameStr + "] resulted in the value " + i);}} }8.5. Calculator1.java程序里, 模式"computation/*"關聯于"LiteralAction"實例, 所以調用LiteralAction實例的begin()方法, 用于把computation/literal的value屬性值添加至棧頂。
package chapters.onJoran.calculator.action;import org.xml.sax.Attributes; import ch.qos.logback.core.joran.action.Action; import ch.qos.logback.core.joran.spi.InterpretationContext; import ch.qos.logback.core.util.OptionHelper;public class LiteralAction extends Action {public static final String VALUE_ATR = "value";public void begin(InterpretationContext ic, String name, Attributes attributes) {String valueStr = attributes.getValue(VALUE_ATR);if (OptionHelper.isEmpty(valueStr)) {ic.addError("The literal action requires a value attribute");return;}try {// 往棧頂添加computation/literal的value屬性值Integer i = Integer.valueOf(valueStr);ic.pushObject(i);} catch (NumberFormatException nfe) {ic.addError("The value [" + valueStr + "] could not be converted to an Integer", nfe);throw nfe;}}public void end(InterpretationContext ic, String name) {} }8.6. SimpleConfigurator.java
package chapters.onJoran;import java.util.List; import java.util.Map; import ch.qos.logback.core.joran.GenericConfigurator; import ch.qos.logback.core.joran.action.Action; import ch.qos.logback.core.joran.action.ImplicitAction; import ch.qos.logback.core.joran.spi.ElementSelector; import ch.qos.logback.core.joran.spi.Interpreter; import ch.qos.logback.core.joran.spi.RuleStore;public class SimpleConfigurator extends GenericConfigurator {final Map<ElementSelector, Action> ruleMap;final List<ImplicitAction> iaList;public SimpleConfigurator(Map<ElementSelector, Action> ruleMap) {this(ruleMap, null);}public SimpleConfigurator(Map<ElementSelector, Action> ruleMap, List<ImplicitAction> iaList) {this.ruleMap = ruleMap;this.iaList = iaList;}@Overrideprotected void addInstanceRules(RuleStore rs) {for (ElementSelector elementSelector : ruleMap.keySet()) {Action action = ruleMap.get(elementSelector);rs.addRule(elementSelector, action);}}@Overrideprotected void addImplicitRules(Interpreter interpreter) {if (iaList == null) {return;}for (ImplicitAction ia : iaList) {interpreter.addImplicitAction(ia);}} }8.7. 運行輸出computation/literal的value屬性值
8.8. 文件calculator2.xml除了computation和literal元素, add元素對literal的value屬性值作加法, multiply元素對literal的value屬性值作乘法:
8.9. Calculator2.java
package chapters.onJoran.calculator2;import java.util.HashMap; import java.util.Map; import ch.qos.logback.core.Context; import ch.qos.logback.core.ContextBase; import ch.qos.logback.core.joran.action.Action; import ch.qos.logback.core.joran.spi.ElementSelector; import ch.qos.logback.core.util.StatusPrinter; import chapters.onJoran.SimpleConfigurator; import chapters.onJoran.calculator.action.AddAction; import chapters.onJoran.calculator.action.ComputationAction2; import chapters.onJoran.calculator.action.LiteralAction; import chapters.onJoran.calculator.action.MultiplyAction;public class Calculator2 {public static void main(String[] args) throws Exception {Map<ElementSelector, Action> ruleMap = new HashMap<ElementSelector, Action>();ruleMap.put(new ElementSelector("*/computation"), new ComputationAction2());ruleMap.put(new ElementSelector("*/computation/literal"), new LiteralAction());ruleMap.put(new ElementSelector("*/computation/add"), new AddAction());ruleMap.put(new ElementSelector("*/computation/multiply"), new MultiplyAction());SimpleConfigurator simpleConfigurator = new SimpleConfigurator(ruleMap);Context context = new ContextBase();simpleConfigurator.setContext(context);simpleConfigurator.doConfigure("calculator2.xml");StatusPrinter.print(context);} }8.10. Calculator2.java程序里, 模式"*/computation/add"關聯于"AddAction"實例, 所以調用AddAction實例的begin()方法, 用于把棧頂的2個數值作加法后把結果重新放入棧頂。
package chapters.onJoran.calculator.action;import java.util.EmptyStackException; import org.xml.sax.Attributes; import ch.qos.logback.core.joran.action.Action; import ch.qos.logback.core.joran.spi.InterpretationContext;public class AddAction extends Action {public void begin(InterpretationContext ic, String name, Attributes attributes) {int first = fetchInteger(ic);int second = fetchInteger(ic);ic.pushObject(first + second);}int fetchInteger(InterpretationContext ic) {int result = 0;try {Object o1 = ic.popObject();if (o1 instanceof Integer) {result = ((Integer) o1).intValue();} else {String errMsg = "Object [" + o1 + "] currently at the top of the stack is not an integer.";ic.addError(errMsg);throw new IllegalArgumentException(errMsg);}} catch (EmptyStackException ese) {ic.addError(("Expecting an integer on the execution stack."));throw ese;}return result;}public void end(InterpretationContext ic, String name) {} }8.11. Calculator2.java程序里, 模式"*/computation/multiply"關聯于"MultiplyAction"實例, 所以調用MultiplyAction實例的begin()方法, 用于把棧頂的2個數值作乘法后把結果重新放入棧頂。
package chapters.onJoran.calculator.action;import org.xml.sax.Attributes; import ch.qos.logback.core.joran.action.Action; import ch.qos.logback.core.joran.spi.InterpretationContext; import java.util.EmptyStackException;public class MultiplyAction extends Action {public void begin(InterpretationContext ic, String name, Attributes attributes) {int first = fetchInteger(ic);int second = fetchInteger(ic);ic.pushObject(first * second);}int fetchInteger(InterpretationContext ic) {int result = 0;try {Object o1 = ic.popObject();if (o1 instanceof Integer) {result = ((Integer) o1).intValue();} else {String errMsg = "Object [" + o1 + "] currently at the top of the stack is not an integer.";ic.addError(errMsg);throw new IllegalArgumentException(errMsg);}} catch (EmptyStackException ese) {ic.addError("Expecting an integer on the execution stack.");throw ese;}return result;}public void end(InterpretationContext ic, String name) {} }8.12. ComputationAction2.java的作用是輸出計算結果
package chapters.onJoran.calculator.action;import java.util.Stack; import org.xml.sax.Attributes; import ch.qos.logback.core.joran.action.Action; import ch.qos.logback.core.joran.spi.InterpretationContext; import ch.qos.logback.core.util.OptionHelper;public class ComputationAction2 extends Action {public static final String NAME_ATR = "name";Stack<String> nameStrStack = new Stack<String>();public void begin(InterpretationContext ec, String name, Attributes attributes) {String nameStr = attributes.getValue(NAME_ATR);nameStrStack.push(nameStr);}public void end(InterpretationContext ec, String name) {String nameStr = (String) nameStrStack.pop();if (OptionHelper.isEmpty(nameStr)) {} else {Integer i = (Integer) ec.peekObject();System.out.println("The computation named [" + nameStr + "] resulted in the value " + i);}} }8.13. 運行輸出計算結果
9. 隱式動作(Implicit actions)
9.1. 到目前為止, 我們定義的規則都稱為顯式動作, 因為對當前元素的模式/動作關聯都能在rule store找到。然而, 在高度可擴展的系統里, 組件的數量和類型都非常多, 因此為每個模式都關聯顯式動作會非常冗長乏味。
9.2. 同時, 即使是在高度可擴展的系統里, 仍然可以觀察到重復出現的規則。假設我們可以識別這些重復規則, 我們就可以處理在logback編譯期內還是未知的包含子組件的組件。
9.3. Joran維持一個隱式動作列表, 如果沒有顯式模式能匹配當前模式時, 就應用隱式動作。然而, 應用隱式動作并不總是適當的。在執行隱式動作前, Joran詢問給定的隱式動作是否適用于當前情況。只有當動作回答"是"時, Joran配置器才會調用隱式動作。注意, 這個額外的步驟讓對給定的情況使用多個或零個動作可能。
9.4. 新建一個名為JoranImplicit的Java項目, 同時添加相關jar包
9.5. 文檔implicit1.xml用于演示隱私動作是如何工作的。
9.6. PrintMe程序為模式"*/foo"關聯了一個NOPAction動作實例, 即所有名為"foo"的元素。如其名字所示, NOPAction的begin和end方法都為空。
package chapters.onJoran.implicit;import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import ch.qos.logback.core.Context; import ch.qos.logback.core.ContextBase; import ch.qos.logback.core.joran.action.Action; import ch.qos.logback.core.joran.action.ImplicitAction; import ch.qos.logback.core.joran.spi.ElementSelector; import ch.qos.logback.core.util.StatusPrinter; import chapters.onJoran.SimpleConfigurator;public class PrintMe {public static void main(String[] args) throws Exception {Map<ElementSelector, Action> ruleMap = new HashMap<ElementSelector, Action>();ruleMap.put(new ElementSelector("*/foo"), new NOPAction());List<ImplicitAction> iaList = new ArrayList<ImplicitAction>();iaList.add(new PrintMeImplicitAction());SimpleConfigurator simpleConfigurator = new SimpleConfigurator(ruleMap, iaList);Context context = new ContextBase();simpleConfigurator.setContext(context);simpleConfigurator.doConfigure("implicit1.xml");StatusPrinter.printInCaseOfErrorsOrWarnings(context);} }9.7. SimpleConfigurator.java
package chapters.onJoran;import java.util.List; import java.util.Map; import ch.qos.logback.core.joran.GenericConfigurator; import ch.qos.logback.core.joran.action.Action; import ch.qos.logback.core.joran.action.ImplicitAction; import ch.qos.logback.core.joran.spi.ElementSelector; import ch.qos.logback.core.joran.spi.Interpreter; import ch.qos.logback.core.joran.spi.RuleStore;public class SimpleConfigurator extends GenericConfigurator {final Map<ElementSelector, Action> ruleMap;final List<ImplicitAction> iaList;public SimpleConfigurator(Map<ElementSelector, Action> ruleMap) {this(ruleMap, null);}public SimpleConfigurator(Map<ElementSelector, Action> ruleMap, List<ImplicitAction> iaList) {this.ruleMap = ruleMap;this.iaList = iaList;}@Overrideprotected void addInstanceRules(RuleStore rs) {for (ElementSelector elementSelector : ruleMap.keySet()) {Action action = ruleMap.get(elementSelector);rs.addRule(elementSelector, action);}}@Overrideprotected void addImplicitRules(Interpreter interpreter) {if (iaList == null) {return;}for (ImplicitAction ia : iaList) {interpreter.addImplicitAction(ia);}} }9.8. PrintMe程序還在其隱式動作列表里注冊了一個PrintMeImplicitAction動作。PrintMeImplicitAction動作適用于所有屬性"printme"為"true"的元素。PrintMeImplicitAction類繼承了ImplicitAction, isApplicable方法返回true, 才會調用動作的begin()和end()方法。
package chapters.onJoran.implicit;import org.xml.sax.Attributes; import ch.qos.logback.core.joran.action.ImplicitAction; import ch.qos.logback.core.joran.spi.ElementPath; import ch.qos.logback.core.joran.spi.InterpretationContext;public class PrintMeImplicitAction extends ImplicitAction {public boolean isApplicable(ElementPath elementPath, Attributes attributes, InterpretationContext ec) {String printmeStr = attributes.getValue("printme");return Boolean.valueOf(printmeStr).booleanValue();}public void begin(InterpretationContext ec, String name, Attributes attributes) {System.out.println("Element [" + name + "] asked to be printed.");}public void end(InterpretationContext ec, String name) {} }9.9. NOPAction.java
package chapters.onJoran.implicit;import org.xml.sax.Attributes; import ch.qos.logback.core.joran.action.Action; import ch.qos.logback.core.joran.spi.InterpretationContext;/*** 不做任何操作*/ public class NOPAction extends Action {public void begin(InterpretationContext ec, String name, Attributes attributes) {}public void end(InterpretationContext ec, String name) {} }9.10. 對任何<foo>元素都不會調用PrintMeImplicitAction。對于其他元素, 由于沒有匹配任何顯式動作, 所以調用PrintMeImplicitAction的isApplicable()方法。只有對 于那些包含屬性"printme"且其屬性值為"true"的元素, isApplicable()方法才返回"true"。<xyz />元素沒有可用的動作, 所以會生成一個內部錯誤消息。這個錯誤消息會被PrintMe程序的最后一條語句"StatusPrinter.print"打印出來。這就解釋了上面的輸出結果。
10. 動態新規則
10.1. Joran的NewRuleAction動作支持在解析XML文檔的過程中讓Joran配置器學習新規則。
10.2. 新建一個名為JoranNewRule的Java項目, 添加相關jar包
10.3. NewRuleCalculator程序使用了兩條規則, 一條規則處理最頂端的元素, 第二條規則學習新規則。
package chapters.onJoran.newRule;import java.util.HashMap; import java.util.Map; import ch.qos.logback.core.Context; import ch.qos.logback.core.ContextBase; import ch.qos.logback.core.joran.action.Action; import ch.qos.logback.core.joran.action.NewRuleAction; import ch.qos.logback.core.joran.spi.ElementSelector; import ch.qos.logback.core.util.StatusPrinter; import chapters.onJoran.SimpleConfigurator; import chapters.onJoran.calculator.ComputationAction2;public class NewRuleCalculator {public static void main(String[] args) throws Exception {Map<ElementSelector, Action> ruleMap = new HashMap<ElementSelector, Action>();ruleMap.put(new ElementSelector("*/computation"), new ComputationAction2());ruleMap.put(new ElementSelector("/computation/newRule"), new NewRuleAction());SimpleConfigurator simpleConfigurator = new SimpleConfigurator(ruleMap);Context context = new ContextBase();simpleConfigurator.setContext(context);simpleConfigurator.doConfigure("newRule.xml");StatusPrinter.printInCaseOfErrorsOrWarnings(context);} }10.4. NewRuleAction是logback-core的一部分, 工作原理與其他動作基本相同, 有begin()和end()方法, 每當解析器找到"new-rule"元素時被調用。當被調用時, begin()方法查找"pattern"和"actionClass"屬性, 然后實例化對應的動作類, 把模式/動作的關聯關系作為一條新規則加入到Joran的rule store。下面是如何在xml文件里聲明新規則:
10.5. SimpleConfigurator.java、AddAction.java、ComputationAction2.java、LiteralAction.java和MultiplyAction.java根之前的例子一樣。
10.6. 運行輸出
《新程序員》:云原生和全面數字化實踐50位技術專家共同創作,文字、視頻、音頻交互閱讀總結
以上是生活随笔為你收集整理的009-Joran配置框架的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 017_layout排版
- 下一篇: 010-映射诊断环境