[Java] 41. Web Spring framework의 Controller에서 ajax의 요청시 json을 반환하는 방법


Study/Java  2021. 6. 4. 16:25

안녕하세요. 명월입니다.


이 글은 Web Spring framework의 Controller에서 ajax의 요청시 json을 반환하는 방법에 대한 글입니다.


이전 글에서 Web Spring에서 Controller를 다루는 방법에 대해서 설명했습니다.

link - [Java] 40. Web Spring framework에서 Controller를 다루는 방법


Controller이란 웹 브라우저에서 요청이 오면 실행되는 함수를 찾아서 실행하여 다시 웹 브라우저로 응답하는 역할까지 합니다.

그런데 여기서 Controller 메서드는 기본적으로 String 값을 리턴하게 되어 있습니다. 이 String값은 view 파일명을 탐색하기 위해 작성하게 되어 있습니다.


그러나 우리가 ajax로 웹 페이지의 html의 데이터를 받는 경우도 없는 것은 아니지만 보통은 json 형식으로 된 데이터 값을 리턴을 받습니다.

그렇다면 view의 html 파일을 매핑하면 안되겠네요.. 반대로 이 return 값을 데이터 값을 받아야할 필요가 있습니다. 서블릿처럼 말입니다.

return 값을 null이나 문자가 없는 String을 리턴하고 파라미터로 받은 HttpServletResponse의 getWriter 함수를 사용하는 건 어떨까요? 가능합니다.

그런데 그렇게 사용하게 되면 Spring framework에서 servlet 문법을 사용하는 느낌이 드네요.

좀 더 Spring처럼 사용하는 방법을 소개하겠습니다.


이전에 제가 Spring 환경 설정할 때, mvc-config.xml로 설정한다고 설명한 적이 있습니다.

link - [Java] 39. Spring web framework를 이용해서 웹 서비스 프로젝트를 만드는 법


여기서 ajax를 위한 설정을 추가하도록 합시다.

먼저 mvc-ajax-config.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:mvc="http://www.springframework.org/schema/mvc"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/mvc
                           http://www.springframework.org/schema/mvc/spring-mvc.xsd
                           http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/context
                           http://www.springframework.org/schema/context/spring-context.xsd">
  <!-- 컨트럴러 패키지 설정 -->
  <context:component-scan base-package="controller.ajax" />
  <!-- 웹 브라우저로 응답하는 데이터 타입 UTF-8 타입-->
  <mvc:annotation-driven>
    <mvc:message-converters>
      <bean class="org.springframework.http.converter.StringHttpMessageConverter">
        <property name="supportedMediaTypes">
          <list>
            <!-- json타입으로 반환하기 때문에 설정 -->
            <value>application/json;charset=UTF-8</value>
          </list>
        </property>
      </bean>
    </mvc:message-converters>
  </mvc:annotation-driven>
  <!-- view 폴더 설정 -->
  <!-- ajax의 경우는 view 파일이 필요가 없기 때문에 값을 비운다 -->
  <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix" value="" />
    <property name="suffix" value="" />
  </bean>
</beans>

위 xml를 web.xml에 연결하도록 하겠습니다.

<servlet>
  <servlet-name>dispatcherServletAjax</servlet-name>
  <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  <init-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>/WEB-INF/mvc-ajax-config.xml</param-value>
  </init-param>
  <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
  <servlet-name>dispatcherServletAjax</servlet-name>
  <url-pattern>*.json</url-pattern>
</servlet-mapping>

여기서 이전의 컨트럴러 패키지는 controller 였습니다만 ajax 컨트럴러 패키지는 controller.ajax입니다.

controller.ajax 패키지 안에 클래스를 생성합니다.

package controller.ajax;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;

// 컨트롤 표시하는 어트리뷰트
@Controller
public class Test {
  // 클래스에서 분류된 아래의 주소를 탐색한다.
  // 이 예제에서는 /test.json의 GET방식으로 호출된다.
  @RequestMapping(value = "test.json", method = RequestMethod.GET)
  // @ResponseBody를 사용하면 view 파일을 탐색하는 것이 아니고 return 되는 값이 반환한다.
  @ResponseBody
  // 이 매핑 함수는 view와 매핑되는 것이 아니기 때문에 ModelMap은 필요없다.
  public String index(HttpSession session, HttpServletRequest req, HttpServletResponse res) {
    // json 타입의 String 문자를 리턴한다.
    return "{\"data\":\"hello world\"}";
  }
}

여기서 중요한 부분은 @ResponseBody 어트리뷰트입니다. @ResponseBody를 설정하면 view를 탐색하지 않고 return 값을 바로 응답합니다.


그럼 이전의 index.jsp를 조금 수정해서 확인해 보겠습니다.

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html>
  <html>
  <head>
    <meta charset="UTF-8">
    <title>Insert title here</title>
  </head>
  <body>
    <!-- controller에서 ModelMap으로 표시되는 데이터 -->
    ${Data}
    <!-- ajax의 값을 담을 div 태그 -->
    <div id="result"></div>
    <script>
      // 브라우저 로딩이 끝나면 발생하는 이벤트
      window.onload = function() {
        // 비동기 XMLHttpRequest 객체 생성
        var xhr = new XMLHttpRequest();
        // 상태가 변할 때의 이벤트
        xhr.onreadystatechange = function() {
          // 수신이 완료가 되면
          if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
            // 결과를 String에서 javascript 구조체로 변환
            var result = JSON.parse(xhr.response);
            // result 태그에 표시
            document.querySelector("#result").innerHTML = result.data;
          }
        }
        // url 설정 GET방식으로 비동기(async)로 처리한다.
        xhr.open("GET", "test.json", true);
        // 송신
        xhr.send();
      }
    </script>
</body>
</html>

위 결과를 보시면 test.json으로 요청을 하게 되면 브라우저에서 json 데이터를 수신하여 처리한 결과가 브라우저에 표시가 되네요.


우리가 비동기 ajax를 꼭 json 타입으로 받지는 않습니다. xml 타입으로도 받을 수 있고 byte데이터나 base64 타입으로도 받을 수 있습니다.

그러나 위 설정에서 mvc-ajax-config.xml에서 application/json으로 설정을 해버렸네요.

그럼 xml에서 설정하면 context-type을 변경할 수 없을까?


기본적으로 우리는 호출 함수에서 반환 값은 String으로 설정했습니다.

String이 아닌 byte[]형식으로 반환할 수도 있고 context-type에 따른 json 타입이 아닌 octet-stream타입이나 xml타입으로도 반환할 수 있습니다.

이러한 설정을 ResponseEntity 클래스를 반환해서 반환 데이터를 설정할 수 있습니다.

package controller.ajax;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;

// 컨트롤 표시하는 어트리뷰트
@Controller
public class Test {
  // 클래스에서 분류된 아래의 주소를 탐색한다.
  // 이 예제에서는 /test.json의 GET방식으로 호출된다.
  // 호출하는 produces의 구분으로 호출 함수를 구분한다.
  @RequestMapping(value = "test.json", method = RequestMethod.GET, produces = {"application/JSON"})
  // @ResponseBody를 사용하면 view 파일을 탐색하는 것이 아니고 return 되는 값이 반환한다.
  @ResponseBody
  public String index(HttpSession session, HttpServletRequest req, HttpServletResponse res) {
    // json 타입의 String 문자를 리턴한다.
    return "{\"data\":\"hello world\"}";
  }
  // 클래스에서 분류된 아래의 주소를 탐색한다.
  // 이 예제에서는 /test.json의 GET방식으로 호출된다.
  // 호출하는 produces의 구분으로 호출 함수를 구분한다.
  @RequestMapping(value = "test.json", method = RequestMethod.GET, consumes = {"application/XML"})
  // @ResponseBody를 사용하면 view 파일을 탐색하는 것이 아니고 return 되는 값이 반환한다.
  @ResponseBody
  public ResponseEntity<String> index1(HttpSession session, HttpServletRequest req, HttpServletResponse res) {
    // 응답 해더를 설정하기 위한 인스턴스 생성
    HttpHeaders header = new HttpHeaders();
    // ContentType을 application/XML로 설정
    header.setContentType(MediaType.APPLICATION_XML);
    // ResponseEntity 클래스로 리턴한다.
    return new ResponseEntity<String>("<?xml version=\"1.0\" encoding=\"UTF-8\"?><data>xml</data>", header, HttpStatus.OK);
  }
}
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Insert title here</title>
  </head>
  <body>
    <!-- controller에서 ModelMap으로 표시되는 데이터 -->
    ${Data}
    <!-- ajax의 값을 담을 div 태그 -->
    <div id="result"></div>
    <!-- xml의 값을 담을 div 태그 -->
    <div id="result1"></div>
    <script>
      // ajax를 보내기 위한 함수
      function ajax(obj) {
        // 비동기 XMLHttpRequest 객체 생성
        let xhr = new XMLHttpRequest();
        // 상태가 변할 때의 이벤트
        xhr.onreadystatechange = function() {
          if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
            // callback 함수 호출
            obj.done.call(this, xhr);
          }
        }
        // url 설정 GET방식으로 비동기(async)로 처리한다.
        xhr.open("GET", obj.url, true);
        // 요청 Content-Type설정
        xhr.setRequestHeader("Content-Type", obj.type);
        // 송신
        xhr.send();
      }
      // 브라우저 로딩이 끝나면 발생하는 이벤트
      window.onload = function() {
        // ajax 함수 호출
        ajax({
          url : "test.json", // url
          type : "application/json", // contentType 설정
          done : function(res) { // 요청이 완료되면
            // 결과를 String에서 javascript 구조체로 변환
            let result = JSON.parse(res.response);
            // result 태그에 표시
            document.querySelector("#result").innerHTML = result.data;
          }
        });
        // ajax 함수 호출
        ajax({
          url : "test.json", // url
          type : "application/xml", // contentType 설정
          done : function(res) {
            // 결과를 xml 구조체로 변환
            let result = res.responseXML;
            // result 태그에 표시
            document.querySelector("#result1").innerHTML = result.getElementsByTagName("data")[0].nodeName;
          }
        });
      }
    </script>
  </body>
</html>

결과는 두번의 test.json을 호출했습니다.

그 중 하나는 application/xml로 요청을 했고, 응답도 application/xml로 왔습니다. 당연히 결과도 xml로 왔기 때문에 화면에 data 결과가 표시되었습니다.


여기까지 Web Spring framework의 Controller에서 ajax의 요청시 json을 반환하는 방법에 대한 글이었습니다.


궁금한 점이나 잘못된 점이 있으면 댓글 부탁드립니다.