MyBatis Generator代碼分析一

【原創(chuàng)文章辜纲,轉(zhuǎn)載請注明原文章地址缩麸,謝謝嵌灰!】

注意:以下代碼都有適當(dāng)修改和刪改菠齿,為了更好看清楚執(zhí)行流程

首先是簡單分析使用Shell Runner執(zhí)行MBG的最概略的執(zhí)行流程分析:

org.mybatis.generator.api.ShellRunner:運行MyBatis Generator 的Main入口類列吼;####

核心代碼(main方法):

//解析命令行
Map<String, String> arguments = parseCommandLine(args);

//創(chuàng)建一個警告列表或衡,整個MBG運行過程中的所有警告信息都放在這個列表中较沪,執(zhí)行完成后統(tǒng)一System.out
List<String> warnings = new ArrayList<String>();

//得到generatorConfig.xml文件
String configfile = arguments.get(CONFIG_FILE);
File configurationFile = new File(configfile);

Set<String> fullyqualifiedTables = new HashSet<String>();//如果參數(shù)有tables逻翁,得到table名稱列表
Set<String> contexts = new HashSet<String>();//如果參數(shù)有contextids薄嫡,得到context名稱列表

try {
    //創(chuàng)建配置解析器
    ConfigurationParser cp = new ConfigurationParser(warnings);
    //調(diào)用配置解析器創(chuàng)建配置對象(Configuration對象非常簡單氧急,可以簡單理解為包含兩個列表,一個列表是List<Context> contexts毫深,包含了解析出來的Context對象吩坝,一個是List<String> classPathEntries,包含了配置的classPathEntry的location值)
    Configuration config = cp.parseConfiguration(configurationFile);
    //創(chuàng)建一個默認(rèn)的ShellCallback對象哑蔫,之前說過钉寝,shellcallback接口主要用來處理文件的創(chuàng)建和合并,傳入overwrite參數(shù)闸迷;默認(rèn)的shellcallback是不支持文件合并的嵌纲;
    DefaultShellCallback shellCallback = new DefaultShellCallback(
                arguments.containsKey(OVERWRITE));
    //創(chuàng)建一個MyBatisGenerator對象。MyBatisGenerator類是真正用來執(zhí)行生成動作的類
    MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config, shellCallback, warnings);
    //創(chuàng)建一個默認(rèn)的ProgressCallback對象腥沽,之前說過逮走,在MBG執(zhí)行過程中在一定的執(zhí)行步驟結(jié)束后調(diào)用ProgressCallback對象的方法,達(dá)到執(zhí)行過程監(jiān)控的效果今阳;
    //如果在執(zhí)行ShellRunner是傳入了-verbose參數(shù)师溅,那么創(chuàng)建一個VerboseProgressCallback(VerboseProgressCallback只是調(diào)用了System.out打印出了執(zhí)行過程而已)
    ProgressCallback progressCallback = arguments.containsKey(VERBOSE) ? new VerboseProgressCallback()
                : null;
    //執(zhí)行真正的MBG創(chuàng)建過程
    //注意茅信,這里的contexts是通過-contextids傳入的需要的上下文id列表;
    //fullyqualifiedTables是通過-tables傳入的本次需要生成的table名稱列表墓臭;
    myBatisGenerator.generate(progressCallback, contexts, fullyqualifiedTables);
}catch(...){...}

//輸出警告信息
for (String warning : warnings) {
    writeLine(warning);
}

org.mybatis.generator.config.xml.ConfigurationParser:配置解析器蘸鲸,用于對generatorConfig.xml配置文件的解析;####

構(gòu)造方法:

//初始化配置解析器中的一些基本數(shù)據(jù)內(nèi)容
public ConfigurationParser(Properties properties, List<String> warnings) {
    super();
    if (properties == null) {
        //properties:存放的系統(tǒng)配置信息
        this.properties = System.getProperties();
    } else {
        this.properties = properties;
    }

    if (warnings == null) {
        //warnings:存放的解析中的警告信息
        this.warnings = new ArrayList<String>();
    } else {
        this.warnings = warnings;
    }
    //parseErrors :存放的解析中的錯誤信息
    parseErrors = new ArrayList<String>();
}

//執(zhí)行配置解析起便,創(chuàng)建配置對象
private Configuration parseConfiguration(InputSource inputSource)
        throws IOException, XMLParserException {
    parseErrors.clear();
    //使用DOM解析器解析XML
    DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
    factory.setValidating(true);

    DocumentBuilder builder = factory.newDocumentBuilder();
    //設(shè)置實體對象處理器(對于MyBatis3來說棚贾,就是處理org/mybatis/generator/config/xml/mybatis-generator-config_1_0.dtd驗證),
    builder.setEntityResolver(new ParserEntityResolver());
    //設(shè)置解析錯誤處理器榆综,把解析過程中的異常和警告保存到warnings和parseErrors兩個String列表中妙痹;
    ParserErrorHandler handler = new ParserErrorHandler(warnings,
                parseErrors);
    builder.setErrorHandler(handler);
    //得到配置文件對應(yīng)的DOM對象;
    Document document =  builder.parse(inputSource);

    //配置對象鼻疮;
    Configuration config;
    Element rootNode = document.getDocumentElement();
    //得到XML文件的xml描述符怯伊;
    DocumentType docType = document.getDoctype();
    if (rootNode.getNodeType() == Node.ELEMENT_NODE
                && docType.getPublicId().equals(XmlConstants.IBATOR_CONFIG_PUBLIC_ID)) {
        //如果xml的PUBLIC_ID為-//Apache Software Foundation//DTD Apache iBATIS Ibator Configuration 1.0//EN,則執(zhí)行解析ibatis過程判沟;
        config = parseIbatorConfiguration(rootNode);
    } else if (rootNode.getNodeType() == Node.ELEMENT_NODE
                && docType.getPublicId().equals(XmlConstants.MYBATIS_GENERATOR_CONFIG_PUBLIC_ID)) {
        //如果xml的PUBLIC_ID為-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN耿芹,則執(zhí)行解析mybatis過程;
        config = parseMyBatisGeneratorConfiguration(rootNode);
    }
    //返回解析出的Configuration對象
    return config;
}

//執(zhí)行MyBatis生成器的配置
private Configuration parseMyBatisGeneratorConfiguration(Element rootNode)
        throws XMLParserException {
    //創(chuàng)建一個MyBatisGeneratorConfigurationParser 
    MyBatisGeneratorConfigurationParser parser = new MyBatisGeneratorConfigurationParser(
            properties);
    //使用配置解析器執(zhí)行XML解析
    return parser.parseConfiguration(rootNode);
}

MyBatisGeneratorConfigurationParser :用于把MBG配置文件解析為MyBatis3需要樣式挪哄;####

public Configuration parseConfiguration(Element rootNode)
        throws XMLParserException {
    //創(chuàng)建一個新的配置對象
    Configuration configuration = new Configuration();
    //得到<generatorConfiguration>下的所有元素吧秕,并遍歷
    NodeList nodeList = rootNode.getChildNodes();
    for (int i = 0; i < nodeList.getLength(); i++) {
        Node childNode = nodeList.item(i);
        if (childNode.getNodeType() != Node.ELEMENT_NODE) {
            continue;
        }
        if ("properties".equals(childNode.getNodeName())) { //$NON-NLS-1$
            //如果是<properties>元素,執(zhí)行properties解析
            parseProperties(configuration, childNode);
        } else if ("classPathEntry".equals(childNode.getNodeName())) { //$NON-NLS-1$
            //如果是<classPathEntry>迹炼,執(zhí)行classPathEntry解析
            parseClassPathEntry(configuration, childNode);
        } else if ("context".equals(childNode.getNodeName())) { //$NON-NLS-1$
            //如果是<context>元素砸彬,執(zhí)行context解析
            parseContext(configuration, childNode);
        }
    }
    return configuration;
}

//所以重點是三個方法:parseProperties/parseClassPathEntry/parseContext

//parseProperties方法最重要的就是加載指定的properties配置到properties中,【注意】斯入,因為在<generatorConfiguration>元素中的<properties>元素最重要的就是用來替換在配置文件中所有的${key}占位符砂碉,所以,properties元素只需要在解析過程存在刻两,所以可以看到properties屬性是只需要在MyBatisGeneratorConfigurationParser中使用增蹭;
private void parseProperties(Configuration configuration, Node node)
        throws XMLParserException {
    //解析得到URL或者resource屬性(兩種配置的加載方式)
    Properties attributes = parseAttributes(node);
    String resource = attributes.getProperty("resource"); 
    String url = attributes.getProperty("url"); 
    //統(tǒng)一把resource/URL轉(zhuǎn)成URL;
    URL resourceUrl;
    if (stringHasValue(resource)) {
        resourceUrl = ObjectFactory.getResource(resource);
    } else {
        resourceUrl = new URL(url);
    }
    //從URL加載properties文件并載入磅摹;
    InputStream inputStream = resourceUrl.openConnection()
                .getInputStream();
    properties.load(inputStream);
    inputStream.close();
}

//上面是解析properties的方法,主要就是提供給這個方法使用:在配置文件中所有的屬性值都先使用${}占位符去測試一下户誓,如果是占位符杀怠,就把${}中的值作為key去properties中查找,把查找到的值作為屬性真正的值返回厅克;
private String parsePropertyTokens(String string) {
    final String OPEN = "${"; //$NON-NLS-1$
    final String CLOSE = "}"; //$NON-NLS-1$
    //中間代碼略,就是解析得到${}中的值橙依,并去properties中查詢证舟;
    return newString;
}

//解析classPathEntry元素硕旗,只是很簡單的把所有的classPathEntry元素的location添加到配置對象的classpathEntry列表中
private void parseClassPathEntry(Configuration configuration, Node node) {
    Properties attributes = parseAttributes(node);
    configuration.addClasspathEntry(attributes.getProperty("location")); //$NON-NLS-1$
}

//最重要的,最復(fù)雜的女责,解析context元素
private void parseContext(Configuration configuration, Node node) {
    //解析出context元素上的所有屬性漆枚,并把所有屬性放到一個properties中;
    Properties attributes = parseAttributes(node);
    /**
     * 得到默認(rèn)的生成對象的樣式(ModeType是一個簡單的枚舉)
     * public enum ModelType {
            HIERARCHICAL("hierarchical"),FLAT("flat"),CONDITIONAL("conditional");
       } 
       ModelType的getModelType只是很簡單的根據(jù)string返回對應(yīng)的類型或者報錯
     */
    ModelType mt = defaultModelType == null ? null : ModelType
            .getModelType(defaultModelType);
    //創(chuàng)建一個Context對象
    Context context = new Context(mt);
    //先添加到配置對象的context列表中抵知,
    configuration.addContext(context);
    //再解析<context>子元素
    NodeList nodeList = node.getChildNodes();
    for (int i = 0; i < nodeList.getLength(); i++) {
        Node childNode = nodeList.item(i);

        if (childNode.getNodeType() != Node.ELEMENT_NODE) {
            continue;
        }
        //以下的內(nèi)容就很模式化了墙基,只是依次把context的不同子元素解析,并添加到Context對象中刷喜;所以我們就先不看每一個具體的解析代碼残制,先看一下Context對象的結(jié)構(gòu);
        if ("property".equals(childNode.getNodeName())) { //$NON-NLS-1$
            parseProperty(context, childNode);
        } else if ("plugin".equals(childNode.getNodeName())) { //$NON-NLS-1$
            parsePlugin(context, childNode);
        } else if ("commentGenerator".equals(childNode.getNodeName())) { //$NON-NLS-1$
            parseCommentGenerator(context, childNode);
        } else if ("jdbcConnection".equals(childNode.getNodeName())) { //$NON-NLS-1$
            parseJdbcConnection(context, childNode);
        } else if ("javaModelGenerator".equals(childNode.getNodeName())) { //$NON-NLS-1$
            parseJavaModelGenerator(context, childNode);
        } else if ("javaTypeResolver".equals(childNode.getNodeName())) { //$NON-NLS-1$
            parseJavaTypeResolver(context, childNode);
        } else if ("sqlMapGenerator".equals(childNode.getNodeName())) { //$NON-NLS-1$
            parseSqlMapGenerator(context, childNode);
        } else if ("javaClientGenerator".equals(childNode.getNodeName())) { //$NON-NLS-1$
            parseJavaClientGenerator(context, childNode);
        } else if ("table".equals(childNode.getNodeName())) { //$NON-NLS-1$
            parseTable(context, childNode);
        }
    }
}

org.mybatis.generator.config.Context:封裝<context>元素內(nèi)容####

public class Context extends PropertyHolder {

/** context的id */
private String id;

/** jdbc連接配置掖疮,包裝成JDBCConnectionConfiguration 對象初茶,對應(yīng)<jdbcConnection>元素 */
private JDBCConnectionConfiguration jdbcConnectionConfiguration;

/** 生成SQL MAP的xml配置,對應(yīng)<sqlMapGenerator>元素浊闪,包裝成 SqlMapGeneratorConfiguration 對象*/
private SqlMapGeneratorConfiguration sqlMapGeneratorConfiguration;

/** 生成java類型處理器配置恼布,對應(yīng)<javaTypeResolver>元素,包裝成 JavaTypeResolverConfiguration 對象 */
private JavaTypeResolverConfiguration javaTypeResolverConfiguration;

/** 生成java模型創(chuàng)建器配置搁宾,對應(yīng)<javaModelGenerator>元素折汞,包裝成 JavaModelGeneratorConfiguration 對象 */
private JavaModelGeneratorConfiguration javaModelGeneratorConfiguration;

/** 生成Mapper接口配置,對應(yīng)<javaClientGenerator>元素盖腿,包裝成 JavaClientGeneratorConfiguration 對象*/
private JavaClientGeneratorConfiguration javaClientGeneratorConfiguration;

/** 解析每一個<table>元素爽待,并包裝成一個一個的TableConfiguration對象 */
private ArrayList<TableConfiguration> tableConfigurations;

/** 生成對象樣式,對應(yīng)context元素的defaultModelType屬性(attribute) */
private ModelType defaultModelType;

/** 對應(yīng)context元素的beginningDelimiter這個property子元素(注意屬性和property的區(qū)別) */
private String beginningDelimiter = "\""; 

/**  對應(yīng)context元素的endingDelimiter 這個property子元素*/
private String endingDelimiter = "\""; 

/** 對應(yīng)<commentGenerator>元素奸忽,注解生成器的配置 */
private CommentGeneratorConfiguration commentGeneratorConfiguration;

/** 注解生成器 */
private CommentGenerator commentGenerator;

/** 這是一個包裝了所有的plugin的插件執(zhí)行對象堕伪,其中的插件就是由pluginConfigurations中的每一個PluginConfiguration生成*/
private PluginAggregator pluginAggregator;

/** 對應(yīng)每一個<plugin>元素的配置 */
private List<PluginConfiguration> pluginConfigurations;

/** 目標(biāo)運行時,對應(yīng)context元素的targetRuntime屬性(attribute) */
private String targetRuntime;

/** 對應(yīng)context元素的introspectedColumnImpl屬性(attribute) */
private String introspectedColumnImpl;

/** 自動識別數(shù)據(jù)庫關(guān)鍵字栗菜,對應(yīng)context元素的autoDelimitKeywords這個property子元素 */
private Boolean autoDelimitKeywords;

/**Java代碼格式化工具欠雌,對應(yīng)context元素的javaFormatter這個property子元素  */
private JavaFormatter javaFormatter;

/** Xml代碼格式化工具,對應(yīng)context元素的xmlFormatter這個property子元素 */
private XmlFormatter xmlFormatter;
}

可以看到疙筹,其實MBG的初始化過程是非常簡單的富俄,說白了,最重要的目的就是把generatorConfig.xml中的DOM通過MyBatisGeneratorConfigurationParser類解析成一個Configuration對象而咆,而主要的工作就是消耗在把<context>元素解析成Configuration對象中的List<Context>霍比,而<context>剛好對應(yīng)著Context對象,那么暴备,實際的生成過程悠瞬,就是MyBatisGenerator對象根據(jù)Configuration對象來生成了。

待續(xù)...

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市浅妆,隨后出現(xiàn)的幾起案子望迎,更是在濱河造成了極大的恐慌,老刑警劉巖凌外,帶你破解...
    沈念sama閱讀 218,682評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件辩尊,死亡現(xiàn)場離奇詭異,居然都是意外死亡康辑,警方通過查閱死者的電腦和手機摄欲,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來疮薇,“玉大人胸墙,你說我怎么就攤上這事〉胄粒” “怎么了劳秋?”我有些...
    開封第一講書人閱讀 165,083評論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長胖齐。 經(jīng)常有香客問我玻淑,道長,這世上最難降的妖魔是什么呀伙? 我笑而不...
    開封第一講書人閱讀 58,763評論 1 295
  • 正文 為了忘掉前任补履,我火速辦了婚禮,結(jié)果婚禮上剿另,老公的妹妹穿的比我還像新娘箫锤。我一直安慰自己,他們只是感情好雨女,可當(dāng)我...
    茶點故事閱讀 67,785評論 6 392
  • 文/花漫 我一把揭開白布谚攒。 她就那樣靜靜地躺著,像睡著了一般氛堕。 火紅的嫁衣襯著肌膚如雪馏臭。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,624評論 1 305
  • 那天讼稚,我揣著相機與錄音括儒,去河邊找鬼。 笑死锐想,一個胖子當(dāng)著我的面吹牛帮寻,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播赠摇,決...
    沈念sama閱讀 40,358評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼固逗,長吁一口氣:“原來是場噩夢啊……” “哼浅蚪!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起抒蚜,我...
    開封第一講書人閱讀 39,261評論 0 276
  • 序言:老撾萬榮一對情侶失蹤掘鄙,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后嗡髓,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,722評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡收津,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年饿这,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片撞秋。...
    茶點故事閱讀 40,030評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡长捧,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出吻贿,到底是詐尸還是另有隱情串结,我是刑警寧澤,帶...
    沈念sama閱讀 35,737評論 5 346
  • 正文 年R本政府宣布舅列,位于F島的核電站肌割,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏帐要。R本人自食惡果不足惜把敞,卻給世界環(huán)境...
    茶點故事閱讀 41,360評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望榨惠。 院中可真熱鬧奋早,春花似錦、人聲如沸赠橙。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽期揪。三九已至掉奄,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間横侦,已是汗流浹背挥萌。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留枉侧,地道東北人引瀑。 一個月前我還...
    沈念sama閱讀 48,237評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像榨馁,于是被迫代替她去往敵國和親憨栽。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,976評論 2 355

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