IT/SpringFramework

SpringFramework ConversionService를 이용한 DataBinding

주현태 2019. 11. 28. 20:27

 토비의 스프링 vol2 Chapter4의 @MVC에서 DataBinding과 관련된 부분을 공부하였다. @ModelAttribute를 사용하여 스프링이 제공하는 기본 바인딩만 하더라도 코드의 수를 상당히 줄일 수 있다고 생각하였는데, DataBinding에 Customizing된 자료형(ex : 열거형)을 바인딩 하는 여러가지 방법을 보고나니 어서 실습을 해보고 싶은 마음이 용솟음 친다.

 

 오늘의 포스팅은 내가 차후 스프링 프로젝트를 하면서 자주 사용할 것같은 Custom화된 자료형을 바인딩 방법중에서 ConversionService를 이용한 바인딩을 수행하고 포스팅 하고자 한다.

 

 @MVC의 바인딩 기능을 사용하는 방법은 PropertyEditor를 사용하는 법과 ConversionService을 이용하는 방법이 있는데 프로퍼티 에디터의 경우 구조상 오브젝트를 매번 생성해야 해서 Scope를 프로토 타입으로 생성해야 한다고 한다.

 반면에 컨버전의 경우 싱글톤으로 빈을 등록하여도 문제가 없어 safe thread 하다. 그러므로 이번 실습에서는 ConversionService를 이용한 바인딩 기법에 대해 알아보고자 한다.

 

 

실습 진행시 문제점)

 

뭔가 계속 오류가 나고있었는데 web.xml을 확인해 보니 웹과 관련된 bean들을 다른 xml을 참조해서 업로드 하는데 이곳과 관련하여 ConversionService가 충돌이 일어나는 것 같았다. 그래서 웹과 관련된 xml참조파일을 내가 작성해 보니까 된다 -_- (내 2, 3시간 돌리도.. 햘키) ..

 

반드시 실습 진행시 STS에서 기본으로 만들어준 servlet-context.xml을 사용할 것이 아니라 직접 만들어서 해야한다.

(기본으로 설정되어 있는 xml에 네임스페이스나 빈들중에 ConversionService와 관련된 빈을 등록해 주는 기능이 있는듯 하다.)

 

 

실습목표)

  1. Custominzing된 enum클래스를 만들어서 HTTP 요청시 DataBinding 확인

  2. enum클래스가 VO 객체안에 들어가 있을경우 HTTP 요청시 DataBinding 확인

 

금일 실습을 진행한 클래스 다이어그램

열거형 [Role.java]

User클래스 내부에 권한 설정을 위한 Role Enum 열거형을 만들었다.

 

열거형에 대해서는 다음의 포스팅을 참조 https://honeyinfo7.tistory.com/135

 

JAVA enum 자료형(직관적인 코드와 상수를 옳게 사용하기 위한)

최근 토비의 스프링을 공부하는데 enum을 사용하는 경우를 가끔씩 볼 수 있다. enum을 사용할 줄은 알지만 정확히 잘 사용하지는 못하는지라 이참에 enum에 대해서 제대로 숙지하기 위해 블로그 포스팅을 하면서..

honeyinfo7.tistory.com

public enum Role {
       ADMIN(1), DEFAULT(2), CLIENT(3);
       private final int role;
       Role(int val) {
              this.role = val;
       }
       public static Role valueOf(int value) {
              switch (value) {
              case 1:
                     return ADMIN;
              case 2:
                     return DEFAULT;
              case 3:
                     return CLIENT;
              default:
                     throw new AssertionError("Unknown value : " + value);
              }
       }
       @Override
       public String toString() {
              String result = "";
              switch (this.role) {
              case 1:
                     result = "ADMIN";
              case 2:
                     result = "DEFAULT";
              case 3:
                     result = "CLIENT";
              }
              return result;
       }
}

 

[RoleTestController.java]

Enum인 Role과 http 요청의 바인딩, Role이 들어있는 User와 http요청의 바인딩 테스트를 위한 Controller이다. 

@Controller
public class RoleTestController {
	@Autowired
	@Qualifier(value="conversionService")
	ConversionService conversionService;

	@InitBinder
	public void initBinder(WebDataBinder binder) {
		binder.setConversionService(this.conversionService);
	}

	@RequestMapping(value = "/roleparamtest")
	public String RoleTest(@RequestParam Role role, ModelMap map) {
		map.put("role", role.name());

		return "home";
	}

	@RequestMapping(value = "/roleobjtest")
	public String RoleObjTest(@ModelAttribute User user) {
		System.out.println(user.getRole().name());

		return "home";
	}
}

 

[RoleToStringConverter.java]

ConversionService에 DI될 Converter를 상속한 클래스(Role -> String)

import org.springframework.core.convert.converter.Converter;
import com.copocalypse.www.enums.Role;
public class RoleToStringConverter implements Converter<Role, String> {
       @Override
       public String convert(Role from) {
              return from.toString();
       }
}

 

 

[StringToRoleConverter.java]

ConversionService에 DI될 Converter를 상속한 클래스(String -> Role)

import org.springframework.core.convert.converter.Converter;
import com.copocalypse.www.enums.Role;
public class StringToRoleConverter implements Converter<String, Role>{
       @Override
       public Role convert(String from) {
              System.out.println(from);
              Role r=Role.valueOf(Integer.parseInt(from));
              System.out.println(r.name()+" "+from);
              return r;
       }
}

 

[src/main/resources/web-context.xml]

servlet이 참조할 빈정보가 들어있는 xml이다.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
              http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">
       <!-- Handles HTTP GET requests for /resources/** by efficiently serving
              up static resources in the ${webappRoot}/resources directory -->
       <!-- Resolves views selected for rendering by @Controllers to .jsp resources
              in the /WEB-INF/views directory -->
       <bean
              class="org.springframework.web.servlet.view.InternalResourceViewResolver">
              <property name="prefix" value="/WEB-INF/views/" />
              <property name="suffix" value=".jsp" />
       </bean>
       
       <bean id="conversionService"
              class="org.springframework.context.support.ConversionServiceFactoryBean">
              <property name="converters">
                     <set>
                           <bean
                                  class="com.copocalypse.www.converter.RoleToStringConverter" />
                           <bean
                                  class="com.copocalypse.www.converter.StringToRoleConverter" />
                     </set>
              </property>
       </bean>
       <context:component-scan
              base-package="com.copocalypse.www" />
</beans>

 

[web.xml]

 

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://java.sun.com/xml/ns/javaee https://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
       <filter>
              <filter-name>setCharacterEncodingFilter</filter-name>
              <filter-class>org.apache.catalina.filters.SetCharacterEncodingFilter</filter-class>
              <init-param>
                     <param-name>encoding</param-name>
                     <param-value>UTF-8</param-value>
              </init-param>
              <async-supported>true</async-supported>
       </filter>
       <!-- The definition of the Root Spring Container shared by all Servlets
              and Filters -->
       <context-param>
              <param-name>contextConfigLocation</param-name>
              <param-value>classpath:config/*-context.xml</param-value>
       </context-param>
       <!-- Creates the Spring Container shared by all Servlets and Filters -->
       <listener>
              <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
       </listener>
       <!-- Processes application requests -->
       <servlet>
              <servlet-name>appServlet</servlet-name>
              <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
              <init-param>
                     <param-name>contextConfigLocation</param-name>
                     <!-- <param-value>/WEB-INF/spring/appServlet/servlet-context.xml</param-value> -->
                     <param-value>classpath:web-context.xml</param-value>
              </init-param>
              <load-on-startup>1</load-on-startup>
       </servlet>
       <servlet-mapping>
              <servlet-name>appServlet</servlet-name>
              <url-pattern>/</url-pattern>
       </servlet-mapping>
</web-app>

 

 

 

 

[RoleTestControllerTest.java]

자 그러면 내가 의도한 대로 바인딩이 되는지 테스트 코드를 돌려서 확인을 진행해 본다.

 

Controller의 테스팅은 다음의 URL 을 참조한다.

https://honeyinfo7.tistory.com/134

 

스프링 컨트롤러 테스트(MockHttpServletRequest, MockHttpServletResponse)

Service나 DAO 같은 비즈니스 로직과 달리 Controller의 경우 테스트 코드를 만들기가 되게 애매하다. 이유는 Service나 Dao와 달리 Controller의 경우 Http Request나 Response등의 요청이 필요하고 또한 결과값..

honeyinfo7.tistory.com

@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration(locations = { "classpath:config/*-context.xml",
		"classpath:web-context.xml" })
public class RoleTestControllerTest {

	@Test
	public void test() throws ServletException, IOException {
		ConfigurableDispatcherServlet servlet = new ConfigurableDispatcherServlet();
		servlet.setLocations("classpath:config/*-context.xml",
				"classpath:web-context.xml");
		servlet.init(new MockServletConfig("appServlet"));

		MockHttpServletRequest req = new MockHttpServletRequest("GET", "/roleparamtest");
		req.addParameter("role", "1");
		MockHttpServletResponse res = new MockHttpServletResponse();
		servlet.service(req, res);
		
		ModelAndView mav = servlet.getModelAndView();
		String role= (String) mav.getModel().get("role");
				
		assertThat("ADMIN", is(role));
		
		req=new MockHttpServletRequest("GET", "/roleobjtest");
		req.addParameter("id", "1");
		req.addParameter("name", "주현태");
		req.addParameter("role", "2");
		req.addParameter("age","30");
		servlet.service(req, res);
		
		mav = servlet.getModelAndView();
		User user= (User) mav.getModel().get("user");
		assertThat("DEFAULT", is(user.getRole().name()));
		
		
	}
}

[결과]

 

,,,중간에 치명적인 실수로 인해 버그를 잡느라 포스팅 하는데까지 4시간이나 소요되었지만 그럼으로써 이제 ConversionService는 확실하게 사용할 수 있을 듯 하다..

 

 

버그잡으면서 배운것)

@ModelAttribute 가 첨부되려면 Default 생성자가 있어야 하는 것으로 보인다. 단, @RequestParam은 필요없다.