• shiro解析ini文件


    来吧,看看shiro是怎么解析ini文件的,这里假设ini文件在classpath下,名字叫做shiro.ini

    Factory<org.apache.shiro.mgt.SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");

    shiro.ini

    [users]
    zhang=123
    wang=123
    
    [main]
    #指定securityManager的authenticator实现
    authenticator=org.apache.shiro.authc.pam.ModularRealmAuthenticator
    securityManager.authenticator=$authenticator
    
    #指定securityManager.authenticator的authenticationStrategy
    allSuccessfulStrategy=org.apache.shiro.authc.pam.FirstSuccessfulStrategy
    securityManager.authenticator.authenticationStrategy=$allSuccessfulStrategy

    一、加载ini配置文件

     1 public static InputStream getInputStreamForPath(String resourcePath) throws IOException {
     2 
     3         InputStream is;
     4         if (resourcePath.startsWith(CLASSPATH_PREFIX)) {//判断是否为classpath:开头的
     5             is = loadFromClassPath(stripPrefix(resourcePath));
     6 
     7         } else if (resourcePath.startsWith(URL_PREFIX)) {//判断是否为url:开头
     8             is = loadFromUrl(stripPrefix(resourcePath));
     9 
    10         } else if (resourcePath.startsWith(FILE_PREFIX)) {//判断是否为file:开头
    11             is = loadFromFile(stripPrefix(resourcePath));
    12 
    13         } else {
    14             is = loadFromFile(resourcePath);
    15         }
    16 
    17         if (is == null) {
    18             throw new IOException("Resource [" + resourcePath + "] could not be found.");
    19         }
    20 
    21         return is;
    22     }

    上面的代码中对我们传进来的配置文件进行前缀判断,再以相应的方法取加载它

    stripPrefix(resourcePath)是去掉前缀,那么传进去的classpath:shiro.ini就变成shiro.ini了,下面就是加载配置文件的方法

     1 public static InputStream getResourceAsStream(String name) {
     2 
     3         InputStream is = THREAD_CL_ACCESSOR.getResourceStream(name);
     4 
     5         if (is == null) {
     6             if (log.isTraceEnabled()) {
     7                 log.trace("Resource [" + name + "] was not found via the thread context ClassLoader.  Trying the " +
     8                         "current ClassLoader...");
     9             }
    10             is = CLASS_CL_ACCESSOR.getResourceStream(name);
    11         }
    12 
    13         if (is == null) {
    14             if (log.isTraceEnabled()) {
    15                 log.trace("Resource [" + name + "] was not found via the current class loader.  Trying the " +
    16                         "system/application ClassLoader...");
    17             }
    18             is = SYSTEM_CL_ACCESSOR.getResourceStream(name);
    19         }
    20 
    21         if (is == null && log.isTraceEnabled()) {
    22             log.trace("Resource [" + name + "] was not found via the thread context, current, or " +
    23                     "system/application ClassLoaders.  All heuristics have been exhausted.  Returning null.");
    24         }
    25 
    26         return is;
    27     }
    加载配置文件的时候,首先使用了线程的上下文加载器,如果加载不到就用类加载器,下面是这些加载器的获取代码

    private static final ClassLoaderAccessor THREAD_CL_ACCESSOR = new ExceptionIgnoringAccessor() {
            @Override
            protected ClassLoader doGetClassLoader() throws Throwable {
                return Thread.currentThread().getContextClassLoader();
            }
        };
    
        /**
         * @since 1.0
         */
        private static final ClassLoaderAccessor CLASS_CL_ACCESSOR = new ExceptionIgnoringAccessor() {
            @Override
            protected ClassLoader doGetClassLoader() throws Throwable {
                return ClassUtils.class.getClassLoader();
            }
        };
    
        /**
         * @since 1.0
         */
        private static final ClassLoaderAccessor SYSTEM_CL_ACCESSOR = new ExceptionIgnoringAccessor() {
            @Override
            protected ClassLoader doGetClassLoader() throws Throwable {
                return ClassLoader.getSystemClassLoader();
            }
        };
    当获取到配置文件的输入流后,使用了isr = new InputStreamReader(is, DEFAULT_CHARSET_NAME);这行代码就输入流变成了字节输入流随后调用过了load(isr)方法
     1  public void load(Reader reader) {
     2         Scanner scanner = new Scanner(reader);
     3         try {
     4             load(scanner);
     5         } finally {
     6             try {
     7                 scanner.close();
     8             } catch (Exception e) {
     9                 log.debug("Unable to cleanly close the InputStream scanner.  Non-critical - ignoring.", e);
    10             }
    11         }
    12     }

    上面使用了Scanner类对reader进行了包装,随后有调用了load(scanner);

     1  public void load(Scanner scanner) {
     2 
     3         String sectionName = DEFAULT_SECTION_NAME;//默认节点名称为空字符串
     4         StringBuilder sectionContent = new StringBuilder();//用于保存节点的内容
     5 
     6         while (scanner.hasNextLine()) {
     7 
     8             String rawLine = scanner.nextLine();//读取一行数据
     9             String line = StringUtils.clean(rawLine);//去除字符串的两边的空白字符,如果这个字符是空字符串,那么返回null
    10 
    11             if (line == null || line.startsWith(COMMENT_POUND) || line.startsWith(COMMENT_SEMICOLON)) {//判断这行数据是否为null,或者是以#或者是;开头的注释.
    12                 //skip empty lines and comments:
    13                 continue;
    14             }
    15 
    16             String newSectionName = getSectionName(line);//判断是否为节点名(如[main]这样的)并且去掉[],如[main]
    17             if (newSectionName != null) {//如果节点不为空,那么就添加节点
    18                 //found a new section - convert the currently buffered one into a Section object
    19                 addSection(sectionName, sectionContent);//添加节点
    20 
    21                 //reset the buffer for the new section:
    22                 sectionContent = new StringBuilder();
    23 
    24                 sectionName = newSectionName; //保存节点名,在读取完配置文件后,还得通过它添加节点(第36行代码需要用到)
    25 
    26                 if (log.isDebugEnabled()) {
    27                     log.debug("Parsing " + SECTION_PREFIX + sectionName + SECTION_SUFFIX);
    28                 }
    29             } else {
    30                 //normal line - add it to the existing content buffer:
    31                 sectionContent.append(rawLine).append("
    ");//说名读取到这行不是节点名,那么就将内容保存到sectionContent中
    32             }
    33         }
    34 
    35         //finish any remaining buffered content:
    36         addSection(sectionName, sectionContent);//读到文件结尾时添加最后的这个节点
    37     }

    第19行是添加节点,下面是添加节点的判断代码,它首先要确认你这个节点内是否有内容,如果没有就不添加,这种情况一般发生在

    shiro解析第一个节点的时候,比如我这里的ini配置文件,shiro一开头读取到是[users]这个节点,到达第17行这条语句的时候,明显shiro还没有读取[users]这个节点内的内容

    所以还不能进行添加

     1 private void addSection(String name, StringBuilder content) {
     2         if (content.length() > 0) {
     3             String contentString = content.toString();
     4             String cleaned = StringUtils.clean(contentString);
     5             if (cleaned != null) {
     6                 Section section = new Section(name, contentString);
     7                 if (!section.isEmpty()) {
     8                     sections.put(name, section);
     9                 }
    10             }
    11         }
    12     }

    二、节点的添加

    接着上面的,当节点内容不为空时,也就是一个节点被完整的读取出来了,那么就会创建节点对象
     1 private void addSection(String name, StringBuilder content) {
     2         if (content.length() > 0) {
     3             String contentString = content.toString();
     4             String cleaned = StringUtils.clean(contentString);
     5             if (cleaned != null) {
     6                 Section section = new Section(name, contentString);
     7                 if (!section.isEmpty()) {
     8                     sections.put(name, section);
     9                 }
    10             }
    11         }
    12     }

    第6行,创建了一个Section对象,这个Section类实现了Map接口,是个map容器,Ini也实现了Map接口是个Map容器,并且Section是Ini的一个嵌套类。

    打开Section这个构造器,它传入了两个参数,一个是节点名,另一个是这个节点下面的内容,如[users],那么节点内容就是

    zhang=123
    wang=123


     1  private Section(String name, String sectionContent) {
     2             if (name == null) {
     3                 throw new NullPointerException("name");
     4             }
     5             this.name = name;
     6             Map<String,String> props;
     7             if (StringUtils.hasText(sectionContent) ) {
     8                 props = toMapProps(sectionContent);//将内容解析存到Map中
     9             } else {
    10                 props = new LinkedHashMap<String,String>();
    11             }
    12             if ( props != null ) {
    13                 this.props = props;
    14             } else {
    15                 this.props = new LinkedHashMap<String,String>();
    16             }
    17         }
    重点看看第8行的内容,这个方法会把

    zhang=123
    wang=123
    解析成键值对的形式存到props这个Map里面
    实现代码为
     1 private static Map<String, String> toMapProps(String content) {
     2             Map<String, String> props = new LinkedHashMap<String, String>();
     3             String line;
     4             StringBuilder lineBuffer = new StringBuilder();
     5             Scanner scanner = new Scanner(content);
     6             while (scanner.hasNextLine()) {
     7                 line = StringUtils.clean(scanner.nextLine());//去掉两边的空白符,如果本身是个空字符串,那么返回null
     8                 if (isContinued(line)) {//判断是否存在反斜杠,如果存在就继续读,反斜杠就像java中的+,表示这些字符串是连在一起的,一行写不下,放到下一行
     9                     //strip off the last continuation backslash:
    10                     line = line.substring(0, line.length() - 1);//去掉反斜杠
    11                     lineBuffer.append(line);
    12                     continue;
    13                 } else {
    14                     lineBuffer.append(line);
    15                 }
    16                 line = lineBuffer.toString();
    17                 lineBuffer = new StringBuilder();
    18                 String[] kvPair = splitKeyValue(line);
    19                 props.put(kvPair[0], kvPair[1]);
    20             }
    21 
    22             return props;
    23         }

    这里有两个比较重点的方法,一个是第8行的isContinued,还有一个是第18行的splitKeyValue方法

    首先看下isContinued

     1 protected static boolean isContinued(String line) {
     2             if (!StringUtils.hasText(line)) {
     3                 return false;
     4             }
     5             int length = line.length();
     6             //find the number of backslashes at the end of the line.  If an even number, the
     7             //backslashes are considered escaped.  If an odd number, the line is considered continued on the next line
     8             int backslashCount = 0;
     9             for (int i = length - 1; i > 0; i--) {
    10                 if (line.charAt(i) == ESCAPE_TOKEN) {//判断时候等于反斜杠
    11                     backslashCount++;
    12                 } else {
    13                     break;
    14                 }
    15             }
    16             return backslashCount % 2 != 0;
    17         }

    上面这段代码的意思是,从一句话的最后开始往前查找反斜杠,如果反斜杠的个数是奇数个,那么就返回true,如果是偶数那么就返回

    false,为什么呢?反斜杠在shiro的配置中被认为是转义字符,比如\那么表示的,只有一个或者奇数个\=》表示用户需要输出一个\,另一个就不会转义,跟java中的反斜杠是

    一样的。

    将每条键值对信息读取完整之后,就可以开始进行key,value的解析了

    现在来看看splitKeyValue方法

     1  protected static String[] splitKeyValue(String keyValueLine) {
     2             String line = StringUtils.clean(keyValueLine);
     3             if (line == null) {
     4                 return null;
     5             }
     6             StringBuilder keyBuffer = new StringBuilder();
     7             StringBuilder valueBuffer = new StringBuilder();
     8 
     9             boolean buildingKey = true; //we'll build the value next:
    10 
    11             for (int i = 0; i < line.length(); i++) {
    12                 char c = line.charAt(i);//循环遍历每个字符
    13 
    14                 if (buildingKey) {//这个值为true时,表示对key值进行解析
    15                     if (isKeyValueSeparatorChar(c) && !isCharEscaped(line, i)) {//isKeyValueSeparatorChar是在判断这个字符是否是:或这=,isCharEscaped表示这个字符前是否存在反斜杠
    16                         buildingKey = false;//now start building the value
    17                     } else {
    18                         keyBuffer.append(c);
    19                     }
    20                 } else {
    21                     if (valueBuffer.length() == 0 && isKeyValueSeparatorChar(c) && !isCharEscaped(line, i)) {
    22                         //swallow the separator chars before we start building the value
    23                     } else {
    24                         valueBuffer.append(c);
    25                     }
    26                 }
    27             }
    28 
    29             String key = StringUtils.clean(keyBuffer.toString());
    30             String value = StringUtils.clean(valueBuffer.toString());
    31 
    32             if (key == null || value == null) {
    33                 String msg = "Line argument must contain a key and a value.  Only one string token was found.";
    34                 throw new IllegalArgumentException(msg);
    35             }
    36 
    37             log.trace("Discovered key/value pair: {}={}", key, value);
    38 
    39             return new String[]{key, value};
    40         }

    第15行的isKeyValueSeparatorChar代码如下

    private static boolean isKeyValueSeparatorChar(char c) {
                return Character.isWhitespace(c) || c == ':' || c == '=';
            }

    isCharEscaped的代码如下

    private static boolean isCharEscaped(CharSequence s, int index) {
                return index > 0 && s.charAt(index - 1) == ESCAPE_TOKEN;//ESCAPE_TOKEN表示反斜杠
            }
    为什么要这么判断,原因很简单就是像=和:都会被转义

    当找到=或者:时,key的解析结束,将buildingKey设置为false,开始解析value,解析value的时候要注意一下第21行的判断语句
    这行判断语句的意思是,当valueBuffer中没有值的时候,如果出现=或这:,那么这些字符将被忽略,比如说zhang===:::123,它会忽略掉第一个等号后面的=或者:
    如果是这样的zhang===qwer=rtet,它只会解析到第一个=后面的=不会被解析,综合以上的判断方式,最后得出的key是zhang,value是qwer=rtet

    解析出key和value后将被存到Section类的props这个Map中
    并且最后节点名字和Section对象会被存到Ini了的sections这个Map中sections.put(name, section);

  • 相关阅读:
    记坑
    常用模板
    ACM-东北赛划水记
    jzoj 4178游戏
    JZOI 4163
    jzoj 4146踩气球
    jzoj 5589. 缩点
    jzoj 5588 %%%
    jzoj 5571 ffs
    BJOI 2017 Kakuro
  • 原文地址:https://www.cnblogs.com/honger/p/6835600.html
Copyright © 2020-2023  润新知