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("\n");//說名讀取到這行不是節點名,那么就將內容保存到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);

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM