相关推荐recommended
@Accessors(chain = true)导致BeanUtils.copyProperties失效问题的排查思路
作者:mmseoamin日期:2023-12-14

@Accessors是由lombok提供的一个注解,chain = true的作用是使成员属性的set方法不再返回void,而是返回对象本身,从而实现链式赋值。效果如下:

@Accessors(chain = true)导致BeanUtils.copyProperties失效问题的排查思路,第1张@Accessors(chain = true)导致BeanUtils.copyProperties失效问题的排查思路,第2张

然而加了该注解后,我发现 org.apache.commons.beanutils.BeanUtils.copyProperties(final Object dest, final Object orig)方法失效。

经试验发现,当我用 org.springframework.beans.BeanUtils.copyProperties(Object source, Object target)方法时仍然能够正常赋值。所以以此为切入点进行源码分析,查找原因。

@Accessors(chain = true)导致BeanUtils.copyProperties失效问题的排查思路,第3张

springframework的copyProperties方法链路追踪

关键点在于它是如何剥离出Goods类的2个set方法并进行后续调用赋值。

@Accessors(chain = true)导致BeanUtils.copyProperties失效问题的排查思路,第4张

进入getPropertyDescriptors方法继续追踪链路,来到 org.springframework.beans.ExtendedBeanInfoFactory#getBeanInfo,这里的supports(beanClass)对Goods类的所有方法进行了判断,即 是否声明或继承了任何返回非void的set方法。

@Accessors(chain = true)导致BeanUtils.copyProperties失效问题的排查思路,第5张

此时如果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的情况进行了适配。具体代码如下:

@Accessors(chain = true)导致BeanUtils.copyProperties失效问题的排查思路,第6张

所以说,spring提供的copyProperties方法,声明了一个单独的ExtendedBeanInfo.java 用于适配 set方法返回非void的情况。

 apache.commons的copyProperties方法链路追踪

 思路同上,当追踪到org.apache.commons.beanutils.DefaultBeanIntrospector#introspect的时候,发现其底层也是调用了jdk提供的Introspector.getBeanInfo方法,且没有适配这种特殊的set方法,最终导致无法成功获取到Goods类的set方法,赋值失败。

@Accessors(chain = true)导致BeanUtils.copyProperties失效问题的排查思路,第7张

总结

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个方法来设置属性。

@Accessors(chain = true)导致BeanUtils.copyProperties失效问题的排查思路,第8张@Accessors(chain = true)导致BeanUtils.copyProperties失效问题的排查思路,第9张