@Accessors是由lombok提供的一个注解,chain = true的作用是使成员属性的set方法不再返回void,而是返回对象本身,从而实现链式赋值。效果如下:
然而加了该注解后,我发现 org.apache.commons.beanutils.BeanUtils.copyProperties(final Object dest, final Object orig)方法失效。
经试验发现,当我用 org.springframework.beans.BeanUtils.copyProperties(Object source, Object target)方法时仍然能够正常赋值。所以以此为切入点进行源码分析,查找原因。
springframework的copyProperties方法链路追踪
关键点在于它是如何剥离出Goods类的2个set方法并进行后续调用赋值。
进入getPropertyDescriptors方法继续追踪链路,来到 org.springframework.beans.ExtendedBeanInfoFactory#getBeanInfo,这里的supports(beanClass)对Goods类的所有方法进行了判断,即 是否声明或继承了任何返回非void的set方法。
此时如果Goods没有@Accessors注解,这一步会return null,然后退回到org.springframework.beans.CachedIntrospectionResults#getBeanInfo(java.lang.Class>)方法,最终return Introspector.getBeanInfo(beanClass)
Introspector.getBeanInfo是一个由jdk提供的方法,传入Goods类后会返回一个BeanInfo.java,其中包含了Goods成员属性的get方法、set方法。但如果是@Accessors注解后的特殊set方法,是无法获取到的。
继续进入new ExtendedBeanInfo 构造方法,ExtendedBeanInfo继承了BeanInfo.java,它对于Goods类的set方法返回非void的情况进行了适配。具体代码如下:
所以说,spring提供的copyProperties方法,声明了一个单独的ExtendedBeanInfo.java 用于适配 set方法返回非void的情况。
apache.commons的copyProperties方法链路追踪
思路同上,当追踪到org.apache.commons.beanutils.DefaultBeanIntrospector#introspect的时候,发现其底层也是调用了jdk提供的Introspector.getBeanInfo方法,且没有适配这种特殊的set方法,最终导致无法成功获取到Goods类的set方法,赋值失败。
总结
org.springframework.beans.BeanUtils.copyProperties 和 org.apache.commons.beanutils.BeanUtils.copyProperties 这2个方法其本质上都是调用了由jdk提供的Introspector.getBeanInfo方法来获取对象的默认get、set方法。但是spring对set方法返回非void的情况进行了适配,使得set方法能够正常调用。所以我们在使用@Accessors(chain = true)时要留意,不能使用apache.commons的BeanUtils 来对其进行赋值,因为其未对这种特殊的set方法进行适配。
拓展延伸
@Accessors注解除了chain属性外,还有一个名为fluent的boolean属性
当fluent=true时,属性名的get、set方法将去掉“get、set”前缀,即 goods.name()、goods.name(“goodsName”) 来表示get、set方法。此时无论springframework还是apache.commons,他们的copyProperties均会失效,goods无法通过这2个方法来设置属性。