`
frank-liu
  • 浏览: 1665449 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

spring container 实现分析:BeanWrapper

阅读更多

简介

    在之前的一篇文章里, 我们基于《Expert One-on-One J2EE Design and Development》这本书里给出的spring框架雏形讨论了实现这么一个依赖注入的框架的大致细节。随着这么些年整个spring框架的演化,里面的很多实现细节已经发生了比较大的变化。我们有必要结合目前最新的代码去探究一下。另外,如果结合构建这么一个框架的一些思想来讨论的话,相信也会有更加大的收获。因为框架实在是非常复杂,因此对于实现细节的讨论要分成几个篇章来写。

 

框架实现的几个点

    之前有两篇文章分别讨论过控制反转和依赖注入的概念以及在spring中如何实现依赖注入。 在这两篇的基础上,我们来讨论要实现一个依赖注入的框架在细节上要注意哪些地方。

   以我们之前讨论过的一个示例为基础:

    在这个示例中,我们希望assembler来提供创建和组装对象的职能,这样,我们只需要通过它来获取我们需要的对象就可以了。而要让assembler知道要组装哪些对象以及怎么去组装这些对象,我们需要提供某种描述的手段。一种就是通过xml的配置文件,一个典型的spring xml配置文件如下:

 

<bean id="handler" class="BusinessHandler">
	<property name="dao" ref="CVSAccountDAO"/>
</bean>

<bean id="CVSAccountDAO" class="CVSAccountDAO"/>

   当然,我们知道除了这种xml配置的方式以外,我们也有通过java config或者component scan的方式,用注解实现对象的构建。

   综合上述的这些讨论,我们发现,要实现这么一个框架,至少有这么几个点要考虑。

1. 要实现灵活支持多种配置的方式,像这里有xml文件,之前的系统里还有properties文件的方式。

2. 我们需要的对象该怎么构建?我们要构建的对象它们又有自己的依赖,这样这些对象就构成了一个依赖图的关系。这些对象该用什么样的结构记录和保存?以前面xml文件记录的配置来说,我们定义的对象属性在读取到的时候仅仅是字符串,它们该怎么样转换成具体的对象呢?

    所以说,从最直观的角度来说,我们也许能得到一个简单的实现这个框架的过程。无非就是一个部分负责解析具体的文件,一个部分根据得到的信息来构建对象。但是在具体的实现里,要考虑的细节还是非常多的。

    在之前的讨论里我们已经知道了,BeanFactory主要负责来读取具体创建的对象,它的一些具体实现会去解析详细的配置文件。而BeanWrapper定义了对于具体对象属性的配置和设置。我们先针对BeanWrapper这部分的流程做一个讨论。

 

类结构图

    BeanWrapper这一块相关的类结构如下图:

    这里相关联的类不是很多,看起来还比较清晰。和之前的实现比起来,这里专门抽象出来了3个接口。分别是PropertyAccessor, TypeConverter和PropertyEditorRegistry。这三个接口里定义的功能综合起来就是为了实现对象的构造和创建的。

    以前面的配置示例来看,我们有bean id为handler,它有一个property为dao。而dao则是另外一个对象。那么,在这里,要构造这个handler对象,就需要首先构造dao这个对象。在dao对象描述里,有它的class信息,从详细的实现来看,可能需要通过classloader来加载这个定义的类。然后调用构造函数来得到这个对象。当然,如果它本身也有依赖,应该需要这么递归的去把这些依赖的对象给构建好。在构建好对象之后,将dao对象赋值给handler对象的dao property。这里也有一个问题。这个dao property是handler对象的成员变量吗?我们该怎么设置呢?是调用专门的set方法吗?我们怎么知道哪个set方法对应这个dao属性呢?结合这几个接口的职责和具体的实现,我们来一步步整理它们的详细实现。

 

PropertyEditorRegistry

   这个接口看起来比较简单,主要就定义了3个方法:

 

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;

    在方法getDefaultEditor里,它会将默认的这些editor给加载进来。这个方法的实现如下:

 

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);
	}
     这些defaultEditors的具体实现都在包org.springframework.beans.propertyeditors里。它们的主要实现细节都是通过实现接口java.beans.PropertyEditorSupport里的setAsText以及getAsText方法。在这个方法里,createDefaultEditors方法就是将这些默认实现的propertyEditors添加到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);
	}
}
   它实际上是通过多次调用setPropertyValue方法来实现设置这些值的。在这里它并没有实现setPropertyValue方法,而是定义了这个虚方法,由它的子类来做具体的实现。它的这个套路就是template method的设计模式。

 

   我们接着来看setPropertyValue的实现。它的两个重载的setPropertyValue方法的实现都依赖如下的方法:

protected void setPropertyValue(PropertyTokenHolder tokens, PropertyValue pv) throws BeansException {
	if (tokens.keys != null) {
		processKeyedProperty(tokens, pv);
	}
	else {
		processLocalProperty(tokens, pv);
	}
}

   这里头的两个方法processKeyedProperty和processLocalProperty分别用来处理嵌套的属性类型以及本地原生的类型。像processKeyedProperty方法里会针对转换的属性值类型来处理,比如它会检查类型为Array, List, Map等类型进行特殊处理。而processLocalProperty方法的实现如下:

 

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规范的方式来设置它们。最终这些来达到构造一个对象并设置它的基础属性的功能。

 

参考材料

 pro spring 5

expert one on one j2ee design and development

 

  • 大小: 84.8 KB
  • 大小: 29.1 KB
分享到:
评论
3 楼 nucleus 2019-02-23  
貌似是因为图片的路径是http的缘故:http://dl2.iteye.com/upload/attachment/0104/6659/6a243da5-8866-384d-8096-2c4a9df6ae7f.png
2 楼 nucleus 2019-02-23  
nucleus 写道
BeanWrapper这一块相关的类结构如下图:
文中提到的上述文字下面忘记贴图了  0.0

刷出来了,不容易啊
1 楼 nucleus 2019-02-23  
BeanWrapper这一块相关的类结构如下图:
文中提到的上述文字下面忘记贴图了  0.0

相关推荐

    25 Spring Core 数据绑定之BeanWrapper实现示例及背后原理探究慕课专栏1

    背景在 Spring 配置文件中使用 &lt;value.../&gt; 元素来为属性指定属性值,如下面的例子所示:上面的 XML 是如何实现的呢?Spring MVC 数

    春天来了,继续手写自己的Spring,从0到1,从IOC到DI、AOP、MVC四个阶段,项目已经传到了github和码云上面

    熬了半个多月的时间,每天坐在电脑面前十几个小时,从Spring官网拉下来了spring-framework这个项目的源码,边看源码,边从网上找资料,整个项目中使用的设计模式也是非常的经典,如工厂模式:BeanFactory,代理模式...

    Springframework开发参考手册chm格式

     Spring Framework 开发手册 chm,一份对Spring特性的参考指南,内容涵盖Spring概述、使用场景、Spring2.0新特性、面向切面编程、中间层、WEB层、校验,数据绑定,BeanWrapper,与属性编辑器、使用Spring进行面向...

    Spring-Reference_zh_CN(Spring中文参考手册)

    12.2.4. 不使用回调的基于Spring的DAO实现 12.2.5. 基于Hibernate3的原生API实现DAO 12.2.6. 编程式的事务划分 12.2.7. 声明式的事务划分 12.2.8. 事务管理策略 12.2.9. 容器资源 vs 本地资源 12.2.10. 在应用服务器...

    Spring 2.0 开发参考手册

    12.2.4. 不使用回调的基于Spring的DAO实现 12.2.5. 基于Hibernate3的原生API实现DAO 12.2.6. 编程式的事务划分 12.2.7. 声明式的事务划分 12.2.8. 事务管理策略 12.2.9. 容器资源 vs 本地资源 12.2.10. 在应用...

    Spring中文帮助文档

    12.2.4. 不使用回调的基于Spring的DAO实现 12.2.5. 基于Hibernate3的原生API实现DAO 12.2.6. 编程式的事务划分 12.2.7. 声明式的事务划分 12.2.8. 事务管理策略 12.2.9. 容器资源 vs 本地资源 12.2.10. 在应用...

    Spring API

    12.2.4. 不使用回调的基于Spring的DAO实现 12.2.5. 基于Hibernate3的原生API实现DAO 12.2.6. 编程式的事务划分 12.2.7. 声明式的事务划分 12.2.8. 事务管理策略 12.2.9. 容器资源 vs 本地资源 12.2.10. 在应用...

    spring chm文档

    12.2.4. 不使用回调的基于Spring的DAO实现 12.2.5. 基于Hibernate3的原生API实现DAO 12.2.6. 编程式的事务划分 12.2.7. 声明式的事务划分 12.2.8. 事务管理策略 12.2.9. 容器资源 vs 本地资源 12.2.10. 在应用...

    Spring.3.x企业应用开发实战(完整版).part2

    经过历时一年的重大调整改版而成的,本书延续了上一版本追求深度,注重原理,不停留在技术表面的写作风格,力求使读者在熟练使用Spring的各项功能的同时,还能透彻理解Spring的内部实现,真正做到知其然知其所以然。...

    gwtbean:一个使用 JavaScriptObject 的概念,如 Spring BeanWrapper

    Gwt-Bean 一个使用 JavaScriptObject 的概念,如 Spring BeanWrapper

    Spring3.x企业应用开发实战(完整版) part1

    经过历时一年的重大调整改版而成的,本书延续了上一版本追求深度,注重原理,不停留在技术表面的写作风格,力求使读者在熟练使用Spring的各项功能的同时,还能透彻理解Spring的内部实现,真正做到知其然知其所以然。...

    Spring @Autowired

    NULL 博文链接:https://zhangzhenting.iteye.com/blog/1455511

    SPRING API 2.0.CHM

    BeanWrapper BeanWrapperImpl BeforeAdvice BeforeAdviceAdapter BindErrorsTag BindException BindingErrorProcessor BindingResult BindingResultUtils BindInitializer BindStatus BindTag BindUtils...

    spring-framework-reference-4.1.2

    4.1. Introduction to the Spring IoC container and beans .............................................. 22 4.2. Container overview .........................................................................

    Spring Framework 开发参考 chm 手册.rar

    Spring Framework 开发参考 chm 手册,一份对Spring特性的参考指南,内容涵盖Spring概述、使用场景、Spring2.0新特性、面向切面编程、中间层、WEB层、校验,数据绑定,BeanWrapper,与属性编辑器、使用Spring进行...

    spring-framework-reference4.1.4

    4.1. Introduction to the Spring IoC container and beans .............................................. 22 4.2. Container overview .........................................................................

    dpspring:手写简单框架

    dpspring 手写弹簧简单框架 豆子 ApplicationContext 简单料理界工厂类 getBean() 从ioc容器中回去一个实例的方法 大流程如下 ...beanWrapper:原生对象和代理对象关联关系 5.完成di 6.handlermapping

    前端-后端java的Util类的工具类

    │ spring.xml │ struts.xml │ ├─28个java常用的工具类 │ │ Base64.java │ │ Base64DecodingException.java │ │ CConst.java │ │ CharTools.java │ │ ConfigHelper.java │ │ Counter.java │ │ C...

Global site tag (gtag.js) - Google Analytics