千古以来要饭的没有要早饭的,知道为什么吗?
【小家Spring】聊聊Spring中的数据转换:Converter、ConversionService、TypeConverter、PropertyEditor 【小家Spring】聊聊Spring中的数据绑定 --- 属性访问器PropertyAccessor和实现类DirectFieldAccessor的使用
Java高工、架构师3群
(文末有二维码)
这篇文章需要依赖于对属性访问器 PropertyAccessor
的理解,也就是上篇文章的内容: 【小家Spring】聊聊Spring中的数据绑定 --- 属性访问器PropertyAccessor和实现类DirectFieldAccessor的使用
如果说上篇文章所说的 PropertyAccessor
你没有接触过和听过,那么本文即将要说的重点: BeanWrapper
你应该多少有所耳闻吧~ BeanWrapper
可以简单的把它理解为:一个方便开发人员 使用字符串 来对 Java Bean
的属性执行get、set操作的工具。关于它的数据转换使用了如下两种机制:
PropertyEditor
:隶属于 Java Bean规范 。 PropertyEditor
只提供了 String <-> Object
的转换。 ConversionService
:Spring自3.0之后提供的替代PropertyEditor的机制( BeanWrapper
在Spring的第一个版本就存在了~) 按照Spring官方文档的说法,当容器内没有注册 ConversionService
的时候,会退回使用 PropertyEditor
机制。言外之意:首选方案是 ConversionService
其实了解的伙伴应该知道,这不是 BeanWrapper
的内容,而是父接口 PropertyAccessor
的内容~
官方解释:Spring低级JavaBeans基础设施的中央接口。通常来说并不直接使用 BeanWrapper
,而是借助 BeanFactory
或者 DataBinder
来一起使用~
//@since 13 April 2001 很清晰的看到,它也是个`PropertyAccessor`属性访问器 public interface BeanWrapper extends ConfigurablePropertyAccessor { // @since 4.1 void setAutoGrowCollectionLimit(int autoGrowCollectionLimit); int getAutoGrowCollectionLimit(); Object getWrappedInstance(); Class<?> getWrappedClass(); // 获取属性们的PropertyDescriptor 获取属性们 PropertyDescriptor[] getPropertyDescriptors(); // 获取具体某一个属性~ PropertyDescriptor getPropertyDescriptor(String propertyName) throws InvalidPropertyException; }复制代码
BeanWrapper
相当于一个代理器,Spring委托 BeanWrapper
完成Bean属性的填充工作。关于此接口的实现类,简单的说它只有唯一实现类: BeanWrapperImpl
它作为 BeanWrapper
接口的默认实现,它足以满足所有的典型应用场景,它会缓存Bean的内省结果而提高效率。
在Spring2.5之前,此实现类是非public的,但在2.5之后给public了并且还提供了工厂: PropertyAccessorFactory
帮助第三方框架能快速获取到一个实例~
public class BeanWrapperImpl extends AbstractNestablePropertyAccessor implements BeanWrapper { // 缓存内省结果~ @Nullable private CachedIntrospectionResults cachedIntrospectionResults; // The security context used for invoking the property methods. @Nullable private AccessControlContext acc; // 构造方法都是沿用父类的~ public BeanWrapperImpl() { this(true); } ... private BeanWrapperImpl(Object object, String nestedPath, BeanWrapperImpl parent) { super(object, nestedPath, parent); setSecurityContext(parent.acc); } // @since 4.3 设置目标对象~~~ public void setBeanInstance(Object object) { this.wrappedObject = object; this.rootObject = object; this.typeConverterDelegate = new TypeConverterDelegate(this, this.wrappedObject); // 设置内省的clazz setIntrospectionClass(object.getClass()); } // 复写父类的方法 增加内省逻辑 @Override public void setWrappedInstance(Object object, @Nullable String nestedPath, @Nullable Object rootObject) { super.setWrappedInstance(object, nestedPath, rootObject); setIntrospectionClass(getWrappedClass()); } // 如果cachedIntrospectionResults它持有的BeanClass并不是传入的clazz 那就清空缓存 重新来~~~ protected void setIntrospectionClass(Class<?> clazz) { if (this.cachedIntrospectionResults != null && this.cachedIntrospectionResults.getBeanClass() != clazz) { this.cachedIntrospectionResults = null; } } private CachedIntrospectionResults getCachedIntrospectionResults() { if (this.cachedIntrospectionResults == null) { // forClass此方法:生成此clazz的类型结果,并且缓存了起来~~ this.cachedIntrospectionResults = CachedIntrospectionResults.forClass(getWrappedClass()); } return this.cachedIntrospectionResults; } ... // 获取到此属性的处理器。此处是个BeanPropertyHandler 内部类~ @Override @Nullable protected BeanPropertyHandler getLocalPropertyHandler(String propertyName) { PropertyDescriptor pd = getCachedIntrospectionResults().getPropertyDescriptor(propertyName); return (pd != null ? new BeanPropertyHandler(pd) : null); } @Override protected BeanWrapperImpl newNestedPropertyAccessor(Object object, String nestedPath) { return new BeanWrapperImpl(object, nestedPath, this); } @Override public PropertyDescriptor[] getPropertyDescriptors() { return getCachedIntrospectionResults().getPropertyDescriptors(); } // 获取具体某一个属性的PropertyDescriptor @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; } ... // 此处理器处理的是PropertyDescriptor private class BeanPropertyHandler extends PropertyHandler { private final PropertyDescriptor pd; // 是否可读、可写 都是由PropertyDescriptor 去决定了~ // java.beans.PropertyDescriptor~~ public BeanPropertyHandler(PropertyDescriptor pd) { super(pd.getPropertyType(), pd.getReadMethod() != null, pd.getWriteMethod() != null); this.pd = pd; } ... @Override @Nullable public Object getValue() throws Exception { ... ReflectionUtils.makeAccessible(readMethod); return readMethod.invoke(getWrappedInstance(), (Object[]) null); } ... } }复制代码
从继承体系上,首先我们应该能看出来 BeanWrapperImpl
的三重身份:
从源码中继续分析还能再得出如下两个结论:
readMethod.invoke
和 writeMethod.invoke
CachedIntrospectionResults
去完成~ 因此若想了解它,必然主要是要先了解 java.beans.PropertyDescriptor
和 org.springframework.beans.CachedIntrospectionResults
,首当其冲的自然还有 Java内省
。
Introspector
首先可以先了解下 JavaBean的概念 :一种特殊的类,主要用于传递数据信息。这种类中的方法主要用于访问私有的字段,且方法名 符合某种命名规则 。如果在两个模块之间传递信息,可以将信息封装进JavaBean中,这种对象称为“值对象”( Value Object
),或“ VO
”。
符合这些特征的类,被称为JavaBean;JDK中提供了一套API用来访问某个属性的getter/setter方法,这些API存放在 java.beans
中,这就是 内省(Introspector)
。
反射:Java反射机制是在运行中,对任意一个类,能够获取得到这个类的所有属性和方法;它针对的是 任意类 内省(Introspector) :是Java语言对JavaBean类属性、事件的处理方法
JavaBean
的属性 既然反射比内省比内省强大这么多, 那内省用在什么时候场景呢 ?下面给出一个示例来说明它的用武之地:
// 就这样简单几步,就完成了表单到User对象的封装~ public void insertUser(HttpServletRequest request) throws Exception { User user = new User(); // 遍历:根据字段名去拿值即可(此处省略判空、类型转换等细节,不在本文讨论范围) PropertyDescriptor[] pds = Introspector.getBeanInfo(User.class).getPropertyDescriptors(); for (PropertyDescriptor pd : pds) { pd.getWriteMethod().invoke(user, request.getParameter(pd.getName())); } }复制代码
通过内省可以很轻松的将form表单的内容填充进对象里面,比反射轻松省力多了。其实像MyBatis这种框架,底层都用到了Java的内省机制。
内省的API主要有Introspector、BeanInfo、PropertyDescriptor等,下面就以他三为例来操作一个JavaBean:
@Getter @Setter @ToString public class Child { private String name; private Integer age; }复制代码
Introspector
+ BeanInfo
: public static void main(String[] args) throws IntrospectionException { BeanInfo beanInfo = Introspector.getBeanInfo(Child.class); BeanDescriptor beanDescriptor = beanInfo.getBeanDescriptor(); MethodDescriptor[] methodDescriptors = beanInfo.getMethodDescriptors(); PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors(); // 打印 System.out.println(beanDescriptor); System.out.println("------------------------------"); Arrays.stream(methodDescriptors).forEach(x -> System.out.println(x)); System.out.println("------------------------------"); Arrays.stream(propertyDescriptors).forEach(x -> System.out.println(x)); System.out.println("------------------------------"); }复制代码
输入内容如下:
java.beans.BeanDescriptor[name=Child; beanClass=class com.fsx.bean.Child] ------------------------------ java.beans.MethodDescriptor[name=getClass; method=public final native java.lang.Class java.lang.Object.getClass()] java.beans.MethodDescriptor[name=getName; method=public java.lang.String com.fsx.bean.Child.getName()] java.beans.MethodDescriptor[name=setAge; method=public void com.fsx.bean.Child.setAge(java.lang.Integer)] java.beans.MethodDescriptor[name=setName; method=public void com.fsx.bean.Child.setName(java.lang.String)] java.beans.MethodDescriptor[name=getAge; method=public java.lang.Integer com.fsx.bean.Child.getAge()] java.beans.MethodDescriptor[name=wait; method=public final void java.lang.Object.wait() throws java.lang.InterruptedException] java.beans.MethodDescriptor[name=notifyAll; method=public final native void java.lang.Object.notifyAll()] java.beans.MethodDescriptor[name=notify; method=public final native void java.lang.Object.notify()] java.beans.MethodDescriptor[name=wait; method=public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException] java.beans.MethodDescriptor[name=hashCode; method=public native int java.lang.Object.hashCode()] java.beans.MethodDescriptor[name=wait; method=public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException] java.beans.MethodDescriptor[name=equals; method=public boolean java.lang.Object.equals(java.lang.Object)] java.beans.MethodDescriptor[name=toString; method=public java.lang.String com.fsx.bean.Child.toString()] ------------------------------ java.beans.PropertyDescriptor[name=age; propertyType=class java.lang.Integer; readMethod=public java.lang.Integer com.fsx.bean.Child.getAge(); writeMethod=public void com.fsx.bean.Child.setAge(java.lang.Integer)] java.beans.PropertyDescriptor[name=class; propertyType=class java.lang.Class; readMethod=public final native java.lang.Class java.lang.Object.getClass()] java.beans.PropertyDescriptor[name=name; propertyType=class java.lang.String; readMethod=public java.lang.String com.fsx.bean.Child.getName(); writeMethod=public void com.fsx.bean.Child.setName(java.lang.String)] ------------------------------复制代码
可以看到 getMethodDescriptors()
它把父类的 MethodDescriptor
也拿出来了。而 PropertyDescriptor
中比较特殊的是因为有 getClass()
方法,因此class也算是一个 PropertyDescriptor
,但是它没有 writeMethod
哦~
关于 BeanInfo
,Spring在3.1提供了一个类 ExtendedBeanInfo
继承自它实现了功能扩展,并且提供了 BeanInfoFactory
来专门生产它~~~(实现类为:`ExtendedBeanInfoFactory`)
但是如果只想拿某一个属性的话,使用 Introspector
就不是那么方便了,下面介绍更为常用的 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
类的一些主要方法描述如下:
---
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
~注意:为了使此内省缓存生效,有个前提条件请保证了:
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`有被使用到过,比较简单没啥太多好说的~
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); } }复制代码
说了这么多,是时候实战一把了~
// 省略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
对它的应用~
==The last:如果觉得本文对你有帮助,不妨点个赞呗。当然分享到你的朋友圈让更多小伙伴看到也是被 作者本人许可的~
==
**若对技术内容感兴趣可以加入wx群交流:`Java高工、架构师3群`。若群二维码失效,请加wx号: fsx641385712
(或者扫描下方wx二维码)。并且备注: "java入群"
字样,会手动邀请入群**