データのコピーに BeanUtils.copyProperties を使用することが推奨されないのはなぜですか?

実際のビジネス開発では、 VO、BO、PO、DTOなどのオブジェクト属性間の代入に遭遇することがよくあります。属性が多い場合、getメソッドやsetメソッドを使用して値を代入する作業負荷が比較的大きいため、作業負荷が高くなります。多くの人は、オブジェクト間でプロパティをコピーするために、Spring が提供するコピー ツールである BeanUtils の copyProperties メソッドを使用することを選択します。この方法を使用すると、オブジェクト属性を割り当てるコードを手動で記述する作業が大幅に軽減されます。非常に便利であるのに、なぜ推奨されないのでしょうか。以下は、私がコンパイルした BeanUtils.copyProperties データのコピーにおけるよくある落とし穴のいくつかです。

1: 一貫性のない属性タイプによりコピーが失敗する

このピットは次の 2 つのタイプに分類できます。
(1) 同じ属性でも型が異なる
実際の開発では、同じフィールドが異なるクラスで定義された型に一貫性がないことがよくあります。たとえば、ID がクラス A では Long として定義され、クラス B では String として定義されている可能性があります。この場合、BeanUtils を使用するとコピー時に が使用されます。 copyProperties を指定すると、コピーが失敗し、対応するフィールドが null になります。対応するケースは次のとおりです。
  
  
  
  
  
public class BeanUtilsTest {
public static void main(String[] args) { SourcePoJo sourcePoJo = new SourcePoJo("jingdong", (long) 35711); TargetPoJo targetPoJo = new TargetPoJo(); BeanUtils.copyProperties(sourcePoJo,targetPoJo); System.out.println(targetPoJo); }}@Data@AllArgsConstructorclass SourcePoJo{ private String username; private Long id;}
@Dataclass TargetPoJo{ private String username; private String id;}
対応する実行結果は次のとおりです。
型が一貫していないため、コピーされた id フィールドの値が null であることがわかります。
(2) 同一フィールドでパッケージタイプと基本タイプをそれぞれ使用
フィールドにラッパー クラスと基本型をそれぞれ使用する場合、実際の値が渡されない場合に例外が発生します。具体的なケースは次のとおりです。
  
  
  
  
  
public class BeanUtilsTest {
public static void main(String[] args) { SourcePoJo sourcePoJo = new SourcePoJo(); sourcePoJo.setUsername("joy"); TargetPoJo targetPoJo = new TargetPoJo(); BeanUtils.copyProperties(sourcePoJo,targetPoJo); System.out.println(targetPoJo); }}@Dataclass SourcePoJo{ private String username; private Long id;}
@Dataclass TargetPoJo{ private String username; private long id;}
テストケースでは、コピー元とコピー先でidフィールドにラッパー型とベーシック型を使用しているため、コピー中に例外が発生していることがわかります。

注: ブール型属性がそれぞれ基本型とラッパー型を使用し、属性名が isSuccess のように is で始まる場合も、コピーが失敗します。

2: Null値の上書きによりデータ異常が発生する

ビジネス開発中に、いくつかのフィールドをコピーする必要がある場合があります。コピーされたデータの一部のフィールドに null 値があるが、コピーする必要がある対応するデータ内の同じフィールドの値が直接の場合は null ではない場合BeanUtils.copyPropertiesを使用してデータをコピーすると、コピーされたデータの null 値がコピー先のデータのフィールドを上書きし、元のデータが無効になります。
該当するケースは以下のとおりです。
  
  
  
  
  
public class BeanUtilsTest {
public static void main(String[] args) { SourcePoJo sourcePoJo = new SourcePoJo(); sourcePoJo.setId("35711"); TargetPoJo targetPoJo = new TargetPoJo(); targetPoJo.setUsername("Joy"); BeanUtils.copyProperties(sourcePoJo,targetPoJo); System.out.println(targetPoJo); }}@Dataclass SourcePoJo{ private String username; private String id;}
@Dataclass TargetPoJo{ private String username; private String id;}
対応する実行結果は次のとおりです。

コピー先の結果で元々値が入っていたユーザー名フィールドがnullに上書きされていることがわかります。BeanUtils.copyProperties のオーバーロード メソッドを使用し、カスタム ConvertUtilsBean を使用して一部のフィールドをコピーすることもできますが、そのこと自体がより複雑になり、BeanUtils.copyProperties を使用してデータをコピーする意味が失われるため、お勧めできません。

3: パッケージのインポートエラーによりコピーデータが異常になる

BeanUtils.copyPropertiesを使用してデータをコピーする場合、Spring の Beans パッケージと Apache の beanutils パッケージの両方がプロジェクトに導入されている場合、パッケージのインポート時にインポート エラーが発生すると、データのコピーが失敗する可能性が高く、これは簡単ではありません。トラブルシューティング中に検出します。通常は Sping パッケージの copy メソッドを使用しますが、両者の違いは次のとおりです。
  
  
  
  
  
//org.springframework.beans.BeanUtils(源对象在左边,目标对象在右边)public static void copyProperties(Object source, Object target) throws BeansException //org.apache.commons.beanutils.BeanUtils(源对象在右边,目标对象在左边)public static void copyProperties(Object dest, Object orig) throws IllegalAccessException, InvocationTargetException

4: フィールド参照が見つからず、変更された内容を追跡することが困難です。

開発またはトラブルシューティング中に、リンク内でフィールド値のソース (呼び出し元によって渡されたものではない) が見つかった場合、全文検索を使用して、対応する割り当てメソッド (set メソッド、build メソッドなど) を見つけることがあります。ただし、リンク内のBeanUtils.copyPropertiesを使用してデータをコピーすると、値が割り当てられている場所をすぐに見つけることが困難になり、トラブルシューティングの効率が低くなります。

5: 内部クラスデータが正常にコピーできません

内部クラスのデータは正常にコピーできず、型やフィールド名が同じであっても、以下のようにコピーが成功しません。
  
  
  
  
  
public class BeanUtilsTest {
public static void main(String[] args) { SourcePoJo sourcePoJo = new SourcePoJo(); sourcePoJo.setUsername("joy"); SourcePoJo.InnerClass innerClass = new SourcePoJo.InnerClass("sourceInner"); sourcePoJo.innerClass=innerClass; System.out.println(sourcePoJo.toString()); TargetPoJo targetPoJo = new TargetPoJo(); BeanUtils.copyProperties(sourcePoJo,targetPoJo); System.out.println(targetPoJo.toString()); }}//下面是类的信息,这里就直接放到一块展示了@Data@ToStringpublic class SourcePoJo{ private String username; private Long id; public InnerClass innerClass; @Data @ToString @AllArgsConstructor public static class InnerClass{ public String innerName; }}
@Data@ToStringpublic class TargetPoJo{ private String username; private Long id; public InnerClass innerClass; @Data @ToString public static class InnerClass{ public String innerName; }}
結果は次のとおりです。

上記の場合、コピー元とコピー先にインナークラス InnerClass が存在しますが、インナークラスの属性は同じでクラス名も同じですが、別のクラスにあるため、Spring では属性が異なるものと認識してしまいます。データはコピーされません。

6: BeanUtils.copyProperties は浅いコピーです

ここではまずディープコピーとシャローコピーを皆さん向けにレビューしていきます。
浅いコピーとは、 元のオブジェクトと同じプロパティ値を持つが、参照型のプロパティに対して同じ参照を共有する新しいオブジェクトを作成することを意味します。つまり、浅いコピーでは、元のコンテンツの参照属性値が変更されると、それに応じてコピーされたオブジェクトの参照属性値も変更されます。
ディープ コピーとは、 参照型のプロパティを含む、元のオブジェクトと同じプロパティ値を持つ新しいオブジェクトを作成することを意味します。ディープ コピーでは、参照オブジェクトを再帰的にコピーして新しいオブジェクトを作成するため、コピーされたオブジェクトは元のオブジェクトから完全に独立しています。
対応するコード例は次のとおりです。
  
  
  
  
  
public class BeanUtilsTest {
public static void main(String[] args) { Person sourcePerson = new Person("sunyangwei",new Card("123456")); Person targetPerson = new Person(); BeanUtils.copyProperties(sourcePerson, targetPerson); sourcePerson.getCard().setNum("35711"); System.out.println(targetPerson); }}

@Data@AllArgsConstructorclass Card { private String num;}
@NoArgsConstructor@AllArgsConstructor@Dataclass Person { private String name; private Card card;}
結果は次のとおりです。
概要: コードの実行結果から、コピー後に元のオブジェクトの参照型データを変更すると、コピーされたデータの値が異常になることがわかり、この種の問題はトラブルシューティングも困難です。

7: 基礎となる実装は効率の低い反射コピーです

BeanUtils.copyProperties の 最下層は、オブジェクトの set メソッドと get メソッドをリフレクションによって取得し、get および set によってデータのコピーを完了するため、全体的なコピー効率は低くなります。
以下は、BeanUtils.copyPropertiesを使用してデータをコピーする場合と、値を直接設定する場合の効率の比較です。効果を視覚的に確認するために、10,000 回コピーする例を示します。
  
  
  
  
  
public class BeanUtilsTest {
public static void main(String[] args) { long copyStartTime = System.currentTimeMillis(); User sourceUser = new User("sunyangwei"); User targetUser = new User(); for(int i = 0; i < 10000; i++) { BeanUtils.copyProperties(sourceUser, targetUser); } System.out.println("copy方式:"+(System.currentTimeMillis()-copyStartTime));
long setStartTime = System.currentTimeMillis(); for(int i = 0; i < 10000; i++) { targetUser.setUserName(sourceUser.getUserName()); } System.out.println("set方式:"+(System.currentTimeMillis()-setStartTime)); }}
@Data@AllArgsConstructor@NoArgsConstructorclass User{ private String userName;}
以下は実行効率の結果の比較です。
 従来のセットと BeanUtils.copyProperties の間のパフォーマンスのギャップが非常に大きいことがわかります したがって、BeanUtils.copyProperties は注意して使用してください。
上記は、BeanUtils.copyProperties を使用してデータをコピーする際の一般的な落とし穴です。これらの落とし穴のほとんどは比較的隠されており、問題が発生した場合のトラブルシューティングが困難です。したがって、ビジネスにおいて BeanUtils.copyProperties を使用してデータをコピーすることはお勧めできません。記事内の欠陥がある場合は、追加および修正を歓迎します。
-終わり-

この記事は、WeChat パブリック アカウント - JD Cloud Developers (JDT_Developers) から共有されています。
侵害がある場合は、削除について [email protected] までご連絡ください。
この記事は「OSC ソース作成計画」に参加していますので、読んでくださっている方もぜひ参加・共有してください。

博通宣布终止现有 VMware 合作伙伴计划 B站崩了两次、腾讯“3.29”一级事故……盘点 2023 十大宕机事故“冥场面” Vue 3.4 “灌篮高手”发布 养乐多公司确认 95 G 数据被泄露 MySQL 5.7、魔趣、李跳跳……盘点 2023“停更”的(开源)项目和网站 《2023 中国开源开发者报告》正式发布 回顾 30 年前的 IDE:只有 TUI、背景颜色亮瞎眼…… Julia 1.10 正式发布 Rust 1.75.0 发布 英伟达推出特供中国销售的 GeForce RTX 4090 D
{{o.name}}
{{m.name}}

おすすめ

転載: my.oschina.net/u/4090830/blog/10555360