DBLE 2.17.08.1与MyCat 1.6.5的启动过程(3)——加载配置文件server.xml

server.xml的构成和内存表现形式


server.xml中的内容,简要来说,主要有<system><user><firewall>这三部分。

<dble:server xmlns:dble="http://dble.cloud/">

    <system>
        <property name="prop_name">prop_value</property>
        ...
    </system>
    
    <user name="login_user_name">

        <property name="prop_name">prop_value</property>
        ...

        <privileges check="true_or_false">
            <schema name="this_user_rights_on_this_schema" dml="">
                <table name="this_user_rights_on_this_table" dml="" />
                ...
            </schema>
            ...
        </privileges>
    </user>
    ...
    
    <firewall>
        <whitehost>
            <host host="user_host" user="user_names" />
            ...
        </whitehost>
        <blacklist check="">
            <property name="prop_name">prop_value</property>
            ...
        </blacklist>
    </firewall>

</server>

相应的,这三部分被分别加载成com.actiontech.dble.config.model中的SystemConfigUserConfigFirewallConfig这三个类。

配置文件元素 对应类 配置的内容
<system> SystemConfig DBLE服务器配置,例如服务端口、管理端口等
<user> UserConfig 访问DBLE的用户配置,例如用户名、密码、允许访问哪些库or表等
<firewall> FirewallConfig 访问权限配置,例如基于用户的机器IP来允许/禁止访问

加载操作的代码入口


无论是server.xml的哪部分,都是在XMLConfigure类的构造函数实例化XMLServerLoader的过程中完成加载的。

com.actiontech.dble.config.loader.xml.XMLConfigure
com.actiontech.dble.config.loader.xml.XMLServerLoader

XMLServerLoader的构造函数的工作思路是,先分配空的内存对象(SystemConfigUserConfigFirewallConfig),然后再用这个类的load()函数来“填充”它们。

public XMLServerLoader() {

    this.system = new SystemConfig();
    this.users = new HashMap<>();
    this.firewall = new FirewallConfig();

    this.load();

}

load()的工作思路就也很单纯:先用ConfigUtil.getDocument()把server.xml读入到内存里,然后再用loadSystem()loadUsers()loadFirewall()来提取出<system><user><firewall>的具体内容,填充到对应的对象里。

private void load() {

    // ...

    dtd = ResourceUtil.getResourceAsStream("/server.dtd");
    xml = ResourceUtil.getResourceAsStream("/server.xml");
    Element root = ConfigUtil.getDocument(dtd, xml).getDocumentElement();
    
    loadSystem(root);
    loadUsers(root);
    loadFirewall(root);

    // ...

}

把server.xml文件载入内存——ConfigUtil.getDocument()


该方法利用JAR包内(源代码中project_loc/src/main/resources文件夹)的server.dtd,进行带DTD校验的XML读取。

最终,将server.xml文件整个读取成一个DOM(Document Object Model)对象,缓存在内存中。

SystemConfig的加载过程——loadSystem()


这个方法虽然使用了DOM操作、反射、Java Bean这些比较复杂的技术,但它的工作思路其实非常朴素:

  1. 提取<system>中的配置项<property>的清单

  2. 提取SystemConfig类中的可配置属性的清单

  3. 把可配置属性清单过一遍,从<property>清单中找到同名配置项,把值赋给SystemConfig

NodeList list = root.getElementsByTagName("system");

for (int i = 0, n = list.getLength(); i < n; i++) {

    Node node = list.item(i);

    if (node instanceof Element) {
        Map<String, Object> props = ConfigUtil.loadElements((Element) node);
        ParameterMapping.mapping(system, props);
    }
}

下面是相关的技术细节:

提取<system>中的配置项<property>的清单,需要两步:

  1. 通过org.w3c.dom.Element.getElementsByTagName(),定位到server.xml的DOM的<system>标签

  2. 调用com.actiontech.dble.config.util.ConfigUtil.loadElements(),将每一个<property>标签创建成一个键值对(name, content),放到同一个Map<String, Object>集合里

ParameterMapping.mapping()则会搞定剩余的工作:

  1. com.actiontech.dble.config.util.ParameterMapping.getDescriptors()找出ServerConfig中的可配置属性(符合Java Bean规范,即有对应的公开getter和setter方法),将它们放到一个数组中

  2. ParameterMapping.mapping()现在手上有了两套清单:一个是server.xml里<server>标签的所有<property>Map<String, Object> parameter,另一个是ServerConfig的所有属性PropertyDescriptor[] pds。于是,剩下来的工作就是把parameter中的值,根据pds,赋值给ServerConfig实例中的同名属性。

// 取一个SystemConfig属性
for (PropertyDescriptor pd : pds) {

    // 找同名的<property>标签
    Object obj = parameter.get(pd.getName());
    Object value = obj;

    // 确定当前SystemConfig属性的数据类型
    Class<?> cls = pd.getPropertyType();

    if if (obj instanceof String) {
        String string = (String) obj;
        if (!StringUtil.isEmpty(string)) {
                // 把<property>中的“${系统变量}”替换成系统变量
            string = ConfigUtil.filter(string);
        }
        // 把<property>标签的值转换成当前SystemConfig属性的数据类型
        if (isPrimitiveType(cls)) {
            value = convert(cls, string);
        }
    } else if (obj instanceof BeanConfig) {
        // <server>中有配置是Java Bean类型时的处理方法,介绍从略
    } else if (obj instanceof BeanConfig[] ) {
        // <server>中有配置是Java Bean类型时的处理方法,介绍从略
    }

    // 如果上面的步骤没出错
    if (cls != null && value != null) {
        // 在外面实例化的SystemConfig中,
        // 调用这个SystemConfig属性的setter,把<property>的值赋进去
        Method method = pd.getWriteMethod();
        if (method != null) {
            method.invoke(object, value);
        }
    }
}

UserConfig的加载过程——loadUsers()


这个方法的工作思路大致如下:

  1. 提取<user>中的name属性和配置项<property>的清单

  2. 直接把UserConfig的password、usingDecrypt、benchmark、readOnly、manager和schemas,在<property>清单中同名配置项的值赋上

  3. 回到<user>中,提取它里面的<privileges><schema><table>,然后加载后面两种标签的name属性和dml属性

下面是相关的技术细节:

提取<property>的清单的方法和loadSystem()一样,都是org.w3c.dom.Element.getElementsByTagName()com.actiontech.dble.config.util.ConfigUtil.loadElements()这两个函数干活。相对“新颖”的是,使用了org.w3c.dom.Element.getAttribute()来提取name属性。

NodeList list = root.getElementsByTagName("user");
// ...
String name = e.getAttribute("name");
Map<String, Object> props = ConfigUtil.loadElements(e);

UserConfig的name、password、usingDecrypt、benchmark、readOnly、manager和schemas属性赋值时,没有通过反射来定位这些属性,而使用了硬编码的方法:先用props.get("XXX")把XXX属性的值取出来,需要的话对这个值进行一下处理,最后调用UserConfig.setXXX()完成XXX属性的赋值。这样做的好处在于代码逻辑清晰简单,但是牺牲了增删这些属性时而无需改动这个方法的灵活性。

// ...
String password = (String) props.get("password");
String usingDecrypt = (String) props.get("usingDecrypt");
String passwordDecrypt = DecryptUtil.decrypt(usingDecrypt, name, password);

user.setName(name);
user.setPassword(passwordDecrypt);
user.setEncryptPassword(password);

String benchmark = (String) props.get("benchmark");
if (null != benchmark) {
    user.setBenchmark(Integer.parseInt(benchmark));
}

String readOnly = (String) props.get("readOnly");
if (null != readOnly) {
    user.setReadOnly(Boolean.parseBoolean(readOnly));
}

String manager = (String) props.get("manager");
if (null != manager) {
    user.setManager(Boolean.parseBoolean(manager));
}
String schemas = (String) props.get("schemas");
if (user.isManager() && schemas != null) {
    throw new ConfigException("manager user can't set any schema!");
} else if (!user.isManager()) {
    if (schemas != null) {
        if (system.isLowerCaseTableNames()) {
            schemas = schemas.toLowerCase();
        }
        String[] strArray = SplitUtil.split(schemas, ',', true);
        user.setSchemas(new HashSet<>(Arrays.asList(strArray)));
    }
    // ...
}

com.actiontech.dble.config.loader.xml.XMLServerLoader.loadPrivileges()则承包了加载<privileges>和它里面的<schema><table>的工作,工作流程如下:

  1. 读取<privileges>check属性

  2. 为每一个<schema>创建schemaPrivilege实例,获取<schema>namedml两个属性来给schemaPrivilege赋值

  3. 为同一个<schema>里的每个<table>创建tablePrivilege实例,获取<table>namedml两个属性来给tablePrivilege赋值

private void loadPrivileges(UserConfig userConfig, Element node) {

    // 实例化<privileges>对应的UserPrivilegesConfig类
    UserPrivilegesConfig privilegesConfig = new UserPrivilegesConfig();

    // 将<user>下的所有<privileges>标签找出来
    NodeList privilegesNodes = node.getElementsByTagName("privileges");

    // 取其中一个<privileges>标签
    int privilegesNodesLength = privilegesNodes.getLength();
    for (int i = 0; i < privilegesNodesLength; i++) {
        Element privilegesNode = (Element) privilegesNodes.item(i);

        // 读取check属性,并给UserPrivilegesConfig.check赋值
        // tips: 虽然允许有多个<privileges>,但由于UserPrivilegesConfig
        //   只实例过一个对象,所以UserPrivilegesConfig.check等于最后一个
        //   <privileges>的check
        String check = privilegesNode.getAttribute("check");
        if (null != check) {
            privilegesConfig.setCheck(Boolean.valueOf(check));
        }

        // 将<privileges>下的所有<schema>标签找出来
        NodeList schemaNodes = privilegesNode.getElementsByTagName("schema");

        // 取其中一个<schema>标签
        int schemaNodeLength = schemaNodes.getLength();
        for (int j = 0; j < schemaNodeLength; j++) {
            Element schemaNode = (Element) schemaNodes.item(j);

            // 读取name属性
            // tips:<system>中设置的isLowerCaseTableNames会导致这里对name取小写
            String name1 = schemaNode.getAttribute("name");
            if (system.isLowerCaseTableNames()) {
                name1 = name1.toLowerCase();
            }

            // 读取dml属性
            String dml1 = schemaNode.getAttribute("dml");
            int[] dml1Array = new int[dml1.length()];
            for (int offset1 = 0; offset1 < dml1.length(); offset1++) {
                dml1Array[offset1] = Character.getNumericValue(dml1.charAt(offset1));
            }

            // 实例化<schema>对应的SchemaPrivilege类,并把dml赋值
            UserPrivilegesConfig.SchemaPrivilege schemaPrivilege = new UserPrivilegesConfig.SchemaPrivilege();
            schemaPrivilege.setDml(dml1Array);


            // 取其中一个<table>标签
            NodeList tableNodes = schemaNode.getElementsByTagName("table");
            int tableNodeLength = tableNodes.getLength();
            for (int z = 0; z < tableNodeLength; z++) {

                // 实例化<table>对应的TablePrivilege类
                UserPrivilegesConfig.TablePrivilege tablePrivilege = new UserPrivilegesConfig.TablePrivilege();

                Element tableNode = (Element) tableNodes.item(z);

                // 读取name属性
                String name2 = tableNode.getAttribute("name");
                if (system.isLowerCaseTableNames()) {
                    name2 = name2.toLowerCase();
                }

                // 读取dml属性
                String dml2 = tableNode.getAttribute("dml");
                int[] dml2Array = new int[dml2.length()];
                for (int offset2 = 0; offset2 < dml2.length(); offset2++) {
                    dml2Array[offset2] = Character.getNumericValue(dml2.charAt(offset2));
                }

                // 对dml赋值
                tablePrivilege.setDml(dml2Array);

                // 把TablePrivilege追加到所属的SchemaPrivilege内
                schemaPrivilege.addTablePrivilege(name2, tablePrivilege);
            }

            // 把SchemaPrivilege追加到所属的UserPrivilegesConfig内
            privilegesConfig.addSchemaPrivilege(name1, schemaPrivilege);
        }
    }

    // 把UserPrivilegesConfig赋值给UserConfig
    userConfig.setPrivilegesConfig(privilegesConfig);
}

FirewallConfig的加载过程——loadFirewall()


只要了解SystemConfig的加载过程,尤其是反射部分(见前文),这部分的工作逻辑非常简单:

  1. 先读取白名单——由于只有<whitehost>含有<host>标签,找到每一个<host>标签,提取其中的hostuser属性,形成{host, { user1, user2, ... , usern }}的项目,然后加入到白名单里

  2. 再读取黑名单——黑名单功能本质上依赖com.alibaba.druid来实现,<blacklist>标签对应的类正是com.alibaba.druid.wall.WallConfig,所以和上面SystemConfig加载的时候类似,使用了反射的方法,把<property>中,WallConfig中的同名值赋值上去,来完成WallConfig的初始化,最后再以此初始化druid的一个特别的语法解析器工厂

以下是技术细节:

private void loadFirewall(Element root) throws IllegalAccessException, InvocationTargetException {

    /*
     * 先加载白名单<whitehost>
     */
    
    // 将<whitehost>下的所有<host>标签找出来
    NodeList list = root.getElementsByTagName("host");
    Map<String, List<UserConfig>> whitehost = new HashMap<>();
    
    // 对每一个<host>标签
    for (int i = 0, n = list.getLength(); i < n; i++) {
        Node node = list.item(i);
        if (node instanceof Element) {
            Element e = (Element) node;
    
            // 取host和user属性
            String host = e.getAttribute("host").trim();
            String userStr = e.getAttribute("user").trim();
    
            // host属性(IP地址)不允许和其他<host>的重复
            if (this.firewall.existsHost(host)) {
                throw new ConfigException("host duplicated : " + host);
            }
    
            // 把”user1,user2,...usern"形式的user属性转变为数组
            String[] arrayUsers = userStr.split(",");
            List<UserConfig> userConfigs = new ArrayList<>();
    
            // 对user属性中的每一个user,
            for (String user : arrayUsers) {
    
                // 这个user一定要在之前加载的用户权限(<user>标签)中配置过
                UserConfig uc = this.users.get(user);
                if (null == uc) {
                    throw new ConfigException("[user: " + user + "] doesn't exist in [host: " + host + "]");
                }
    
                // 这个user不是管理用户的话,它必须能访问起码一个库
                if (!uc.isManager() && (uc.getSchemas() == null || uc.getSchemas().size() == 0)) {
                    throw new ConfigException("[host: " + host + "] contains one root privileges user: " + user);
                }
    
                // 这个user符合上述条件的话,则将它在用户权限中的实体索引到这里来
                userConfigs.add(uc);
            }
    
            // 将{host, { user1, user2, ... , usern }}加入到白名单中
            whitehost.put(host, userConfigs);
        }
    }

    // 完成白名单<whitehost>的加载
    firewall.setWhitehost(whitehost);

    /*
     * 然后加载黑名单<blacklist>
     */

    // 初始化com.alibaba.druid.wall.WallConfig
    WallConfig wallConfig = new WallConfig();

    // 将<firewall>的所有<blacklist>标签找出来
    NodeList blacklist = root.getElementsByTagName("blacklist");

    // 对于每一个<blacklist>标签
    for (int i = 0, n = blacklist.getLength(); i < n; i++) {
        Node node = blacklist.item(i);
        if (node instanceof Element) {
            Element e = (Element) node;

            // 加载check属性
            String check = e.getAttribute("check");
            if (null != check) {
                firewall.setBlackListCheck(Boolean.parseBoolean(check));
            }

            // 利用用反射的方法,把<property name="xxx">加载给WallConfig.xxx,从而完成WallConfig的赋值
            // loadElements()和mapping()细节同本文的loadSystem()部分,不再冗述
            Map<String, Object> props = ConfigUtil.loadElements((Element) node);
            ParameterMapping.mapping(wallConfig, props);
        }
    }

    // 完成黑名单<blacklist>的加载
    firewall.setWallConfig(wallConfig);

    // 以加载后的<blacklist>信息去初始化`com.alibaba.druid.wall.spi.MySqlWallProvider`(一个语法解析器工厂)
    firewall.init();

}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,482评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,377评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,762评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,273评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,289评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,046评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,351评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,988评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,476评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,948评论 2 324
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,064评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,712评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,261评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,264评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,486评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,511评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,802评论 2 345

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,599评论 18 139
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,581评论 18 399
  • 前言 Tomcat隶属于Apache基金会,是开源的轻量级Web应用服务器,使用非常广泛。server.xml是T...
    余平的余_余平的平阅读 1,923评论 0 23
  • Spring Boot 参考指南 介绍 转载自:https://www.gitbook.com/book/qbgb...
    毛宇鹏阅读 46,748评论 6 342
  • 梨花碎语,瓦楞沟渠,天淡银风坠地。 鳞次栉比,昨叶纷堆砌。 大抵孤寒难息,都应是,飞花舞雩。 画双鲵,一半非鱼,一...
    静水楼台阅读 470评论 0 1