※当サイトの記事には、広告・プロモーションが含まれます。

BeanUtils.copyPropertiesをラッパーしたメソッドを試してみる

www.itmedia.co.jp

⇧ 原因と対応策とかを詳らかに公開してくれる文化があるのは善きですね。

BeanUtils.copyPropertiesをラッパーしたメソッドを試してみる

ライブラリを使っていると、別のライブラリに切り替えたい時が来ることあるあるだと思います。

その際に、直にライブラリのメソッドを利用していて呼び出し箇所も大量であるとすると、変更が結構面倒くさいことになりがちですと。

そこで、メソッドをラッパーすることで変更箇所を1カ所に集約できますと。

というわけで、

ts0818.hatenablog.com

⇧ 上記の時のソースコードに追加。

追加した部分のソースコードは以下。

■/dto-convert-to-csv/src/main/java/com/example/demo/common/BeanUtil.java

package com.example.demo.common;

import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Component;

@Component
public class BeanUtil {

	public static void beanCopy(Object source, Object target) {
		BeanUtils.copyProperties(source, target);
	}
	
}    

■/dto-convert-to-csv/src/test/java/com/example/demo/service/TestCreateCsvDataService.java

package com.example.demo.service;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import com.example.demo.common.BeanUtil;
import com.example.demo.dto.ExaminationInfo;
import com.example.demo.dto.ExaminationInfoData;

@SpringBootTest
public class TestCreateCsvDataService {

	@Autowired
	private CreateCsvDataService createCsvDataService;
	
	@Test
	public void test() {
		List<ExaminationInfo> examinationInfoList = new ArrayList<>(Arrays.asList(
				new ExaminationInfo("A000001", "ほげほげアメリカンプロテイン", BigDecimal.valueOf(10000), 100, LocalDateTime.now(), "0001", "ほげほげ株式会社", "アメリカ", BigDecimal.valueOf(0.05))
				,new ExaminationInfo("A000002", "ほげほげアジアンプロテイン", BigDecimal.valueOf(8000), 100, LocalDateTime.now(), "0001", "ほげほげ株式会社", "アメリカ", BigDecimal.valueOf(0.05))
				,new ExaminationInfo("A000003", "ほげほげヨーロピアンプロテイン", BigDecimal.valueOf(12000), 100, LocalDateTime.now(), "0001", "ほげほげ株式会社", "アメリカ", BigDecimal.valueOf(0.05))
				,new ExaminationInfo("A000004", "ぴよぴよアメリカンプロテイン", BigDecimal.valueOf(11000), 100, LocalDateTime.now(), "0002", "ぴよぴよ株式会社", "アメリカ", BigDecimal.valueOf(0.05))
				,new ExaminationInfo("A000004", "ぴよぴよアメリカンプロテイン", BigDecimal.valueOf(11000), 100, LocalDateTime.now(), "0002", "ぴよぴよ株式会社", "アメリカ", BigDecimal.valueOf(0.05))
				,new ExaminationInfo("A000005", "ぽよぽよアメリカンプロテイン", BigDecimal.valueOf(13000), 100, LocalDateTime.now(), "0003", "ぽよぽよ株式会社", "アメリカ", BigDecimal.valueOf(0.05))
				,new ExaminationInfo("A000006", "ぽよぽよアフリカンプロテイン", BigDecimal.valueOf(7000), 100, LocalDateTime.now(), "0003", "ぽよぽよ株式会社", "アメリカ", BigDecimal.valueOf(0.05))
				));
		
		List<String> csvDataList = createCsvDataService.createCsvDataForExmainationInfo(examinationInfoList);
		for (int index = 0; index < csvDataList.size(); index++) {
			System.out.println("【" +(index + 1) + "】ファイル目のデータ");
			System.out.print(csvDataList.get(index));
		}
		
		// BeanUtils.copyPropertiesをラッパーしたメソッドの挙動確認
		List<ExaminationInfoData> examinationInfoDataList = new ArrayList<>();
		for (ExaminationInfo examinationInfo: examinationInfoList) {
			ExaminationInfoData examinationInfoData = new ExaminationInfoData(); 
			
			BeanUtil.beanCopy(examinationInfo, examinationInfoData);
			examinationInfoData.setFileKbn("2");	
			examinationInfoDataList.add(examinationInfoData);
		}
		
		System.out.println("■BeanCopyの結果");
		for (ExaminationInfoData examinationInfoData: examinationInfoDataList) {
			
			System.out.println(examinationInfoData);
		}
	}

}
    

⇧ で実行すると、

⇧ Beanのコピーができているようです。

ちなみに、今回は、Spring Frameworkで用意されているorg.springframework.beans.BeanUtils.copyPropertiesを利用しているのだけど、

github.com

ソースコードを見た感じ、

https://github.com/spring-projects/spring-framework/blob/main/spring-beans/src/main/java/org/springframework/beans/BeanUtils.java

public abstract class BeanUtils {
...省略

	/**
	 * Copy the property values of the given source bean into the target bean.
	 * <p>Note: The source and target classes do not have to match or even be derived
	 * from each other, as long as the properties match. Any bean properties that the
	 * source bean exposes but the target bean does not will silently be ignored.
	 * <p>This is just a convenience method. For more complex transfer needs,
	 * consider using a full {@link BeanWrapper}.
	 * <p>As of Spring Framework 5.3, this method honors generic type information
	 * when matching properties in the source and target objects.
	 * <p>The following table provides a non-exhaustive set of examples of source
	 * and target property types that can be copied as well as source and target
	 * property types that cannot be copied.
	 * <table border="1">
	 * <tr><th>source property type</th><th>target property type</th><th>copy supported</th></tr>
	 * <tr><td>{@code Integer}</td><td>{@code Integer}</td><td>yes</td></tr>
	 * <tr><td>{@code Integer}</td><td>{@code Number}</td><td>yes</td></tr>
	 * <tr><td>{@code List<Integer>}</td><td>{@code List<Integer>}</td><td>yes</td></tr>
	 * <tr><td>{@code List<?>}</td><td>{@code List<?>}</td><td>yes</td></tr>
	 * <tr><td>{@code List<Integer>}</td><td>{@code List<?>}</td><td>yes</td></tr>
	 * <tr><td>{@code List<Integer>}</td><td>{@code List<? extends Number>}</td><td>yes</td></tr>
	 * <tr><td>{@code String}</td><td>{@code Integer}</td><td>no</td></tr>
	 * <tr><td>{@code Number}</td><td>{@code Integer}</td><td>no</td></tr>
	 * <tr><td>{@code List<Integer>}</td><td>{@code List<Long>}</td><td>no</td></tr>
	 * <tr><td>{@code List<Integer>}</td><td>{@code List<Number>}</td><td>no</td></tr>
	 * </table>
	 * @param source the source bean
	 * @param target the target bean
	 * @throws BeansException if the copying failed
	 * @see BeanWrapper
	 */
	public static void copyProperties(Object source, Object target) throws BeansException {
		copyProperties(source, target, null, (String[]) null);
	}

	/**
	 * Copy the property values of the given source bean into the given target bean,
	 * only setting properties defined in the given "editable" class (or interface).
	 * <p>Note: The source and target classes do not have to match or even be derived
	 * from each other, as long as the properties match. Any bean properties that the
	 * source bean exposes but the target bean does not will silently be ignored.
	 * <p>This is just a convenience method. For more complex transfer needs,
	 * consider using a full {@link BeanWrapper}.
	 * <p>As of Spring Framework 5.3, this method honors generic type information
	 * when matching properties in the source and target objects. See the
	 * documentation for {@link #copyProperties(Object, Object)} for details.
	 * @param source the source bean
	 * @param target the target bean
	 * @param editable the class (or interface) to restrict property setting to
	 * @throws BeansException if the copying failed
	 * @see BeanWrapper
	 */
	public static void copyProperties(Object source, Object target, Class<?> editable) throws BeansException {
		copyProperties(source, target, editable, (String[]) null);
	}

	/**
	 * Copy the property values of the given source bean into the given target bean,
	 * ignoring the given "ignoreProperties".
	 * <p>Note: The source and target classes do not have to match or even be derived
	 * from each other, as long as the properties match. Any bean properties that the
	 * source bean exposes but the target bean does not will silently be ignored.
	 * <p>This is just a convenience method. For more complex transfer needs,
	 * consider using a full {@link BeanWrapper}.
	 * <p>As of Spring Framework 5.3, this method honors generic type information
	 * when matching properties in the source and target objects. See the
	 * documentation for {@link #copyProperties(Object, Object)} for details.
	 * @param source the source bean
	 * @param target the target bean
	 * @param ignoreProperties array of property names to ignore
	 * @throws BeansException if the copying failed
	 * @see BeanWrapper
	 */
	public static void copyProperties(Object source, Object target, String... ignoreProperties) throws BeansException {
		copyProperties(source, target, null, ignoreProperties);
	}

	/**
	 * Copy the property values of the given source bean into the given target bean.
	 * <p>Note: The source and target classes do not have to match or even be derived
	 * from each other, as long as the properties match. Any bean properties that the
	 * source bean exposes but the target bean does not will silently be ignored.
	 * <p>As of Spring Framework 5.3, this method honors generic type information
	 * when matching properties in the source and target objects. See the
	 * documentation for {@link #copyProperties(Object, Object)} for details.
	 * @param source the source bean
	 * @param target the target bean
	 * @param editable the class (or interface) to restrict property setting to
	 * @param ignoreProperties array of property names to ignore
	 * @throws BeansException if the copying failed
	 * @see BeanWrapper
	 */
	private static void copyProperties(Object source, Object target, @Nullable Class<?> editable,
			@Nullable String... ignoreProperties) throws BeansException {

		Assert.notNull(source, "Source must not be null");
		Assert.notNull(target, "Target must not be null");

		Class<?> actualEditable = target.getClass();
		if (editable != null) {
			if (!editable.isInstance(target)) {
				throw new IllegalArgumentException("Target class [" + target.getClass().getName() +
						"] not assignable to editable class [" + editable.getName() + "]");
			}
			actualEditable = editable;
		}
		PropertyDescriptor[] targetPds = getPropertyDescriptors(actualEditable);
		Set<String> ignoredProps = (ignoreProperties != null ? new HashSet<>(Arrays.asList(ignoreProperties)) : null);
		CachedIntrospectionResults sourceResults = (actualEditable != source.getClass() ?
				CachedIntrospectionResults.forClass(source.getClass()) : null);

		for (PropertyDescriptor targetPd : targetPds) {
			Method writeMethod = targetPd.getWriteMethod();
			if (writeMethod != null && (ignoredProps == null || !ignoredProps.contains(targetPd.getName()))) {
				PropertyDescriptor sourcePd = (sourceResults != null ?
						sourceResults.getPropertyDescriptor(targetPd.getName()) : targetPd);
				if (sourcePd != null) {
					Method readMethod = sourcePd.getReadMethod();
					if (readMethod != null) {
						if (isAssignable(writeMethod, readMethod, sourcePd, targetPd)) {
							try {
								ReflectionUtils.makeAccessible(readMethod);
								Object value = readMethod.invoke(source);
								ReflectionUtils.makeAccessible(writeMethod);
								writeMethod.invoke(target, value);
							}
							catch (Throwable ex) {
								throw new FatalBeanException(
										"Could not copy property '" + targetPd.getName() + "' from source to target", ex);
							}
						}
					}
				}
			}
		}
	}
...省略
}

⇧ 引数が異なる3つのメソッドが用意されているので、必要な数のメソッドをラッパーする感じになるかと。

とりあえず、org.springframework.beans.BeanUtils.copyPropertiesメソッドをラッパーしても上手く動作することが分かったので良しとしますか。

それにしても、ラッパーして使っている情報がネットに見当たらないのだけど、利用箇所が多くなるならラッパーしておいた方が良いような気もするんですが、どうなんだろうか?

毎度モヤモヤ感が半端ない…

今回はこのへんで。