shiro解析ini文件
来吧,看看shiro是怎么解析ini文件的,这里假设ini文件在classpath下,名字叫做shiro.ini
Factory<org.apache.shiro.mgt.SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
shiro.ini
[users]
zhang=
wang= [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配置文件
public static InputStream getInputStreamForPath(String resourcePath) throws IOException {
InputStream is;
if (resourcePath.startsWith(CLASSPATH_PREFIX)) {//判断是否为classpath:开头的
is = loadFromClassPath(stripPrefix(resourcePath));
} else if (resourcePath.startsWith(URL_PREFIX)) {//判断是否为url:开头
is = loadFromUrl(stripPrefix(resourcePath));
} else if (resourcePath.startsWith(FILE_PREFIX)) {//判断是否为file:开头
is = loadFromFile(stripPrefix(resourcePath));
} else {
is = loadFromFile(resourcePath);
}
if (is == null) {
throw new IOException("Resource [" + resourcePath + "] could not be found.");
}
return is;
}
上面的代码中对我们传进来的配置文件进行前缀判断,再以相应的方法取加载它
stripPrefix(resourcePath)是去掉前缀,那么传进去的classpath:shiro.ini就变成shiro.ini了,下面就是加载配置文件的方法
public static InputStream getResourceAsStream(String name) {
InputStream is = THREAD_CL_ACCESSOR.getResourceStream(name);
if (is == null) {
if (log.isTraceEnabled()) {
log.trace("Resource [" + name + "] was not found via the thread context ClassLoader. Trying the " +
"current ClassLoader...");
}
is = CLASS_CL_ACCESSOR.getResourceStream(name);
}
if (is == null) {
if (log.isTraceEnabled()) {
log.trace("Resource [" + name + "] was not found via the current class loader. Trying the " +
"system/application ClassLoader...");
}
is = SYSTEM_CL_ACCESSOR.getResourceStream(name);
}
if (is == null && log.isTraceEnabled()) {
log.trace("Resource [" + name + "] was not found via the thread context, current, or " +
"system/application ClassLoaders. All heuristics have been exhausted. Returning null.");
}
return is;
}
加载配置文件的时候,首先使用了线程的上下文加载器,如果加载不到就用类加载器,下面是这些加载器的获取代码
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)方法
public void load(Reader reader) {
Scanner scanner = new Scanner(reader);
try {
load(scanner);
} finally {
try {
scanner.close();
} catch (Exception e) {
log.debug("Unable to cleanly close the InputStream scanner. Non-critical - ignoring.", e);
}
}
}
上面使用了Scanner类对reader进行了包装,随后有调用了load(scanner);
public void load(Scanner scanner) {
String sectionName = DEFAULT_SECTION_NAME;//默认节点名称为空字符串
StringBuilder sectionContent = new StringBuilder();//用于保存节点的内容
while (scanner.hasNextLine()) {
String rawLine = scanner.nextLine();//读取一行数据
String line = StringUtils.clean(rawLine);//去除字符串的两边的空白字符,如果这个字符是空字符串,那么返回null
if (line == null || line.startsWith(COMMENT_POUND) || line.startsWith(COMMENT_SEMICOLON)) {//判断这行数据是否为null,或者是以#或者是;开头的注释.
//skip empty lines and comments:
continue;
}
String newSectionName = getSectionName(line);//判断是否为节点名(如[main]这样的)并且去掉[],如[main]
if (newSectionName != null) {//如果节点不为空,那么就添加节点
//found a new section - convert the currently buffered one into a Section object
addSection(sectionName, sectionContent);//添加节点
//reset the buffer for the new section:
sectionContent = new StringBuilder();
sectionName = newSectionName; //保存节点名,在读取完配置文件后,还得通过它添加节点(第36行代码需要用到)
if (log.isDebugEnabled()) {
log.debug("Parsing " + SECTION_PREFIX + sectionName + SECTION_SUFFIX);
}
} else {
//normal line - add it to the existing content buffer:
sectionContent.append(rawLine).append("\n");//说名读取到这行不是节点名,那么就将内容保存到sectionContent中
}
}
//finish any remaining buffered content:
addSection(sectionName, sectionContent);//读到文件结尾时添加最后的这个节点
}
第19行是添加节点,下面是添加节点的判断代码,它首先要确认你这个节点内是否有内容,如果没有就不添加,这种情况一般发生在
shiro解析第一个节点的时候,比如我这里的ini配置文件,shiro一开头读取到是[users]这个节点,到达第17行这条语句的时候,明显shiro还没有读取[users]这个节点内的内容
所以还不能进行添加
private void addSection(String name, StringBuilder content) {
if (content.length() > ) {
String contentString = content.toString();
String cleaned = StringUtils.clean(contentString);
if (cleaned != null) {
Section section = new Section(name, contentString);
if (!section.isEmpty()) {
sections.put(name, section);
}
}
}
}
二、节点的添加 接着上面的,当节点内容不为空时,也就是一个节点被完整的读取出来了,那么就会创建节点对象
private void addSection(String name, StringBuilder content) {
if (content.length() > ) {
String contentString = content.toString();
String cleaned = StringUtils.clean(contentString);
if (cleaned != null) {
Section section = new Section(name, contentString);
if (!section.isEmpty()) {
sections.put(name, section);
}
}
}
}
第6行,创建了一个Section对象,这个Section类实现了Map接口,是个map容器,Ini也实现了Map接口是个Map容器,并且Section是Ini的一个嵌套类。
打开Section这个构造器,它传入了两个参数,一个是节点名,另一个是这个节点下面的内容,如[users],那么节点内容就是
zhang=123
wang=123
private Section(String name, String sectionContent) {
if (name == null) {
throw new NullPointerException("name");
}
this.name = name;
Map<String,String> props;
if (StringUtils.hasText(sectionContent) ) {
props = toMapProps(sectionContent);//将内容解析存到Map中
} else {
props = new LinkedHashMap<String,String>();
}
if ( props != null ) {
this.props = props;
} else {
this.props = new LinkedHashMap<String,String>();
}
}
重点看看第8行的内容,这个方法会把
zhang=123
wang=123
解析成键值对的形式存到props这个Map里面
实现代码为
private static Map<String, String> toMapProps(String content) {
Map<String, String> props = new LinkedHashMap<String, String>();
String line;
StringBuilder lineBuffer = new StringBuilder();
Scanner scanner = new Scanner(content);
while (scanner.hasNextLine()) {
line = StringUtils.clean(scanner.nextLine());//去掉两边的空白符,如果本身是个空字符串,那么返回null
if (isContinued(line)) {//判断是否存在反斜杠\,如果存在就继续读,反斜杠就像java中的+,表示这些字符串是连在一起的,一行写不下,放到下一行
//strip off the last continuation backslash:
line = line.substring(, line.length() - );//去掉反斜杠
lineBuffer.append(line);
continue;
} else {
lineBuffer.append(line);
}
line = lineBuffer.toString();
lineBuffer = new StringBuilder();
String[] kvPair = splitKeyValue(line);
props.put(kvPair[], kvPair[]);
}
return props;
}
这里有两个比较重点的方法,一个是第8行的isContinued,还有一个是第18行的splitKeyValue方法
首先看下isContinued
protected static boolean isContinued(String line) {
if (!StringUtils.hasText(line)) {
return false;
}
int length = line.length();
//find the number of backslashes at the end of the line. If an even number, the
//backslashes are considered escaped. If an odd number, the line is considered continued on the next line
int backslashCount = ;
for (int i = length - ; i > ; i--) {
if (line.charAt(i) == ESCAPE_TOKEN) {//判断时候等于反斜杠
backslashCount++;
} else {
break;
}
}
return backslashCount % != ;
}
上面这段代码的意思是,从一句话的最后开始往前查找反斜杠,如果反斜杠的个数是奇数个,那么就返回true,如果是偶数那么就返回
false,为什么呢?反斜杠在shiro的配置中被认为是转义字符,比如\\那么表示的\,只有一个\或者奇数个\\\=》表示用户需要输出一个\,另一个\就不会转义,跟java中的反斜杠是
一样的。
将每条键值对信息读取完整之后,就可以开始进行key,value的解析了
现在来看看splitKeyValue方法
protected static String[] splitKeyValue(String keyValueLine) {
String line = StringUtils.clean(keyValueLine);
if (line == null) {
return null;
}
StringBuilder keyBuffer = new StringBuilder();
StringBuilder valueBuffer = new StringBuilder();
boolean buildingKey = true; //we'll build the value next:
for (int i = ; i < line.length(); i++) {
char c = line.charAt(i);//循环遍历每个字符
if (buildingKey) {//这个值为true时,表示对key值进行解析
if (isKeyValueSeparatorChar(c) && !isCharEscaped(line, i)) {//isKeyValueSeparatorChar是在判断这个字符是否是:或这=,isCharEscaped表示这个字符前是否存在反斜杠
buildingKey = false;//now start building the value
} else {
keyBuffer.append(c);
}
} else {
if (valueBuffer.length() == && isKeyValueSeparatorChar(c) && !isCharEscaped(line, i)) {
//swallow the separator chars before we start building the value
} else {
valueBuffer.append(c);
}
}
}
String key = StringUtils.clean(keyBuffer.toString());
String value = StringUtils.clean(valueBuffer.toString());
if (key == null || value == null) {
String msg = "Line argument must contain a key and a value. Only one string token was found.";
throw new IllegalArgumentException(msg);
}
log.trace("Discovered key/value pair: {}={}", key, value);
return new String[]{key, value};
}
第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 > && s.charAt(index - ) == 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);
shiro解析ini文件的更多相关文章
- boost::property_tree读取解析ini文件--推荐
boost::property_tree读取解析ini文件 #include "stdafx.h" #include <iostream> #include <b ...
- 实战parse_ini_file()及扩展函数解析ini文件完整版
文章来源:PHP开发学习门户 地址:http://www.phpthinking.com/archives/587 在PHP站点开发的过程中,往往会用到读取ini參数配置文件,比方须要訪问一些复杂的借 ...
- python解析ini文件
python解析ini文件 使用configparser - Configuration file parser sections() add_section(section) has_section ...
- 解决ini-parser解析ini文件中文乱码问题
rickyah/ini-parser 是一个.net 平台解析ini文件的库,当ini文件中含有中文字符时会乱码. 解决:将文件通过Editplus 等文本编辑工具保存为 utf-8 + bom 格式 ...
- C++ 中使用boost::property_tree读取解析ini文件
boost 官网 http://www.boost.org/ 下载页面 http://sourceforge.net/projects/boost/files/boost/1.53.0/ 我下载的是 ...
- 状态机学习(二)解析INI文件
题目来自<系统程序员成长计划> 作者:李先静. 状态变化如下 #include <string> #include <iostream> using namespa ...
- python操作ini文件
简介 ini文件作为常见的配置文件,因此需要对ini文件做处理,此处使用configparser模块,本文介绍以下ini文件常用的处理方式. 需要读取的ini文件 如下文件,[ ]包含的称为secti ...
- C语言解析Ini格式文件
引用别人的博文: http://www.open-open.com/lib/view/open1402278076447.html 可以解析 INI 格式的字符串.解析文件.保存到文件. 下面是头文件 ...
- ini文件解析c库(iniparser)
一.交叉编译ini解析库 1.官方网站http://ndevilla.free.fr/iniparser 下载iniparser-3.1.tar.gz 2.解压 tar -zxvf iniparser ...
随机推荐
- WebP 大战 JPEG,谁才是真正的王者?
目前在互联网上,图片流量仍占据较大的一部分.因此,在保证图片质量不变的情况下,节省流量带宽是大家一直需要去解决的问题.传统的图片格式,如 JPEG,PNG,GIF 等格式图片已经没有太多的优化空间.因 ...
- 自定义vue全局组件use使用、vuex的使用
自定义vue全局组件use使用(解释vue.use()的原理)我们在前面学习到是用别人的组件:Vue.use(VueRouter).Vue.use(Mint)等等.其实使用的这些都是全剧组件,这里我们 ...
- 利用切片操作,实现一个trim()函数,去除字符串首尾的空格,注意不要调用str的strip()方法
1.利用切片操作,实现一个trim()函数,去除字符串首尾的空格,注意不要调用str的strip()方法 首先判断字符串的长度是否为0,如果是,直接返回字符串 第二,循环判断字符串的首部是否有空格,如 ...
- WebGL 着色器偏导数dFdx和dFdy介绍
本文适合对webgl.计算机图形学.前端可视化感兴趣的读者. 偏导数函数(HLSL中的ddx和ddy,GLSL中的dFdx和dFdy)是片元着色器中的一个用于计算任何变量基于屏幕空间坐标的变化率的指令 ...
- Cisco packet tracer6.0下的网络工程实训
期末的专业实训,在cisco6.0下配置一个简单的局域网.主要用到了下面几个技术:dhcp中继.vlan的划分.链路聚合.静态nat.ospf协议.访问控制列表.先看一下总的拓扑图,在分步实现功能. ...
- CSU 1808:地铁(Dijkstra)
http://acm.csu.edu.cn/csuoj/problemset/problem?pid=1808 题意:…… 思路:和之前的天梯赛的一题一样,但是简单点. 没办法直接用点去算.把边看成点 ...
- c#中bin,obj,properties文件夹的作用
Bin 目录用来存放编译的结果,bin是二进制binrary的英文缩写,因为最初C编译的程序文件都是二进制文件,它有Debug和Release两个版本,分别对应的文件夹为bin/Debug和bin/R ...
- 为什么Python 3.6以后字典有序并且效率更高?
在Python 3.5(含)以前,字典是不能保证顺序的,键值对A先插入字典,键值对B后插入字典,但是当你打印字典的Keys列表时,你会发现B可能在A的前面. 但是从Python 3.6开始,字典是变成 ...
- C程序中可怕的野指针
一.疑问点指针是C语言一个很强大的功能,同时也是很容易让人犯错的一个功能,用错了指针,轻者只是报个错,重者可能整个系统都崩溃了.下面是大家在编写C程序时,经常遇到的一种错误的使用方法,也许在你的学习和 ...
- 小代学Spring Boot之集成MyBatis
想要获取更多文章可以访问我的博客 - 代码无止境. 上一篇小代同学在Spring Boot项目中配置了数据源,但是通常来讲我们访问数据库都会通过一个ORM框架,很少会直接使用JDBC来执行数据库操作的 ...