들어가면서...
지난번에 올렸던 apche BeanUtils vs Spring BeanUtils 비교 문장을 일단 읽어보았을것이다. 그 문장에 이어 오늘은 BeanUtils 에 대해서 다시 언급해볼까 한다.
예전에 도org.apache.commons.beanutils.BeanUtils 을 일반적으로 많이 사용해왔다.
하지만 Spring내의 BeanUtils 이 성능이 좋다고 들었다. 이 클래스의 공식적 영어설명은 아래와 같았다.
Static convenience methods for JavaBeans: for instantiating beans, checking bean property types, copying bean properties, etc.
Mainly for use within the framework, but to some degree also useful for application classes.
Spring BeanUtils 의 문제를 파헤쳐보자
예시코드
Copy import lombok . Data ;
import java . util . List ;
@ Data
public class A {
private String name;
private List < Integer > ids;
}
Copy @ Data
public class B {
private String name;
private List < String > ids;
}
Copy import org . springframework . beans . BeanUtils ;
import java . util . Arrays ;
public class BeanUtilDemo {
public static void main ( String [] args) {
A first = new A() ;
first . setName ( "demo" );
first . setIds ( Arrays . asList ( 1 , 2 , 3 ));
B second = new B() ;
BeanUtils . copyProperties (first , second);
for ( String each : second . getIds ()) { // 에러발생
System . out . println (each);
}
}
}
Run time 시 exception 이 발생하는걸 알수 있다.
디버깅 찍고 확인 해보면 아래와 같이 property copy 후에도 B 클래스의 second 객체내의 ids 는 여전희 Integer 형이다.
문자열로 캐스팅 하는 과정업이 print 하면 바로 예외가 발생한다.
그리고 CGlib 사용시 Convert를 지정하지 않으면 마찬가지로 동일한 문제가 발생한다.
Copy import org . easymock . cglib . beans . BeanCopier ;
import java . util . Arrays ;
public class BeanUtilDemo {
public static void main ( String [] args) {
A first = new A() ;
first . setName ( "demo" );
first . setIds ( Arrays . asList ( 1 , 2 , 3 ));
B second = new B() ;
final BeanCopier beanCopier = BeanCopier . create ( A . class , B . class , false );
beanCopier . copy (first , second , null );
for ( String each : second . getIds ()) { // 에러 발생
System . out . println (each);
}
}
}
항상 run time 시에 발생하는 에러라 코딩시에는 아무런 문제 없다.
mapstruct 는 ?
Copy import org . mapstruct . Mapper ;
import org . mapstruct . factory . Mappers ;
@ Mapper
public interface Converter {
Converter INSTANCE = Mappers . getMapper ( Converter . class );
B aToB ( A car);
}
Copy import java . util . Arrays ;
public class BeanUtilDemo {
public static void main ( String [] args) {
A first = new A() ;
first . setName ( "demo" );
first . setIds ( Arrays . asList ( 1 , 2 , 3 ));
B second = Converter . INSTANCE . aToB (first);
for ( String each : second . getIds ()) { // 정상
System . out . println (each);
}
}
}
A 클래스의 List<Integer> 를 B클래스의 List<String> 으로 전환성공!
아래는 컴파일은 성공한 Converter 클래스를 보자
Copy import java . util . ArrayList ;
import java . util . List ;
import javax . annotation . Generated ;
import org . springframework . stereotype . Component ;
@ Generated (
value = "org.mapstruct.ap.MappingProcessor" ,
comments = "version: 1.3.1.Final, compiler: javac, environment: Java 1.8.0_202 (Oracle Corporation)"
)
@ Component
public class ConverterImpl implements Converter {
@ Override
public B aToB ( A car) {
if ( car == null ) {
return null ;
}
B b = new B() ;
b . setName ( car . getName () );
b . setIds ( integerListToStringList( car . getIds() ) );
return b;
}
protected List < String > integerListToStringList ( List < Integer > list) {
if ( list == null ) {
return null ;
}
List < String > list1 = new ArrayList < String >( list . size () );
for ( Integer integer : list ) {
list1 . add ( String . valueOf ( integer ) );
}
return list1;
}
}
자동으로 전화해주어서 , 개발자 입장에서 인지하지 못하고 있었음!
만약 A 클래스에 String number 속성 추가 , B 클래스에 Long number 속성 추가후 mapstruect 가 number 를 Integer 외 데이터 타입으로 세팅하려고 시도하면 .NumberFormatException 예외가 발생할것 이다.
Copy @ Override
public B aToB( A car) {
if ( car == null ) {
return null ;
}
B b = new B() ;
b . setName ( car . getName () );
if ( car . getNumber () != null ) { // 문제발생지점
b . setNumber ( Long . parseLong ( car . getNumber () ) );
}
b . setIds ( integerListToStringList( car . getIds() ) );
return b;
}
왜냐면 cglib 사용시 number 속성을 매핑하지 않을것이고 B 클래스의 number 가 null 이된다.
수동으로 convert 정의시 ( 예를 들어 IDEA 플러그인 generateO2O) 를 사용한다면.
Copy public final class A2BConverter {
public static B from ( A first) {
B b = new B() ;
b . setName ( first . getName ());
b . setIds ( first . getIds ());
return b;
}
}
코딩시 바로 에러에 대하여 감지한다.
결론!
Java의 제너릭은 사실 컴파일 기간 검사이기 때문에 컴파일 후 제러릭은 지워지기 때문에 rum time 시 List<Integer>와 List<String>은 모두 List 타입이므로 정상적으로 값을 할당할 수 있다.이로 인해 많은 속성 매핑 도구를 사용할 때 컴파일 시 오류는 코딩과정에서 쉽게 찾을수 없다.
mapstruct 는 주석 프로세서를 사용자 정의하여 컴파일 단계에서 양쪽을 매핑하는 범유형 유형을 읽고 매핑할 수 있습니다.그런데 이런 매핑도 위헙하다고 하는 이유는 때로는 우리가 부주의 등으로 유형을 잘못 정의해서 자동으로 변환을 도와줘서 많은 문제를 가져왔었기때문이다.
다양한 속성 매핑 도구의 성능을 간단히 비교한 결과이니 참고용으로 만사용해보라 .결국은 노가다 getter,setter 가 짱이다.
끝!