概述
本文属于源码分析,只想了解Shiro使用的读者请略过。
《Shiro学习(一)——Shiro配置与快速开始》提出了几个问题。本文要分析:
1、ini文件是怎么读入并被初始化?
2、securityManager具体是用哪个实现类,是怎么初始化得来的?
3、默认Realm是什么,怎么来的?
读取配置文件
在Shiro使用的例子中,最开始一般都会调用以下语句:
Factory<org.apache.shiro.mgt.SecurityManager> factory =
new IniSecurityManagerFactory("classpath:shiro.ini");
尽管官方推荐使用Environment,我们这里仍然用IniSecurityManagerFactory来举例,因为Environment内部也是调用IniSecurityManagerFactory并且通过Environment初始化,同时初始化SecurityManager,我希望分开两步来讲。查看下BasicIniEnvironment.java:
public class BasicIniEnvironment extends DefaultEnvironment {
public BasicIniEnvironment(Ini ini) {
setSecurityManager(new IniSecurityManagerFactory(ini).getInstance());
}
public BasicIniEnvironment(String iniResourcePath) {
this(Ini.fromResourcePath(iniResourcePath));
}
}
可以看到3-5行实际上等于调用了创建IniSecurityManagerFactory并且初始化SecurityManager。
不谈Environment,回到IniSecurityManagerFactory上。看看IniSecurityManagerFactory的初始化
public class IniSecurityManagerFactory extends IniFactorySupport<SecurityManager> {
//......
public IniSecurityManagerFactory() {
this.builder = new ReflectionBuilder();
}
public IniSecurityManagerFactory(Ini config) {
this();
setIni(config);
}
public IniSecurityManagerFactory(String iniResourcePath) {
this(Ini.fromResourcePath(iniResourcePath));
}
//......
}
13-15行是例子一般调用的构造方法,构造方法很简单,就是根据配置文件初始化Ini类,然后设置到IniSecurityManagerFactory的成员变量上。所以这个构造方法的核心是通过配置文件构造Ini类。我们从Ini.fromResourcePath(iniResourcePath)跟踪下去:
public class Ini implements Map<String, Ini.Section> {
public static final String DEFAULT_SECTION_NAME = ""; //empty string means the first unnamed section
public static final String DEFAULT_CHARSET_NAME = "UTF-8";
public static final String COMMENT_POUND = "#";
public static final String COMMENT_SEMICOLON = ";";
public static final String SECTION_PREFIX = "[";
public static final String SECTION_SUFFIX = "]";
protected static final char ESCAPE_TOKEN = '\\';
private final Map<String, Section> sections;
//......
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 = new Ini();
ini.loadFromPath(resourcePath);
return ini;
}
//......
private void addSection(String name, StringBuilder content) {
if (content.length() > 0) {
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);
}
}
}
}
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);
if (line == null || line.startsWith(COMMENT_POUND) || line.startsWith(COMMENT_SEMICOLON)) {
continue;
}
String newSectionName = getSectionName(line);
if (newSectionName != null) {
addSection(sectionName, sectionContent);
sectionContent = new StringBuilder();
sectionName = newSectionName;
} else {
sectionContent.append(rawLine).append("\n");
}
}
addSection(sectionName, sectionContent);
}
//......
}
21行新建Ini类,22行通过读取配置文件进行设置。这其中获取输入流涉及层层调用,这里不叙述这些调用,直接到41行的load通过Scanner读取配置文件。45行开始逐行循环调用,47行读取一行文本,50行判断若为null或者前缀为#或者//的话,则略过这行。54获取sectionName,sectionName就是“[XXX]”里面的东西,例如上一篇文章中的[user]和[main]里面的user和main就是sectionName。55行判断若该行为sectionName,56-58行则把前一个sectionName及其内容进行保存,若该行不为sectionName,则表示该行是sectionName下面的key-value配置,增加到sectionContent字符串中。
28-39行为新增Section操作。若传入的content经过去出前后空格操作后不为空,则33行创建一个Section,35行保存至Ini类的map中。
那Section又是什么呢?它是Ini类的内部类,我们跟踪看看创建Section的情况:
public static class Section implements Map<String, String> {
private final String name;
private final Map<String, String> props;
//......
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);
} else {
props = new LinkedHashMap<String,String>();
}
if ( props != null ) {
this.props = props;
} else {
this.props = new LinkedHashMap<String,String>();
}
}
//......
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());
if (isContinued(line)) {
line = line.substring(0, line.length() - 1);
lineBuffer.append(line);
continue;
} else {
lineBuffer.append(line);
}
line = lineBuffer.toString();
lineBuffer = new StringBuilder();
String[] kvPair = splitKeyValue(line);
props.put(kvPair[0], kvPair[1]);
}
return props;
}
//......
}
14行调用toMapProps方法,把Section下的sectionContent字符串内容变为map形式的key-value。32行开始逐行循环读取sectionContent字符串,34行的isContinued判断行末是否为"\\",即该行还没结束,是的话则继续读取下一行,否则该行就是一条key-value。43行splitKeyValue作用是把字符串按“:”或“=”进行分割,44行分别作为key和value存入props中。
总的来说,就是把ini文件中[XXX]里面的内容作为sectionName,sectionName下面的内容作为key-value保存到Section类的props属性中。可以写一段测试程序测试一下:
@Test
public void testIni() {
Collection<Section> sections = Ini.fromResourcePath("classpath:shiro-jdbc-realm.ini").getSections();
Iterator<Section> iterator = sections.iterator();
while(iterator.hasNext()) {
Section section = iterator.next();
String sectionName = section.getName();
System.out.println("sectionName:" + sectionName);
Set<String> set = section.keySet();
for(String prop : set) {
System.out.println("key:" + prop + "\t" + "value:" + section.get(prop));
}
}
}
这里classpath:shiro-jdbc-realm.ini就是《Shiro学习(一)——Shiro配置与快速开始》中数据库ini配置,执行后控制台输出:
sectionName:main
key:jdbcRealm value:org.apache.shiro.realm.jdbc.JdbcRealm
key:dataSource value:com.alibaba.druid.pool.DruidDataSource
key:dataSource.driverClassName value:com.mysql.jdbc.Driver
key:dataSource.url value:jdbc:mysql://localhost:3306/test
key:dataSource.username value:root
key:jdbcRealm.dataSource value:$dataSource
key:securityManager.realms value:$jdbcRealm
可以看到Ini.fromResourcePath这个操作就是把ini文件读入并按key-value保存好。
创建SecurityManager
读取ini配置之后,下一个步骤就是要生成SecurityManager。例子通过以下代码实现:
org.apache.shiro.mgt.SecurityManager securityManager = factory.getInstance();
显然要跟踪factory.getInstance()方法。这里会调用AbstractFactory的getInstance:
public abstract class AbstractFactory<T> implements Factory<T> {
private boolean singleton;
private T singletonInstance;
public AbstractFactory() {
this.singleton = true;
}
public boolean isSingleton() {
return singleton;
}
public void setSingleton(boolean singleton) {
this.singleton = singleton;
}
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;
}
protected abstract T createInstance();
}
可以看到AbstractFactory默认构造方法设置singleton为true,即单例模式。20-27行根据singleton判断是否按单例模式创建SecurityManager实,createInstance方法就是具体创建方法,这里调用的是IniFactorySupport的createInstance:
public abstract class IniFactorySupport<T> extends AbstractFactory<T> {
//......
protected Ini resolveIni() {
Ini ini = getIni();
if (CollectionUtils.isEmpty(ini)) {
log.debug("Null or empty Ini instance. Falling back to the default {} file.", DEFAULT_INI_RESOURCE_PATH);
ini = loadDefaultClassPathIni();
}
return ini;
}
public T createInstance() {
Ini ini = resolveIni();
T instance;
if (CollectionUtils.isEmpty(ini)) {
instance = createDefaultInstance();
if (instance == null) {
String msg = getClass().getName() + " implementation did not return a default instance in " +
"the event of a null/empty Ini configuration. This is required to support the " +
"Factory interface. Please check your implementation.";
throw new IllegalStateException(msg);
}
} else {
instance = createInstance(ini);
if (instance == null) {
String msg = getClass().getName() + " implementation did not return a constructed instance from " +
"the createInstance(Ini) method implementation.";
throw new IllegalStateException(msg);
}
}
return instance;
}
//......
}
19行若ini为空,则调用createDefaultInstance,这个调用最终会创建DefaultSecurityManager。若存在ini(例子中我们存在),则调用28行,这里会调用IniSecurityManagerFactory的createSecurityManager:
public class IniSecurityManagerFactory extends IniFactorySupport<SecurityManager> {
//......
protected SecurityManager createDefaultInstance() {
return new DefaultSecurityManager();
}
protected SecurityManager createInstance(Ini ini) {
if (CollectionUtils.isEmpty(ini)) {
throw new NullPointerException("Ini argument cannot be null or empty.");
}
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) {
return createSecurityManager(ini, getConfigSection(ini));
}
//......
private SecurityManager createSecurityManager(Ini ini, Ini.Section mainSection) {
getReflectionBuilder().setObjects(createDefaults(ini, mainSection));
Map<String, ?> objects = buildInstances(mainSection);
SecurityManager securityManager = getSecurityManagerBean();
boolean autoApplyRealms = isAutoApplyRealms(securityManager);
if (autoApplyRealms) {
Collection<Realm> realms = getRealms(objects);
if (!CollectionUtils.isEmpty(realms)) {
applyRealmsToSecurityManager(realms, securityManager);
}
}
return securityManager;
}
protected Map<String, ?> createDefaults(Ini ini, Ini.Section mainSection) {
Map<String, Object> defaults = new LinkedHashMap<String, Object>();
SecurityManager securityManager = createDefaultInstance();
defaults.put(SECURITY_MANAGER_NAME, securityManager);
if (shouldImplicitlyCreateRealm(ini)) {
Realm realm = createRealm(ini);
if (realm != null) {
defaults.put(INI_REALM_NAME, realm);
}
}
// The values from 'getDefaults()' will override the above.
Map<String, ?> defaultBeans = getDefaults();
if (!CollectionUtils.isEmpty(defaultBeans)) {
defaults.putAll(defaultBeans);
}
return defaults;
}
//......
}
首先调用22行,然后跳到27行。29行createDefaults创建SecurityManager并设置到ReflectionBuilder中保存。39行把Realm设置到SecurityManager中。跟踪createDefaults看它如何创建SecurityManager,49行调用createDefaultInstance,跳到第5行,第6行就是我们要找的,新建DefaultSecurityManager!
默认Realm的创建
Realm是什么?读者可以理解为它就是一个获取用户帐号密码信息与授权信息,用于后续比对的一个模块。有默认的实现方式,我们也可以自定义,例如我们把用户信息和授权信息放在数据库,具体要查哪个表,我们就自定义Realm去实现。
上面一节也提到了会把Realm设置到SecurityManager中,那么默认的Realm又是什么?我们跟踪IniSecurityManagerFactory里面createSecurityManager方法中:
public class IniSecurityManagerFactory extends IniFactorySupport<SecurityManager> {
//......
private SecurityManager createSecurityManager(Ini ini, Ini.Section mainSection) {
getReflectionBuilder().setObjects(createDefaults(ini, mainSection));
Map<String, ?> objects = buildInstances(mainSection);
SecurityManager securityManager = getSecurityManagerBean();
boolean autoApplyRealms = isAutoApplyRealms(securityManager);
if (autoApplyRealms) {
Collection<Realm> realms = getRealms(objects);
if (!CollectionUtils.isEmpty(realms)) {
applyRealmsToSecurityManager(realms, securityManager);
}
}
return securityManager;
}
protected Map<String, ?> createDefaults(Ini ini, Ini.Section mainSection) {
Map<String, Object> defaults = new LinkedHashMap<String, Object>();
SecurityManager securityManager = createDefaultInstance();
defaults.put(SECURITY_MANAGER_NAME, securityManager);
if (shouldImplicitlyCreateRealm(ini)) {
Realm realm = createRealm(ini);
if (realm != null) {
defaults.put(INI_REALM_NAME, realm);
}
}
Map<String, ?> defaultBeans = getDefaults();
if (!CollectionUtils.isEmpty(defaultBeans)) {
defaults.putAll(defaultBeans);
}
return defaults;
}
//......
protected boolean shouldImplicitlyCreateRealm(Ini ini) {
return !CollectionUtils.isEmpty(ini) &&
(!CollectionUtils.isEmpty(ini.getSection(IniRealm.ROLES_SECTION_NAME)) ||
!CollectionUtils.isEmpty(ini.getSection(IniRealm.USERS_SECTION_NAME)));
}
protected Realm createRealm(Ini ini) {
IniRealm realm = new IniRealm();
realm.setName(INI_REALM_NAME);
realm.setIni(ini);
return realm;
}
//......
}
第7行调用createDefaults方法,跳到24行。30行shouldImplicitlyCreateRealm判断是否需要隐式创建Realm,其判断依据是ini文件中包含名称为users或roles的sectionName。显然例子的ini中,我们包含了users,因此31行调用createRealm,跳到53行,54行创建IniRealm,这就是我们要找的东西!
小结
本文主要是针对上一篇文章提出的三个问题进行了一些研究。总结就是:
1、ini文件中的信息,[XXX]会以sectionName作为key,其下的配置以Section类作为value保存。其Section又是以key-value形式保存这些配置属性;
2、securityManager的具体实现类是DefaultSecurityManager;
3、默认Realm是IniRealm。
当然上面总结的是以最简单的例子默认创建securityManager、Realm的情况来说明。从中我们也分析了初始化大致的来龙去脉。