源码分析的第一篇以SecurityManager的初始化为题。
根据ini配置文件初始化shiro的代码主要为两段:
//解析ini文件为Ini对象
Factory<SecurityManager> factory = new IniSecurityManagerFactory(
"classpath:shiro-config.ini");
//根据Ini对象初始化SecurityManager对象
SecurityManager securityManager = factory.getInstance();
ini文件格式说明请参考:
http://zh.wikipedia.org/wiki/INI%E6%AA%94%E6%A1%88
java解析ini的方式也比较多,有兴趣可以参考:
http://my.oschina.net/tinyframework/blog/214309
一、Shiro解析ini的步骤如下:
1、org.apache.shiro.config.IniSecurityManagerFactory类构造方法:
public IniSecurityManagerFactory(String iniResourcePath) {
this(Ini.fromResourcePath(iniResourcePath));
}
将ini文件解析交给Ini的静态方法fromResourcePath完成。并把解析后的Ini对象设置由自身持有
public IniSecurityManagerFactory(Ini config) {
setIni(config);
}
2、org.apache.shiro.config.Ini类解析逻辑:
public static Ini fromResourcePath(String resourcePath) throws ConfigurationException {
if (!StringUtils.hasLength(resourcePath)) {
throw new IllegalArgumentException("Resource Path argument cannot be null or empty.");
}
//此处新建Ini对象
Ini ini = new Ini();
ini.loadFromPath(resourcePath);
return ini;
}
//根据资源路径获取输入流,交给load方法进行处理
public void loadFromPath(String resourcePath) throws ConfigurationException {
InputStream is;
try {
is = ResourceUtils.getInputStreamForPath(resourcePath);
} catch (IOException e) {
throw new ConfigurationException(e);
}
load(is);
}
ResourceUtils.getInputStreamForPath(resourcePath);这里支持三种获取资源的方式:
classpath |
shiro-config.ini |
从类路径中查找ini配置 |
url |
http://....../shiro-config.ini |
从指定的url获取ini配置 |
file |
D:\shiro-config.ini |
从指定的文件路径获取ini配置 |
3、对获取的资源输入流最终交给文本扫描器Scaner,执行过程代码片段:
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);
//此处跳过ini文件格式的注释及空值
if (line == null || line.startsWith(COMMENT_POUND) || line.startsWith(COMMENT_SEMICOLON)) {
//skip empty lines and comments:
continue;
}
//此处主要获取section部分,根据[]规则
String newSectionName = getSectionName(line);
if (newSectionName != null) {
//此处代码主要用于构造Section对象,并放进sections集合中
addSection(sectionName, sectionContent);
//reset the buffer for the new section:
sectionContent = new StringBuilder();
sectionName = newSectionName;
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");
}
}
//finish any remaining buffered content:
addSection(sectionName, sectionContent);
}
上段代码主要是组装ini文件中的section。细心的同学会发现Section、Ini都是实现了Map接口。Section持有的LinkedHashMap容器实际上是当前section中的所有键值对,而Ini持有的LinkedHashMap容器实际上是所有section名称与section对象的键值对。
至此,ini文件的解析已经完成,其ini中的内容已全部以map的形式存放在Ini实例中。
至于保存的结果是什么样的呢?以下面的一段配置为例:
[main]
#authenticator
authenticator=org.apache.shiro.authc.pam.ModularRealmAuthenticator
authenticationStrategy=org.apache.shiro.authc.pam.AtLeastOneSuccessfulStrategy
authenticator.authenticationStrategy=$authenticationStrategy
securityManager.authenticator=$authenticator
Ini的sections属性[key=main,value=Section对象],此Section对象内部的props保存了所有main部分key-value映射。目前都是String类型,实例化在下一步完成的。
二、由Ini实例构造SecurityManager对象
SecurityManager securityManager = factory.getInstance();
IniSecurityManagerFactory的继承关系为:
IniSecurityManagerFactory->IniFactorySupport->AbstractFactory->Factory
这里的getInstance方法由最上级的抽象类:org.apache.shiro.util.AbstractFactory提供,源码如下:
//该方法只是用于判断是否单例(默认为单例)
public T getInstance() {
T instance;
if (isSingleton()) {
if (this.singletonInstance == null) {
this.singletonInstance = createInstance();
}
instance = this.singletonInstance;
} else {
instance = createInstance();
}
if (instance == null) {
String msg = "Factory 'createInstance' implementation returned a null object.";
throw new IllegalStateException(msg);
}
return instance;
}
//由子类IniFactorySupport创建实例
protected abstract T createInstance();
IniFactorySupport的createInstance实现如下(省略了无关紧要的日志、判断代码):
public T createInstance() {
//此处获取解析ini文件产生的Ini对象
Ini ini = resolveIni();
T instance;
if (CollectionUtils.isEmpty(ini)) {
//如果ini为空,则创建默认实例
instance = createDefaultInstance();
} else {
//根据ini对象创建实例
instance = createInstance(ini);
}
return instance;
}
protected abstract T createInstance(Ini ini);
protected abstract T createDefaultInstance();
上面两个抽象方法,则交给实现类IniSecurityManagerFactory完成了。
先阅读createDefaultInstance方法源码
protected SecurityManager createDefaultInstance() {
return new DefaultSecurityManager();
}
终于在这个不起眼的地方,看到了初始化最核心的接口SecurityManager了。稍微暂停一下,发个SecurityManager的类图:
由此图可以看出来,SecurityManager继承了三个接口(认证、授权、session管理),认证授权是安全框架最核心的功能,而shiro提供了自身的session管理机制(这也是shiro的一大亮点)。图中除了DefaultSecurityManager,其它所有类都是抽象类,由此可看出,DefaultSecurityManager是作为默认的安全管理器。
再来看new DefaultSecurityManager();做的事情
public DefaultSecurityManager() {
super();
this.subjectFactory = new DefaultSubjectFactory();
this.subjectDAO = new DefaultSubjectDAO();
}
这里暂时不深究每个构造器所做的具体事情。有兴趣的同学,可一直观察DefaultSecurityManager的所有父类构造器的处理逻辑。
类名称 |
构造方法创建的默认对象 |
DefaultSecurityManager |
DefaultSubjectFactory,DefaultSubjectDAO |
SessionsSecurityManager |
DefaultSessionManager |
AuthorizingSecurityManager |
ModularRealmAuthorizer |
AuthenticatingSecurityManager |
ModularRealmAuthenticator |
RealmSecurityManager |
无 |
CachingSecurityManager |
无 |
在上面的表格中已经清晰的描述了各个类所设置的默认对象,至于用途后面再讲解。