一、基本介绍
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);
}
}
}
}
}
}