Introduction
In a previous article , we discussed the general details of implementing such a dependency injection framework based on the prototype of the spring framework given in the book "Expert One-on-One J2EE Design and Development". With the evolution of the entire spring framework over the years, many implementation details have undergone major changes. It is necessary for us to explore with the latest code. In addition, if we discuss with some ideas of building such a framework, I believe there will be even greater gains. Because the framework is so complex, the discussion of implementation details is divided into several chapters.
Several points of the framework implementation
Two previous articles discussed the concepts of inversion of control and dependency injection and how to implement dependency injection in spring . On the basis of these two articles, let's discuss what to pay attention to in the details of implementing a dependency injection framework.
Building on an example we discussed earlier:
In this example, we want the assembler to provide the function of creating and assembling objects, so that we only need to use it to get the objects we need. To let the assembler know which objects to assemble and how to assemble these objects, we need to provide some means of description. One is through the xml configuration file. A typical spring xml configuration file is as follows:
<bean id="handler" class="BusinessHandler"> <property name="dao" ref="CVSAccountDAO"/> </bean> <bean id="CVSAccountDAO" class="CVSAccountDAO"/>
Of course, we know that in addition to this xml configuration method, we also use annotations to construct objects through java config or component scan.
Combining the above discussions, we found that to implement such a framework, there are at least a few points to consider.
1. To achieve flexible support for multiple configurations, such as the xml file here, and the properties file in the previous system.
2. How to construct the objects we need? The objects we want to build have their own dependencies, so that these objects form a dependency graph relationship. In what structure should these objects be recorded and stored? Taking the configuration recorded in the previous xml file, the object attributes we define are only strings when they are read. How should they be converted into specific objects?
So, from the most intuitive point of view, we may be able to get a simple process of implementing this framework. It is nothing more than one part is responsible for parsing specific files, and one part builds objects based on the information obtained. However, in the specific implementation, there are still many details to be considered.
As we already know in the previous discussion, the BeanFactory is mainly responsible for reading the specifically created objects, and some of its specific implementations will parse the detailed configuration files. The BeanWrapper defines the configuration and settings for specific object properties. Let's first discuss the process of this part of BeanWrapper.
class diagram
The related class structure of BeanWrapper is as follows:
There are not many related classes here, and it looks relatively clear. Compared with the previous implementation, three interfaces are specially abstracted here. They are PropertyAccessor, TypeConverter and PropertyEditorRegistry. The functions defined in these three interfaces are combined to realize the construction and creation of objects.
From the previous configuration example, we have a bean id of handler, which has a property of dao. And dao is another object. So, here, to construct the handler object, you need to construct the dao object first. In the dao object description, there is its class information. From the detailed implementation point of view, it may be necessary to load the defined class through the classloader. Then call the constructor to get the object. Of course, if it also has dependencies, it should be necessary to construct these dependent objects recursively. After constructing the object, assign the dao object to the dao property of the handler object. There is also a problem here. Is this dao property a member variable of the handler object? How should we set it up? Is it calling a special set method? How do we know which set method corresponds to this dao property? Combining the responsibilities and specific implementations of these interfaces, let's sort out their detailed implementation step by step.
PropertyEditorRegistry
This interface looks relatively simple, and mainly defines three methods:
void registerCustomEditor(Class<?> requiredType, PropertyEditor propertyEditor); void registerCustomEditor(@Nullable Class<?> requiredType, @Nullable String propertyPath, PropertyEditor propertyEditor); @Nullable PropertyEditor findCustomEditor(@Nullable Class<?> requiredType, @Nullable String propertyPath);
它主要提供支持注册customPropertyEditor以及查找对应的customPropertyEditor等功能。为什么要这么个功能呢?这个功能放到这里有什么用呢?这就牵涉到前面我们定义的配置文件和要构建的对象之间的关系。一般来说,我们定义的配置文件里定义的所有对象的值或者属性都是采用字符串的形式的。但是要构造的对象里它是对应到具体类型的。所以,就需要将这些字符串转换成我们设定的目标对象。在spring里面,它已经提供了对大多数基本类型的转换支持,但是对于某些用户自定义的类型,我们希望它也能够支持的话,我们就需要提供一个让用户来定义它们自己的propertyEditor并让框架识别使用。这就是这个接口的作用。
关于spring property editor的注册和使用可以参考这个示例链接,这里就不展开讲了。我们深入一些源码实现细节看看。刚才讲到过,既然我们在构造目标对象属性的时候,需要将string类型转换为具体的对象,这个转化就指望property editor。那么,我们就需要有地方来记录这种转换信息并执行具体的转化工作。
这部分property editor注册信息的实现主要放在类PropertyEditorRegistrySupport里。这个类里,它是通过一系列的map结构来保存propertyEditor信息的。这些map结构的定义如下:
@Nullable private Map<Class<?>, PropertyEditor> defaultEditors; @Nullable private Map<Class<?>, PropertyEditor> overriddenDefaultEditors; @Nullable private Map<Class<?>, PropertyEditor> customEditors; @Nullable private Map<String, CustomEditorHolder> customEditorsForPath; @Nullable private Map<Class<?>, PropertyEditor> customEditorCache;
In the method getDefaultEditor, it will load the default editors. The implementation of this method is as follows:
public PropertyEditor getDefaultEditor(Class<?> requiredType) { if (!this.defaultEditorsActive) { return null; } if (this.overriddenDefaultEditors != null) { PropertyEditor editor = this.overriddenDefaultEditors.get(requiredType); if (editor != null) { return editor; } } if (this.defaultEditors == null) { createDefaultEditors(); } return this.defaultEditors.get(requiredType); }The specific implementations of these defaultEditors are in the package org.springframework.beans.propertyeditors. Their main implementation details are achieved by implementing the setAsText and getAsText methods in the interface java.beans.PropertyEditorSupport. In this method, the createDefaultEditors method is to add these default implemented propertyEditors to the defaultEditors map.
接口里对应的两个方法findCustomEditor和registerCustomEditor的实现则相对比较简单,主要就是将customEditor添加到map里或者在map里查找对应的customEditor。这两个方法的实现如下:
@Override public void registerCustomEditor(@Nullable Class<?> requiredType, @Nullable String propertyPath, PropertyEditor propertyEditor) { if (requiredType == null && propertyPath == null) { throw new IllegalArgumentException("Either requiredType or propertyPath is required"); } if (propertyPath != null) { if (this.customEditorsForPath == null) { this.customEditorsForPath = new LinkedHashMap<>(16); } this.customEditorsForPath.put(propertyPath, new CustomEditorHolder(propertyEditor, requiredType)); } else { if (this.customEditors == null) { this.customEditors = new LinkedHashMap<>(16); } this.customEditors.put(requiredType, propertyEditor); this.customEditorCache = null; } } @Override @Nullable public PropertyEditor findCustomEditor(@Nullable Class<?> requiredType, @Nullable String propertyPath) { Class<?> requiredTypeToUse = requiredType; if (propertyPath != null) { if (this.customEditorsForPath != null) { // Check property-specific editor first. PropertyEditor editor = getCustomEditor(propertyPath, requiredType); if (editor == null) { List<String> strippedPaths = new LinkedList<>(); addStrippedPropertyPaths(strippedPaths, "", propertyPath); for (Iterator<String> it = strippedPaths.iterator(); it.hasNext() && editor == null;) { String strippedPath = it.next(); editor = getCustomEditor(strippedPath, requiredType); } } if (editor != null) { return editor; } } if (requiredType == null) { requiredTypeToUse = getPropertyType(propertyPath); } } // No property-specific editor -> check type-specific editor. return getCustomEditor(requiredTypeToUse); }
这里我们定义的几个方法在后面支持对象创建的时候会用到。这里先做一个前期的介绍。
TypeConverter
为了实现前面类型转换的过程,还有一个重要的接口TypeConverter:
public interface TypeConverter { @Nullable <T> T convertIfNecessary(@Nullable Object value, @Nullable Class<T> requiredType) throws TypeMismatchException; @Nullable <T> T convertIfNecessary(@Nullable Object value, @Nullable Class<T> requiredType, @Nullable MethodParameter methodParam) throws TypeMismatchException; @Nullable <T> T convertIfNecessary(@Nullable Object value, @Nullable Class<T> requiredType, @Nullable Field field) throws TypeMismatchException; }
这个接口的实现主要基于propertyEditor来进行类型转换。所以,它和前面的接口PropertyEditorRegistry有紧密的关系。
对这些方法的实现定义主要放在类TypeConverterSupport里,它有一个具体实现方法doConvert:
@Nullable private <T> T doConvert(@Nullable Object value,@Nullable Class<T> requiredType, @Nullable MethodParameter methodParam, @Nullable Field field) throws TypeMismatchException { Assert.state(this.typeConverterDelegate != null, "No TypeConverterDelegate"); try { if (field != null) { return this.typeConverterDelegate.convertIfNecessary(value, requiredType, field); } else { return this.typeConverterDelegate.convertIfNecessary(value, requiredType, methodParam); } } catch (ConverterNotFoundException | IllegalStateException ex) { throw new ConversionNotSupportedException(value, requiredType, ex); } catch (ConversionException | IllegalArgumentException ex) { throw new TypeMismatchException(value, requiredType, ex); } }
从这里的实现细节里可以看到,它具体的类型转换是在typeConverterDelegate对象里实现的。在类TypeConverterDelegate里有详细的实现方法convertIfNecessary。这部分的实现内容比较多,它的大体实现思路如下:
1. 首先尝试通过propertyEditorRegistry来获取customEditor以及conversionService。
2. 如果找到conversionService而得到的customEditor为空,则调用conversionService的convert方法实现类型转换。
3.否则,尝试检查customEditor是否为空,为空的话则调用findDefaultEditor方法来查找它的默认propertyEditor。然后再调用doConvertValue来进行类型转换。
doConvertValue方法里的实现其实很简单,就是调用propertyEditor方法的getValue方法,将String类型转换成目标对象。当然,对于其他类型的输入,还有一些其他的判断处理,它主要的处理逻辑代码如下:
private Object doConvertValue(@Nullable Object oldValue, @Nullable Object newValue, @Nullable Class<?> requiredType, @Nullable PropertyEditor editor) { Object convertedValue = newValue; if (editor != null && !(convertedValue instanceof String)) { // Not a String -> use PropertyEditor's setValue. // With standard PropertyEditors, this will return the very same object; // we just want to allow special PropertyEditors to override setValue // for type conversion from non-String values to the required type. try { editor.setValue(convertedValue); Object newConvertedValue = editor.getValue(); if (newConvertedValue != convertedValue) { convertedValue = newConvertedValue; // Reset PropertyEditor: It already did a proper conversion. // Don't use it again for a setAsText call. editor = null; } } catch (Exception ex) { if (logger.isDebugEnabled()) { logger.debug("PropertyEditor [" + editor.getClass().getName() + "] does not support setValue call", ex); } // Swallow and proceed. } } Object returnValue = convertedValue; if (requiredType != null && !requiredType.isArray() && convertedValue instanceof String[]) { // Convert String array to a comma-separated String. // Only applies if no PropertyEditor converted the String array before. // The CSV String will be passed into a PropertyEditor's setAsText method, if any. if (logger.isTraceEnabled()) { logger.trace("Converting String array to comma-delimited String [" + convertedValue + "]"); } convertedValue = StringUtils.arrayToCommaDelimitedString((String[]) convertedValue); } if (convertedValue instanceof String) { if (editor != null) { // Use PropertyEditor's setAsText in case of a String value. if (logger.isTraceEnabled()) { logger.trace("Converting String to [" + requiredType + "] using property editor [" + editor + "]"); } String newTextValue = (String) convertedValue; return doConvertTextValue(oldValue, newTextValue, editor); } else if (String.class == requiredType) { returnValue = convertedValue; } } return returnValue; } private Object doConvertTextValue(@Nullable Object oldValue, String newTextValue, PropertyEditor editor) { try { editor.setValue(oldValue); } catch (Exception ex) { if (logger.isDebugEnabled()) { logger.debug("PropertyEditor [" + editor.getClass().getName() + "] does not support setValue call", ex); } // Swallow and proceed. } editor.setAsText(newTextValue); return editor.getValue(); }
上面这部分的代码看起来比较多,但是其实质上就是根据给定类型的对象转化成目标对象。
关于上面这部分的类型转换到底在哪些地方有用到呢?实际上,在BeanFactory的一些实现里,它们会读取BeanDefinition信息,然后做转换。在下图的顺序图里,就有一个类型转换的过程:
上图中就是类型转换的一个应用。当然,这里的convertIfNecessary方法等转换在其他地方也有应用。
PropertyAccessor
在这几个接口中,PropertyAccessor是定义我们实现核心属性设置功能的那个。它里面定义的几个方法如下:
Class<?> getPropertyType(String propertyName) throws BeansException; TypeDescriptor getPropertyTypeDescriptor(String propertyName) throws BeansException; Object getPropertyValue(String propertyName) throws BeansException; void setPropertyValue(String propertyName, @Nullable Object value) throws BeansException; void setPropertyValues(Map<?, ?> map) throws BeansException;
对于属性的设置来说,这里的两个重点方法是getPropertyValue和setPropertyValue。我们先针对这两个方法的实现来进行分析。
setPropertyValue
在PropertyAccessor的子类AbstractPropertyAccessor里有一个对setPropertyValues的实现如下:
public void setPropertyValues(PropertyValues pvs, boolean ignoreUnknown, boolean ignoreInvalid) throws BeansException { List<PropertyAccessException> propertyAccessExceptions = null; List<PropertyValue> propertyValues = (pvs instanceof MutablePropertyValues ? ((MutablePropertyValues) pvs).getPropertyValueList() : Arrays.asList(pvs.getPropertyValues())); for (PropertyValue pv : propertyValues) { try { // This method may throw any BeansException, which won't be caught // here, if there is a critical failure such as no matching field. // We can attempt to deal only with less serious exceptions. setPropertyValue(pv); } catch (NotWritablePropertyException ex) { if (!ignoreUnknown) { throw ex; } // Otherwise, just ignore it and continue... } catch (NullValueInNestedPathException ex) { if (!ignoreInvalid) { throw ex; } // Otherwise, just ignore it and continue... } catch (PropertyAccessException ex) { if (propertyAccessExceptions == null) { propertyAccessExceptions = new LinkedList<>(); } propertyAccessExceptions.add(ex); } } // If we encountered individual exceptions, throw the composite exception. if (propertyAccessExceptions != null) { PropertyAccessException[] paeArray = propertyAccessExceptions.toArray(new PropertyAccessException[propertyAccessExceptions.size()]); throw new PropertyBatchUpdateException(paeArray); } }It actually sets these values by calling the setPropertyValue method multiple times. Here it does not implement the setPropertyValue method, but defines this virtual method, which is implemented by its subclasses. This routine of it is the design pattern of template method.
Let's look at the implementation of setPropertyValue next. The implementation of its two overloaded setPropertyValue methods relies on the following methods:
protected void setPropertyValue(PropertyTokenHolder tokens, PropertyValue pv) throws BeansException { if (tokens.keys != null) { processKeyedProperty(tokens, pv); } else { processLocalProperty(tokens, pv); } }
The two methods in this header, processKeyedProperty and processLocalProperty, are used to handle nested property types and local native types, respectively. For example, the processKeyedProperty method will deal with the converted property value type. For example, it will check the type of Array, List, Map and other types for special processing. The implementation of the processLocalProperty method is as follows:
private void processLocalProperty(PropertyTokenHolder tokens, PropertyValue pv) { PropertyHandler ph = getLocalPropertyHandler(tokens.actualName); if (ph == null || !ph.isWritable()) { if (pv.isOptional()) { if (logger.isDebugEnabled()) { logger.debug("Ignoring optional value for property '" + tokens.actualName + "' - property not found on bean class [" + getRootClass().getName() + "]"); } return; } else { throw createNotWritablePropertyException(tokens.canonicalName); } } Object oldValue = null; try { Object originalValue = pv.getValue(); Object valueToApply = originalValue; if (!Boolean.FALSE.equals(pv.conversionNecessary)) { if (pv.isConverted()) { valueToApply = pv.getConvertedValue(); } else { if (isExtractOldValueForEditor() && ph.isReadable()) { try { oldValue = ph.getValue(); } catch (Exception ex) { if (ex instanceof PrivilegedActionException) { ex = ((PrivilegedActionException) ex).getException(); } if (logger.isDebugEnabled()) { logger.debug("Could not read previous value of property '" + this.nestedPath + tokens.canonicalName + "'", ex); } } } valueToApply = convertForProperty( tokens.canonicalName, oldValue, originalValue, ph.toTypeDescriptor()); } pv.getOriginalPropertyValue().conversionNecessary = (valueToApply != originalValue); } ph.setValue(valueToApply); } catch (TypeMismatchException ex) { throw ex; } catch (InvocationTargetException ex) { PropertyChangeEvent propertyChangeEvent = new PropertyChangeEvent( getRootInstance(), this.nestedPath + tokens.canonicalName, oldValue, pv.getValue()); if (ex.getTargetException() instanceof ClassCastException) { throw new TypeMismatchException(propertyChangeEvent, ph.getPropertyType(), ex.getTargetException()); } else { Throwable cause = ex.getTargetException(); if (cause instanceof UndeclaredThrowableException) { // May happen e.g. with Groovy-generated methods cause = cause.getCause(); } throw new MethodInvocationException(propertyChangeEvent, cause); } } catch (Exception ex) { PropertyChangeEvent pce = new PropertyChangeEvent( getRootInstance(), this.nestedPath + tokens.canonicalName, oldValue, pv.getValue()); throw new MethodInvocationException(pce, ex); } }
这部分的代码看起来比较复杂, 他里面最关键的点就是两行代码。一个是
valueToApply = convertForProperty( tokens.canonicalName, oldValue, originalValue, ph.toTypeDescriptor());
在讨论前面TypeConverter的实现时我们已经讨论过,这个方法就是实现将读取到的配置信息转换成对应的property对象。
另外一个关键点就是如下这行代码:
ph.setValue(valueToApply);
这里是调用了它的一个内部类PropertyHandler的setValue方法。这个PropertyHandler类的定义如下:
protected abstract static class PropertyHandler { private final Class<?> propertyType; private final boolean readable; private final boolean writable; public PropertyHandler(Class<?> propertyType, boolean readable, boolean writable) { this.propertyType = propertyType; this.readable = readable; this.writable = writable; } public Class<?> getPropertyType() { return this.propertyType; } public boolean isReadable() { return this.readable; } public boolean isWritable() { return this.writable; } public abstract TypeDescriptor toTypeDescriptor(); public abstract ResolvableType getResolvableType(); @Nullable public Class<?> getMapKeyType(int nestingLevel) { return getResolvableType().getNested(nestingLevel).asMap().resolveGeneric(0); } @Nullable public Class<?> getMapValueType(int nestingLevel) { return getResolvableType().getNested(nestingLevel).asMap().resolveGeneric(1); } @Nullable public Class<?> getCollectionType(int nestingLevel) { return getResolvableType().getNested(nestingLevel).asCollection().resolveGeneric(); } @Nullable public abstract TypeDescriptor nested(int level); @Nullable public abstract Object getValue() throws Exception; public abstract void setValue(@Nullable Object value) throws Exception; }
它是一个抽象类,具体的实现类是位于BeanWrapperImpl里的类BeanPropertyHandler。它的实现如下:
@Override public void setValue(final @Nullable Object value) throws Exception { final Method writeMethod = (this.pd instanceof GenericTypeAwarePropertyDescriptor ? ((GenericTypeAwarePropertyDescriptor) this.pd).getWriteMethodForActualAccess() : this.pd.getWriteMethod()); if (System.getSecurityManager() != null) { AccessController.doPrivileged((PrivilegedAction<Object>) () -> { ReflectionUtils.makeAccessible(writeMethod); return null; }); try { AccessController.doPrivileged((PrivilegedExceptionAction<Object>) () ->writeMethod.invoke(getWrappedInstance(), value), acc); } catch (PrivilegedActionException ex) { throw ex.getException(); } } else { ReflectionUtils.makeAccessible(writeMethod); writeMethod.invoke(getWrappedInstance(), value); } }
从具体实现来看,这部分其实很简单,无非就是通过它的propertyDescriptor来拿到它的writeMethod,然后通过反射来调用这个方法实现赋值的。
getPropertyValue
有了前面这个的思路,getPropertyValue的实现也非常接近。它的具体获取值的方法也在BeanPropertyHandler里,详细的实现如下:
public Object getValue() throws Exception { final Method readMethod = this.pd.getReadMethod(); if (System.getSecurityManager() != null) { AccessController.doPrivileged((PrivilegedAction<Object>) () -> { ReflectionUtils.makeAccessible(readMethod); return null; }); try { return AccessController.doPrivileged((PrivilegedExceptionAction<Object>) () -> readMethod.invoke(getWrappedInstance(), (Object[]) null), acc); } catch (PrivilegedActionException pae) { throw pae.getException(); } } else { ReflectionUtils.makeAccessible(readMethod); return readMethod.invoke(getWrappedInstance(), (Object[]) null); } }
它实现就是通过propertyDescriptor来获取readMethod,然后通过反射调用它。
实现getPropertyValue的方法在AbstractNestablePropertyAccessor里有定义。它分别定义了两个重载的方法:
@Override @Nullable public Object getPropertyValue(String propertyName) throws BeansException { AbstractNestablePropertyAccessor nestedPa = getPropertyAccessorForPropertyPath(propertyName); PropertyTokenHolder tokens = getPropertyNameTokens(getFinalPath(nestedPa, propertyName)); return nestedPa.getPropertyValue(tokens); } @SuppressWarnings("unchecked") @Nullable protected Object getPropertyValue(PropertyTokenHolder tokens) throws BeansException { String propertyName = tokens.canonicalName; String actualName = tokens.actualName; PropertyHandler ph = getLocalPropertyHandler(actualName); if (ph == null || !ph.isReadable()) { throw new NotReadablePropertyException(getRootClass(), this.nestedPath + propertyName); } try { Object value = ph.getValue(); if (tokens.keys != null) { if (value == null) { if (isAutoGrowNestedPaths()) { value = setDefaultValue(new PropertyTokenHolder(tokens.actualName)); } else { throw new NullValueInNestedPathException(getRootClass(), this.nestedPath + propertyName, "Cannot access indexed value of property referenced in indexed " + "property path '" + propertyName + "': returned null"); } } String indexedPropertyName = tokens.actualName; // apply indexes and map keys for (int i = 0; i < tokens.keys.length; i++) { String key = tokens.keys[i]; if (value == null) { throw new NullValueInNestedPathException(getRootClass(), this.nestedPath + propertyName, "Cannot access indexed value of property referenced in indexed " + "property path '" + propertyName + "': returned null"); } else if (value.getClass().isArray()) { int index = Integer.parseInt(key); value = growArrayIfNecessary(value, index, indexedPropertyName); value = Array.get(value, index); } else if (value instanceof List) { int index = Integer.parseInt(key); List<Object> list = (List<Object>) value; growCollectionIfNecessary(list, index, indexedPropertyName, ph, i + 1); value = list.get(index); } else if (value instanceof Set) { // Apply index to Iterator in case of a Set. Set<Object> set = (Set<Object>) value; int index = Integer.parseInt(key); if (index < 0 || index >= set.size()) { throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName, "Cannot get element with index " + index + " from Set of size " + set.size() + ", accessed using property path '" + propertyName + "'"); } Iterator<Object> it = set.iterator(); for (int j = 0; it.hasNext(); j++) { Object elem = it.next(); if (j == index) { value = elem; break; } } } else if (value instanceof Map) { Map<Object, Object> map = (Map<Object, Object>) value; Class<?> mapKeyType = ph.getResolvableType().getNested(i + 1).asMap().resolveGeneric(0); // IMPORTANT: Do not pass full property name in here - property editors // must not kick in for map keys but rather only for map values. TypeDescriptor typeDescriptor = TypeDescriptor.valueOf(mapKeyType); Object convertedMapKey = convertIfNecessary(null, null, key, mapKeyType, typeDescriptor); value = map.get(convertedMapKey); } else { throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName, "Property referenced in indexed property path '" + propertyName + "' is neither an array nor a List nor a Set nor a Map; returned value was [" + value + "]"); } indexedPropertyName += PROPERTY_KEY_PREFIX + key + PROPERTY_KEY_SUFFIX; } } return value; } catch (IndexOutOfBoundsException ex) { throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName, "Index of out of bounds in property path '" + propertyName + "'", ex); } catch (NumberFormatException ex) { throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName, "Invalid index in property path '" + propertyName + "'", ex); } catch (TypeMismatchException ex) { throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName, "Invalid index in property path '" + propertyName + "'", ex); } catch (InvocationTargetException ex) { throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName, "Getter for property '" + actualName + "' threw exception", ex); } catch (Exception ex) { throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName, "Illegal attempt to get property '" + actualName + "' threw exception", ex); } }
这部分的代码也比较冗长,看起来有点吓人,它实际上是因为要考虑处理的情况比较多。主要的实现骨架就是i通过PropertyHandler里的getValue方法来获取到给定属性的值,然后根据这个值的类型进行各种具体转换。这里考虑到的数据类型有Array, List, Set, Map等几种类型。
BeanWrapper
有了前面几个类的基础,这里剩下的这个接口BeanWrapper的职能相对来说简单了一些,它主要用来分析我们构造的对象的元数据信息。比如说我们给定一个对象的class信息时,我们需要知道它有哪些是符合java bean规范的属性。哪些方法是可读的,哪些是可写的。其实这一切都依赖于它里面定义的PropertyDescriptor这个类的支持。我们重点分析它里面的两个方法:
PropertyDescriptor[] getPropertyDescriptors(); PropertyDescriptor getPropertyDescriptor(String propertyName) throws InvalidPropertyException;
这两个方法的实现主要都定义在BeanWrapperImpl里,它的实现细节如下:
@Override public PropertyDescriptor[] getPropertyDescriptors() { return getCachedIntrospectionResults().getPropertyDescriptors(); } @Override public PropertyDescriptor getPropertyDescriptor(String propertyName) throws InvalidPropertyException { BeanWrapperImpl nestedBw = (BeanWrapperImpl) getPropertyAccessorForPropertyPath(propertyName); String finalPath = getFinalPath(nestedBw, propertyName); PropertyDescriptor pd = nestedBw.getCachedIntrospectionResults().getPropertyDescriptor(finalPath); if (pd == null) { throw new InvalidPropertyException(getRootClass(), getNestedPath() + propertyName, "No property '" + propertyName + "' found"); } return pd; }
这部分的代码非常简单,它主要是通过CachedIntrospectionResults类里的方法来获取PropertyDescriptor信息。这个CachedIntrospectionResults的实现我们在之前的文章里也讨论过。它主要是通过反射来得到目标对象里的这些元数据信息。在这个类里,实现获取元数据信息主要放在方法forClass里:
static CachedIntrospectionResults forClass(Class<?> beanClass) throws BeansException { CachedIntrospectionResults results = strongClassCache.get(beanClass); if (results != null) { return results; } results = softClassCache.get(beanClass); if (results != null) { return results; } results = new CachedIntrospectionResults(beanClass); ConcurrentMap<Class<?>, CachedIntrospectionResults> classCacheToUse; if (ClassUtils.isCacheSafe(beanClass, CachedIntrospectionResults.class.getClassLoader()) || isClassLoaderAccepted(beanClass.getClassLoader())) { classCacheToUse = strongClassCache; } else { if (logger.isDebugEnabled()) { logger.debug("Not strongly caching class [" + beanClass.getName() + "] because it is not cache-safe"); } classCacheToUse = softClassCache; } CachedIntrospectionResults existing = classCacheToUse.putIfAbsent(beanClass, results); return (existing != null ? existing : results); }
这里定义的一系列信息都会放在若干个Map里。为了性能考虑,它采用的是ConcurrentHashMap。CachedIntrospectionResults定义的这些用于缓存类元信息的结构如下:
static final Set<ClassLoader> acceptedClassLoaders = Collections.newSetFromMap(new ConcurrentHashMap<>(16)); /** * Map keyed by Class containing CachedIntrospectionResults, strongly held. * This variant is being used for cache-safe bean classes. */ static final ConcurrentMap<Class<?>, CachedIntrospectionResults> strongClassCache = new ConcurrentHashMap<>(64); /** * Map keyed by Class containing CachedIntrospectionResults, softly held. * This variant is being used for non-cache-safe bean classes. */ static final ConcurrentMap<Class<?>, CachedIntrospectionResults> softClassCache = new ConcurrentReferenceHashMap<>(64);
结合前面forClass的实现可以看到CachedIntrospectionResults是一个单例对象。它的构造函数是私有的。在forClass方法里,如果在两个cache里(strongClassCache, softClassCache)都没有找到我们给定的class,那么就会调用它的这个构造函数。在构造函数里,它会通过获取beanInfo信息得到更详细的信息。这个构造函数的实现如下:
private CachedIntrospectionResults(Class<?> beanClass) throws BeansException { try { if (logger.isTraceEnabled()) { logger.trace("Getting BeanInfo for class [" + beanClass.getName() + "]"); } this.beanInfo = getBeanInfo(beanClass, shouldIntrospectorIgnoreBeaninfoClasses); if (logger.isTraceEnabled()) { logger.trace("Caching PropertyDescriptors for class [" + beanClass.getName() + "]"); } this.propertyDescriptorCache = new LinkedHashMap<>(); // This call is slow so we do it once. PropertyDescriptor[] pds = this.beanInfo.getPropertyDescriptors(); for (PropertyDescriptor pd : pds) { if (Class.class == beanClass && ("classLoader".equals(pd.getName()) || "protectionDomain".equals(pd.getName()))) { // Ignore Class.getClassLoader() and getProtectionDomain() methods - nobody needs to bind to those continue; } if (logger.isTraceEnabled()) { logger.trace("Found bean property '" + pd.getName() + "'" + (pd.getPropertyType() != null ? " of type [" + pd.getPropertyType().getName() + "]" : "") + (pd.getPropertyEditorClass() != null ? "; editor [" + pd.getPropertyEditorClass().getName() + "]" : "")); } pd = buildGenericTypeAwarePropertyDescriptor(beanClass, pd); this.propertyDescriptorCache.put(pd.getName(), pd); } // Explicitly check implemented interfaces for setter/getter methods as well, // in particular for Java 8 default methods... Class<?> clazz = beanClass; while (clazz != null && clazz != Object.class) { Class<?>[] ifcs = clazz.getInterfaces(); for (Class<?> ifc : ifcs) { if (!ClassUtils.isJavaLanguageInterface(ifc)) { BeanInfo ifcInfo = getBeanInfo(ifc, true); PropertyDescriptor[] ifcPds = ifcInfo.getPropertyDescriptors(); for (PropertyDescriptor pd : ifcPds) { if(!this.propertyDescriptorCache.containsKey(pd.getName())) { pd = buildGenericTypeAwarePropertyDescriptor(beanClass, pd); this.propertyDescriptorCache.put(pd.getName(), pd); } } } } clazz = clazz.getSuperclass(); } this.typeDescriptorCache = new ConcurrentReferenceHashMap<>(); } catch (IntrospectionException ex) { throw new FatalBeanException("Failed to obtain BeanInfo for class [" + beanClass.getName() + "]", ex); } }
这里不仅构造了当前类的元数据信息并保存起来,还会将该类的父类信息一并给处理了。因为整个反射处理这些数据的速度比较慢,所以需要将他们都缓存到一个地方以提升效率。于是这些处理后的数据都保存到这两个map里:
private final Map<String, PropertyDescriptor> propertyDescriptorCache; private final ConcurrentMap<PropertyDescriptor, TypeDescriptor> typeDescriptorCache;
这样,关于BeanWrapper的整体功能分析就完成了。但是这里牵涉到的类和关系非常多也非常复杂。
总结
从我们最初开始看到的spring雏形到演化到5.0版本的spring。里面变化的东西确实有太多太多。从我们分析的这一部分仅仅能看到冰山的一角。当然,结合我们前面设计一个依赖注入框架的想法来看。这里有好几个地方是值得学习和借鉴的。首先,为了支持我们定义的配置来构建需要的对象,需要一方面针对这些对象的类来建模和保存他们的元数据信息。这样可以方便的构造需要的对象。在实际中为了提升处理的效率,我们可以考虑使用缓存来保存这些元数据信息。其次,为了解析这些构造的信息并构造对象,我们可以划分出PropertyEditorRegistery, TypeConverter, PropertyAccessor这儿几个接口。他们分别用来负责不同的职能。这样能实现一个良好的职责划分。比如PropertyEditorRegistry,主要用来支持所有将配置转换成各种类型对象的propertyEditor的注册。而TypeConverter则定义实现各种具体转换的规范。而在PropertyAccessor里,我们需要将要构造的对象建模成一系列的属性组。通过一种通用的getPropertyValue, setPropertyValue等符合java Bean规范的方式来设置它们。最终这些来达到构造一个对象并设置它的基础属性的功能。
参考材料
expert one on one j2ee design and development