java reflection에서 type conversion 어떻게 처리하면 좋을까?

2013-02-07 18:08

어제 스터디에서 mvc 프레임워크를 구현하는 중에 나온 이슈 사항입니다. 요구사항은 HttpServletRequest에 담겨 전달되는 인자를 JavaBean에 자동으로 값을 set할 수 있으면 좋겠다. 대부분의 mvc 프레임워크가 이 같은 기능을 제공하고 있기 때문에 우리도 만들어보자는 의도에서 시작했다.

구현은 java reflection 사용해서 쉽게 해결할 수 있었다. 그런데 문제는 String Type이 아닌 다른 데이터 타입일 경우에 어떻게 처리하는 것이 좋을까에 대해서는 해결책을 찾지 못했다. if/else를 사용한다면 다음과 같은 방식으로 구현하는 것이 가능하다.

package net.slipp.web;


import java.lang.reflect.Field;


import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.mock.web.MockHttpServletRequest;


public class ReflectionTest {
    private static Logger log = LoggerFactory.getLogger(ReflectionTest.class);


    @Test
    public void populateFromRequestToUser() throws Exception {
        MockHttpServletRequest request = new MockHttpServletRequest();
        request.addParameter("userId", "test");
        request.addParameter("name", "슬립");
        request.addParameter("userNo", "12356");
        request.addParameter("age", "35");


        MockUser user = new MockUser();
        Field[] fields = MockUser.class.getDeclaredFields();
        for (Field field : fields) {
            log.debug("field name : {}", field.getName());
            field.setAccessible(true);


            String value = request.getParameter(field.getName());
            if (field.getType() == Integer.class) {
                field.set(user, Integer.parseInt(value));
            } else if (field.getType() == Long.TYPE) {
                field.set(user, Long.parseLong(value));
            } else {
                field.set(user, value);
            }
        }


        log.debug("User : {}", user);
    }


    private class MockUser {
        private long userNo;
        private Integer age;
        private String userId;
        private String name;


        @Override
        public String toString() {
            return "MockUser [userNo=" + userNo + ", age=" + age + ", userId=" + userId + ", name=" + name + "]";
        }
    }
}

그런데 위와 같이 구현할 경우 모든 데이터 타입에 대한 if/else를 추가해야 되고, Custom Type이 존재할 경우에는 해결 방법이 없는 상황이다. 일단 Custom Type은 제외하더라도 자바에서 제공하는 기본 데이터 타입에 대해 쉽게 형 변환을 할 수 있는 방법이 있을까?

3개의 의견 from SLiPP

2013-02-07 18:30

Spring MVC에서 자동을 conversion을 해주고 있어서 소스를 열어 봤더니 이런 부분이 보이네요. PropertyEditorRegistrySupport.java 파일입니다.

private void createDefaultEditors() {
	this.defaultEditors = new HashMap<Class<?>, PropertyEditor>(64);


	// Simple editors, without parameterization capabilities.
	// The JDK does not contain a default editor for any of these target types.
	this.defaultEditors.put(Charset.class, new CharsetEditor());
	this.defaultEditors.put(Class.class, new ClassEditor());
	this.defaultEditors.put(Class[].class, new ClassArrayEditor());
	this.defaultEditors.put(Currency.class, new CurrencyEditor());
	this.defaultEditors.put(File.class, new FileEditor());
	this.defaultEditors.put(InputStream.class, new InputStreamEditor());
	this.defaultEditors.put(InputSource.class, new InputSourceEditor());
	this.defaultEditors.put(Locale.class, new LocaleEditor());
	this.defaultEditors.put(Pattern.class, new PatternEditor());
	this.defaultEditors.put(Properties.class, new PropertiesEditor());
	this.defaultEditors.put(Resource[].class, new ResourceArrayPropertyEditor());
	this.defaultEditors.put(TimeZone.class, new TimeZoneEditor());
	this.defaultEditors.put(URI.class, new URIEditor());
	this.defaultEditors.put(URL.class, new URLEditor());
	this.defaultEditors.put(UUID.class, new UUIDEditor());


	// Default instances of collection editors.
	// Can be overridden by registering custom instances of those as custom editors.
	this.defaultEditors.put(Collection.class, new CustomCollectionEditor(Collection.class));
	this.defaultEditors.put(Set.class, new CustomCollectionEditor(Set.class));
	this.defaultEditors.put(SortedSet.class, new CustomCollectionEditor(SortedSet.class));
	this.defaultEditors.put(List.class, new CustomCollectionEditor(List.class));
	this.defaultEditors.put(SortedMap.class, new CustomMapEditor(SortedMap.class));


	// Default editors for primitive arrays.
	this.defaultEditors.put(byte[].class, new ByteArrayPropertyEditor());
	this.defaultEditors.put(char[].class, new CharArrayPropertyEditor());


	// The JDK does not contain a default editor for char!
	this.defaultEditors.put(char.class, new CharacterEditor(false));
	this.defaultEditors.put(Character.class, new CharacterEditor(true));


	// Spring's CustomBooleanEditor accepts more flag values than the JDK's default editor.
	this.defaultEditors.put(boolean.class, new CustomBooleanEditor(false));
	this.defaultEditors.put(Boolean.class, new CustomBooleanEditor(true));


	// The JDK does not contain default editors for number wrapper types!
	// Override JDK primitive number editors with our own CustomNumberEditor.
	this.defaultEditors.put(byte.class, new CustomNumberEditor(Byte.class, false));
	this.defaultEditors.put(Byte.class, new CustomNumberEditor(Byte.class, true));
	this.defaultEditors.put(short.class, new CustomNumberEditor(Short.class, false));
	this.defaultEditors.put(Short.class, new CustomNumberEditor(Short.class, true));
	this.defaultEditors.put(int.class, new CustomNumberEditor(Integer.class, false));
	this.defaultEditors.put(Integer.class, new CustomNumberEditor(Integer.class, true));
	this.defaultEditors.put(long.class, new CustomNumberEditor(Long.class, false));
	this.defaultEditors.put(Long.class, new CustomNumberEditor(Long.class, true));
	this.defaultEditors.put(float.class, new CustomNumberEditor(Float.class, false));
	this.defaultEditors.put(Float.class, new CustomNumberEditor(Float.class, true));
	this.defaultEditors.put(double.class, new CustomNumberEditor(Double.class, false));
	this.defaultEditors.put(Double.class, new CustomNumberEditor(Double.class, true));
	this.defaultEditors.put(BigDecimal.class, new CustomNumberEditor(BigDecimal.class, true));
	this.defaultEditors.put(BigInteger.class, new CustomNumberEditor(BigInteger.class, true));


	// Only register config value editors if explicitly requested.
	if (this.configValueEditorsActive) {
		StringArrayPropertyEditor sae = new StringArrayPropertyEditor();
		this.defaultEditors.put(String[].class, sae);
		this.defaultEditors.put(short[].class, sae);
		this.defaultEditors.put(int[].class, sae);
		this.defaultEditors.put(long[].class, sae);
	}
}

위와 같이 Map으로 Type에 각 Type에 따른 형변환을 할 수 있는 PropertyEditor 클래스를 두어서 관리하도록 구현하고 있네요. 이런 방식으로 구현하면 if/else는 사라지고 CustomType이 추가될 경우 이 Map에다 CustomType과 이에 대한 CustomPropertyEditor를 추가하는 방식으로 구현하면 되겠네요. 위 본문에 있는 소스 코드를 위와 같이 한번 구현해 봐야겠네요.

2013-02-07 18:45

Spring 소스에서 일부 Type을 가져온 다음 아래와 같이 수정해서 구현해 봤습니다. 모든 Type을 고려하려면 Spring MVC 소스 보세요.

package net.slipp.web;


import java.beans.PropertyEditor;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;


import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.propertyeditors.CustomBooleanEditor;
import org.springframework.beans.propertyeditors.CustomNumberEditor;
import org.springframework.mock.web.MockHttpServletRequest;


public class ReflectionTest {
    private static Logger log = LoggerFactory.getLogger(ReflectionTest.class);


    @SuppressWarnings("serial")
    private static Map<Class<?>, PropertyEditor> defaultEditors = new HashMap<Class<?>, PropertyEditor>() {
        {
            put(boolean.class, new CustomBooleanEditor(false));
            put(Boolean.class, new CustomBooleanEditor(true));


            put(byte.class, new CustomNumberEditor(Byte.class, false));
            put(Byte.class, new CustomNumberEditor(Byte.class, true));
            put(int.class, new CustomNumberEditor(Integer.class, false));
            put(Integer.class, new CustomNumberEditor(Integer.class, true));
            put(long.class, new CustomNumberEditor(Long.class, false));
            put(Long.class, new CustomNumberEditor(Long.class, true));
        }
    };


    @Test
    public void populateFromRequestToUser() throws Exception {
        MockHttpServletRequest request = new MockHttpServletRequest();
        request.addParameter("userId", "test");
        request.addParameter("name", "슬립");
        request.addParameter("userNo", "12356");
        request.addParameter("age", "35");


        MockUser user = new MockUser();
        Field[] fields = MockUser.class.getDeclaredFields();
        for (Field field : fields) {
            log.debug("field name : {}", field.getName());
            field.setAccessible(true);


            String value = request.getParameter(field.getName());
            if (field.getType() == String.class) {
                field.set(user, value);
                continue;
            }


            PropertyEditor propertyEditor = defaultEditors.get(field.getType());
            if (propertyEditor != null) {
                propertyEditor.setAsText(value);
                field.set(user, propertyEditor.getValue());
            }
        }


        log.debug("User : {}", user);
    }


    private class MockUser {
        private long userNo;
        private Integer age;
        private String userId;
        private String name;


        @Override
        public String toString() {
            return "MockUser [userNo=" + userNo + ", age=" + age + ", userId=" + userId + ", name=" + name + "]";
        }
    }
}
의견 추가하기

연관태그

← 목록으로