Shiro源码分析-初始化-SecurityManager

开涛的《跟我学Shiro》系列已即将完成,该系列囊括了shiro的绝大部分实用功能,并且在讲解如何用的过程中,也添加了其内部实现的原理。开涛主要以Shiro的使用者为角度,所以其原理部分是穿插在各个章节内的。

源码分析的第一篇以SecurityManager的初始化为题。
根据ini配置文件初始化shiro的代码主要为两段:

//解析ini文件为Ini对象
Factory<SecurityManager> factory = new IniSecurityManagerFactory(
"classpath:shiro-config.ini");
//根据Ini对象初始化SecurityManager对象
SecurityManager securityManager = factory.getInstance();

[color=red]
ini文件格式说明请参考:[/color]
[url]http://zh.wikipedia.org/wiki/INI%E6%AA%94%E6%A1%88[/url]
java解析ini的方式也比较多,有兴趣可以参考:
[url]http://my.oschina.net/tinyframework/blog/214309[/url]

[color=red][b]一、Shiro解析ini的步骤如下:[/b][/color]
[color=red]1、org.apache.shiro.config.IniSecurityManagerFactory类构造方法:[/color]

public IniSecurityManagerFactory(String iniResourcePath) {
this(Ini.fromResourcePath(iniResourcePath));
}

将ini文件解析交给Ini的静态方法fromResourcePath完成。并把解析后的Ini对象设置由自身持有

public IniSecurityManagerFactory(Ini config) {
setIni(config);
}


[color=red]2、org.apache.shiro.config.Ini类解析逻辑:[/color]

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);这里支持三种获取资源的方式:
[table]
|classpath|shiro-config.ini|从类路径中查找ini配置|
|url|http://....../shiro-config.ini|从指定的url获取ini配置|
|file|D:\shiro-config.ini|从指定的文件路径获取ini配置|
[/table]


[color=red]3、对获取的资源输入流最终交给文本扫描器Scaner,执行过程代码片段:[/color]

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类型,实例化在下一步完成的。

[color=red][b]二、由Ini实例构造SecurityManager对象[/b][/color]

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的类图:
[img]http://dl2.iteye.com/upload/attachment/0095/6391/5470ab90-8208-382f-9711-a6efadfbab46.png[/img]
由此图可以看出来,SecurityManager继承了三个接口(认证、授权、session管理),认证授权是安全框架最核心的功能,而shiro提供了自身的session管理机制(这也是shiro的一大亮点)。图中除了DefaultSecurityManager,其它所有类都是抽象类,由此可看出,DefaultSecurityManager是作为默认的安全管理器。
再来看new DefaultSecurityManager();做的事情

public DefaultSecurityManager() {
super();
this.subjectFactory = new DefaultSubjectFactory();
this.subjectDAO = new DefaultSubjectDAO();
}

这里暂时不深究每个构造器所做的具体事情。有兴趣的同学,可一直观察DefaultSecurityManager的所有父类构造器的处理逻辑。
[table]
|类名称|构造方法创建的默认对象|
|DefaultSecurityManager|DefaultSubjectFactory,DefaultSubjectDAO|
|SessionsSecurityManager|DefaultSessionManager|
|AuthorizingSecurityManager|ModularRealmAuthorizer|
|AuthenticatingSecurityManager|ModularRealmAuthenticator|
|RealmSecurityManager|无|
|CachingSecurityManager|无|
[/table]
在上面的表格中已经清晰的描述了各个类所设置的默认对象,至于用途后面再讲解。需要注意的是,RealmSecurityManager、CachingSecurityManager并没有设置默认的对象,所以这个是交给开发人员自己配置的。

接下来看createInstance实现细节:

protected SecurityManager createInstance(Ini ini) {
if (CollectionUtils.isEmpty(ini)) {
throw new NullPointerException("Ini argument cannot be null or empty.");
}
//SecurityManager的创建工作交给createSecurityManager方法
SecurityManager securityManager = createSecurityManager(ini);
if (securityManager == null) {
String msg = SecurityManager.class + " instance cannot be null.";
throw new ConfigurationException(msg);
}
return securityManager;
}

private SecurityManager createSecurityManager(Ini ini) {
//单独获取main部分进行初始化
Ini.Section mainSection = ini.getSection(MAIN_SECTION_NAME);
if (CollectionUtils.isEmpty(mainSection)) {
//try the default:
mainSection = ini.getSection(Ini.DEFAULT_SECTION_NAME);
}
return createSecurityManager(ini, mainSection);
}

private SecurityManager createSecurityManager(Ini ini, Ini.Section mainSection) {
//创建DefaultSecurityManager,并且根据ini初始化realm
Map<String, ?> defaults = createDefaults(ini, mainSection);
//根据main部分构造对象(包括属性依赖,创建实例等)
Map<String, ?> objects = buildInstances(mainSection, defaults);
//从ReflectionBuilder的objects获取SecurityManager实例。ReflectionBuilder负责shiro的所有反射构造实例的工作,并且把实例放在objects的集合中持有。
SecurityManager securityManager = getSecurityManagerBean();
//如果显示指定了realm,则不会自动配置realm。
boolean autoApplyRealms = isAutoApplyRealms(securityManager);

if (autoApplyRealms) {
//如果在ini文件中未显示指定realm(即:securityManager.realms=$myRealm1)
//那么ini配置的所有realm会自动设置(即:realm1=test.Realm1)
//getRealms方法只是把ini配置的所有实例对象中实现Realm、RealmFactory接口的实例添加到SecurityManager中
Collection<Realm> realms = getRealms(objects);
//set them on the SecurityManager
if (!CollectionUtils.isEmpty(realms)) {
applyRealmsToSecurityManager(realms, securityManager);
}
}

return securityManager;
}

//创建默认的SecurityManager
protected Map<String, ?> createDefaults(Ini ini, Ini.Section mainSection) {
Map<String, Object> defaults = new LinkedHashMap<String, Object>();
//这里还是先创建了DefaultSecurityManager
SecurityManager securityManager = createDefaultInstance();
defaults.put(SECURITY_MANAGER_NAME, securityManager);
//判断ini是否配置了roles、users
if (shouldImplicitlyCreateRealm(ini)) {
//如果配置了roles或users,则创建IniRealm
Realm realm = createRealm(ini);
if (realm != null) {
defaults.put(INI_REALM_NAME, realm);
}
}

return defaults;
}
//对main部分配置的所有类型、属性进行实例化,并设置依赖,放到objects集合中
private Map<String, ?> buildInstances(Ini.Section section, Map<String, ?> defaults) {
this.builder = new ReflectionBuilder(defaults);
return this.builder.buildObjects(section);
}


再看看ReflectionBuilder如何对main进行实例化及依赖的设置:

public Map<String, ?> buildObjects(Map<String, String> kvPairs) {
if (kvPairs != null && !kvPairs.isEmpty()) {
//实例配置集合
Map<String, String> instanceMap = new LinkedHashMap<String, String>();
//属性依赖配置集合
Map<String, String> propertyMap = new LinkedHashMap<String, String>();

for (Map.Entry<String, String> entry : kvPairs.entrySet()) {
//如果ini配置的key包括.号,或者以.class后缀的名称,则认为类型配置
if (entry.getKey().indexOf('.') < 0 || entry.getKey().endsWith(".class")) {
instanceMap.put(entry.getKey(), entry.getValue());
//其它情况,则认为是属性依赖配置
} else {
propertyMap.put(entry.getKey(), entry.getValue());
}
}

// Create all instances
for (Map.Entry<String, String> entry : instanceMap.entrySet()) {
//实例化每个类型
createNewInstance((Map<String, Object>) objects, entry.getKey(), entry.getValue());
}

// Set all properties
for (Map.Entry<String, String> entry : propertyMap.entrySet()) {
//配置所有的依赖。必须有个先后顺序,先实例化再设置依赖
applyProperty(entry.getKey(), entry.getValue(), objects);
}
}

//SHIRO-413: init method must be called for constructed objects that are Initializable
//如果配置的类型实现了Initializable接口,则调用其init方法
LifecycleUtils.init(objects.values());

return objects;
}


SecurityManager的初始化部分已经完成。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值