深入理解Tomcat(六)Digester組件

前言

Tomcat中的xml解析,是使用的apache開源組件digester婴谱。在tomcat源碼中析砸,Digester類所在位置為org.apache.tomcat.util.digester.Digester厨钻,它把開源組件digester的源代碼拷貝了過來咆畏。

本文,我們主要是想了解一下digester的用法攒砖。以便在閱讀tomcat源碼的時候缸兔,看到xml解析相關(guān)代碼的時候不會一臉茫然和懵逼日裙。

digester有兩種使用方式:

  1. 一種為tomat內(nèi)嵌的org.apache.tomcat.util.digester.Digester
  2. 另一種為digester maven依賴灶体。

本文采用第二種--maven依賴的方式阅签。

digester實現(xiàn)原理

digester最初是作為struct的一個工具模塊,來完成xml解析的功能蝎抽。但是很快地政钟,有人發(fā)現(xiàn)并覺得digester不應(yīng)該僅僅局限在struct,而應(yīng)該變得更通用樟结。于是經(jīng)過apache的孵化养交,最終加入到了apache commons類庫家族中,并形成了一個xml另類解析的工具類庫瓢宦。

digester底層是基于SAX+事件驅(qū)動+的方式來搭建實現(xiàn)的碎连。那么在digester中,這三種元素分別起到什么作用呢驮履?

  1. SAX鱼辙,用于解析xml
  2. 事件驅(qū)動,在SAX解析的過程中加入事件來支持我們的對象映射
  3. 棧玫镐,當(dāng)解析xml元素的開始和結(jié)束的時候倒戏,需要通過xml元素映射的類對象的入棧和出棧來完成事件的調(diào)用

通過一些實實在在的場景和例子,我們發(fā)現(xiàn)一個元素的作用無非是在其解析前后加入一些擴(kuò)展邏輯恐似!例如:

  1. 開始解析某個節(jié)點的時候杜跷,是否需要創(chuàng)建一個類
  2. 開始解析某個節(jié)點的時候,是否需要入棧操作
  3. 結(jié)束解析某個節(jié)點的時候矫夷,是否需要執(zhí)行某個方法
  4. 結(jié)束解析某個節(jié)點的時候葛闷,是否需要出棧操作

如何引入依賴包

以maven為例,使用下面的dependency双藕。

<dependency>
    <groupId>commons-digester</groupId>
    <artifactId>commons-digester</artifactId>
    <version>2.1</version>
</dependency>

如何使用

假如我們需要解析的xml為下面的格式淑趾。

<?xml version='1.0' encoding='utf-8'?>
<School name="Jen">
    <Grade name="1">
        <Class name="1" number="31"/>
        <Class name="2" number="32"/>
    </Grade>
    <Grade name="2">
        <Class name="1" number="41"/>
        <Class name="2" number="42"/>
        <Class name="3" number="37"/>
    </Grade>
</School>

同時,我們假設(shè)下面的約定成立:

  1. 一個學(xué)校有名字屬性蔓彩,下面有多個年級
  2. 每個年級有名字屬性治笨,下面有多個班
  3. 每個班有名字和學(xué)生人數(shù)兩個屬性

根據(jù)上面的規(guī)則,我們需要創(chuàng)建關(guān)聯(lián)的3個類赤嚼,SchoolGradeClass顺又。
School有一個方法addGrade用于往學(xué)校對象中添加年級更卒。

package com.juconcurrent.learn.apache.digester;

public class School {
    private String name;
    private Grade grades[] = new Grade[0];
    private final Object servicesLock = new Object();

    public void addGrade(Grade g) {
        synchronized (servicesLock) {
            Grade results[] = new Grade[grades.length + 1];
            System.arraycopy(grades, 0, results, 0, grades.length);
            results[grades.length] = g;
            grades = results;
        }
    }

    public Grade[] getGrades() {
        return grades;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

同樣的,年級有一個addClass方法稚照,用于往Grade對象添加Class班對象蹂空。

package com.juconcurrent.learn.apache.digester;

public class Grade {
    private String name;
    private Class classes[] = new Class[0];
    private final Object servicesLock = new Object();

    public void addClass(Class c) {
        synchronized (servicesLock) {
            Class results[] = new Class[classes.length + 1];
            System.arraycopy(classes, 0, results, 0, classes.length);
            results[classes.length] = c;
            classes = results;
        }
    }

    public Class[] getClasses() {
        return classes;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

Class就比較簡單了俯萌,只是一個簡單的POJO對象。

package com.juconcurrent.learn.apache.digester;

public class Class {
    private String name;
    private int number;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getNumber() {
        return number;
    }

    public void setNumber(int number) {
        this.number = number;
    }
}

好了上枕,我們已經(jīng)定義好了我們所需要創(chuàng)建對象的類咐熙。那么如何使用digester來創(chuàng)建我們所需的數(shù)據(jù)呢?我們先給出例子辨萍,然后再來詳細(xì)分析其中的關(guān)鍵方法棋恼。

package com.juconcurrent.learn.apache.digester;

import org.apache.commons.digester.Digester;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

public class DigesterTest {
    // 屬性和get/set方法,假設(shè)我們解析出來的School對象放在這兒
    private School school;
    public School getSchool() {
        return school;
    }
    public void setSchool(School s) {
        this.school = s;
    }

    private void digester() throws IOException, SAXException {
        // 讀取根據(jù)文件的路徑锈玉,創(chuàng)建InputSource對象爪飘,digester解析的時候需要用到
        File file = new File("/Users/pro/ws/learn/learn-javaagent/src/main/resources/School.xml");
        InputStream inputStream = new FileInputStream(file);
        InputSource inputSource = new InputSource(file.toURI().toURL().toString());
        inputSource.setByteStream(inputStream);

        // 創(chuàng)建Digester對象
        Digester digester = new Digester();
        // 是否需要用DTD驗證XML文檔的合法性
        digester.setValidating(true);
        // 將當(dāng)前對象放到對象堆的最頂層,這也是這個類為什么要有school屬性的原因拉背!
        digester.push(this);

        /*
         * 下面開始為Digester創(chuàng)建匹配規(guī)則
         * Digester中的School师崎、School/Grade、School/Grade/Class椅棺,分別對應(yīng)School.xml的School犁罩、Grade、Class節(jié)點
         */

        // 為School創(chuàng)建規(guī)則

        /*
         * Digester.addObjectCreate(String pattern, String className, String attributeName)
         * pattern, 匹配的節(jié)點
         * className, 該節(jié)點對應(yīng)的默認(rèn)實體類
         * attributeName, 如果該節(jié)點有className屬性, 用className的值替換默認(rèn)實體類
         *
         * Digester匹配到School節(jié)點
         *
         * 1. 如果School節(jié)點沒有className屬性两疚,將創(chuàng)建com.juconcurrent.learn.apache.digester.School對象床估;
         * 2. 如果School節(jié)點有className屬性,將創(chuàng)建指定的(className屬性的值)對象
         */
        digester.addObjectCreate("School", School.class.getName(), "className");
        // 將指定節(jié)點的屬性映射到對象鬼雀,即將School節(jié)點的name的屬性映射到School.java
        digester.addSetProperties("School");

        /*
         * Digester.addSetNext(String pattern, String methodName, String paramType)
         * pattern, 匹配的節(jié)點
         * methodName, 調(diào)用父節(jié)點的方法
         * paramType, 父節(jié)點的方法接收的參數(shù)類型
         * Digester匹配到School節(jié)點顷窒,將調(diào)用DigesterTest(School的父節(jié)點)的setSchool方法,參數(shù)為School對象
         */
        digester.addSetNext("School", "setSchool", School.class.getName());

        // 為School/Grade創(chuàng)建規(guī)則
        digester.addObjectCreate("School/Grade", Grade.class.getName(), "className");
        digester.addSetProperties("School/Grade");

        // Grade的父節(jié)點為School
        digester.addSetNext("School/Grade", "addGrade", Grade.class.getName());

        // 為School/Grade/Class創(chuàng)建規(guī)則
        digester.addObjectCreate("School/Grade/Class", Class.class.getName(), "className");
        digester.addSetProperties("School/Grade/Class");
        digester.addSetNext("School/Grade/Class", "addClass", Class.class.getName());
        // 解析輸入源
        digester.parse(inputSource);
    }

    // 只是將School對象進(jìn)行控制臺輸出
    private void print(School s) {
        if (s != null) {
            System.out.println(s.getName() + "有" + s.getGrades().length + "個年級");
            for (int i = 0; i < s.getGrades().length; i++) {
                if (s.getGrades()[i] != null) {
                    Grade g = s.getGrades()[i];
                    System.out.println(g.getName() + "年級 有 " + g.getClasses().length + "個班:");
                    for (int j = 0; j < g.getClasses().length; j++) {
                        if (g.getClasses()[j] != null) {
                            Class c = g.getClasses()[j];
                            System.out.println(c.getName() + "班有" + c.getNumber() + "人");
                        }
                    }
                }
            }
        }
    }

    // 入口main()方法
    public static void main(String[] args) throws IOException, SAXException {
        DigesterTest digesterTest = new DigesterTest();
        digesterTest.digester();
        digesterTest.print(digesterTest.school);
    }
}

這兒我們需要著重說明一下digester里面的幾個方法源哩,大體上我們可以將其方法分為兩類:操作類和規(guī)則類鞋吉。

  1. 操作類
    • public void setValidating(boolean validating) // 是否根據(jù)DTD校驗XML
    • public void push(Object object) // 將對象壓入棧
    • public Object peek() // 獲取棧頂對象
    • public Object pop() // 彈出棧頂對象
    • public Object parse(InputSource input) // 解析輸入源
  2. 規(guī)則類
    • public void addObjectCreate(String pattern, String className, String attributeName) // 增加對象創(chuàng)建規(guī)則,當(dāng)匹配到pattern模式時励烦,如果指定了attributeName谓着,則根據(jù)attributeName創(chuàng)建類對象;否則根據(jù)className創(chuàng)建類對象
    • public void addSetProperties(String pattern) // 增加屬性設(shè)置規(guī)則坛掠,當(dāng)匹配到pattern模式時赊锚,就填充其屬性
    • public void addSetNext(String pattern, String methodName, String paramType) // 增加設(shè)置下一個規(guī)則,當(dāng)匹配到pattern模式時屉栓,調(diào)用父節(jié)點的methodName方法舷蒲,paramType為方法傳入?yún)?shù)的類型
    • public void addRule(String pattern, Rule rule) // 當(dāng)匹配到pattern模式時,增加一個自定義規(guī)則
    • public void addRuleSet(RuleSet ruleSet) // 增加規(guī)則集友多,一個規(guī)則集指的是對一個節(jié)點及下面的所有后續(xù)節(jié)點(子節(jié)點牲平、子節(jié)點的子節(jié)點...)的解析

Tomcat中的規(guī)則解析例子

上面我們寫了一個非常簡單的例子,相信通過這樣的例子我們可以很快地入門了域滥。那么tomcat里面又是怎樣寫的呢纵柿?我們看看org.apache.catalina.startup.Catalina.createStartDigester這個方法蜈抓,這個方法用于定義對server.xml的解析。該方法比較長昂儒,但是我并不打算對這個方法進(jìn)行閹割和壓縮沟使,而是原封不動地拷貝到這兒,以便大家對此有一個比較完整的認(rèn)識渊跋。

protected Digester createStartDigester() {
    long t1=System.currentTimeMillis();
    // Initialize the digester
    Digester digester = new Digester();
    digester.setValidating(false);
    digester.setRulesValidation(true);

    // 這兒設(shè)置無效的屬性腊嗡,fake是贗品的意思,也就是在檢查到這些屬性直接認(rèn)為是無效的
    Map<Class<?>, List<String>> fakeAttributes = new HashMap<>();
    List<String> objectAttrs = new ArrayList<>();
    objectAttrs.add("className");
    fakeAttributes.put(Object.class, objectAttrs);
    // Ignore attribute added by Eclipse for its internal tracking
    List<String> contextAttrs = new ArrayList<>();
    contextAttrs.add("source");
    fakeAttributes.put(StandardContext.class, contextAttrs);
    digester.setFakeAttributes(fakeAttributes);

    // 設(shè)置是否使用線程上下文類加載器
    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", "sslImplementationName"}));
    digester.addSetNext("Server/Service/Connector",
                        "addConnector",
                        "org.apache.catalina.connector.Connector");

    digester.addObjectCreate("Server/Service/Connector/SSLHostConfig",
                             "org.apache.tomcat.util.net.SSLHostConfig");
    digester.addSetProperties("Server/Service/Connector/SSLHostConfig");
    digester.addSetNext("Server/Service/Connector/SSLHostConfig",
            "addSslHostConfig",
            "org.apache.tomcat.util.net.SSLHostConfig");

    digester.addRule("Server/Service/Connector/SSLHostConfig/Certificate",
                     new CertificateCreateRule());
    digester.addRule("Server/Service/Connector/SSLHostConfig/Certificate",
                     new SetAllPropertiesRule(new String[]{"type"}));
    digester.addSetNext("Server/Service/Connector/SSLHostConfig/Certificate",
                        "addCertificate",
                        "org.apache.tomcat.util.net.SSLHostConfigCertificate");

    digester.addObjectCreate("Server/Service/Connector/SSLHostConfig/OpenSSLConf",
                             "org.apache.tomcat.util.net.openssl.OpenSSLConf");
    digester.addSetProperties("Server/Service/Connector/SSLHostConfig/OpenSSLConf");
    digester.addSetNext("Server/Service/Connector/SSLHostConfig/OpenSSLConf",
                        "setOpenSslConf",
                        "org.apache.tomcat.util.net.openssl.OpenSSLConf");

    digester.addObjectCreate("Server/Service/Connector/SSLHostConfig/OpenSSLConf/OpenSSLConfCmd",
                             "org.apache.tomcat.util.net.openssl.OpenSSLConfCmd");
    digester.addSetProperties("Server/Service/Connector/SSLHostConfig/OpenSSLConf/OpenSSLConfCmd");
    digester.addSetNext("Server/Service/Connector/SSLHostConfig/OpenSSLConf/OpenSSLConfCmd",
                        "addCmd",
                        "org.apache.tomcat.util.net.openssl.OpenSSLConfCmd");

    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");

    digester.addObjectCreate("Server/Service/Connector/UpgradeProtocol",
                              null, // MUST be specified in the element
                              "className");
    digester.addSetProperties("Server/Service/Connector/UpgradeProtocol");
    digester.addSetNext("Server/Service/Connector/UpgradeProtocol",
                        "addUpgradeProtocol",
                        "org.apache.coyote.UpgradeProtocol");

    // 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/");

    // 根據(jù)t1和t2刹枉,算出整個server.xml的Digester創(chuàng)建花費(fèi)的時間
    long t2=System.currentTimeMillis();
    if (log.isDebugEnabled()) {
        log.debug("Digester for server.xml created " + ( t2-t1 ));
    }
    return (digester);
}

這兒我們看到叽唱,在tomcat中明顯地用到了前面例子中說明的幾個規(guī)則,我們再簡單羅列一下:

  1. addObjectCreate微宝,對象創(chuàng)建規(guī)則
  2. addSetProperties棺亭,屬性設(shè)置規(guī)則
  3. addSetNext,設(shè)置下一個規(guī)則
  4. addRule蟋软,自定義規(guī)則
  5. digester.addRuleSet镶摘,自定義規(guī)則集

總結(jié)

本文我們對digester做了一個使用說明。

我們首先簡單地說明了一下digester是什么岳守,內(nèi)部基于什么原理來實現(xiàn)的凄敢。然后通過一個School、Grade和Class這樣的生活中的例子來說明digester的用法湿痢。最后通過查看tomcat中關(guān)于digester的例子代碼涝缝,加深了我們對于digester的理解。

相信通過這篇文章譬重,讓我們在閱讀tomcat源碼的過程中不再對xml解析產(chǎn)生疑惑拒逮!

參考鏈接

  1. https://blog.csdn.net/qq_24451605/article/details/51289519
  2. https://blog.csdn.net/flyliuweisky547/article/details/23872231
  3. https://tomcat.apache.org/tomcat-8.5-doc/api/org/apache/tomcat/util/digester/package-summary.html
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市臀规,隨后出現(xiàn)的幾起案子滩援,更是在濱河造成了極大的恐慌,老刑警劉巖塔嬉,帶你破解...
    沈念sama閱讀 216,324評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件玩徊,死亡現(xiàn)場離奇詭異,居然都是意外死亡谨究,警方通過查閱死者的電腦和手機(jī)恩袱,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來胶哲,“玉大人憎蛤,你說我怎么就攤上這事〖退保” “怎么了俩檬?”我有些...
    開封第一講書人閱讀 162,328評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長碾盟。 經(jīng)常有香客問我棚辽,道長,這世上最難降的妖魔是什么冰肴? 我笑而不...
    開封第一講書人閱讀 58,147評論 1 292
  • 正文 為了忘掉前任屈藐,我火速辦了婚禮,結(jié)果婚禮上熙尉,老公的妹妹穿的比我還像新娘联逻。我一直安慰自己,他們只是感情好检痰,可當(dāng)我...
    茶點故事閱讀 67,160評論 6 388
  • 文/花漫 我一把揭開白布包归。 她就那樣靜靜地躺著,像睡著了一般铅歼。 火紅的嫁衣襯著肌膚如雪公壤。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,115評論 1 296
  • 那天椎椰,我揣著相機(jī)與錄音厦幅,去河邊找鬼。 笑死慨飘,一個胖子當(dāng)著我的面吹牛确憨,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播瓤的,決...
    沈念sama閱讀 40,025評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼休弃,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了堤瘤?” 一聲冷哼從身側(cè)響起玫芦,我...
    開封第一講書人閱讀 38,867評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎本辐,沒想到半個月后桥帆,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,307評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡慎皱,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,528評論 2 332
  • 正文 我和宋清朗相戀三年老虫,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片茫多。...
    茶點故事閱讀 39,688評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡祈匙,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情夺欲,我是刑警寧澤跪帝,帶...
    沈念sama閱讀 35,409評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站些阅,受9級特大地震影響伞剑,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜市埋,卻給世界環(huán)境...
    茶點故事閱讀 41,001評論 3 325
  • 文/蒙蒙 一黎泣、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧缤谎,春花似錦抒倚、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,657評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至洋访,卻和暖如春镣陕,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背姻政。 一陣腳步聲響...
    開封第一講書人閱讀 32,811評論 1 268
  • 我被黑心中介騙來泰國打工呆抑, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人汁展。 一個月前我還...
    沈念sama閱讀 47,685評論 2 368
  • 正文 我出身青樓鹊碍,卻偏偏與公主長得像,于是被迫代替她去往敵國和親食绿。 傳聞我的和親對象是個殘疾皇子侈咕,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,573評論 2 353

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)器紧,斷路器耀销,智...
    卡卡羅2017閱讀 134,651評論 18 139
  • 從三月份找實習(xí)到現(xiàn)在,面了一些公司铲汪,掛了不少熊尉,但最終還是拿到小米、百度掌腰、阿里狰住、京東、新浪齿梁、CVTE催植、樂視家的研發(fā)崗...
    時芥藍(lán)閱讀 42,239評論 11 349
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法肮蛹,類相關(guān)的語法,內(nèi)部類的語法创南,繼承相關(guān)的語法伦忠,異常的語法,線程的語...
    子非魚_t_閱讀 31,622評論 18 399
  • 序 佛說扰藕,三千世界缓苛,如是我聞。 我說邓深,蕓蕓眾生,轉(zhuǎn)眼消散笔刹。 “跟神打賭芥备,嘖嘖嘖鶴球你最近閑的可以喲!“名為慕念卿的...
    陰樂閱讀 1,914評論 1 8
  • 根據(jù)您提供的資料舌菜,我為您量身設(shè)計了這份健康管理計劃 它包括了生命保障萌壳、重大疾病保障、住院保障日月、意外和豁免5方面的保...
    李大女兒閱讀 324評論 0 0