免責事項:この記事は元記事に属し、公共の番号で発信元:自己プログラマー、そして同時に日にリリースjuejin.im/user/5a4dbd ...ソースを明記してください!
起源
開発プロセスたら、ちょうどPO対象外のデータベースを照会するための呼び出しセット法の小さなパートナーを表示するには、20の以上の属性、このようなVoのオブジェクトにコピーし、:
図から分かるように、ポーとVoの2つのクラスはフィールドのほとんどは、我々が設定した方法はいくつかの長い操作を繰り返したの呼び出しを一つずつ同じです。この操作は、オブジェクトの、あまりにも多くのプロパティので、肉眼を検出するために1つまたは2つが、困難を欠場することが可能で、間違いを犯すことは非常に簡単です。
このような操作は、我々は簡単で、想像できる反射解決します。実際には、それほど一般的で汎用的な機能、々 BeanUtilsのツールは、あなたが得ることができます。
だから私は、Apache BeanUtils.copyPropertiesは、私たちのプログラムには、この属性をコピーする小さなパートナーの使用をお勧めしますピットを掘っを!
アリ・コード規程
我々はコードスキャンプラグイン・アリを開くと、あなたが使用している場合、Apache BeanUtils.copyProperties
属性のコピーを、それはあなたを与えるだろう非常に深刻な警告を。なぜなら、Apacheの々 BeanUtilsパフォーマンスの低下、またはその代わりに春々 BeanUtils CGLIB BeanCopierを使用することができます。
こうした警告、少し不快な人々を参照してください。有名なApacheのパッケージが提供する、そして実際に存在しますパフォーマンスの問題アリが深刻な警告を与えるように、。
まあ、このパフォーマンスの問題は、正確にどのように深刻であること?結局のところ、私たちのシナリオでは、ごくわずかなパフォーマンスの低下場合が、大きな利便性をもたらすことができる、それが許容可能です。
この質問では。我々はそれをテストするための実験を行います。
あなたは、特定のテストモードに興味を持っていない場合は、可能な結果を見て、直接スキップああ〜
インタフェースおよび実装定義された試験方法
まず、テストを容易にするために、のインタフェースを定義し、いくつかの実装を統一してみましょう:
public interface PropertiesCopier {
void copyProperties(Object source, Object target) throws Exception;
}
public class CglibBeanCopierPropertiesCopier implements PropertiesCopier {
@Override
public void copyProperties(Object source, Object target) throws Exception {
BeanCopier copier = BeanCopier.create(source.getClass(), target.getClass(), false);
copier.copy(source, target, null);
}
}
// 全局静态 BeanCopier,避免每次都生成新的对象
public class StaticCglibBeanCopierPropertiesCopier implements PropertiesCopier {
private static BeanCopier copier = BeanCopier.create(Account.class, Account.class, false);
@Override
public void copyProperties(Object source, Object target) throws Exception {
copier.copy(source, target, null);
}
}
public class SpringBeanUtilsPropertiesCopier implements PropertiesCopier {
@Override
public void copyProperties(Object source, Object target) throws Exception {
org.springframework.beans.BeanUtils.copyProperties(source, target);
}
}
public class CommonsBeanUtilsPropertiesCopier implements PropertiesCopier {
@Override
public void copyProperties(Object source, Object target) throws Exception {
org.apache.commons.beanutils.BeanUtils.copyProperties(target, source);
}
}
public class CommonsPropertyUtilsPropertiesCopier implements PropertiesCopier {
@Override
public void copyProperties(Object source, Object target) throws Exception {
org.apache.commons.beanutils.PropertyUtils.copyProperties(target, source);
}
}
复制代码
ユニットテスト
そして、パラメータ化されたユニットテストを書きます:
@RunWith(Parameterized.class)
public class PropertiesCopierTest {
@Parameterized.Parameter(0)
public PropertiesCopier propertiesCopier;
// 测试次数
private static List<Integer> testTimes = Arrays.asList(100, 1000, 10_000, 100_000, 1_000_000);
// 测试结果以 markdown 表格的形式输出
private static StringBuilder resultBuilder = new StringBuilder("|实现|100|1,000|10,000|100,000|1,000,000|\n").append("|----|----|----|----|----|----|\n");
@Parameterized.Parameters
public static Collection<Object[]> data() {
Collection<Object[]> params = new ArrayList<>();
params.add(new Object[]{new StaticCglibBeanCopierPropertiesCopier()});
params.add(new Object[]{new CglibBeanCopierPropertiesCopier()});
params.add(new Object[]{new SpringBeanUtilsPropertiesCopier()});
params.add(new Object[]{new CommonsPropertyUtilsPropertiesCopier()});
params.add(new Object[]{new CommonsBeanUtilsPropertiesCopier()});
return params;
}
@Before
public void setUp() throws Exception {
String name = propertiesCopier.getClass().getSimpleName().replace("PropertiesCopier", "");
resultBuilder.append("|").append(name).append("|");
}
@Test
public void copyProperties() throws Exception {
Account source = new Account(1, "test1", 30D);
Account target = new Account();
// 预热一次
propertiesCopier.copyProperties(source, target);
for (Integer time : testTimes) {
long start = System.nanoTime();
for (int i = 0; i < time; i++) {
propertiesCopier.copyProperties(source, target);
}
resultBuilder.append((System.nanoTime() - start) / 1_000_000D).append("|");
}
resultBuilder.append("\n");
}
@AfterClass
public static void tearDown() throws Exception {
System.out.println("测试结果:");
System.out.println(resultBuilder);
}
}
复制代码
テスト結果
時間単位ミリ秒
実現 | 100回 | 千 | 万 | 10万 | 1,000,000 |
---|---|---|---|---|---|
StaticCglibBeanCopier | 0.055022 | 0.541029 | 0.999478 | 2.754824 | 9.88556 |
CglibBeanCopier | 5.320798 | 11.086323 | 61.037446 | 72.484607 | 333.384007 |
SpringBeanUtils | 5.180483 | 21.328542 | 30.021662 | 103.266375 | 966.439272 |
CommonsPropertyUtils | 9.729159 | 42.927356 | 74.063789 | 386.127787 | 1955.5437 |
CommonsBeanUtils | 24.99513 | 170.728558 | 572.335327 | 2970.3068 | 27563.3459 |
結果は、ことを示したCGLIB BeanCopierコピー速度は最速でコピー百万ものはわずか10ミリ秒を要し、!これとは対照的に、最悪の方法はBeanUtils.copyPropertiesパッケージ・コモンズ、テストと最高のパフォーマンスCGLIB差の100枚のコピーである400回ほど。万枚はの登場ですパフォーマンスの2800倍の差!
結果は本当に目を見張る。
しかし、なぜこのような大きな違いがあるのですか?
原因分析
ビューソースは、我々はいくつかの時間がかかりCommonsBeanUtilsは、次のような場所があることがわかります。
- 多数の出力ログ情報をデバッグ
- 重複したオブジェクトの型チェック
- 型変換
public void copyProperties(final Object dest, final Object orig)
throws IllegalAccessException, InvocationTargetException {
// 类型检查
if (orig instanceof DynaBean) {
...
} else if (orig instanceof Map) {
...
} else {
final PropertyDescriptor[] origDescriptors = ...
for (PropertyDescriptor origDescriptor : origDescriptors) {
...
// 这里每个属性都调一次 copyProperty
copyProperty(dest, name, value);
}
}
}
public void copyProperty(final Object bean, String name, Object value)
throws IllegalAccessException, InvocationTargetException {
...
// 这里又进行一次类型检查
if (target instanceof DynaBean) {
...
}
...
// 需要将属性转换为目标类型
value = convertForCopy(value, type);
...
}
// 而这个 convert 方法在日志级别为 debug 的时候有很多的字符串拼接
public <T> T convert(final Class<T> type, Object value) {
if (log().isDebugEnabled()) {
log().debug("Converting" + (value == null ? "" : " '" + toString(sourceType) + "'") + " value '" + value + "' to type '" + toString(targetType) + "'");
}
...
if (targetType.equals(String.class)) {
return targetType.cast(convertToString(value));
} else if (targetType.equals(sourceType)) {
if (log().isDebugEnabled()) {
log().debug("No conversion required, value is already a " + toString(targetType));
}
return targetType.cast(value);
} else {
// 这个 convertToType 方法里也需要做类型检查
final Object result = convertToType(targetType, value);
if (log().isDebugEnabled()) {
log().debug("Converted to " + toString(targetType) + " value '" + result + "'");
}
return targetType.cast(result);
}
}
复制代码
具体的な性能やソースコード解析は、これらの記事を参照できます。
- いくつかのcopyProperties性能比較ツール:www.jianshu.com/p/bcbacab3b ...
- 達成するためにBeanCopierのソースコード内のCGLIB:www.jianshu.com/p/f8b892e08 ...
- 性能比較のためのJava Beanがコピーフレームワーク:yq.aliyun.com/articles/39 ...
もう一つ
パフォーマンスの問題に加えて、だけでなく、CommonsBeanUtilsの使用中に他のピットは特に注意する必要があります!
デフォルト値の梱包
属性のコピー中に、CommonsBeanUtilsは元の包装アサインデフォルト値にデフォルトではないでしょうが、しかし、下位バージョン(1.8.0以下)を使用した場合、およびあなたのクラスはDate型の属性を持っている場合は、ソースオブジェクトの属性値がnullの場合、例外が発生します。
org.apache.commons.beanutils.ConversionException: No value specified for 'Date'
この問題を解決するには、DateConverterを登録することです。
ConvertUtils.register(new DateConverter(null), java.util.Date.class);
ただし、この文はになりますプリミティブ型のデフォルト値が与えられます、包装の種類暗黙的にフィールドにオブジェクトのソースがnullであるにも関わらず、0が割り当てられ、そのようなInteger型のプロパティとして、。
高いバージョン(1.9.3)では、発行日との問題は、デフォルトのラッパークラスが固定されているNULL値を割り当てます。
これでは、シーンのラッパークラスがnull値に属性を特別な意味を有するピットにステップするのは非常に簡単に、!そのような検索条件としてオブジェクトは、一般的にヌル値がこのフィールドに限定されていないことを示し、0はゼロである必要があり、このフィールドの値を表します。
他のツールに切り替えると
私たちは、プロンプトアリを参照するか、あなたがこの記事を読んだとき、私はパフォーマンスの問題のCommonsBeanUtilsを知っている、とあなたは春の々BeanUtilsを使用する場合、注意してください:
org.apache.commons.beanutils.BeanUtils.copyProperties(Object target, Object source);
org.springframework.beans.BeanUtils.copyProperties(Object source, Object target);
复制代码
メソッドシグネチャ、これらから分かるように、同一の2つのツールクラス名、メソッド名はまた、パラメータの同じ偶数、同じ名前のタイプです。しかし、位置パラメータが逆転しています。あなたは必ず覚えておいてください、時間を変更したいのであれば、ターゲットとソースのパラメータもオーバースワップの2つです!
また、それは様々な理由であることができる、あなたは不完全な情報の積み重ねがあなたを思い出させるためにどのような問題なので、ここでは道を見つけることができません取得します:
あなたが発生した場合java.lang.IllegalArgumentException: Source must not be null
やjava.lang.IllegalArgumentException: Target must not be null
、そのような異常はどこにでもなく、原因を見つけることができないときは、原因ときcopyPropertiesにNULL値を渡すので、私は、見つけることはありません。
ます。https://juejin.im/post/5d0b68a36fb9a07ee1692ed9で再現