ContentNegotiatingViewResolver 활용 : 하나의 RequestMapping 으로 JSP, JSON, JSONP 처리하기

뭔가를 맨들다 보면 데이터를 가져와서 대부분 JSP 를 이용해서 랜더링을 하게 되지만 똑같은 데이터를 ajax 호출을 통해 json 형식으로 받고 싶은 경우도 종종 있다.

그리고 JSONP 형식으로 같은 도메인이 아닌 다른 도메인으로 데이터를 제공하고 싶은 경우도 있다.

고럴때 ContentNegotiatingViewResolver 를 활용하면 하나의 리퀘스트 핸들러를 맹글어 놓고 맨 뒤에 URL 만 살짝 바꾸면 URL 에 따라 다른 View 를 이용해서 랜더링 시킬 수 있다.

예를 들어 

http://127.0.0.1:8080/test/view                  요럴땐 jsp 로 랜더링
http://127.0.0.1:8080/test/view.json           요럴땐 json 형식으로 랜더링
http://127.0.0.1:8080/test/view.jsonp         요럴땐 jsonp 형식으로 랜더링~

또 약간의 노력을 더해주면 xml 형식이나 rss 형식으로도 랜더링 시킬 수 있다.

죠런 기능을 구현하기 위해서 ContentNegotiatingViewResolver 를 사용하도록 설정해 주고 필요에 따라 AbstractView 클래스를 구현해서 원하는 View를 맹글면 된다.



스프링 설정파일
<bean class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver">
	<!-- ViewResolver 우선순위 설정 -->
	<property name="order" value="1" />
	<property name="mediaTypes">
		<!-- 맵핑될 확장자 정의 -->
		<map>
			<entry key="json" value="application/json" />
			<entry key="jsonp" value="javascript/jsonp" />
		</map>
	</property>

	<property name="defaultViews">
		<list>
			<!-- JSON 요청을 처리할 뷰 -->
			<bean class="org.springframework.web.servlet.view.json.MappingJacksonJsonView"/>
			
			<!-- JSONP 요청을 처리할 뷰 -->
			<bean class="stove99.views.JSONPView">
				<property name="contentType" value="javascript/jsonp"/>
			</bean>
		</list>
	</property>
	<property name="ignoreAcceptHeader" value="true" />
</bean>

<!-- 맵핑되는 확장자가 없을때 JSP 로 뷰 처리 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
	<property name="order" value="2" />
	<property name="prefix" value="/WEB-INF/views/"/>
	<property name="suffix" value=".jsp"/>
</bean>
mediaTypes에 정의된 entry 들은 URL 맨 뒤에 붙은 확장자와 mediaType 을 설정한다. 여기에서 설정된 mediaType 정보를 바탕으로 아래쪽 defaultViews 목록에 정의된 View 들 중에서 랜더링 가능한 View 를 찾아서 그 View 로 랜더링 시킨다. 뭐 설명이 허접하긴 하지만 내가 이해한 바로는 그렇다.



stove99.views.JSONPView 클래스 소스
package stove99.views;

import java.io.Writer;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.codehaus.jackson.map.ObjectMapper;
import org.springframework.web.servlet.view.AbstractView;

public class JSONPView extends AbstractView{

	@Override
	protected void renderMergedOutputModel(Map<String, Object> model, 
                                               HttpServletRequest request,
                                               HttpServletResponse response) throws Exception {
		String callback = request.getParameter("callback");
		ObjectMapper om = new ObjectMapper();
		String json = om.writeValueAsString(model);
		
		Writer out = response.getWriter();
		out.append(callback).append("(").append(json).append(")");
	}
}


컨트롤러 소스
package stove99.controller;

import java.util.HashMap;
import java.util.Map;

import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
@RequestMapping("/test/*")
public class TestController {

	@RequestMapping
	public void view(ModelMap model) {
		Map<String, String> data = new HashMap<String, String>();
		data.put("data1", "value1");
		data.put("data2", "value2");
		data.put("data3", "value3");

		model.addAttribute("model", data);
	}
}

테스트 결과 화면

JSP 로 랜더링


JSON 으로 랜더링


JSONP로 랜더링


ContentNegotiatingViewResolverTest.war