본문 바로가기

프로그래밍/Spring

Xplatform과 Spring Framework 연동 템플릿으로 보는 HandlerMethodArgumentResolver와 ViewResolver (4) - Spring Controller에서 하려는 것

저번 글에서는 HttpServeletRequest 클래스의 Wrapper 클래스인 HttpRequestWrapper 클래스의 필요성과 제작 및 설정에 대한 설명을 했다. 이번에는 HttpRequestWrapper 클래스를 통해 전달된 Xplatform의 데이터를 Spring에서 어떻게 사용할 수 있는지에 대해 살펴보도록 하자.

 

Spring MVC 에서는 Web Client가 전달한 데이터를 서버측에서 받아들일때 서버측에서 사용하기 좋은 형태의 타입으로 변환할 수 있다. 이때 사용되는 방법으로 2가지가 있다.

 

  1. HttpMessageConverter
  2. HandlerMethodArgumentResolver

 

HttpMessageConverter 와 HandlerMethodArgumentResolver 모두 Spring에서 제공하는 인터페이스이며 이 인터페이스를 구현하면 된다. 또한 Spring에서도 이들 인터페이스를 구현한 클래스를 제공해주고 있다. 그러나 이 2개의 인터페이스는 기능과 동작에서 차이점이 존재한다. 

 

HttpMessageConverter의 경우는 사용에 있어서 제한적인 부분이 있다. HttpMessageConverter를 사용해야 하는가에 대한 결정을 header의 MIME Type  정보를 통해서 결정이 된다. 특정 HttpMessageConverter를 사용하려면 그에 맞춰서 MIME Type 정보를 설정해주어야 하는데 이것이 사용에 있어서 불편한 점이 있을 수 있다. 이러한 상황은 Request 뿐만 아니라 Response에서도 적용된다. 즉 Response header에 MIME Type 정보를 설정해주어야 그거에 맞는 HttpMessageConverter가 사용되어서 Write 작업을 할 수 있게 된다. 이에 반해 HandlerMethodArgumentResolver 인터페이스는 MIME 정보를 사용하지는 않지만 어떤 HandlerMethodArgumentResolver 인터페이스를 사용해야 할 지를 판단하는 기준이 Request와 Response에 있는 것이 아니라 Controller 클래스에 있는 method의 parameter 정보를 가지고 판단하기 때문에 Spring Context에 등록되어 있는 모든 HandlerMethodArgumentResolver 인터페이스 구현 객체에 parameter 정보가 전달되는 방식으로 동작되게 된다.  parameter 정보를 받아서 해당 구현 클래스가 이를 사용할 수 있는지를 판단해서 진행하게 된다.

 

또 한가지의 차이점은 HttpMessageConverter의 경우는 특정 클래스 1개만을 대상으로 진행된다. 예를 들어 A란 클래스와 B란 클래스에 대해 HttpMessageConverter의 경우 A 클래스와 관련된 HttpMessageConverter 인터페이스 구현 클래스를 만들고, B 클래스와 관련된 HttpMessageConverter 인터페이스 구현 클래스를 만들어야 하는..2개의 클래스를 만들어야 한다. 이러한 방법은 변환 대상이 되는 클래스의 갯수가 적을때는 효과적이지만 그 갯수가 많거나 갯수를 예측할 수 없을때는 사용하기엔 한계가 있는 방법이다. 이에 반해 HandlerMethodArgumentResolver의 경우는 변환 대상 클래스를 지정하지 않는다. Controller의 메소드에 return이 되는 클래스가 Object 클래스이기 때문에 어떠한 클래스로든 적용이 가능해진다. 이러한 차이점때문에 여기서는 HandlerMethodArgumentResolver 인터페이스를 구현한 클래스를 만들어서 사용하게 되었다. 

 

이 연재를 시작하는 시점에 잠깐 언급했지만 HandlerMethodArgumentResolver를 이용해서 구체적으로 어떤 결과물이 나오는 것을 목표로 하는 것인지 한번 더 정리하도록 하겠다. 앞서 얘기했던 사전지식 내용을 통해 Xplatform은 DataSet과 Variable 이란 2가지 형태의 데이터 개념이 있다고 얘기했다. Xplatform Client는 자신이 서버에 보내고자 하는 데이터들을 DataSet과 Variable을 적절하게 사용하여 보내지게 될텐데 Spring Controller에서 이를 액면 그대로 받아들이는 경우 서버 코드가 클라이언트에 종속이 되는 상황이 벌어진다고 얘기했다. 그래서 Spring Controller 에서는 Xplatform Client가 보내는 DataSet과 Variable을 다음과 같은 형식으로 받아들이려 한다. 다음부터 언급하는 것은 Spring Controller의 메소드 예시이다.

 

public void list1(Model model
					, SampleVO sampleVO
					, @RequestVariable Map<String, Object> requestVariableMap
					, HttpServletRequest httpServletRequest) {
	...
}

public void list2(@RequestVariable SampleVO requestVariableSampleVO
					, @RequestVariable(name="firstIndex") int firstIndex) {
	...
}

public void list3(@RequestVariable(name="recordCountPerPage") String recordCountPerPage) {
	...
}

public void modify1(@RequestDataSetList DataSetList dataSetList) {
	...	
}

public void modify2(@RequestDataSet(name="ds_input") List<SampleVO> dataSet
					, @RequestDataSetList DataSetList dataSetList) {
	...	
}

public void modify3(@RequestDataSet(name="ds_input")ArrayList<SampleVO> dataSet) {
	...	
}

public void modify4(@RequestDataSet(name="ds_input")List<Map<String, Object>> dataSet) {
	...	
}

public void modify5(@RequestDataSet(name="ds_input")Set<SampleVO> dataHashSet) {
	...	
}

 

위의 코드들은 Spring Controller 에서 url과 매핑되는 메소드를 구현할때의 예시를 들은 것이다. @RequestMapping 같은 URL 매핑 어노테이션은 지금부터 하게 되는 설명과는 무관한 사항이라 생략한것이니 오해가 없길 바란다. 위의 예시들을 보면 @RequestVariable, @RequestDataSetList, @RequestDataSet 이렇게 3종류의 어노테이션이 사용되고 있다. 이 어노테이션들에 대한 설명을 먼저 한 뒤에 메소드 예시들에 대한 설명을 이어가도록 하겠다.

 

● @RequestVariable

 

Xplatform Client가 보내는 Variable을 이 어노테이션 뒤에 설정되는 변수에 받아들이게 된다. 어노테이션의 name 속성에 해당 Variable의 이름을 지정해주면 이름에 대한 Variable 값을 변수에 설정해주게 된다. name을 주지 않게 되면 전체 Variable을 읽어오는 개념으로 Map<String, Object> 형태의 변수에 Xplatform Client가 전달하는 모든 Variable을 저장하게 된다. Map으로 받을 경우 key 부분에는 Variable의 이름이 설정된다. Map이 아니라 VO 타입으로 받을 경우엔 해당 VO의 멤버변수명과 Variable의 변수이름이 같을 경우 해당 변수에 값이 설정된다. 받아들일수 있는 변수 타입은 HandlerMethodArgumentResolver 설명시에 별도로 더 언급하도록 하겠다.

 

● @RequestDataSetList

 

Xplatform Client가 보내는 DataSetList를 이 어노테이션 뒤에 설정되는 변수에 받아들이게 된다. DataSetList 는 이전에 작성했던 글에서 언급한 구조를 생각해보면 자바에서 이것을 받아들일 Native 한 구조가 없다(여기서 Native 한 구조를 언급한 것은 별도 가공 없이 자바가 제공하는 데이터 구조체에서 이 DataSetList와 match 되는게 없다는 것을 의미하는 것이지 자바 변수로 받아들이는 방법이 아주 불가능하진 않다) 부연설명에서 처럼 하지 않은 것은 이런 구조로 만드는거 보단 그냥 Xplatform이 제공하는 DataSetList 클래스로 받아들이는 것이 더 낫다고 판단했다. 이것 또한 상세적인 코드 내용은 HandlerMethodArumentResolver 설명시에 언급하도록 하겠다

 

● @RequestDataSet

 

Xplatform Client가 보내는 DataSet을 이 어노테이션 뒤에 설정되는 변수에 받아들이게 된다. 어노테이션의 name 속성에 해당 DataSet의 이름을 지정해주면 이름에 대한 DataSet 을 변수에 설정해주게 된다. 이전에 작성했던 글에서 DataSet 구조를 생각해보면 이것은 단일 타입의 객체 여러개를 저장하는 자바 데이터 타입으로 변환이 가능하다고 생각할 수 있다. 그래서 자바의 Collection 인터페이스를 구현한 클래스 객체를 이용해서 단일 타입의 객체 여러개를 저장하도록 했다. 더 자세한 내용은 HandlerMethodArgumentResolver 설명시에 언급하도록 하겠다

 

지금까지 어노테이션에 대한 설명을 마치도록 하겠다. 다음은 위에서 언급한 Controller 메소드 예시에 대한 설명을 하도록 하겠다. 위에서 언급한 어노테이션 설명과 다음의 설명을 알아둔 뒤에 이 메소드에 대한 설명을 진행하도록 하겠다. Xplatform에서는 서버와의 통신을 위해 transaction 이란 메소드를 사용하게 된다. 이 메소드를 Xplatform에서 다음과 같이 사용했다고 가정해보자

 

var strSvcid 		= "selectSvc";
var strController 	= "egovSampleSelect.do?id=50&firstIndex=0&recordCountPerPage=10®User=terry";
var strInputDs 		= "ds_input=ds_list:U";
var strOutputDs 	= "ds_list=ds_output";
var strParam 		= "firstIndex=5 recordCountPerPage=20 id=100 regUser=chang";
var strFnCallback 	= "fn_callBack";
transaction(svcid, 
				"svc::" + strURL, 
				inputDs, 
				outputDs,
				params,
				"fn_callBack");

 

이 글은 Xplatform 개발을 하는 사람들은 다 아실만한 내용이겠지만 순수 자바 서버 개발자는 이러한 코드를 알지 못할수 있다. 그러나 이 글을 보는 서버개발자는 Xplatform 개발을 하는 사람과 같이 일하기때문에 이 코드가 무엇을 의미하는지 물어볼수는 있을꺼라 생각한다. 그래서 여기서는 구체적으로 언급은 하지않겠다. 그러나 설명하는 과정에 있어서 이 코드에 대한 설명이 필요할 시에는 부분부분 설명하도록 하겠다. 구체적인 설명을 하기에 앞서 미리 설명을 하자면 strController 변수에는 접속하고자 하는 서버 URL을 설정한다. 여기서는 일부러 Get 방식으로 변수를 좀더 붙여넣었는데 왜 이렇게 했는지는 좀 이따가 언급하도록 하겠다. strInputDs에는 입력 DataSet에 대한 설정을 진행한다. 여기서는 Xplatform Client에서 ds_list 란 이름의 DataSet을 서버에서는 ds_input으로 매치해서 읽어올 수 있다. strOutputDs는 출력 DataSet에 대한 설정을 진행한다. ds_output 이란 이름의 서버 DataSet을 Xplatform Client의 ds_list 에 넣어지게 해서 Xplatform Client에서는 ds_list로 읽어올 수 있다. strParam 변수에 설정하는 것은 Xplatform 에서 설정하는 Variable 변수를 설정하는 것으로 이름=값 형태를 가지고 있으며 여러개를 설정할 경우 공백을 두어 여러개를 설정할 수 있다. 이러한 것을 알고 다음의 Controller 메소드 예시를 보도록 하자

 

 public void list1(Model model, SampleVO sampleVO, @RequestVariable Map<String, Object> requestVariableMap, HttpServletRequest httpServletRequest)

 

Spring 개발자라면 Model이나 HttpServletRequest로 받는게 어떤것인지는 알것이니 이 부분에 대한 설명은 넘어가도록 하겠다. SampleVO 클래스 객체로 받는 것은 URL을 통해 넘어오는 변수들에 대한 값들을 SampleVO 란 클래스 객체로 받는 것이다. 일반적인 web page 방식에서 <form> 태그로 GET, POST 방식을 설정하는 것과는 달리 Xplatform Client 에서는 GET, POST 방식을 지정해서 전달하는 방법이 없기 때문에 기본적으로 url에 querystring을 통해 전달하는 GET 방식을 사용하게 된다(POST 방식의 전송이 지원 안되는 것은 아니다. 다만 여기 템플릿에서는 Xplatform 샘플 코드 기반으로 개발하다보니 POST로의 전송이 존재하질 않아서 여기서는 구현하지 않았다). 그래서 SampleVO 클래스 객체로 받아지는 것은 querystring으로 전달되는 값들을 받게 된다. querystring에 있는 변수명이 SampleVO 클래스의 멤버변수와 매핑되어서 값이 설정된다. HttpServletRequest 객체를 받는 것은 현재 요청 자체를 받는 것이 된다. 여기까지는 기존 Spring 에서의 변수값 전달과 동일하다. 이제껏 해왔던 Spring MVC에서 프로그래밍 했던 것과 차이가 있는 부분은 @RequestVariable Map<String, Object> requestVariableMap 이 부분이다. 이것은 기존의 Spring MVC에서 사용하던게 아니라 우리가 Xplatform 연동을 위해 새로이 만들 HandlerMethodArgumentResolver 가 requestVariableMap 변수에 값을 할당할 것이다. 아까 Xplatform transaction 메소드 설명시에 Variable을 설정하는 변수인 strParam 변수에 대한 설명과 @RequestVariable 어노테이션에서 Map으로 받는 내용에 대해 같이 묶어 생각해보면 이 변수에 무엇이 들어갈것인지 대강 짐작할 수 있다. 즉 strParam으로 전달되는 key=value 값들이 Map에 <key, value> 형태로 저장이 되는 것이다. 이렇게 Map으로 받은 Variable들을 변수명을 key로 잡고 해당 값을 꺼내와서 원하는 작업을 할 수 있게 된다

 

 public void list2(@RequestVariable SampleVO requestVariableSampleVO, @RequestVariable(name="firstIndex") int firstIndex)

 

list2 메소드에서는 파라미터로 @RequestVariable 어노테이션만을 사용하고 있다. 근데 사용 방법이 약간 다르다. 바로 위에서 사용했던 방법과도 다른 부분이 있다. 위에서는 @RequestVariable을 Map으로 받았지면 여기서는 특정 클래스로 받았다. 특정 클래스로 받을 경우에는 클래스의 멤버변수 이름과 Variable 이름이 같을 경우 해당 멤버변수에 값이 설정된다. 그러면 예를 들어 RequestVariable에 사용된 클래스의 멤버변수 이름이 Variable에 사용된 변수 이름에 있지 않거나 거꾸로 Variable에 사용된 변수 이름이 클래스의 멤버변수 이름으로 있지 않을 경우엔 어떻게 동작하나? 아무 변화도 없다. 클래스의 멤버변수 이름이 Variable 변수 이름에 존재하지 않으면 해당 클래스의 멤버변수에는 아무 값도 할당되지 않는다. 역으로 Variable 변수 이름은 존재하지만 이에 대한 해당 클래스의 멤버변수 이름이 없을 경우에도 아무런 할당 작업 없이 그냥 넘어가게 된다. 이와는 달리 @RequestVariable 어노테이션에 name 속성에 Variable 이름을 주어 해당 Variable 값을 가져오는 경우에는 단일 형태의 값을 가져오는 것이기 때문에 멤버변수들이 1개 이상으로 구성되어 있는 특정 클래스로 받는 것이 아니라 java에서 제공하는 데이터형으로만 받게 된다. 위의 예에서는 firstIndex 란 이름의 Variable을 java의 int 타입으로 받게 된다.

 
● public void list3(@RequestVariable(name="recordCountPerPage") String recordCountPerPage) 

 

list3 메소드에서도 파라미터로 @RequestVariable 어노테이션만을 사용하고 있다. recordCountPerPage 란 이름의 Variable 을 java의 String 타입으로 받게 된다. 위에서도 이와 같은 케이스의 사용 방법에 대해 설명했으니 구체적인 설명은 안하겠다. 

 

● public void modify1(@RequestDataSetList DataSetList dataSetList) 

 

modify1 메소드에서는 @RequestDataSetList 어노테이션을 사용했다. 위에서도 설명했지만 여러개의 DataSet이 들어있는 DataSetList 객체를 받을 때 이 어노테이션을 사용하게 된다.

 

● public void modify2(@RequestDataSet(name="ds_input") List<SampleVO> dataSet, @RequestDataSetList DataSetList dataSetList)

 

modify2 메소드에서는 @RequestDataSet과 @RequestDataSetList 어노테이션을 사용했다. @RequestDataSetList 어노테이션에 대한 설명은 바로 위에서 얘기했기 때문에 더는 언급하지 않도록 하겠다. @RequestDataSet 어노테이션의 name 속성에 ds_input 을 주어 ds_input 이란 이름의 DataSet을 가져오게 된다. 위에서 @RequestDataSet 어노테이션을 설명했을때 DataSet을 Collection 인터페이스를 구현한 객체로 받는다고 했다. 그래서 Collection 인터페이스를 상속받은 List 인터페이스를 구현한 객체로 받게 된다. List에 들어가는 클래스 객체로 여기서는 SampleVO 클래스 객체로 설정되어 있다. DataSet을 구성하는 row 갯수 만큼 SampleVO 클래스 객체가 만들어져서 List 객체에 들어가게 된다. 그리고 해당 row와 매치되는 SampleVO의 객체에서 해당 column 이름과 SampleVO의 멤버변수 이름이 같으면 DataSet의 row, column에 해당되는 값이 멤버변수 값으로 할당된다. 여기서는 List 인터페이스를 구현한 클래스(ex: ArrayList, LinkedList 등)로 설정한게 아니라 List 인터페이스로 되어 있는데 List 인터페이스를 사용할 경우 HandlerMethodArgumentResolver 에서는 ArrayList 클래스 객체로 만들어준다. 구체적인 내용에 대해서는 HandlerMethodArgumentResolver에 대한 설명을 할때 좀더 언급하겠다

 

● public void modify3(@RequestDataSet(name="ds_input") ArrayList<SampleVO> dataSet)

 

modify3 메소드에서도 @RequestDataSet 어노테이션을 사용했다. modify2 메소드에서는 List 인터페이스를 사용한것과는 달리 List 인터페이스를 구현한 클래스인 ArrayList 클래스를 사용했다.

 

● public void modify4(@RequestDataSet(name="ds_input") List<Map<String, Object>> dataSet)

 

modify4 메소드에서도 @RequestDataSet 어노테이션을 사용했다. modify2, modify3 메소드와는 달리 List 객체에 들어가는 객체로 특정 클래스가 아니라 Map을 사용했다. Map을 사용할 경우 DataSet의 row 갯수 만큼 Map 객체가 만들어지게 된다. 그리고 Map 객체를 만들때 DataSet의 column 이름을 Map의 key로 사용해서 Map 객체 하나를 만들게 된다. 이때 만드는 Map은 HashMap 클래스 객체를 만들게 된다. 이것 또한 구체적인 내용은 HandlerMethodArgumentResolver에 대한 설명을 할때 좀더 언급하겠다.

 

● public void modify5(@RequestDataSet(name="ds_input") Set<SampleVO> dataHashSet) 

 

modify5 메소드에서도 @RequestDataSet 어노테이션을 사용했다. 지금까지 설명했던 @RequestDataSet 어노테이션에서는 List 인터페이스 또는 List 인터페이스 구현 클래스를 사용한것과는 달리 Set 인터페이스를 사용했다. Set 인터페이스 또한 Collection 인터페이스를 상속받은 인터페이스이기 때문에 사용이 가능하다. Set 인터페이스를 사용할 경우엔 HashSet 클래스를 만든뒤에 여기에 Generic으로 설정한 클래스 객체를 만들어 넣는다. 

 

지금까지 Xplatform에서 전달되는 DataSet 및 Variable을 Controller 에서 HandlerMethodArgumentResolver 를 통해 어떠한 형태로 받을지에 대한 설명을 했다. 다음글에서는 HandlerMethodArgumentResolver 에 대한 구체적인 설명을 진행하도록 하겠다.

 

 1. Xplatform과 Spring Framework 연동 템플릿으로 보는 HandlerMethodArgumentResolver와 ViewResolver (1) - 개요

 2. Xplatform과 Spring Framework 연동 템플릿으로 보는 HandlerMethodArgumentResolver와 ViewResolver (2) - 사전지식

 3. Xplatform과 Spring Framework 연동 템플릿으로 보는 HandlerMethodArgumentResolver와 ViewResolver (3) - HttpServletRequestWrapper

 4. Xplatform과 Spring Framework 연동 템플릿으로 보는 HandlerMethodArgumentResolver와 ViewResolver (4) - Spring Controller에서 하려는 것

 5. Xplatform과 Spring Framework 연동 템플릿으로 보는 HandlerMethodArgumentResolver와 ViewResolver (5) - HandlerMethodArgumentResolver 분석(1)

 6. Xplatform과 Spring Framework 연동 템플릿으로 보는 HandlerMethodArgumentResolver와 ViewResolver (6) - HandlerMethodArgumentResolver 분석(2)

 7. Xplatform과 Spring Framework 연동 템플릿으로 보는 HandlerMethodArgumentResolver와 ViewResolver (7) - HandlerMethodArgumentResolver 분석(3)

 8. Xplatform과 Spring Framework 연동 템플릿으로 보는 HandlerMethodArgumentResolver와 ViewResolver (8) - HandlerMethodArgumentResolver 분석(4)

 9. Xplatform과 Spring Framework 연동 템플릿으로 보는 HandlerMethodArgumentResolver와 ViewResolver (9) - XplatformView 분석

 10. Xplatform과 Spring Framework 연동 템플릿으로 보는 HandlerMethodArgumentResolver와 ViewResolver (10) - 예외처리