1. 程式人生 > >beans包原始碼分析(一)----抽象類BeanUtils

beans包原始碼分析(一)----抽象類BeanUtils

一、基本介紹

BeansUtils是springframework.beans包中的一個抽象類,其中主要是一些處理JavaBeans的靜態方法,例如例項化Beans,檢查bean的屬性型別,複製屬性性質。其主要用於框架的內部,但是在某些情況下也適用於一些程式類。

二、構造方法及屬性

1.屬性

其中屬性是一個同步的WeakHashMap<Class<?>, Boolean>來作為集合,程式碼如下:

	private static final Map<Class<?>, Boolean> unknownEditorTypes =
			Collections.synchronizedMap(new WeakHashMap<Class<?>, Boolean>());

WeakhashMap的作用分析,可以看另外一篇文章,地址:

2.構造方法

需要注意的是其的構造方法,它使用了其無參建構函式來例項化物件的方法,由於這種方式不是通過類名來載入類,所以需要注意類的載入問題,因為其呼叫newInstance方法的時候,必須保證類已經載入和連線。其程式碼如下,可以看出,主要是通過clazz.newInstance()來實現bean的例項化。

	public static <T> T instantiate(Class<T> clazz) throws BeanInstantiationException {
		Assert.notNull(clazz, "Class must not be null");
		if (clazz.isInterface()) {
			throw new BeanInstantiationException(clazz, "Specified class is an interface");
		}
		try {
			return clazz.newInstance();
		}
		catch (InstantiationException ex) {
			throw new BeanInstantiationException(clazz, "Is it an abstract class?", ex);
		}
		catch (IllegalAccessException ex) {
			throw new BeanInstantiationException(clazz, "Is the constructor accessible?", ex);
		}
	}
另外一個構造方法instantiateClass,區別在於,如果給定的建構函式是私有的,它會嘗試把建構函式設定為可以訪問的,可以看到的是clazz.getDclaredConstructor()獲取了這個類的構造方法,然後呼叫一個過載的instantiateClass方法,兩個方法的程式碼如下所示,第二個instantiateClass方法接收的是構造方法和引數,將建構函式設定為可以訪問的,然後呼叫newInstance方法。
	public static <T> T instantiateClass(Class<T> clazz) throws BeanInstantiationException {
		Assert.notNull(clazz, "Class must not be null");
		if (clazz.isInterface()) {
			throw new BeanInstantiationException(clazz, "Specified class is an interface");
		}
		try {
			return instantiateClass(clazz.getDeclaredConstructor());
} catch (NoSuchMethodException ex) { throw new BeanInstantiationException(clazz, "No default constructor found", ex); } }
	public static <T> T instantiateClass(Constructor<T> ctor, Object... args) throws BeanInstantiationException {
		Assert.notNull(ctor, "Constructor must not be null");
		try {
			ReflectionUtils.makeAccessible(ctor);
			return ctor.newInstance(args);
		}
		catch (InstantiationException ex) {
			throw new BeanInstantiationException(ctor.getDeclaringClass(),
					"Is it an abstract class?", ex);
		}
		catch (IllegalAccessException ex) {
			throw new BeanInstantiationException(ctor.getDeclaringClass(),
					"Is the constructor accessible?", ex);
		}
		catch (IllegalArgumentException ex) {
			throw new BeanInstantiationException(ctor.getDeclaringClass(),
					"Illegal arguments for constructor", ex);
		}
		catch (InvocationTargetException ex) {
			throw new BeanInstantiationException(ctor.getDeclaringClass(),
					"Constructor threw exception", ex.getTargetException());
		}
	}

下面程式碼的構造方法實際上也是呼叫instantiateClass方法,所以也是構造方法是私有的,通過設定將構造方法變為可以訪問的,然後去例項化物件,這個方法主要用於我們要例項化的物件是不可用的,但是第二個引數的期望型別是可知的。

	public static <T> T instantiateClass(Class<?> clazz, Class<T> assignableTo) throws BeanInstantiationException {
		Assert.isAssignable(assignableTo, clazz);
		return (T) instantiateClass(clazz);
	}

上面一整套例項化類的流程如下程式碼所示

Person類的建構函式都是私有的,利用反射,將建構函式設定為可以訪問的,通過newInstance()例項化一個類Person

package model;
public class Person {
    private String name;
    private int age;
    private Person(){
    }
    private Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
}
import model.Person;
import java.lang.reflect.Constructor;
public class Main {
    public static void main(String[] args)  {

        try {
            Class<?> clazz = ClassLoader.getSystemClassLoader().loadClass("model.Person");
            Constructor<?> declaredConstructor = clazz.getDeclaredConstructor();
            declaredConstructor.setAccessible(true);
            Person person = (Person) declaredConstructor.newInstance();
            System.out.println(person);
        }catch (ClassNotFoundException e){
            e.printStackTrace();
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

三、方法

1.findMethod方法

通過給定的名稱和引數型別,其中名稱和引數宣告在給定的類上或者其超類上。

	public static Method findMethod(Class<?> clazz, String methodName, Class<?>... paramTypes) {
		try {
			return clazz.getMethod(methodName, paramTypes);
		}
		catch (NoSuchMethodException ex) {
			return findDeclaredMethod(clazz, methodName, paramTypes);
		}
	}

其中findDeclaredMethod和find方法類似,但是不是的是當如果沒有找到方法的時候,會首先判斷這個類是否有父類,然後遞迴迴圈去呼叫findDeclaredMethod方法,尋找其父類是否包含這個方法,其中程式碼標紅的部分就是其遞迴呼叫的部分。

	public static Method findDeclaredMethod(Class<?> clazz, String methodName, Class<?>... paramTypes) {
		try {
			return clazz.getDeclaredMethod(methodName, paramTypes);
		}
		catch (NoSuchMethodException ex) {
			if (clazz.getSuperclass() != null) {
                                //遞迴呼叫方法尋找父類的方法
				return findDeclaredMethod(clazz.getSuperclass(), methodName, paramTypes);
			}
			return null;
		}
	}

findMethodWithMinimalParameters和findDeclaredMethodWithMinimalParameters這兩個方法主要用於通過給定一個方法名稱和最小的引數來尋找方法,文件中對這個最小引數的描述是“最好的的情況是沒有引數”,所以說這兩個方法的引數都是一個類和類名,而沒有引數型別引數。其中比較核心的是會呼叫findMethodWithMinimalParameters方法,所以來看下這個方法的程式碼,如下所示:

這個方法主要是通過for迴圈去遍歷methods引數,如果發現和methodName相匹配的,就會進入處理邏輯,這個邏輯是如果開始targetMethod為空,就將這個匹配方法賦予targetMethod,將對應的引數變為1,即找到了一個方法,然後接著for迴圈,如果又找到了一個名字相同的方法,這時候要判斷新找到的方法的引數是不是比之前的小,如果小則將targetMethod賦值為這個方法,如果大則接著迴圈,如果找到了一個相同的長度引數的方法,會將方法計數+1,最後通過計數引數判斷這個最小引數方法是不是唯一的一個,不是唯一的則會丟擲異常,唯一的話會返回這個方法。

	public static Method findMethodWithMinimalParameters(Method[] methods, String methodName)
			throws IllegalArgumentException {

		Method targetMethod = null;
		int numMethodsFoundWithCurrentMinimumArgs = 0;
		for (Method method : methods) {
			if (method.getName().equals(methodName)) {
				int numParams = method.getParameterTypes().length;
				if (targetMethod == null || numParams < targetMethod.getParameterTypes().length) {
					targetMethod = method;
					numMethodsFoundWithCurrentMinimumArgs = 1;
				}
				else {
					if (targetMethod.getParameterTypes().length == numParams) {
						// Additional candidate with same length
						numMethodsFoundWithCurrentMinimumArgs++;
					}
				}
			}
		}
		if (numMethodsFoundWithCurrentMinimumArgs > 1) {
			throw new IllegalArgumentException("Cannot resolve method '" + methodName +
					"' to a unique method. Attempted to resolve to overloaded method with " +
					"the least number of parameters, but there were " +
					numMethodsFoundWithCurrentMinimumArgs + " candidates.");
		}
		return targetMethod;
	}

2.getPropertyDescriptors方法

這方法主要用來檢索給定的類的JavaBeans,其中涉及到了一個CachedIntrospectionResults的類,這個類主要是用於快取JavaBeans的內部類Java類的資訊,這個類使用了工廠設計模式,可以看一下其中的設計方法。返回型別PropertyDescriptor[],這個類描述了通過一對訪問器方法匯出的一個javabean的屬性。

第二個過載方法多了一個引數性質名稱,所以返回一個PropertyDescriptor屬性描述器類,兩個方法程式碼如下:

	public static PropertyDescriptor[] getPropertyDescriptors(Class<?> clazz) throws BeansException {
		CachedIntrospectionResults cr = CachedIntrospectionResults.forClass(clazz);
		return cr.getPropertyDescriptors();
	}
	public static PropertyDescriptor getPropertyDescriptor(Class<?> clazz, String propertyName)
			throws BeansException {

		CachedIntrospectionResults cr = CachedIntrospectionResults.forClass(clazz);
		return cr.getPropertyDescriptor(propertyName);
	}

3.findPropertyForMethod

找到給定方法的JavaBean,方法既可以是讀取方法,也可以是該bean屬性的寫入方法

	public static PropertyDescriptor findPropertyForMethod(Method method) throws BeansException {
		Assert.notNull(method, "Method must not be null");
		PropertyDescriptor[] pds = getPropertyDescriptors(method.getDeclaringClass());
		for (PropertyDescriptor pd : pds) {
			if (method.equals(pd.getReadMethod()) || method.equals(pd.getWriteMethod())) {
				return pd;
			}
		}
		return null;
	}

4.findPropertyType

如果可以的話,從給定的類或者介面的屬性中確定bean屬性的型別

	public static Class<?> findPropertyType(String propertyName, Class<?>... beanClasses) {
		if (beanClasses != null) {
			for (Class<?> beanClass : beanClasses) {
				PropertyDescriptor pd = getPropertyDescriptor(beanClass, propertyName);
				if (pd != null) {
					return pd.getPropertyType();
				}
			}
		}
		return Object.class;
	}

5.一些判斷方法

判斷給定的型別是否是一個“簡單的”屬性,例如primitive,String,Date,URI,CLASS等等型別,實際上會呼叫第二段程式碼中的方法isSimpleValueType()

	public static boolean isSimpleProperty(Class<?> clazz) {
		Assert.notNull(clazz, "Class must not be null");
		return isSimpleValueType(clazz) || (clazz.isArray() && isSimpleValueType(clazz.getComponentType()));
	}
	public static boolean isSimpleValueType(Class<?> clazz) {
		return ClassUtils.isPrimitiveOrWrapper(clazz) || clazz.isEnum() ||
				CharSequence.class.isAssignableFrom(clazz) ||
				Number.class.isAssignableFrom(clazz) ||
				Date.class.isAssignableFrom(clazz) ||
				clazz.equals(URI.class) || clazz.equals(URL.class) ||
				clazz.equals(Locale.class) || clazz.equals(Class.class);
	}

6.copyProperties方法

這個方法主要用於將給定bean資源複製到目標bean當中,注意的是,源類和目標類不用匹配,甚至也不用派生,只需要屬性匹配就可以。

	public static void copyProperties(Object source, Object target) throws BeansException {
		copyProperties(source, target, null, (String[]) null);
	}
這個方法其過載的方法還比較多,主要是引數上的一些區別,以後有時間再將這部分新增上

其最核心的是會呼叫一個私有的過載方法,程式碼如下:

	private static void copyProperties(Object source, Object target, Class<?> editable, String... ignoreProperties)
			throws BeansException {

		Assert.notNull(source, "Source must not be null");
		Assert.notNull(target, "Target must not be null");

		Class<?> actualEditable = target.getClass();
		if (editable != null) {
			if (!editable.isInstance(target)) {
				throw new IllegalArgumentException("Target class [" + target.getClass().getName() +
						"] not assignable to Editable class [" + editable.getName() + "]");
			}
			actualEditable = editable;
		}
		PropertyDescriptor[] targetPds = getPropertyDescriptors(actualEditable);
		List<String> ignoreList = (ignoreProperties != null) ? Arrays.asList(ignoreProperties) : null;

		for (PropertyDescriptor targetPd : targetPds) {
			Method writeMethod = targetPd.getWriteMethod();
			if (writeMethod != null && (ignoreProperties == null || (!ignoreList.contains(targetPd.getName())))) {
				PropertyDescriptor sourcePd = getPropertyDescriptor(source.getClass(), targetPd.getName());
				if (sourcePd != null) {
					Method readMethod = sourcePd.getReadMethod();
					if (readMethod != null &&
							writeMethod.getParameterTypes()[0].isAssignableFrom(readMethod.getReturnType())) {
						try {
							if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {
								readMethod.setAccessible(true);
							}
							Object value = readMethod.invoke(source);
							if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {
								writeMethod.setAccessible(true);
							}
							writeMethod.invoke(target, value);
						}
						catch (Throwable ex) {
							throw new FatalBeanException(
									"Could not copy property '" + targetPd.getName() + "' from source to target", ex);
						}
					}
				}
			}
		}
	}