관리 메뉴

거니의 velog

(11) 스프링 REST API 사용하기 2 본문

Java_Spring Framework part2

(11) 스프링 REST API 사용하기 2

Unlimited00 2023. 11. 20. 08:50

3. @PathVariable 사용하기

* @PathVariable을 사용하면 브라우저에서 요청 URL로 전달된 매개변수를 가져올 수 있다.

[TestController.java]

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

	@RequestMapping(value = "/notice/{num}", method = RequestMethod.GET) // 브라우저에서 요청 시 {num} 부분의 값이 @PathVariable로 지정된다.
	public int notice(@PathVariable("num") int num) throws Exception { // 요청 URL 에서 지정된 값이 num에 자동으로 할당된다.
		return num;
	}

* 브라우저에 요청하여 notice/112로 전송할 경우 112가 num에 할당된다.

- http://localhost/pro29/test/notice/112


4. @RequestBody와 @ResponseBody 사용하기

* 실제로 REST는 Ajax 기능과 연동해서 자주 사용한다. 브라우저에서 JSON  데이터를 컨트롤러로 전송할 때 컨트롤러에서 JSON을 객체로 변환하는 기능을 구현해 보자.


(1) @RequestBody 사용하기

* @RequestBody를 사용하면 브라우저에서 전달되는 JSON 데이터를 객체로 자동 변환해 준다.

1. 관련된 자바 클래스와 JSP 파일을 다음과 같이 추가한다.

2. TestController를 다음과 같이 작성한다. @RequestBody를 이용해 JSON 데이터를 MemberVO 객체로 자동 변환한다.

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

	static Logger logger = LoggerFactory.getLogger(TestController.class);

	@RequestMapping(value = "/info", method = RequestMethod.POST)
	public void modify(@RequestBody MemberVO vo) { // JSON으로 전송된 데이터를 MemberVO 객체의 속성에 자동으로 전달해 준다.
		logger.info(vo.toString());
	}

3. /pro29로 요청 시 JSONTest.jsp를 표시하도록 지정한다.

@Controller
public class HomeController {
	
	private static final Logger logger = LoggerFactory.getLogger(HomeController.class);
	
	@RequestMapping(value = "/", method = RequestMethod.GET)
	public String home(Locale locale, Model model) {
	  return "JSONTest";
	}
}

4. 회원 정보 보내기를 클릭하면 Ajax를 이용해 회원 정보를 JSON으로 만들어서 컨트롤러로 전송한다.

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"  isELIgnored="false" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"  %>

<c:set var="contextPath" value="${pageContext.request.contextPath}" />
<!DOCTYPE html>
<html lang="ko">

    <head>
        <title>JSONTest</title>
        <script src="http://code.jquery.com/jquery-latest.js"></script>
        <script>
            $(function() {
                $("#checkJson").click(function() {
                    var member = {
                        id: "park",
                        name: "박지성",
                        pwd: "1234",
                        email: "park@test.com"
                    }; // 회원 정보를 JSON으로 생성한다.
                    $.ajax({
                        type: "post",
                        url: "${contextPath}/test/info", // /test/info로 요청한다.
                        contentType: "application/json",
                        data: JSON.stringify(member), // 회원 정보를 JSON 문자열로 반환한다.
                        success: function(data, textStatus) {},
                        error: function(data, textStatus) {
                            alert("에러가 발생했습니다.");
                        },
                        complete: function(data, textStatus) {}
                    }); //end ajax	

                });
            });
        </script>
    </head>

    <body>
        <input type="button" id="checkJson" value="회원 정보 보내기" /><br><br>
        <div id="output"></div>
    </body>

</html>

5. 다음의 주소로 요청한 후 회원 정보 보내기를 클릭한다.

- http://localhost/pro29/

6. 이클립스 콘솔에 JSON으로 전송된 회원 정보가 출력된 것을 볼 수 있다.


(2) @ResponseBody 사용하기

* 지금까지 사용한 컨트롤러의 메서드들은 JSP를 뷰리졸버로 리턴한 후 브라우저에 결과를 표시했다. 그런데 컨트롤러의 특정 메서드에 @ResponseBody를 적용하면 JSP가 아닌 텍스트나 JSON으로 결과를 전송할 수 있다.

1. 다음과 같이 실습에 필요한 파일들을 준비한다.

2. ResController 클래스를 다음과 같이 작성한다. @RestController로 지정하지 않았으므로 결과를 JSP로 표시한다. 그러나 @ResponseBody가 적용된 메서드는 데이터를 브라우저로 전송한다.

package com.myspring.pro29.ex02;

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

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;

@Controller
public class ResController { // @RestController로 지정되지 않았다.
	
	@RequestMapping(value = "/res1")
	@ResponseBody // 메서드 호출 시 데이터를 전송하도록 설정한다.
	public Map<String, Object> res1() {
		Map<String, Object> map = new HashMap<String, Object>();
		map.put("id", "hong");
		map.put("name", "홍길동");
		return map;
	} // Map 데이터를 브라우저로 전송한다.
	
	@RequestMapping(value = "/res2")
	public ModelAndView res2() {
		return new ModelAndView("home");
	} // 메서드 호출 시 home.jsp를 브라우저로 전송한다.
	
}

3. 다음의 주소로 요청하여 JSON 데이터를 표시한다.

- http://localhost/pro29/res1

4. 다음의 주소로 요청하면 home.jsp를 표시한다.

- http://localhost/pro29/res2


(3) @ResponseEntity 사용해서 응답하기

* @RestController는 별도의 View를 제공하지 않은 채 데이터를 전달하므로 전달 과정에서 예외가 발생할 수 있다. 예외에 대해 좀 더 세밀한 제어가 필요한 경우 ResponseEntity 클래스를 사용하면 된다.

* 예를 들어 안드로이드 기반의 어떤 모바일 쇼핑몰 앱이 있는데, 명절 기간에 주문자가 한꺼번에 몰리면서 서버에 부하가 걸렸다고 가정하자. 일정 시간이 지나도 주문이 처리되지 않으면 서버에서 ResponseEntity 클래스에 HTTP 상태 코드를 설정하여 앱으로 전송하도록 한다. 그러면 앱에서 HTTP 상태 코드를 인식할 수 있는 기능을 이용해 주문 상태나 예외 발생을 알려준다. 

* 아래 표는 ResponseEntity에 설정할 수 있는 여러 가지 HTTP 상태 코드들이다.

< HTTP 상태 코드 표 >

그룹 코드 상수 설명
정보 응답 100 CONTINUE 상태가 괜찮으며, 클라이언트가 계속해서 요청하거나 요청이 완료된 경우에는 무시해도 된다는 정보를 알려준다.
101 SWITCHING_PROTOCOL 클라이언트가 보낸 upgrage 요청 헤더에 대한 응답으로, 서버에서 프로토콜을 변경할 것임을 알려준다.
성공 응답 200 OK 요청이 성공적으로 완료되었다는 의미이다.
201 CREATED 요청이 성공적이었으며 그 결과로 새로운 리소스가 생성되었다는 의미이다.
202 ACCEPTED 요청을 수신했지만 그에 응하여 행동할 수 없다는 의미이다.
리다이렉션 메시지 300 MULTIPLE_CHOICE 요청에 대해 하나 이상의 응답이 가능하다는 의미이다.
301 MOVED_PERMANENTLY 요청한 리소스의 URI가 변경되었다는 의미이다.
302 FOUND 요청한 리소스의 URI가 일시적으로 변경되었다는 의미이다.
303 SEE_OTHER 클라이언트가 요청한 리소스를 다른 URI에서 GET 요청을 통해 얻어야 할 경우 서버가 클라이언트로 직접 보내는 응답이다.
클라이언트 오류 응답 400 BAD_REQUEST 이 응답은 잘못된 문법으로 인해 서버가 요청을 이해할 수 없다는 의미이다.
401 UNAUTHORIZED 인증되지 않았다는 의미이다.
403 FORBIDDEN 클라이언트가 콘텐츠에 접근할 권리를 가지고 있지 않다는 의미이다.
404 NOT_FOUND 서버는 요청 받은 리소스를 찾을 수 없다는 의미이다.
서버 오류 응답 500 INTERNAL_SERVER_ERROR 처리할 수 없는 내부 오류가 발생했다는 의미이다.
501 NOT_IMPLEMENTED 요청 메서드는 서버가 지원하지 않거나 처리할 수 없다는 의미이다.
503 SERVICE_UNAVAILABLE 서버는 요청을 처리할 준비가 되지 않았다는 의미이다.

* 자, 그럼 TestController 클래스를 작성해 보자. 컨트롤러에 추가된 메서드는 ResponseEntity에 오류 코드를 설정하여 응답한다.

	@RequestMapping("/membersList2")
	public ResponseEntity<List<MemberVO>> listMembers2() { // ResponseEntity로 응답한다.
		List<MemberVO> list = new ArrayList<MemberVO>();
		for (int i = 0; i < 10; i++) {
			MemberVO vo = new MemberVO();
			vo.setId("lee" + i);
			vo.setPwd("123" + i);
			vo.setName("이순신" + i);
			vo.setEmail("lee" + i + "@test.com");
			list.add(vo);
		}
		return new ResponseEntity(list, HttpStatus.INTERNAL_SERVER_ERROR); // 오류 코드 500으로 응답한다.
	}

* 브라우저에서 요청시 정상적으로 데이터를 표시한다.

- http://localhost/pro29/test/membersList2

* 크롬 개발자 도구로 확인하면 Status Code(상태 코드)가 ResponseEntity에서 설정한 500임을 알 수 있다.

* 이번에는 HttpHeaders 클래스를 이용해 ResponseEntity로 전송할 데이터의 종류와 한글 인코딩을 설정해 보자.

	@RequestMapping(value = "/res3")
	public ResponseEntity res3() {
		HttpHeaders responseHeaders = new HttpHeaders();
		responseHeaders.add("Content-Type", "text/html; charset=utf-8"); // 전송할 데이터의 종류와 인코딩을 설정한다.
		String message = "<script>";
		message += " alert('새 회원을 등록합니다.');";
		message += " location.href='/pro29/test/membersList2'; ";
		message += " </script>"; // 전송할 자바스크립트 코드를 문자열로 작성한다.
		return new ResponseEntity(message, responseHeaders, HttpStatus.CREATED); // ResponseEntity를 이용해 HTML 형식으로 전송한다.
	}

* 브라우저에서 /test/res3 으로 요청하면 다음과 같이 전송된 자바스크립트 경고 메시지를 표시한다.

- http://localhost/pro29/test/res3

* 이처럼 ResponseEntity를 이용하면 JSON 뿐만 아니라 HTML이나 자바스크립트를 브라우저로 전송할 수 있어 결과 메시지나 오류 메시지를 전송할 때 편리하다.


5. REST 방식으로 URI 표현하기

* 서버에 데이터를 조회하는 것뿐만 아니라 추가, 수정, 삭제 기능도 REST 방식으로 요청해야 한다.

* 이 때 서버에 어떤 행위를 요청할 것인가는 HTTP 메서드를 이용해 처리한다.

< HTTP 메서드의 기능 >

메서드 설명
POST 추가(Create)
GET 조회(Select)
PUT 수정(Update)
DELETE 삭제(Delete)

* 다음은 각 작업을 REST 방식으로 요청하는 전형적인 URI의 예이다.

/작업명/기본키 + 메서드 + 데이터

- 작업명 : 요청하는 작업 종류
- 기본키 : 요청하는 작업에 해당하는 대상의 기본키
- 메서드 : 요청하는 기능
- 데이터 : 기능 수행에 필요한 JSON 데이터

* 다음 표는 게시판 기능과 관련된 URI 이다.

< REST로 게시판 기능 관련 URI 만들기 >

메서드 URI 설명
POST /boards + 데이터 새 글 등록하기
GET /boards/133 133번 글 조회하기
PUT /boards/133 + 데이터 133번 글 수정하기
DELETE /boards/133 133번 글 삭제하기

* URI 항목에서 /boards는 게시판 작업을 의미하고 /133은 게시판 테이블의 133번 글에 대해 작업을 하겠다는 의미이다. 메서드는 각 URI의 구체적인 기능을 의미한다. 예를 들어 GET이면 '133번 글의 조회를 요청한다'는 의미이고, POST면 '전달된 데이터를 새 글로 등록하겠다'는 의미이다.


(1) 게시판 기능 REST API 만들기

* 이번에는 REST 방식으로 URI 요청 시 처리하는 컨트롤러의 메서드를 구현해 보자.

1. 게시판 글에 대한 CRUD 기능을 하는 BoardController 와 관련된 파일들을 준비한다.

2. 앞에서 설명한 REST 방식으로 컨트롤러의 메서드들을 구현한다. method의 속성에 GET, POST, PUT, DELETE를 지정하여 각 메서드들의 기능을 정의한다.

[BoardController.java]

package com.myspring.pro29.ex03;

import java.util.ArrayList;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;


@RestController
@RequestMapping("/boards") // 첫 번째 단계의 요청명을 매핑한다.
public class BoardController {
	static Logger logger = LoggerFactory.getLogger(BoardController.class);
	
	@RequestMapping(value = "/all", method = RequestMethod.GET) // GET 방식으로 요청하므로 모든 글의 정보를 조회한다.
	public ResponseEntity<List<ArticleVO>> listArticles() {
		logger.info("listArticles 메서드 호출");
		List<ArticleVO> list = new ArrayList<ArticleVO>();
		for (int i = 0; i < 10; i++) {
			ArticleVO vo = new ArticleVO();
			vo.setArticleNO(i);
			vo.setWriter("이순신"+i);
			vo.setTitle("안녕하세요"+i);
			vo.setContent("새 상품을 소개합니다."+i);
			list.add(vo);
		}
		
		return new ResponseEntity(list,HttpStatus.OK);
	}
	
	@RequestMapping(value = "/{articleNO}", method = RequestMethod.GET) // GET 방식으로 요청하면서 글 번호를 전달하므로 글 번호에 대한 글 정보를 조회한다.
	public ResponseEntity<ArticleVO> findArticle (@PathVariable("articleNO") Integer articleNO) {
		logger.info("findArticle 메서드 호출");
		ArticleVO vo = new ArticleVO();
		vo.setArticleNO(articleNO);
		vo.setWriter("홍길동");
		vo.setTitle("안녕하세요");
		vo.setContent("홍길동 글입니다");
		return new ResponseEntity(vo,HttpStatus.OK);
	}	
	
	@RequestMapping(value = "", method = RequestMethod.POST) // POST 방식으로 요청하므로 요청 시 JSON으로 전달되는 객체를 새 글로 추가한다.
	public ResponseEntity<String> addArticle (@RequestBody ArticleVO articleVO) {
		ResponseEntity<String>  resEntity = null;
		try {
			logger.info("addArticle 메서드 호출");
			logger.info(articleVO.toString());
			resEntity =new ResponseEntity("ADD_SUCCEEDED",HttpStatus.OK);
		}catch(Exception e) {
			resEntity = new ResponseEntity(e.getMessage(),HttpStatus.BAD_REQUEST);
		}
		
		return resEntity;
	}	
	
	//수정하기
	@RequestMapping(value = "/{articleNO}", method = RequestMethod.PUT) // PUT 방식으로 요청하므로 articleNO에 대한 글을 전달되는 JSON 정보로 수정한다.
	public ResponseEntity<String> modArticle (@PathVariable("articleNO") Integer articleNO, @RequestBody ArticleVO articleVO) { // 전송된 JSON 회원 정보를 바로 ArticleVO 객체의 속성에 설정한다.
		ResponseEntity<String>  resEntity = null;
		try {
			logger.info("modArticle 메서드 호출");
			logger.info(articleVO.toString());
			resEntity =new ResponseEntity("MOD_SUCCEEDED",HttpStatus.OK);
		}catch(Exception e) {
			resEntity = new ResponseEntity(e.getMessage(),HttpStatus.BAD_REQUEST);
		}
		
		return resEntity;
	}
	
	//삭제하기
	@RequestMapping(value = "/{articleNO}", method = RequestMethod.DELETE) // DELETE 방식으로 요청하므로 전달되는 articleVO에 대한 글을 삭제한다.
	public ResponseEntity<String> removeArticle (@PathVariable("articleNO") Integer articleNO) {
		ResponseEntity<String>  resEntity = null;
		try {
			logger.info("removeArticle 메서드 호출");
			logger.info(articleNO.toString());
			resEntity =new ResponseEntity("REMOVE_SUCCEEDED",HttpStatus.OK);
		}catch(Exception e) {
			resEntity = new ResponseEntity(e.getMessage(),HttpStatus.BAD_REQUEST);
		}
		
		return resEntity;
	}	

}

3. 앞에서 실습한 HomeController 클래스의 반환 값을 JSONTest2로 변경한다.

package com.myspring.pro29;

import java.text.DateFormat;
import java.util.Date;
import java.util.Locale;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

/**
 * Handles requests for the application home page.
 */
@Controller
public class HomeController {
	
	private static final Logger logger = LoggerFactory.getLogger(HomeController.class);
	/*
	*//**
	 * Simply selects the home view to render by returning its name.
	 *//*
	@RequestMapping(value = "/", method = RequestMethod.GET)
	public String home(Locale locale, Model model) {
		logger.info("Welcome home! The client locale is {}.", locale);
		
		Date date = new Date();
		DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG, locale);
		
		String formattedDate = dateFormat.format(date);
		
		model.addAttribute("serverTime", formattedDate );
		
		return "home";
	}*/
	
	@RequestMapping(value = "/", method = RequestMethod.GET)
	public String home(Locale locale, Model model) {
	  return "JSONTest2";
	}
}

4. 새 글 등록, 수정, 삭제에 사용할 JSONTest2.jsp를 다음과 같이 작성한다. Ajax 요청 시 type 속성에는 메서드의 속성을 지정하고, url 속성에는 REST에서 지정한 URI 형식으로 요청하도록 지정한다.

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"  isELIgnored="false" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"  %>

<c:set var="contextPath" value="${pageContext.request.contextPath}" />
<!DOCTYPE html>
<html>

<head>
    <title>JSONTest2</title>
    <script src="http://code.jquery.com/jquery-latest.js"></script>
    <script>
        $(function() {
            $("#checkJson").click(function() {
                var article = {
                    articleNO: "114",
                    writer: "박지성",
                    title: "안녕하세요",
                    content: "상품 소개 글입니다."
                }; // 새 글 정보를 JSON으로 생성한다.

                $.ajax({
                    type:"POST", // 새 글 등록은 POST 방식으로 요청한다.
                    url:"${contextPath}/boards", // 새 글을 등록하는 메서드를 호출한다.
                    //type: "PUT",
                    //url: "${contextPath}/boards/114", // 글 번호 114번에 대해 수정을 요청한다.
                    contentType: "application/json",
                    data: JSON.stringify(article), // 글 정보를 JSON 형식으로 전송한다.
                    success: function(data, textStatus) {
                        alert(data);
                    },
                    error: function(data, textStatus) {
                        alert("에러가 발생했습니다.");
                        ㅣ
                    },
                    complete: function(data, textStatus) {}
                }); //end ajax	

            });
        });
    </script>
</head>

<body>
    <input type="button" id="checkJson" value="새글 쓰기" /><br><br>
    <div id="output"></div>
</body>

</html>

5. 브라우저 주소 창에 다음의 주소로 요청 시 다음과 같은 전체 글 정보를 전송한다.

- http://localhost/pro29/boards/all

6. 브라우저 주소 창에 다음의 주소로 요청하면 144번 글에 대한 정보만 조회한다.

- http://localhost/pro29/boards/144

7. 브라우저 주소 창에 다음의 주소로 요청하여 JSONTest2.jsp를 표시한 후 새 글 쓰기를 클릭한다.

- http://localhost/pro29/

8. JSONTest2.jsp에서 Ajax로 전송한 새 글을 이클립스 콘솔로 출력한다.

9. JSONTest2.jsp의 Ajax 구문에서 type을 PUT으로, url 속성을 /board/114로 수정하여 다시 요청한다. 그러면 다음과 같이 글 수정 메서드를 호출한다.

- http://localhost/pro29/boards/114

* 이번에는 모바일 기기에서는 주로 Ajax를 이용해 REST 방식으로 데이터 송수신 기능을 구현하기 때문에 실행이 빠르다는 것을 배웠다. 게시판의 삭제 기능은 여러분이 직접 구현하기 바란다.