在执行
Factory<org.apache.shiro.mgt.SecurityManager> factory =
new IniSecurityManagerFactory("classpath:shiro.ini");
这条语句后,解析了ini配置文件,将需要的信息保存到了Ini的sections这个Map容器中,以节点名=》节点对象的方式保存
这个节点对象也是个Map,它保存了节点内容的key value值
解析已经完整,那么接下来的就是创建配置文件中配置的一些类了,比如自定义的realm,resolver,matcher,permission,sessionDao啥的
从下面这条语句开始
org.apache.shiro.mgt.SecurityManager securityManager = factory.getInstance();
下面是实现Factory类的类图
一、创建SecurityManager
1 private SecurityManager createSecurityManager(Ini ini, Ini.Section mainSection) { 2 3 Map<String, ?> defaults = createDefaults(ini, mainSection);//创建了一个叫做defaults的Map容器,这个容器用来装默认需要创建的类的实例,比如SecuirtyManager,IniRealm 4 Map<String, ?> objects = buildInstances(mainSection, defaults);// 5 6 SecurityManager securityManager = getSecurityManagerBean(); 7 8 boolean autoApplyRealms = isAutoApplyRealms(securityManager); 9 10 if (autoApplyRealms) { 11 //realms and realm factory might have been created - pull them out first so we can 12 //initialize the securityManager: 13 Collection<Realm> realms = getRealms(objects); 14 //set them on the SecurityManager 15 if (!CollectionUtils.isEmpty(realms)) { 16 applyRealmsToSecurityManager(realms, securityManager); 17 } 18 } 19 20 return securityManager; 21 }
第3行的创建了一个叫做defaults的Map容器,第4行创建了一个叫做objects的Map容器,先来看看defaults是怎么创建的
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);//将默认管理器存到defaults这个Map容器中,并且它的key为securityManager,这个名字是我们在ini配置文件中可以使用的
if (shouldImplicitlyCreateRealm(ini)) {//这里是创建IniRealm这个类的对象,只有在ini配置文件中有其他节点的存在并且有roles或者users其中一个或者两个节点时才会创建
Realm realm = createRealm(ini);
if (realm != null) {
defaults.put(INI_REALM_NAME, realm);//将IniRealm实例保存到默认的Map中
}
}
return defaults;
}
这个defaults容器里面存储了一些默认需要创建的实例,比如SecurityManager,IniRealm
接下来再看看objects这个容器是怎么创建的
private Map<String, ?> buildInstances(Ini.Section section, Map<String, ?> defaults) { this.builder = new ReflectionBuilder(defaults); return this.builder.buildObjects(section); }
进到代码里面后发现它首先先创建了一个builder,从字面上是一个反射建造者,它的构造体如下
public ReflectionBuilder(Map<String, ?> defaults) { this.objects = CollectionUtils.isEmpty(defaults) ? new LinkedHashMap<String, Object>() : defaults; }
上面这段代码,只是为了给这个建造者的成员容器objects添加引用,给的是前面创建的defaults容器的引用,这个容器中已经创建了secruityManager,还有IniRealm
这个反射建造者定义了许多的静态常量。
private static final String OBJECT_REFERENCE_BEGIN_TOKEN = "$";//这个很熟悉吧,ini配置文件中引用某个变量时使用的美元符号 private static final String ESCAPED_OBJECT_REFERENCE_BEGIN_TOKEN = "\\$";//转义的对象引用 private static final String GLOBAL_PROPERTY_PREFIX = "shiro";//全局属性前缀 private static final char MAP_KEY_VALUE_DELIMITER = ':';//这个也很熟悉吧,权限的 private static final String HEX_BEGIN_TOKEN = "0x"; private static final String NULL_VALUE_TOKEN = "null"; private static final String EMPTY_STRING_VALUE_TOKEN = "\"\"";//空字符串,也许会有人有疑问,为啥不是直接""呢,因为在ini配置文件里面,表示空字符串就得写上"" private static final char STRING_VALUE_DELIMETER = '"';//权限中会用到,main节点中map类型的会用到比如"user:delete,query","resource:delete,update" private static final char MAP_PROPERTY_BEGIN_TOKEN = '[';//过滤器中传递参数 private static final char MAP_PROPERTY_END_TOKEN = ']';//过滤器中传递参数
创建了放射建造者之后,代码继续调用了this.builder.buildObjects(section);这条语句
从字面上讲用节点来构造对象
1 public Map<String, ?> buildObjects(Map<String, String> kvPairs) {//记住这个kvPairs是一个Section,它继承了Map容器,里面存了节点中的内容 2 if (kvPairs != null && !kvPairs.isEmpty()) { 3 4 // Separate key value pairs into object declarations and property assignment 5 // so that all objects can be created up front 6 7 //https://issues.apache.org/jira/browse/SHIRO-85 - need to use LinkedHashMaps here: 8 Map<String, String> instanceMap = new LinkedHashMap<String, String>();//用于保存实例的Map 9 Map<String, String> propertyMap = new LinkedHashMap<String, String>();//用于保存属性的Map 10 11 for (Map.Entry<String, String> entry : kvPairs.entrySet()) { 12 if (entry.getKey().indexOf('.') < 0 || entry.getKey().endsWith(".class")) {//判断这个key是否没有包含.或者是否是以.class结束的字符串 13 instanceMap.put(entry.getKey(), entry.getValue()); 14 } else { 15 propertyMap.put(entry.getKey(), entry.getValue());//如果这个key有.并且不是以.class结尾的,那么就表示这个key值对应的是一个属性注入的值 16 } 17 } 18 19 // Create all instances 20 for (Map.Entry<String, String> entry : instanceMap.entrySet()) { 21 createNewInstance((Map<String, Object>) objects, entry.getKey(), entry.getValue());//循环遍历这个实例Map创建对应的类的实例 22 } 23 24 // Set all properties 25 for (Map.Entry<String, String> entry : propertyMap.entrySet()) { 26 applyProperty(entry.getKey(), entry.getValue(), objects); 27 } 28 } 29 30 //SHIRO-413: init method must be called for constructed objects that are Initializable 31 LifecycleUtils.init(objects.values()); 32 33 return objects; 34 }
这里有三个重点,第一个是第21行的创建实例
第二个是注入属性值,
第三个是初始化创建的对象。
首先先分析一下createNewInstance这个方法
1 protected void createNewInstance(Map<String, Object> objects, String name, String value) { 2 3 Object currentInstance = objects.get(name);//从objects容器中取得当前name指定的对象 4 if (currentInstance != null) {//如果已经存在,将打印日志,提示这个对象已经存在了,没有必要多次定义 5 log.info("An instance with name '{}' already exists. " + 6 "Redefining this object as a new instance of type {}", name, value); 7 } 8 9 Object instance;//name with no property, assume right hand side of equals sign is the class name: 10 try { 11 instance = ClassUtils.newInstance(value);//创建实例 12 if (instance instanceof Nameable) {//如果这个实例实现了Nameable接口,那么就可以进行命名,比如realm的实现类就是一种可以命名的类 13 ((Nameable) instance).setName(name); 14 } 15 } catch (Exception e) { 16 String msg = "Unable to instantiate class [" + value + "] for object named '" + name + "'. " + 17 "Please ensure you've specified the fully qualified class name correctly."; 18 throw new ConfigurationException(msg, e); 19 } 20 objects.put(name, instance);//以name=》instance保存 21 }
重点第11行,ClassUtil是shiro实现的一个类助手
public static Object newInstance(String fqcn) { return newInstance(forName(fqcn)); }
看一下forName方法
public static Class forName(String fqcn) throws UnknownClassException { Class clazz = THREAD_CL_ACCESSOR.loadClass(fqcn); if (clazz == null) { if (log.isTraceEnabled()) { log.trace("Unable to load class named [" + fqcn + "] from the thread context ClassLoader. Trying the current ClassLoader..."); } clazz = CLASS_CL_ACCESSOR.loadClass(fqcn); } if (clazz == null) { if (log.isTraceEnabled()) { log.trace("Unable to load class named [" + fqcn + "] from the current ClassLoader. " + "Trying the system/application ClassLoader..."); } clazz = SYSTEM_CL_ACCESSOR.loadClass(fqcn); } if (clazz == null) { String msg = "Unable to load class named [" + fqcn + "] from the thread context, current, or " + "system/application ClassLoaders. All heuristics have been exhausted. Class could not be found."; throw new UnknownClassException(msg); } return clazz; }
上面的是通过类名来加载类,标红的都是类加载器,如果线程上下文加载器没有加载到类,那么就叫类加载器加载,还加载不到就叫系统上下文加载器加载
下面是这些类加载器的获取源码
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(); } };
很好,既然已经将类加载进来了,那么接下来就应该进行实例化了
1 public static Object newInstance(Class clazz) { 2 if (clazz == null) { 3 String msg = "Class method parameter cannot be null."; 4 throw new IllegalArgumentException(msg); 5 } 6 try { 7 return clazz.newInstance(); 8 } catch (Exception e) { 9 throw new InstantiationException("Unable to instantiate class [" + clazz.getName() + "]", e); 10 } 11 }
第7行,多么眼熟的构造实例的方式啊
好了,对象全都创建好了,那肯定就存起来啊,统统都存到反射建造者的objects容器中了
对象的建造告一段落了,那么接下就应该进行属性注入了,从下面这段开始
for (Map.Entry<String, String> entry : propertyMap.entrySet()) { applyProperty(entry.getKey(), entry.getValue(), objects); }
进入applyProperty方法
1 protected void applyProperty(String key, String value, Map objects) { 2 3 int index = key.indexOf('.'); 4 5 if (index >= 0) { 6 String name = key.substring(0, index);//截取.前面的字符串。比如securityManager.authenticator.authenticationStrategy,会获取到security这个字符串 7 String property = key.substring(index + 1, key.length());//获取属性,比如上面这个字符串会被获取成authenticator.authenticationStrategy 8 9 if (GLOBAL_PROPERTY_PREFIX.equalsIgnoreCase(name)) {//标红的常量值是shiro 10 applyGlobalProperty(objects, property, value); 11 } else { 12 applySingleProperty(objects, name, property, value); 13 } 14 15 } else { 16 throw new IllegalArgumentException("All property keys must contain a '.' character. " + 17 "(e.g. myBean.property = value) These should already be separated out by buildObjects()."); 18 } 19 }
重点先看到第12行,第10行稍后再看,看懂了第12行,那么第10行也是懂了
这个方法传入了创建了securityManager和IniRealm以及其他的实例,比如sessionManager,啊我们在[main]节点配置的类的容器,还有变量名name,属性名,还有配置文件中的属性值,比如$sessionDao
applySingleProperty方法
1 protected void applySingleProperty(Map objects, String name, String property, String value) { 2 Object instance = objects.get(name);//从objects中获取到name对应的对象 3 if (property.equals("class")) { 4 throw new IllegalArgumentException("Property keys should not contain 'class' properties since these " + 5 "should already be separated out by buildObjects()."); 6 7 } else if (instance == null) { 8 String msg = "Configuration error. Specified object [" + name + "] with property [" + 9 property + "] without first defining that object's class. Please first " + 10 "specify the class property first, e.g. myObject = fully_qualified_class_name " + 11 "and then define additional properties."; 12 throw new IllegalArgumentException(msg); 13 14 } else { 15 applyProperty(instance, property, value); 16 } 17 }
继续进入applyProperty方法
1 protected void applyProperty(Object object, String propertyName, String stringValue) { 2 3 Object value; 4 5 if (NULL_VALUE_TOKEN.equals(stringValue)) {//null 6 value = null; 7 } else if (EMPTY_STRING_VALUE_TOKEN.equals(stringValue)) {//"" 8 value = StringUtils.EMPTY_STRING; 9 } else if (isIndexedPropertyAssignment(propertyName)) {//内部判断这个属性是不是以]结尾的 10 String checked = checkForNullOrEmptyLiteral(stringValue);//检查stringValue是否为null或者empty,否则直接返回原值 11 value = resolveValue(checked); //解析这个这个值,获得真正的对象 12 } else if (isTypedProperty(object, propertyName, Set.class)) { 13 value = toSet(stringValue); 14 } else if (isTypedProperty(object, propertyName, Map.class)) { 15 value = toMap(stringValue); 16 } else if (isTypedProperty(object, propertyName, List.class)) { 17 value = toList(stringValue); 18 } else if (isTypedProperty(object, propertyName, Collection.class)) { 19 value = toCollection(stringValue); 20 } else if (isTypedProperty(object, propertyName, byte[].class)) { 21 value = toBytes(stringValue); 22 } else if (isTypedProperty(object, propertyName, ByteSource.class)) { 23 byte[] bytes = toBytes(stringValue); 24 value = ByteSource.Util.bytes(bytes); 25 } else { 26 String checked = checkForNullOrEmptyLiteral(stringValue); 27 value = resolveValue(checked); 28 } 29 30 applyProperty(object, propertyName, value); 31 } 32 33 }
我们发现第12行,14,16等,都调用了一个叫isTypedProperty的方法,我们先来看看这个方法,
重点要说的resolveValue方法先押后
1 protected boolean isTypedProperty(Object object, String propertyName, Class clazz) { 2 if (clazz == null) { 3 throw new NullPointerException("type (class) argument cannot be null."); 4 } 5 try { 6 PropertyDescriptor descriptor = PropertyUtils.getPropertyDescriptor(object, propertyName);//获得属性描述器 7 if (descriptor == null) { 8 String msg = "Property '" + propertyName + "' does not exist for object of " + 9 "type " + object.getClass().getName() + "."; 10 throw new ConfigurationException(msg); 11 } 12 Class propertyClazz = descriptor.getPropertyType(); 13 return clazz.isAssignableFrom(propertyClazz); 14 } catch (ConfigurationException ce) { 15 //let it propagate: 16 throw ce; 17 } catch (Exception e) { 18 String msg = "Unable to determine if property [" + propertyName + "] represents a " + clazz.getName(); 19 throw new ConfigurationException(msg, e); 20 } 21 }
第6行的属性描述器干了好多的事情
1 public PropertyDescriptor getPropertyDescriptor(Object bean, 2 String name) 3 throws IllegalAccessException, InvocationTargetException, 4 NoSuchMethodException { 5 6 if (bean == null) { 7 throw new IllegalArgumentException("No bean specified"); 8 } 9 if (name == null) { 10 throw new IllegalArgumentException("No name specified for bean class '" + 11 bean.getClass() + "'"); 12 } 13 14 // Resolve nested references 15 while (resolver.hasNested(name)) {//判断是否存在内嵌的属性引用,如name为a.b 16 String next = resolver.next(name);//解析,如a.b,返回后变成a 17 Object nestedBean = getProperty(bean, next); 18 if (nestedBean == null) { 19 throw new NestedNullException 20 ("Null property value for '" + next + 21 "' on bean class '" + bean.getClass() + "'"); 22 } 23 bean = nestedBean; 24 name = resolver.remove(name); 25 } 26 27 // Remove any subscript from the final name value 28 name = resolver.getProperty(name); 29 30 // Look up and return this property from our cache 31 // creating and adding it to the cache if not found. 32 if (name == null) { 33 return (null); 34 } 35 36 PropertyDescriptor[] descriptors = getPropertyDescriptors(bean); 37 if (descriptors != null) { 38 39 for (int i = 0; i < descriptors.length; i++) { 40 if (name.equals(descriptors[i].getName())) { 41 return (descriptors[i]); 42 } 43 } 44 } 45 46 PropertyDescriptor result = null; 47 FastHashMap mappedDescriptors = 48 getMappedPropertyDescriptors(bean); 49 if (mappedDescriptors == null) { 50 mappedDescriptors = new FastHashMap(); 51 mappedDescriptors.setFast(true); 52 mappedDescriptorsCache.put(bean.getClass(), mappedDescriptors); 53 } 54 result = (PropertyDescriptor) mappedDescriptors.get(name); 55 if (result == null) { 56 // not found, try to create it 57 try { 58 result = new MappedPropertyDescriptor(name, bean.getClass()); 59 } catch (IntrospectionException ie) { 60 /* Swallow IntrospectionException 61 * TODO: Why? 62 */ 63 } 64 if (result != null) { 65 mappedDescriptors.put(name, result); 66 } 67 } 68 69 return result; 70 71 }
第15行的hasNested
public boolean hasNested(String expression) { if (expression == null || expression.length() == 0) { return false; } else { return (remove(expression) != null);//如果返回的不是空,那表明有嵌套属性,否则没有 } }
remove方法的代码如下
1 public String remove(String expression) { 2 if (expression == null || expression.length() == 0) { 3 return null; 4 } 5 String property = next(expression);//获取截取过后的字符串,如a.b返回a 6 if (expression.length() == property.length()) {//如果经过处理后的字符串和源字符串的长度一样说明没有嵌套的属性 7 return null; 8 } 9 int start = property.length(); 10 if (expression.charAt(start) == NESTED) {//截取.后面的字符串。 11 start++; 12 } 13 return expression.substring(start);//截取剩下的字符串 14 } 15 }
下面是next方法的代码,判断这个属性是否包含嵌入的属性,比如authenticator.authenticationStrategy像这样的属性是需要进行判断的,具体代码如下
if (expression == null || expression.length() == 0) { return null; } boolean indexed = false; boolean mapped = false; for (int i = 0; i < expression.length(); i++) { char c = expression.charAt(i); if (indexed) { if (c == INDEXED_END) {//[ return expression.substring(0, i + 1); } } else if (mapped) { if (c == MAPPED_END) {//) return expression.substring(0, i + 1); } } else { if (c == NESTED) {//. return expression.substring(0, i); } else if (c == MAPPED_START) {//( mapped = true; } else if (c == INDEXED_START) {//] indexed = true; } } } return expression;
以上代码分这些个情况,a.b,a[],a()
String next = resolver.next(name);
Object nestedBean = getProperty(bean, next);
它调用完了next后,继续调用了getProperty方法,而这个方法又立即调用了getNestedProperty方法,如果原始属性值为a.b,那么以下name参数应该为a
1 public Object getNestedProperty(Object bean, String name) 2 throws IllegalAccessException, InvocationTargetException, 3 NoSuchMethodException { 4 5 if (bean == null) { 6 throw new IllegalArgumentException("No bean specified"); 7 } 8 if (name == null) { 9 throw new IllegalArgumentException("No name specified for bean class '" + 10 bean.getClass() + "'"); 11 } 12 13 // Resolve nested references 14 while (resolver.hasNested(name)) {//又使用了解析器对这个name进行了一次嵌套引用的判断 15 String next = resolver.next(name); 16 Object nestedBean = null; 17 if (bean instanceof Map) {//判断当前的bean是否为Map 18 nestedBean = getPropertyOfMapBean((Map) bean, next);//比如next为a(b) 19 } else if (resolver.isMapped(next)) {//判断这个next是否被映射的,也是a(b)这种形式的 20 nestedBean = getMappedProperty(bean, next);//获取到被映射的属性 21 } else if (resolver.isIndexed(next)) {//判断这个next有没有[] 22 nestedBean = getIndexedProperty(bean, next);//比如a[1],[1] 23 } else { 24 nestedBean = getSimpleProperty(bean, next); 25 } 26 if (nestedBean == null) { 27 throw new NestedNullException 28 ("Null property value for '" + name + 29 "' on bean class '" + bean.getClass() + "'"); 30 } 31 bean = nestedBean; 32 name = resolver.remove(name); 33 } 34 35 if (bean instanceof Map) { 36 bean = getPropertyOfMapBean((Map) bean, name); 37 } else if (resolver.isMapped(name)) { 38 bean = getMappedProperty(bean, name); 39 } else if (resolver.isIndexed(name)) { 40 bean = getIndexedProperty(bean, name); 41 } else { 42 bean = getSimpleProperty(bean, name); 43 } 44 return bean; 45 46 }
第18这个用法我好像没有用过,不过从源码中可以看出它可以创个a(b)这个字符串,最后它把a和b拆开,如果a不为空字符串或者null,那么就直接bean.get(a),如果a为空,那么就bean.get(b)
第20行判断当前的bean是否是DynaBean的子类的实例,并且从一个实现了DynaClass的实例中的一个Map取数据,也就是说我们可以在ini配置文件中配置配置DynaBean,DynaClass连个实例,并且可以给DynaClass实例的Map写入初始化的值。。。。。。。。。。。算了,我不知道,我编不下去了,我在Google上没搜到,shiro文档中没找到。
第22行的如果只有[i],那么取到index就是i,然后判断这个bean是否为数组还是List,然后直接返回相应下标的值
直接看到第42行,获得简单属性
1 public Object getSimpleProperty(Object bean, String name) 2 throws IllegalAccessException, InvocationTargetException, 3 NoSuchMethodException { 4 //省略部分代码39 40 // Retrieve the property getter method for the specified property 41 PropertyDescriptor descriptor = 42 getPropertyDescriptor(bean, name);//获得这个name对应的属性描述 43 if (descriptor == null) { 44 throw new NoSuchMethodException("Unknown property '" + 45 name + "' on class '" + bean.getClass() + "'" ); 46 } 47 Method readMethod = getReadMethod(bean.getClass(), descriptor); 48 if (readMethod == null) { 49 throw new NoSuchMethodException("Property '" + name + 50 "' has no getter method in class '" + bean.getClass() + "'"); 51 } 52 53 // Call the property getter and return the value 54 Object value = invokeMethod(readMethod, bean, EMPTY_OBJECT_ARRAY); 55 return (value); 56 57 }
第42行通过name获得对应这个bean的属性描述器,内部调用了
beanInfo = Introspector.getBeanInfo(beanClass);
Introspector类是JDK提供的一个工具,它在java.beans包下,getBeanInfo是获取其所有属性、公开的方法和事件。
通过返回的beanInfo信息,beanInfo.getPropertyDescriptors()得到一个PropertyDescriptor[],然后
for (int i = 0; i < descriptors.length; i++) { if (name.equals(descriptors[i].getName())) { return (descriptors[i]); } }
找到对应的属性描述返回即可
返回属性描述器之后,我们又回到了isTypeProperty方法
1 protected boolean isTypedProperty(Object object, String propertyName, Class clazz) { 2 if (clazz == null) { 3 throw new NullPointerException("type (class) argument cannot be null."); 4 } 5 try { 6 PropertyDescriptor descriptor = PropertyUtils.getPropertyDescriptor(object, propertyName);//此时已经获得了属性描述器 7 if (descriptor == null) { 8 String msg = "Property '" + propertyName + "' does not exist for object of " + 9 "type " + object.getClass().getName() + "."; 10 throw new ConfigurationException(msg); 11 } 12 Class propertyClazz = descriptor.getPropertyType();//判断这个属性是什么类型的 13 return clazz.isAssignableFrom(propertyClazz);//判断这个属性所属的类型是否是clazz的子类,或者是它本身 14 } catch (ConfigurationException ce) { 15 //let it propagate: 16 throw ce; 17 } catch (Exception e) { 18 String msg = "Unable to determine if property [" + propertyName + "] represents a " + clazz.getName(); 19 throw new ConfigurationException(msg, e); 20 } 21 }
这个方法返回到这个applyProperty方法
1 protected void applyProperty(Object object, String propertyName, String stringValue) { 2 3 Object value; 4 5 if (NULL_VALUE_TOKEN.equals(stringValue)) { 6 value = null; 7 } else if (EMPTY_STRING_VALUE_TOKEN.equals(stringValue)) { 8 value = StringUtils.EMPTY_STRING; 9 } else if (isIndexedPropertyAssignment(propertyName)) { 10 String checked = checkForNullOrEmptyLiteral(stringValue); 11 value = resolveValue(checked); 12 } else if (isTypedProperty(object, propertyName, Set.class)) { 13 value = toSet(stringValue); 14 } else if (isTypedProperty(object, propertyName, Map.class)) { 15 value = toMap(stringValue); 16 } else if (isTypedProperty(object, propertyName, List.class)) { 17 value = toList(stringValue); 18 } else if (isTypedProperty(object, propertyName, Collection.class)) { 19 value = toCollection(stringValue); 20 } else if (isTypedProperty(object, propertyName, byte[].class)) { 21 value = toBytes(stringValue); 22 } else if (isTypedProperty(object, propertyName, ByteSource.class)) { 23 byte[] bytes = toBytes(stringValue); 24 value = ByteSource.Util.bytes(bytes); 25 } else { 26 String checked = checkForNullOrEmptyLiteral(stringValue); 27 value = resolveValue(checked); 28 } 29 30 applyProperty(object, propertyName, value); 31 }
看到了吧,刚才的clazz就是这里面的的集合,Set.class,Map.class等等
就假设当前属性是Map的,看看这个就好了,其他的也就很简单了
1 protected Map<?, ?> toMap(String sValue) { 2 String[] tokens = StringUtils.split(sValue, StringUtils.DEFAULT_DELIMITER_CHAR, 3 StringUtils.DEFAULT_QUOTE_CHAR, StringUtils.DEFAULT_QUOTE_CHAR, true, true);//, " 4 if (tokens == null || tokens.length <= 0) { 5 return null; 6 } 7 8 //SHIRO-423: check to see if the value is a referenced Map already, and if so, return it immediately: 9 if (tokens.length == 1 && isReference(tokens[0])) { 10 Object reference = resolveReference(tokens[0]); 11 if (reference instanceof Map) { 12 return (Map)reference; 13 } 14 } 15 16 Map<String, String> mapTokens = new LinkedHashMap<String, String>(tokens.length); 17 for (String token : tokens) { 18 String[] kvPair = StringUtils.split(token, MAP_KEY_VALUE_DELIMITER); 19 if (kvPair == null || kvPair.length != 2) { 20 String msg = "Map property value [" + sValue + "] contained key-value pair token [" + 21 token + "] that does not properly split to a single key and pair. This must be the " + 22 "case for all map entries."; 23 throw new ConfigurationException(msg); 24 } 25 mapTokens.put(kvPair[0], kvPair[1]); 26 } 27 28 //now convert into correct values and/or references: 29 Map<Object, Object> map = new LinkedHashMap<Object, Object>(mapTokens.size()); 30 for (Map.Entry<String, String> entry : mapTokens.entrySet()) { 31 Object key = resolveValue(entry.getKey()); 32 Object value = resolveValue(entry.getValue()); 33 map.put(key, value); 34 } 35 return map; 36 } 37 38 // @since 1.2.2 39 // TODO: make protected in 1.3+ 40 private Collection<?> toCollection(String sValue) { 41 42 String[] tokens = StringUtils.split(sValue); 43 if (tokens == null || tokens.length <= 0) { 44 return null; 45 } 46 47 //SHIRO-423: check to see if the value is a referenced Collection already, and if so, return it immediately: 48 if (tokens.length == 1 && isReference(tokens[0])) { 49 Object reference = resolveReference(tokens[0]); 50 if (reference instanceof Collection) { 51 return (Collection)reference; 52 } 53 } 54 55 //now convert into correct values and/or references: 56 List<Object> values = new ArrayList<Object>(tokens.length); 57 for (String token : tokens) { 58 Object value = resolveValue(token); 59 values.add(value); 60 } 61 return values; 62 }
我们看看第2行是怎么对字符串值进行分割的,Map的字符串值是这种形式的key1:$object1, key2:$object2
看下split的方法
//aLine是字符串值,比如key1:$object1, key2:$object2,delimiter=>, beginQuoteChar=>" endQuoteChar=>" retainQuotes=>true trimTokens=>true
1 public static String[] split(String aLine, char delimiter, char beginQuoteChar, char endQuoteChar, 2 boolean retainQuotes, boolean trimTokens) { 3 String line = clean(aLine); 4 if (line == null) { 5 return null; 6 } 7 8 List<String> tokens = new ArrayList<String>(); 9 StringBuilder sb = new StringBuilder(); 10 boolean inQuotes = false; 11 12 for (int i = 0; i < line.length(); i++) { 13 14 char c = line.charAt(i); 15 if (c == beginQuoteChar) {//在引号内的逗号不会产生分割的作用。 16 // this gets complex... the quote may end a quoted block, or escape another quote. 17 // do a 1-char lookahead: 18 if (inQuotes // we are in quotes, therefore there can be escaped quotes in here.//连续的引号会去掉一个,比如""=>","""=>"",""""=>"" 19 && line.length() > (i + 1) // there is indeed another character to check. 20 && line.charAt(i + 1) == beginQuoteChar) { // ..and that char. is a quote also. 21 // we have two quote chars in a row == one quote char, so consume them both and 22 // put one on the token. we do *not* exit the quoted text. 23 sb.append(line.charAt(i + 1)); 24 i++; 25 } else { 26 inQuotes = !inQuotes; 27 if (retainQuotes) { 28 sb.append(c); 29 } 30 } 31 } else if (c == endQuoteChar) { 32 inQuotes = !inQuotes; 33 if (retainQuotes) { 34 sb.append(c); 35 } 36 } else if (c == delimiter && !inQuotes) { 37 String s = sb.toString(); 38 if (trimTokens) { 39 s = s.trim(); 40 } 41 tokens.add(s); 42 sb = new StringBuilder(); // start work on next token 43 } else { 44 sb.append(c); 45 } 46 } 47 String s = sb.toString(); 48 if (trimTokens) { 49 s = s.trim(); 50 } 51 tokens.add(s); 52 return tokens.toArray(new String[tokens.size()]); 53 }
上面这段程序呢,完全可以拷贝到自己的代码中进行测试,debug,我觉得这样更好理解,这段代码的表示在引号内的逗号是不会产生分割作用的
并且如果有多个连续的引号,那么会去掉一部分引号。那这里使用引号用来干什么了,有了引号你就可以在里面写一些shiro的关键字符,
比如"key1:hello":122, key2:"helo,object2",用引号包裹起来的字符串会被视为一个整体,不会做解析,不过$符号还是会被解析的
为什么呢,因为shiro无法确认你会不会走非主流,比如你来这样的
,\=bean=com.test.User
,feizhuliu=com.test.Test -------------->里面有一个Map的属性
,feizhuliu.map=user:"$,\=bean" -------------->所以啊,你这个$还是得解析的,要不然,直接被解析成字符串,用户是不会答应的,用户是上帝嘛(虽然这个用户不是个好人,我也承认我不是个好人)
用户可能又要说了,我要的就是包含$的字符串。
很简单,直接加个反斜杠"\$,\=bean"
当字符串被解析字符串数组之后,shiro对这些字符串数组进行了引用判断,判断是否带有$符号
if (tokens.length == 1 && isReference(tokens[0])) { Object reference = resolveReference(tokens[0]); if (reference instanceof Map) { return (Map)reference; } }
上面这段代码表示当tokens数组的值只有一个的时候,就检查这个值的是否是$开头,如果是,说明应用了一个Map实例,这个Map实例肯定是在配置文件中定义的,并且像其他的
实例一样被存在了反射建造者的objects容器中,直接去掉$符号,去这个容器中查找就是了,找到就返回,找不到就直接报错,因为你引用了一个不存在的实例。
如果能够拿到值,那么还要判断这个值是不是Map类型,如果是,直接返回
isReference
protected boolean isReference(String value) { return value != null && value.startsWith(OBJECT_REFERENCE_BEGIN_TOKEN);//$ }
resolveReference
protected Object resolveReference(String reference) { String id = getId(reference);//内部代码referenceToken.substring(OBJECT_REFERENCE_BEGIN_TOKEN.length());去掉$符号 log.debug("Encountered object reference '{}'. Looking up object with id '{}'", reference, id); final Object referencedObject = getReferencedObject(id);//Object o = objects != null && !objects.isEmpty() ? objects.get(id) : null; if (referencedObject instanceof Factory) {//如果这个对象是一个工厂对象,那么就直接调用他的工厂方法返回实例 return ((Factory) referencedObject).getInstance(); } return referencedObject; }
如果返回的实例不是Map类型怎么办?
1 Map<String, String> mapTokens = new LinkedHashMap<String, String>(tokens.length); 2 for (String token : tokens) { 3 String[] kvPair = StringUtils.split(token, MAP_KEY_VALUE_DELIMITER);//其实这个方法层层调用,又调用到了上面我们说的那个分割字符串的方法,只不过,它把其中的retainQuotes变成了false,分隔符改成了: 4 if (kvPair == null || kvPair.length != 2) { 5 String msg = "Map property value [" + sValue + "] contained key-value pair token [" + 6 token + "] that does not properly split to a single key and pair. This must be the " + 7 "case for all map entries."; 8 throw new ConfigurationException(msg); 9 } 10 mapTokens.put(kvPair[0], kvPair[1]); 11 }
第3行分割字符串最终调用的方法和上面讲的一样,只不过它将其中的一个retainQuotes参数修改成了false,如果修改成了false,那么"就会被去掉
除非出现了两个引号连在一起的情况下会被保留一个,比如key3:"$,"\=user"=》key3:$,\=user key3:""$,"\=user"=>key3:"$,\=user
它们以分隔符:进行分割
接着上面讲的,如果是以$符号开头的,得到的对象却不是Map类型的,这个字符串可能存在这么几个可能性,比如$key:value或者$key
$key:value可以被分割
$key不能被分割,那么这种情况会直接报错
字符串分割完成后就开始拿到对应的属性值了
Map<Object, Object> map = new LinkedHashMap<Object, Object>(mapTokens.size()); for (Map.Entry<String, String> entry : mapTokens.entrySet()) { Object key = resolveValue(entry.getKey()); Object value = resolveValue(entry.getValue()); map.put(key, value); }
看到resolveValue方法
protected Object resolveValue(String stringValue) { Object value; if (isReference(stringValue)) {//讲过了,就是判断一下是不是以$开头 value = resolveReference(stringValue);//上面说过了,就是从objects这个容器中获取到对应的值 } else { value = unescapeIfNecessary(stringValue);//解析普通的字符串,如果有用\$做前缀的,那么就去掉\ } return value; }
解析成值后,返回,开始注入依赖了
1 protected void applyProperty(Object object, String propertyPath, Object value) { 2 3 int mapBegin = propertyPath.indexOf(MAP_PROPERTY_BEGIN_TOKEN);//判断这个属性是否存在[ 4 int mapEnd = -1; 5 String mapPropertyPath = null; 6 String keyString = null; 7 8 String remaining = null; 9 10 if (mapBegin >= 0) { 11 //a map is being referenced in the overall property path. Find just the map's path: 12 mapPropertyPath = propertyPath.substring(0, mapBegin); 13 //find the end of the map reference: 14 mapEnd = propertyPath.indexOf(MAP_PROPERTY_END_TOKEN, mapBegin); 15 //find the token in between the [ and the ] (the map/array key or index): 16 keyString = propertyPath.substring(mapBegin+1, mapEnd);//得到【】内的内容 17 18 //find out if there is more path reference to follow. If not, we're at a terminal of the OGNL expression 19 if (propertyPath.length() > (mapEnd+1)) { 20 remaining = propertyPath.substring(mapEnd+1); 21 if (remaining.startsWith(".")) { 22 remaining = StringUtils.clean(remaining.substring(1)); 23 } 24 } 25 } 26 27 if (remaining == null) { 28 //we've terminated the OGNL expression. Check to see if we're assigning a property or a map entry: 29 if (keyString == null) { 30 //not a map or array value assignment - assign the property directly: 31 setProperty(object, propertyPath, value); 32 } else { 33 //we're assigning a map or array entry. Check to see which we should call: 34 if (isTypedProperty(object, mapPropertyPath, Map.class)) { 35 Map map = (Map)getProperty(object, mapPropertyPath); 36 Object mapKey = resolveValue(keyString); 37 //noinspection unchecked 38 map.put(mapKey, value); 39 } else { 40 //must be an array property. Convert the key string to an index: 41 int index = Integer.valueOf(keyString); 42 setIndexedProperty(object, mapPropertyPath, index, value); 43 } 44 } 45 } else { 46 //property is being referenced as part of a nested path. Find the referenced map/array entry and 47 //recursively call this method with the remaining property path 48 Object referencedValue = null; 49 if (isTypedProperty(object, mapPropertyPath, Map.class)) { 50 Map map = (Map)getProperty(object, mapPropertyPath); 51 Object mapKey = resolveValue(keyString); 52 referencedValue = map.get(mapKey); 53 } else { 54 //must be an array property: 55 int index = Integer.valueOf(keyString); 56 referencedValue = getIndexedProperty(object, mapPropertyPath, index); 57 } 58 59 if (referencedValue == null) { 60 throw new ConfigurationException("Referenced map/array value '" + mapPropertyPath + "[" + 61 keyString + "]' does not exist."); 62 } 63 64 applyProperty(referencedValue, remaining, value); 65 } 66 }
上面这段代码又属性名称进行了一系列的判断,判断它是否包含中括号,如果有就拿到中括号中的下标值,并且检查这个中括号后面还有没有.之类,如果有那么又继续isTyped....啥的
这些判断的方法都说过了,没必要再说,这些判断都是为了得到最后那个需要进行注入的bean和属性。
跳过一堆需要进行判断的语句,直接看看它是怎么注入的
翻过几座山,跳过几条河,看到了这么一句代码
BeanUtilsBean.getInstance().setProperty(bean, name, value);
下面是setProperty的方法(删除了部分日志信息)
1 public void setProperty(Object bean, String name, Object value) 2 throws IllegalAccessException, InvocationTargetException { 3 4 32 // Resolve any nested expression to get the actual target bean 33 Object target = bean; 34 Resolver resolver = getPropertyUtils().getResolver();//又拿到了这个解析器 35 while (resolver.hasNested(name)) {//又进行了内嵌判断 36 try { 37 target = getPropertyUtils().getProperty(target, resolver.next(name)); 38 name = resolver.remove(name); 39 } catch (NoSuchMethodException e) { 40 return; // Skip this property setter 41 } 42 } 43 48 // Declare local variables we will require 49 String propName = resolver.getProperty(name); // Simple name of target property 50 Class type = null; // Java type of target property 51 int index = resolver.getIndex(name); // Indexed subscript value (if any)获得下表值 52 String key = resolver.getKey(name); // Mapped key value (if any)获得key值,这些在前面已经说过了 53 54 // Calculate the property type 55 if (target instanceof DynaBean) { 56 DynaClass dynaClass = ((DynaBean) target).getDynaClass(); 57 DynaProperty dynaProperty = dynaClass.getDynaProperty(propName); 58 if (dynaProperty == null) { 59 return; // Skip this property setter 60 } 61 type = dynaProperty.getType(); 62 } else if (target instanceof Map) { 63 type = Object.class; 64 } else if (target != null && target.getClass().isArray() && index >= 0) { 65 type = Array.get(target, index).getClass(); 66 } else { 67 PropertyDescriptor descriptor = null; 68 try { 69 descriptor = 70 getPropertyUtils().getPropertyDescriptor(target, name);//获得属性描述器,前面说过了,它是通过jdk的java.beans包下的Introspector来获取bean信息的 71 if (descriptor == null) { 72 return; // Skip this property setter 73 } 74 } catch (NoSuchMethodException e) { 75 return; // Skip this property setter 76 } 77 if (descriptor instanceof MappedPropertyDescriptor) {//判断当前的属性描述是否为被映射属性的属性描述 78 if (((MappedPropertyDescriptor) descriptor).getMappedWriteMethod() == null) { 79 if (log.isDebugEnabled()) { 80 log.debug("Skipping read-only property"); 81 } 82 return; // Read-only, skip this property setter 83 } 84 type = ((MappedPropertyDescriptor) descriptor). 85 getMappedPropertyType(); 86 } else if (index >= 0 && descriptor instanceof IndexedPropertyDescriptor) {//判断是否为索引属性描述 87 if (((IndexedPropertyDescriptor) descriptor).getIndexedWriteMethod() == null) { 88 if (log.isDebugEnabled()) { 89 log.debug("Skipping read-only property"); 90 } 91 return; // Read-only, skip this property setter 92 } 93 type = ((IndexedPropertyDescriptor) descriptor). 94 getIndexedPropertyType(); 95 } else if (key != null) { 96 if (descriptor.getReadMethod() == null) {//获得可以读取属性的方法,如getter方法 97 if (log.isDebugEnabled()) { 98 log.debug("Skipping read-only property"); 99 } 100 return; // Read-only, skip this property setter 101 } 102 type = (value == null) ? Object.class : value.getClass(); 103 } else { 104 if (descriptor.getWriteMethod() == null) {//获得可以写属性的方法,如setter方法 105 if (log.isDebugEnabled()) { 106 log.debug("Skipping read-only property"); 107 } 108 return; // Read-only, skip this property setter 109 } 110 type = descriptor.getPropertyType();//获得这个属性的类型 111 } 112 } 113 //下面一堆的判断,判断这个属性是不是数组,是不是String,如果是就采用相应的convert取处理 114 // Convert the specified value to the required type 115 Object newValue = null; 116 if (type.isArray() && (index < 0)) { // Scalar value into array 117 if (value == null) { 118 String[] values = new String[1]; 119 values[0] = null; 120 newValue = getConvertUtils().convert(values, type); 121 } else if (value instanceof String) { 122 newValue = getConvertUtils().convert(value, type); 123 } else if (value instanceof String[]) { 124 newValue = getConvertUtils().convert((String[]) value, type); 125 } else { 126 newValue = convert(value, type); 127 } 128 } else if (type.isArray()) { // Indexed value into array 129 if (value instanceof String || value == null) { 130 newValue = getConvertUtils().convert((String) value, 131 type.getComponentType()); 132 } else if (value instanceof String[]) { 133 newValue = getConvertUtils().convert(((String[]) value)[0], 134 type.getComponentType()); 135 } else { 136 newValue = convert(value, type.getComponentType()); 137 } 138 } else { // Value into scalar 139 if (value instanceof String) { 140 newValue = getConvertUtils().convert((String) value, type); 141 } else if (value instanceof String[]) { 142 newValue = getConvertUtils().convert(((String[]) value)[0], 143 type); 144 } else { 145 newValue = convert(value, type); 146 } 147 } 148 149 // Invoke the setter method 150 try { 151 getPropertyUtils().setProperty(target, name, newValue); 152 } catch (NoSuchMethodException e) { 153 throw new InvocationTargetException 154 (e, "Cannot set " + propName); 155 } 156 157 }
上面有对value值进行类型的判断,它可能是个数组,也可能是一个字符串,比如
[main]
zhang=123,253,45,1246,5 ------------------->这个是一个数组
haha=hehe ------------------->这是个普通字符串
我们来看下convert这个方法的代码
1 protected Object convert(Object value, Class type) { 2 Converter converter = getConvertUtils().lookup(type); 3 if (converter != null) { 4 log.trace(" USING CONVERTER " + converter); 5 return converter.convert(type, value); 6 } else { 7 return value; 8 } 9 }
第2行代码是到一个WeakFastHashMap类型的Map中找相应类型的转换器,这个Map中注册了这些转换器
register(Boolean.TYPE, throwException ? new BooleanConverter() : new BooleanConverter(Boolean.FALSE)); register(Byte.TYPE, throwException ? new ByteConverter() : new ByteConverter(ZERO)); register(Character.TYPE, throwException ? new CharacterConverter() : new CharacterConverter(SPACE)); register(Double.TYPE, throwException ? new DoubleConverter() : new DoubleConverter(ZERO)); register(Float.TYPE, throwException ? new FloatConverter() : new FloatConverter(ZERO)); register(Integer.TYPE, throwException ? new IntegerConverter() : new IntegerConverter(ZERO)); register(Long.TYPE, throwException ? new LongConverter() : new LongConverter(ZERO)); register(Short.TYPE, throwException ? new ShortConverter() : new ShortConverter(ZERO));
拿其中的一个叫BooleanConverter的转换器看看
protected Object convertToType(Class type, Object value) throws Throwable { // All the values in the trueStrings and falseStrings arrays are // guaranteed to be lower-case. By converting the input value // to lowercase too, we can use the efficient String.equals method // instead of the less-efficient String.equalsIgnoreCase method. String stringValue = value.toString().toLowerCase(); //private String[] trueStrings = {"true", "yes", "y", "on", "1"};等于这里面某个值就为true for(int i=0; i<trueStrings.length; ++i) { if (trueStrings[i].equals(stringValue)) { return Boolean.TRUE; } } //private String[] falseStrings = {"false", "no", "n", "off", "0"};等于这里面某个值就为false for(int i=0; i<falseStrings.length; ++i) { if (falseStrings[i].equals(stringValue)) { return Boolean.FALSE; } } throw new ConversionException("Can't convert value '" + value + "' to a Boolean"); }
以上这些就是转换器,如果存在相应的转换器就会将原先的value值进行转换成其他的value值返回,转换完成后,接下来就是获得setter方法,进行反射调用了
// Invoke the setter method try { getPropertyUtils().setProperty(target, name, newValue); } catch (NoSuchMethodException e) { throw new InvocationTargetException (e, "Cannot set " + propName); }
进入setProperty一路跟踪,期间又回到那些嵌套属性判断的方法了,最后调用
PropertyDescriptor descriptor =
getPropertyDescriptor(bean, name);
获得这个属性对应的属性描述器
继续调用了
Method writeMethod = getWriteMethod(bean.getClass(), descriptor);
Method getWriteMethod(Class clazz, PropertyDescriptor descriptor) { return (MethodUtils.getAccessibleMethod(clazz, descriptor.getWriteMethod())); }
descriptor.getWriteMethod()这个代码是获得一个bean的写方法,就是setter方法,这个是JDK中提供的方法
MethodUtils.getAccessibleMethod方法的代码如下
1 public static Method getAccessibleMethod(Class clazz, Method method) { 2 3 // Make sure we have a method to check 4 if (method == null) { 5 return (null); 6 } 7 8 // If the requested method is not public we cannot call it 9 if (!Modifier.isPublic(method.getModifiers())) {//判断这个方法是不是public的 10 return (null); 11 } 12 13 boolean sameClass = true; 14 if (clazz == null) { 15 clazz = method.getDeclaringClass(); 16 } else { 17 sameClass = clazz.equals(method.getDeclaringClass());//比较当前传进来的这个bean和这个写方法所在的类是否是相同或者是它的子类 18 if (!method.getDeclaringClass().isAssignableFrom(clazz)) { 19 throw new IllegalArgumentException(clazz.getName() + 20 " is not assignable from " + method.getDeclaringClass().getName()); 21 } 22 } 23 24 // If the class is public, we are done 25 if (Modifier.isPublic(clazz.getModifiers())) {//判断这个类是不是public的 26 if (!sameClass && !Modifier.isPublic(method.getDeclaringClass().getModifiers())) { 27 setMethodAccessible(method); // Default access superclass workaround//如果这个类是public的,方法不是public的,方法对应的类不是public的,那么就设置accessible为true 28 } 29 return (method); 30 }
上面这个方法没什么好说的就是对这个方法和所传的类类型是否相同,或者存在父子关系,还有就是对访问权限的设置
好了,得到setter方法了,那么接下来就是进行调用了
invokeMethod(writeMethod, bean, values);
return method.invoke(bean, values);
总结
难就难在各种递归解析,考虑各种需要解析的情况,逻辑上比较复杂,真服了写这些代码的大神,ok!!!结束。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?