zoukankan      html  css  js  c++  java
  • tomcat源码学习(2)  关于apache digest

      好久不写博文,罪过罪过。因为最近公司比较忙加上琐事有点多,所以隔了好久才来更新博文。

      apache digest本来是struts2框架中来加载xml文件并实例化对象的一个jar包,后来使用的越来越多。
    我们都知道tomcat的conf文件夹下有一个server.xml配置文件,我们经常会其中的来进行配置以来运行一个java web项目,也经常修改中的port属性以来实现修改tomcat监听的端口。其实每个标签基本上都对应着一个对象,那tomcat是如何将这些对象实例化到java 虚拟机的运行内存中的呢,这就是apache digest类做的事情。
     
      上篇讲到catalina的load的方法,在load方法中调用了两个函数  
    1 initDirs();
    2 // Before digester - it may be needed
    3 initNaming();
     
      上述两个函数之后调用createStartDigester()。
      
    protected Digester createStartDigester() {
            long t1=System.currentTimeMillis();
            // Initialize the digester
            Digester digester = new Digester();
            digester.setValidating(false);
            digester.setRulesValidation(true);
            HashMap, List> fakeAttributes = new HashMap<>();
            ArrayList attrs = new ArrayList<>();
            attrs.add("className");
            fakeAttributes.put(Object.class, attrs);
            digester.setFakeAttributes(fakeAttributes);
            digester.setUseContextClassLoader(true);
     
            // Configure the actions we will be using
            digester.addObjectCreate("Server",
                                     "org.apache.catalina.core.StandardServer",
                                     "className");
            digester.addSetProperties("Server");
            digester.addSetNext("Server",
                                "setServer",
                                "org.apache.catalina.Server");
     
            digester.addObjectCreate("Server/GlobalNamingResources",
                                     "org.apache.catalina.deploy.NamingResourcesImpl");
            digester.addSetProperties("Server/GlobalNamingResources");
            digester.addSetNext("Server/GlobalNamingResources",
                                "setGlobalNamingResources",
                                "org.apache.catalina.deploy.NamingResourcesImpl");
     
            digester.addObjectCreate("Server/Listener",
                                     null, // MUST be specified in the element
                                     "className");
            digester.addSetProperties("Server/Listener");
            digester.addSetNext("Server/Listener",
                                "addLifecycleListener",
                                "org.apache.catalina.LifecycleListener");
     
            digester.addObjectCreate("Server/Service",
                                     "org.apache.catalina.core.StandardService",
                                     "className");
            digester.addSetProperties("Server/Service");
            digester.addSetNext("Server/Service",
                                "addService",
                                "org.apache.catalina.Service");
     
            digester.addObjectCreate("Server/Service/Listener",
                                     null, // MUST be specified in the element
                                     "className");
            digester.addSetProperties("Server/Service/Listener");
            digester.addSetNext("Server/Service/Listener",
                                "addLifecycleListener",
                                "org.apache.catalina.LifecycleListener");
     
            //Executor
            digester.addObjectCreate("Server/Service/Executor",
                             "org.apache.catalina.core.StandardThreadExecutor",
                             "className");
            digester.addSetProperties("Server/Service/Executor");
     
            digester.addSetNext("Server/Service/Executor",
                                "addExecutor",
                                "org.apache.catalina.Executor");
     
     
            digester.addRule("Server/Service/Connector",
                             new ConnectorCreateRule());
            digester.addRule("Server/Service/Connector",
                             new SetAllPropertiesRule(new String[]{"executor"}));
            digester.addSetNext("Server/Service/Connector",
                                "addConnector",
                                "org.apache.catalina.connector.Connector");
     
     
            digester.addObjectCreate("Server/Service/Connector/Listener",
                                     null, // MUST be specified in the element
                                     "className");
            digester.addSetProperties("Server/Service/Connector/Listener");
            digester.addSetNext("Server/Service/Connector/Listener",
                                "addLifecycleListener",
                                "org.apache.catalina.LifecycleListener");
     
            // Add RuleSets for nested elements
            digester.addRuleSet(new NamingRuleSet("Server/GlobalNamingResources/"));
            digester.addRuleSet(new EngineRuleSet("Server/Service/"));
            digester.addRuleSet(new HostRuleSet("Server/Service/Engine/"));
            digester.addRuleSet(new ContextRuleSet("Server/Service/Engine/Host/"));
            addClusterRuleSet(digester, "Server/Service/Engine/Host/Cluster/");
            digester.addRuleSet(new NamingRuleSet("Server/Service/Engine/Host/Context/"));
     
            // When the 'engine' is found, set the parentClassLoader.
            digester.addRule("Server/Service/Engine",
                             new SetParentClassLoaderRule(parentClassLoader));
            addClusterRuleSet(digester, "Server/Service/Engine/Cluster/");
     
            long t2=System.currentTimeMillis();
            if (log.isDebugEnabled()) {
                log.debug("Digester for server.xml created " + ( t2-t1 ));
            }
            return (digester);
    }
    我们可以看到创建了一个digester对象之后,暂时不看对digester对象的各种属性的设置set方法,我们可以看到一大堆的
    addObjectCreate("Server","org.apache.catalina.core.StandardServer","className");
    digester.addSetProperties("Server");
    digester.addSetNext("Server","setServer", "org.apache.catalina.Server");

    这里就是digest对象中的所谓的各种规则了,这里看到的server就是我们在tomcat源码学习后期看到的server对象啦,这个以后再说。

     
    load()方法中,在创建了digester对象后,接下来调用了digester.parse(inputSource);方法即开始解析xml文件并根据上述规则开始实例化各种对象了。这里的xml文件即为conf文件下的server.xml文件拉。
     
    回过头来再看digester对象。以下为digester类的成员变量。
    protected StringBuilder bodyText = new StringBuilder();
       
    protected ArrayStack bodyTexts = new ArrayStack<>();
     
    protected ArrayStack> matches = new ArrayStack<>(10);
       
    protected ClassLoader classLoader = null;
       
    protected boolean configured = false;
       
    protected EntityResolver entityResolver;
       
    protected HashMap entityValidator = new HashMap<>();
       
    protected ErrorHandler errorHandler = null;
       
    protected SAXParserFactory factory = null;
    
    protected Locator locator = null;
       
    protected String match = "";
     
    protected boolean namespaceAware = false;
     
    protected HashMap> namespaces = new HashMap<>();
     
    protected ArrayStackparams = new ArrayStack<>();
     
    protected SAXParser parser = null;
     
    protected String publicId = null;
     
    protected XMLReader reader = null;
     
    protected Object root = null;
       
    protected Rules rules = null;
       
    protected ArrayStackstack = new ArrayStack<>();
     
    protected boolean useContextClassLoader = false;
     
    protected boolean validating = false;
     
    protected boolean rulesValidation = false;
     
    protected Map, List> fakeAttributes = null;

    在讨论digester类是如何实现在解析xml时来实例化相应的类前,我们先看一篇博文,关于java如何解析xml的文件的    http://www.iteye.com/topic/763895

     
    原生java包中已经提供了sax来解析源码的api,在解析的时候触发不同的事件,对事件函数进行处理,我们只要继承org.xml.sax.helpers.DefaultHandler类来实现我们的业务需求即可。
    最主要的是重写startDocument(),startElement(String uri, String localName, String qName, Attributes attributes),endElement(String uri, String localName, String qName)  ,characters(char[] ch, int start, int length) 函数。
     
    我们来看Digetster类的成员变量,果然含有SAXParser类和XMLReader的成员对象,这里tomcat调用的是XMLReader的parse方法。
     
    我们从
    public void addObjectCreate(String pattern, String className,String attributeName) {
        addRule(pattern,new ObjectCreateRule(className, attributeName));
    }
    ObjectCreateRule 顾名思义,这是一个创建对象的规则了,继承Rule类,Rule是一个抽象类,成员变量为protected类型,可以被子类获取到,分别为
      protected Digester digester = null;
      protected String namespaceURI = null; 。
    注释写的很明白。
     
    还可以看到除了set,get方法之外的begin() body() end() finish()方法。这些方法将会被子类重写以实现业务需求。后面我们将看到这些方法将会被上面谈到的startElement()等方法使用到。
     
    接着看Digester类的方法
    public void addRule(String pattern, Rule rule) {
        rule.setDigester(this);
        getRules().add(pattern, rule);
    }

    将rule绑定到digester上,getRules()返回的是Digester类的成员变量rules,为Rules类型的成员变量,Rules是一个接口,实现有RulesBase类,成员变量如下

    protected HashMap> cache = new HashMap<>();
    
    protected Digester digester = null;
    
    protected String namespaceURI = null;
    
    protected ArrayList rules = new ArrayList<>();

    rules的add方法

     @Override
        public void add(String pattern, Rule rule) {
            // to help users who accidently add '/' to the end of their patterns
            int patternLength = pattern.length();
            if (patternLength>1 && pattern.endsWith("/")) {
                pattern = pattern.substring(0, patternLength-1);
            }
     
            List list = cache.get(pattern);
            if (list == null) {
                list = new ArrayList<>();
                cache.put(pattern, list);
            }
            list.add(rule);
            rules.add(rule);
            if (this.digester != null) {
                rule.setDigester(this.digester);
            }
            if (this.namespaceURI != null) {
                rule.setNamespaceURI(this.namespaceURI);
            }
    }

    即将需要匹配的pattern和rule放置到cache的hashmap中,并绑定digester和namespaceURI

    后面还有两种
    digester.addSetProperties("Server");
    digester.addSetNext("Server","setServer","org.apache.catalina.Server");

    分别是两种不同的规则,就不再一一赘述了。

     
    后面继续还有addRuleSet,我们会发现该函数设置了一下namespace,还有增加了一堆以上的规则,比如说NamingRuleSet就增加了一堆EJB相关的规则以对EJB进行支持等等,EngineRuleSet,HostRuleSet,ContextRuleSet 应该分别对应tomcat的几大组件engine,host,context,当然还有wrapper,也就是servlet相关的没有出现。
     
    其中有一个addClusterRuleSet 函数是调用了反射,通过Class.forName来动态加载一个RuleSet,不知道为什么,这里先记下。
     
    上面说到是XML的parse方法。在catalina类的load方法中调用了digester的parse方法
    public Object parse(InputSource input) throws IOException,SAXException {
            configure();
            getXMLReader().parse(input);
            return (root);
    }
    
    protected void configure() {
    
            // Do not configure more than once
            if (configured) {
                return;
            }
    
            log = LogFactory.getLog("org.apache.tomcat.util.digester.Digester");
            saxLog = LogFactory.getLog("org.apache.tomcat.util.digester.Digester.sax");
    
            // Set the configuration flag to avoid repeating
            configured = true;
    }
    
    public XMLReader getXMLReader() throws SAXException {
            if (reader == null){
                reader = getParser().getXMLReader();
            }
    
            reader.setDTDHandler(this);
            reader.setContentHandler(this);
    
            if (entityResolver == null){
                reader.setEntityResolver(this);
            } else {
                reader.setEntityResolver(entityResolver);
            }
    
            reader.setProperty(
                    "http://xml.org/sax/properties/lexical-handler", this);
    
            reader.setErrorHandler(this);
            return reader;
    }
    
    public SAXParser getParser() {
    
            // Return the parser we already created (if any)
            if (parser != null) {
                return (parser);
            }
    
            // Create a new parser
            try {
                parser = getFactory().newSAXParser();
            } catch (Exception e) {
                log.error("Digester.getParser: ", e);
                return (null);
            }
    
            return (parser);
    
    }

    可以看到 getXMLReader 方法中,设置XMLReader的解析处理handler均为digester类。主要看

    reader.setContentHandler(this);

    即在解析xml文件中将调用digester类中的startElement,endElement,endElement方法等。  

    我们先来看startElement方法

     @Override
        public void startElement(String namespaceURI, String localName,
                                 String qName, Attributes list)
                throws SAXException {
            boolean debug = log.isDebugEnabled();
    
            if (saxLog.isDebugEnabled()) {
                saxLog.debug("startElement(" + namespaceURI + "," + localName + "," +
                        qName + ")");
            }
    
            // Parse system properties
            list = updateAttributes(list);
    
            // Save the body text accumulated for our surrounding element
            bodyTexts.push(bodyText);
            if (debug) {
                log.debug("  Pushing body text '" + bodyText.toString() + "'");
            }
            bodyText = new StringBuilder();
    
            // the actual element name is either in localName or qName, depending
            // on whether the parser is namespace aware
            String name = localName;
            if ((name == null) || (name.length() < 1)) {
                name = qName;
            }
    
            // Compute the current matching rule
            StringBuilder sb = new StringBuilder(match);
            if (match.length() > 0) {
                sb.append('/');
            }
            sb.append(name);
            match = sb.toString();
            if (debug) {
                log.debug("  New match='" + match + "'");
            }
    
            // Fire "begin" events for all relevant rules
            List<Rule> rules = getRules().match(namespaceURI, match);
            matches.push(rules);
            if ((rules != null) && (rules.size() > 0)) {
                for (int i = 0; i < rules.size(); i++) {
                    try {
                        Rule rule = rules.get(i);
                        if (debug) {
                            log.debug("  Fire begin() for " + rule);
                        }
                        rule.begin(namespaceURI, name, list);
                    } catch (Exception e) {
                        log.error("Begin event threw exception", e);
                        throw createSAXException(e);
                    } catch (Error e) {
                        log.error("Begin event threw error", e);
                        throw e;
                    }
                }
            } else {
                if (debug) {
                    log.debug("  No rules found matching '" + match + "'.");
                }
            }
    
        }

     我们主要看这个函数

    List<Rule> rules = getRules().match(namespaceURI, match);
      根据namespaceURI 和 match来匹配rule,match即xml中的标签,我们打个断点可以发现,第一个匹配server的其实就是上述按顺序的
    digester.addObjectCreate("Server","org.apache.catalina.core.StandardServer","className");
    digester.addSetProperties("Server");
    digester.addSetNext("Server","setServer","org.apache.catalina.Server");

    然后分别调用以上rule的begin方法

    ObjectCreateRule的begin方法即利用反射创建了相关对象,并将该对象push到digest的成员变量protected ArrayStack<Object> stack = new ArrayStack<>();中

    Class<?> clazz = digester.getClassLoader().loadClass(realClassName);
    Object instance = clazz.newInstance();
    digester.push(instance);

    SetPropertiesRule的begin方法即会对处于digest的stack的栈顶元素的属性进行设置,隐藏的很隐蔽

    if (!digester.isFakeAttribute(top, name)&& !IntrospectionUtils.setProperty(top, name, value)&& digester.getRulesValidation()) {
        digester.log.warn("[SetPropertiesRule]{" + digester.match +
                            "} Setting property '" + name + "' to '" +
                            value + "' did not find a matching property.");
    }

    在 !IntrospectionUtils.setProperty(top, name, value)中进行了设置。

    SetNextRule中未对begin方法进行重载,不进行描述

    接下来是characters(char buffer[], int start, int length)  方法

    @Override
        public void characters(char buffer[], int start, int length)
                throws SAXException {
    
            if (saxLog.isDebugEnabled()) {
                saxLog.debug("characters(" + new String(buffer, start, length) + ")");
            }
    
            bodyText.append(buffer, start, length);
    
        }

    仅是对bodyText进行append

    接下来就是end方法了

    无非即对其中的stack进行一系列的pop,并调用了rule的body方法,这里很多继承的rule并未重写body方法,这里不做表述。

    还有一点要说明的是,digest类中的成员变量:protected ArrayStack<Object> stack = new ArrayStack<>();是一个通过ArrayList实现的 stack

    其中startElement和endElement未做描述,感兴趣的可以自己查阅源代码看,至此结束。

    我们可以认为digest使用了decorator设计模式,为了在该类上实现更多的功能,利用一个list,来不断调用不同rule中的方法。

    有不足或错误,敬请指正,thanks。

     
  • 相关阅读:
    Spring与MyBatis整合应用
    Spring与JDBC整合应用
    登录权限检查(SpringMVC)
    SpringMVC中文乱码问题
    SpringMVC异常处理
    SpringMVC注解应用
    Viewpager+fragment数据更新问题解析
    android adb 读写模式 挂载文件系统
    Android网络通信两种方法
    Win7、win2008中让IIS7支持asp的方法
  • 原文地址:https://www.cnblogs.com/vikeria/p/4375081.html
Copyright © 2011-2022 走看看