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

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


server.xml中的内容,简要来说,主要有这三部分。



    
        prop_value
        ...
    
    
    

        prop_value
        ...

        
            
                
                ...
            
            ...
        
    ...
    
    
        
            
            ...
        
        
            prop_value
            ...
        
    

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

配置文件元素 对应类 配置的内容
SystemConfig DBLE服务器配置,例如服务端口、管理端口等
UserConfig 访问DBLE的用户配置,例如用户名、密码、允许访问哪些库or表等
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()来提取出的具体内容,填充到对应的对象里。

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. 提取中的配置项的清单

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

  3. 把可配置属性清单过一遍,从清单中找到同名配置项,把值赋给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 props = ConfigUtil.loadElements((Element) node);
        ParameterMapping.mapping(system, props);
    }
}

下面是相关的技术细节:

提取中的配置项的清单,需要两步:

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

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

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

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

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

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

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

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

UserConfig的加载过程——loadUsers()


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

  1. 提取中的name属性和配置项的清单

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

  3. 回到中,提取它里面的

    ,然后加载后面两种标签的name属性和dml属性

    下面是相关的技术细节:

    提取的清单的方法和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 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()则承包了加载和它里面的

    的工作,工作流程如下:

    1. 读取check属性

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

    3. 为同一个里的每个

    创建tablePrivilege实例,获取
    namedml两个属性来给tablePrivilege赋值

    private void loadPrivileges(UserConfig userConfig, Element node) {
    
        // 实例化对应的UserPrivilegesConfig类
        UserPrivilegesConfig privilegesConfig = new UserPrivilegesConfig();
    
        // 将下的所有标签找出来
        NodeList privilegesNodes = node.getElementsByTagName("privileges");
    
        // 取其中一个标签
        int privilegesNodesLength = privilegesNodes.getLength();
        for (int i = 0; i < privilegesNodesLength; i++) {
            Element privilegesNode = (Element) privilegesNodes.item(i);
    
            // 读取check属性,并给UserPrivilegesConfig.check赋值
            // tips: 虽然允许有多个,但由于UserPrivilegesConfig
            //   只实例过一个对象,所以UserPrivilegesConfig.check等于最后一个
            //   的check
            String check = privilegesNode.getAttribute("check");
            if (null != check) {
                privilegesConfig.setCheck(Boolean.valueOf(check));
            }
    
            // 将下的所有标签找出来
            NodeList schemaNodes = privilegesNode.getElementsByTagName("schema");
    
            // 取其中一个标签
            int schemaNodeLength = schemaNodes.getLength();
            for (int j = 0; j < schemaNodeLength; j++) {
                Element schemaNode = (Element) schemaNodes.item(j);
    
                // 读取name属性
                // tips:中设置的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));
                }
    
                // 实例化对应的SchemaPrivilege类,并把dml赋值
                UserPrivilegesConfig.SchemaPrivilege schemaPrivilege = new UserPrivilegesConfig.SchemaPrivilege();
                schemaPrivilege.setDml(dml1Array);
    
    
                // 取其中一个
    标签 NodeList tableNodes = schemaNode.getElementsByTagName("table"); int tableNodeLength = tableNodes.getLength(); for (int z = 0; z < tableNodeLength; z++) { // 实例化
    对应的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. 先读取白名单——由于只有含有标签,找到每一个标签,提取其中的hostuser属性,形成{host, { user1, user2, ... , usern }}的项目,然后加入到白名单里

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

    以下是技术细节:

    private void loadFirewall(Element root) throws IllegalAccessException, InvocationTargetException {
    
        /*
         * 先加载白名单
         */
        
        // 将下的所有标签找出来
        NodeList list = root.getElementsByTagName("host");
        Map> whitehost = new HashMap<>();
        
        // 对每一个标签
        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地址)不允许和其他的重复
                if (this.firewall.existsHost(host)) {
                    throw new ConfigException("host duplicated : " + host);
                }
        
                // 把”user1,user2,...usern"形式的user属性转变为数组
                String[] arrayUsers = userStr.split(",");
                List userConfigs = new ArrayList<>();
        
                // 对user属性中的每一个user,
                for (String user : arrayUsers) {
        
                    // 这个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);
            }
        }
    
        // 完成白名单的加载
        firewall.setWhitehost(whitehost);
    
        /*
         * 然后加载黑名单
         */
    
        // 初始化com.alibaba.druid.wall.WallConfig
        WallConfig wallConfig = new WallConfig();
    
        // 将的所有标签找出来
        NodeList blacklist = root.getElementsByTagName("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));
                }
    
                // 利用用反射的方法,把加载给WallConfig.xxx,从而完成WallConfig的赋值
                // loadElements()和mapping()细节同本文的loadSystem()部分,不再冗述
                Map props = ConfigUtil.loadElements((Element) node);
                ParameterMapping.mapping(wallConfig, props);
            }
        }
    
        // 完成黑名单的加载
        firewall.setWallConfig(wallConfig);
    
        // 以加载后的信息去初始化`com.alibaba.druid.wall.spi.MySqlWallProvider`(一个语法解析器工厂)
        firewall.init();
    
    }
    

    你可能感兴趣的:(DBLE 2.17.08.1与MyCat 1.6.5的启动过程(3)——加载配置文件server.xml)