PropertyDescriptor 属性描述器
属性描述符描述了Java bean通过一对访问器方法导出的一个属性。上面的示例此处用PropertyDescriptor
试试:
public static void main(String[] args) throws IntrospectionException { PropertyDescriptor age = new PropertyDescriptor("age", Child.class); System.out.println(age.getPropertyType()); //class java.lang.Integer System.out.println(age.getDisplayName()); //age // 最重要的两个方法~~~ System.out.println(age.getReadMethod()); //public java.lang.Integer com.fsx.bean.Child.getAge() System.out.println(age.getWriteMethod()); //public void com.fsx.bean.Child.setAge(java.lang.Integer) }
可以看到它可以实现更加细粒度的控制。将PropertyDescriptor类的一些主要方法描述如下:
- getPropertyType(),获得属性的Class对象;
- getReadMethod(),获得用于读取属性值的方法;
- getWriteMethod(),获得用于写入属性值的方法;
- setReadMethod(Method readMethod),设置用于读取属性值的方法;
- setWriteMethod(Method writeMethod),设置用于写入属性值的方法。
CachedIntrospectionResults
Spring如果需要依赖注入那么就必须依靠Java内省这个特性了,说到Spring IOC与JDK内省的结合那么就不得不说一下Spring中的CachedIntrospectionResults这个类了。
它是Spring提供的专门用于缓存JavaBean的PropertyDescriptor描述信息的类,不能被应用代码直接使用。
它的缓存信息是被静态存储起来的(应用级别),因此对于同一个类型的被操作的JavaBean并不会都创建一个新的CachedIntrospectionResults,因此,这个类使用了工厂模式,使用私有构造器和一个静态的forClass工厂方法来获取实例。
public final class CachedIntrospectionResults { // 它可以通过在spring.properties里设置这个属性,来关闭内省的缓存~~~ public static final String IGNORE_BEANINFO_PROPERTY_NAME = "spring.beaninfo.ignore"; private static final boolean shouldIntrospectorIgnoreBeaninfoClasses = SpringProperties.getFlag(IGNORE_BEANINFO_PROPERTY_NAME); // 此处使用了SpringFactoriesLoader这个SPI来加载BeanInfoFactory,唯一实现类是ExtendedBeanInfoFactory /** Stores the BeanInfoFactory instances. */ private static List<BeanInfoFactory> beanInfoFactories = SpringFactoriesLoader.loadFactories( BeanInfoFactory.class, CachedIntrospectionResults.class.getClassLoader()); static final Set<ClassLoader> acceptedClassLoaders = Collections.newSetFromMap(new ConcurrentHashMap<>(16)); static final ConcurrentMap<Class<?>, CachedIntrospectionResults> strongClassCache = new ConcurrentHashMap<>(64); static final ConcurrentMap<Class<?>, CachedIntrospectionResults> softClassCache = new ConcurrentReferenceHashMap<>(64); // 被包裹类的BeanInfo~~~也就是目标类 private final BeanInfo beanInfo; // 它缓存了被包裹类的所有属性的属性描述器PropertyDescriptor。 private final Map<String, PropertyDescriptor> propertyDescriptorCache; ... // 其它的都是静态方法 // 只有它会返回一个实例,此类是单例的设计~ 它保证了每个beanClass都有一个CachedIntrospectionResults 对象,然后被缓存起来~ static CachedIntrospectionResults forClass(Class<?> beanClass) throws BeansException { ... } }
本处理类的核心内容是Java内省getBeanInfo()以及PropertyDescriptor~注意:为了使此内省缓存生效,有个前提条件请保证了:
确保将Spring框架的Jar包和你的应用类使用的是同一个ClassLoader加载的,这样在任何情况下会允许随着应用的生命周期来清楚缓存。
因此对于web应用来说,Spring建议给web容器注册一个IntrospectorCleanupListener监听器来防止多ClassLoader布局,这样也可以有效的利用caching从而提高效率~
监听器的配置形如这样(此处以web.xml里配置为例)
<listener> <listener-class>org.springframework.web.util.IntrospectorCleanupListener</listener-class> </listener>
说明:请保证此监听器配置在第一个位置,比ContextLoaderListener还靠前~ 此监听器能有效的防止内存泄漏问题~~~(因为内省的缓存是应用级别的全局缓存,很容易造成泄漏的~)
其实流行框架比如struts, Quartz等在使用JDK的内省时,存在没有释的内存泄漏问题~
DirectFieldAccessFallbackBeanWrapper
说完了BeanWrapperImpl,可以看看它的子类DirectFieldAccessFallbackBeanWrapper,他就像BeanWrapperImpl和DirectFieldAccessor的结合体。它先用BeanWrapperImpl.getPropertyValue(),若抛出异常了(毕竟内省不是十分靠谱,哈哈)再用DirectFieldAccessor~~~此子类在JedisClusterConnection有被使用到过,比较简单没啥太多好说的~
PropertyAccessorFactory
Spring2.5后提供的快速获取PropertyAccessor两个重要实现类的工厂。
public final class PropertyAccessorFactory { private PropertyAccessorFactory() { } // 生产一个BeanWrapperImpl(最为常用) public static BeanWrapper forBeanPropertyAccess(Object target) { return new BeanWrapperImpl(target); } // 生产一个DirectFieldAccessor public static ConfigurablePropertyAccessor forDirectFieldAccess(Object target) { return new DirectFieldAccessor(target); } }
BeanWrapper使用Demo
说了这么多,是时候实战一把了~
// 省略Apple类和Size类,有需要的请参照上篇文章(加上@Getter、@Setter即可 public static void main(String[] args) { Apple apple = new Apple(); BeanWrapper beanWrapper = PropertyAccessorFactory.forBeanPropertyAccess(apple); // ================当作一个普通的PropertyAccessor来使用 默认情况下字段也都必须有初始值才行~=================== // 设置普通属性 beanWrapper.setPropertyValue("color", "红色"); //请保证对应字段有set方法才行,否则抛错:Does the parameter type of the setter match the return type of the getter? // 设置嵌套属性(注意:此处能够正常work是因为有= new Size(), // 否则报错:Value of nested property 'size' is null 下同~) beanWrapper.setPropertyValue("size.height", 10); // 设置集合/数组属性 beanWrapper.setPropertyValue("arrStr[0]", "arrStr"); beanWrapper.setPropertyValue("arrStr[1]", "arrStr1"); // 注意:虽然初始化时初始化过数组了,但是仍以此处的为准 // =========打印输出 System.out.println(apple); //Apple(color=红色, size=Size(height=10, width=null), arrStr=[arrStr, arrStr1], listStr=[], map={}, listList=[[]], listMap=[{}]) // 当作BeanWrapper使用 PropertyDescriptor[] propertyDescriptors = beanWrapper.getPropertyDescriptors(); PropertyDescriptor color = beanWrapper.getPropertyDescriptor("color"); System.out.println(propertyDescriptors.length); // 8 System.out.println(color); //org.springframework.beans.GenericTypeAwarePropertyDescriptor[name=color] System.out.println(beanWrapper.getWrappedClass()); //class com.fsx.bean.Apple System.out.println(beanWrapper.getWrappedInstance()); //Apple(color=红色, size=Size(height=10... }
上面代码能够清晰的表示了通过BeanWrapper
来操作JavaBean还是非常之简便的。
最后,上一张比较丑的结构图,画一画属性编辑器、类型转换器、属性解析器、属性访问器大致的一个关系(此图不喜勿碰):
总结
BeanWrapper接口,作为Spring内部的一个核心接口,正如其名,它是bean的包裹类,即在内部中将会保存该bean的实例,提供其它一些扩展功能。
Spring对Bean的属性存取都是通过BeanWrapperImpl实现的,BeanWrapperImpl和Bean是一对一的关系,BeanWrapperImpl通过属性的读方法和写方法来存取Bean属性的。为了更加深刻的了解BeanWrapper,下篇文章会深入分析Spring BeanFactory对它的应用~