딸기말차

[Spring] 9. REST, Interceptor 본문

Bootcamp/Spring

[Spring] 9. REST, Interceptor

딸기말차 2023. 8. 23. 18:49

엔코아 플레이데이터(Encore Playdata) Backend 2기 백엔드 개발 부트캠프 (playdata.io)

 

백엔드 개발 부트캠프

백엔드 기초부터 배포까지! 매력있는 백엔드 개발자 포트폴리오를 완성하여 취업하세요.

playdata.io


1.  REST

1. REST (Representational State Transfer) ?

REST란 자원을 이름으로 구분하여 해당 자원의 상태를 주고받는 모든 것을 의미한다.

즉, 화면을 이동하며 데이터를 주고받는 것이 아니라 화면을 유지하며 필요한 데이터를 주고 받는것을 의미한다.

 

2. REST의 구성요소

1. HTTP URI
자원(Resource)을 명시
2. HTTP Method
GET, POST, PUT, PATCH, DELETE
3. HTTP Message Pay Load
해당 자원(URI)에 대한 CRUD Operation을 적용한 결과, 즉 Message Body에 담기는 데이터

3. REST의 특징

1. Server-Client(서버-클라이언트) 구조 
자원이 있는 쪽이 서버, 자원을 요청하는 쪽이 클라이언트가 된다. 
서버는 클라이언트 측에 API를 제공하고, 비즈니스 로직 처리 및 데이터를 저장한다.

2. Stateless(무상태)
HTTP 프로토콜은 기본적으로 무상태이다. REST 또한 HTTP를 기본으로 하기 때문에 무상태이다.

* 무상태란?
클라이언트의 상태(State)를 서버에 저장하지 않는다는 뜻으로, 클라이언트와 서버간의 통신에 필요한 
모든 상태 정보들을 클라이언트가 가지고 있다가 서버와 통신 시 데이터를 보내는 것을 의미한다.
즉, 서버는 단순히 요청이 오면 응답을 보내는 역할만 수행하게 된다.

서버는 응답만 보내면 되기 때문에 작업에 일관성이 생기고, 부담이 줄어들게 된다.
또한, 상태를 보관하지 않기 때문에 1번 서버에 문제가 생길 경우 2번 서버가 이어받아
응답을 하더라도 문제가 생기지 않는다.

3. Cacheable(캐시 처리 가능)
HTTP의 캐싱 기능을 적용하여, 대량의 요청을 효율적으로 처리하기 위한 캐시를 사용할 수 있다.
때문에 응답 시간이 빨라지고 서버의 자원 이용률을 향상시킬 수 있다.

4. Layered System(계층화)
클라이언트는 REST API 서버만 호출한다. 
여기서 REST 서버는 다중 계층으로 구성될 수 있고, API 서버는 비즈니스 로직을 수행하며 로직 수행 전
보안, 로드 밸런싱, 암호화, 사용자 인증 등의 기능을 구성하여 구조상 유연성을 줄 수 있다.

5. Code-On-Demand 
서버로부터 스크립트를 받아서 클라이언트에서 실행한다. 이 특징은 반드시 충족할 필요는 없다.

6. Uniform Interface(인터페이스 일관성)
한정 된 인터페이스, 즉 GET / POST / PUT / DELETE 4가지 인터페이스 중 하나를 택하여 URI를 통해 요청을 보낸다.
즉, HTTP 표준 프로토콜에 따르는 모든 플랫폼에서 사용이 가능하기 때문에 특정 언어나 기술에 종속되지 않는다.

4. REST의 장단점

* 장점
1. 쉽게 사용 가능하다.
HTTP 프로토콜의 인프라를 그대로 사용하므로 REST API 사용을 위한 별도의 인프라를 구축할 필요가 없다.

2. 클라이언트, 서버의 분리
기본적으로 stateless(무상태) 이기 때문에, 클라이언트와 서버 각자의 역할이 명확히 구분 되어있다.
때문에 HTTP 프로토콜에 따르는 모든 플랫폼에서 사용이 가능하다.

3. 데이터의 명확한 표현
HTTP Message의 Header 부분에 요청 처리 메서드 및 데이터 양식을 표기하고, 
Body 부분에 데이터를 담기 때문에 메시지가 의도하는 동작을 쉽게 파악할 수 있다.
* 단점 
1. 표준이 자체가 존재하지 않아 정의가 필요하다.
2. HTTP Method가 다양하지 않아 제한적이다.
3. 브라우저에 따라 지원하지 않는 동작이 존재한다. (PATCH)

2.  REST 실습_1)  객체를 json 형태로 response

일반적으로 객체를 브라우저로 return하는 경우, @ResponseBody를 통해 객체가 json형태로 변환되어 response 된다.

스프링4 이후부터는, @ResponseBody와 @Controller를 붙인 어노테이션인 @RestController를 지원한다.

 

이 때 주의할 점이 있는데, VO(Entity) 객체는 DB의 레코드와 일치하는 정보이기 때문에 해당 객체를 그대로 return하게 되면 데이터를 요청한 클라이언트 측에서 private한 정보까지 모두 알아버릴 수 있는 단점이 존재한다.

객체를 return한 경우

때문에 이를 해결하기 위해 DTO (Data Transfer Object) 를 사용해, VO(Entity) 객체 내 필요한 정보만 담을 수 있는 DTO 객체를 만들어 return하는 방법이 존재한다.

 

또한, 이렇게 response를 해버리면 객체 단 하나만 보내는 것이기 때문에 해당 json 형식 객체에 무언가를 추가하거나 삭제하는 등 데이터의 유연성이 떨어진다.

때문에 이를 해결하기 위해, Map을 사용하거나 JS와 공통 된 형식을 위해 반환을 위한 객체로 한번 더 데이터를 감싸 return을 하게 된다.

Map을 사용한 return

* return한 json 객체의 유연성을 높이기 위해 한번 더 객체로 감싸서 return하는 예시

public class Result {
    public Long id;
    public ResponseDTO dto;

    public Result(Long id, ResponseDTO dto) {
        this.id = id;
        this.dto = dto;
    }
}

 

1. 객체를 return하는 RestController

@RestController 
@RequestMapping("/test")
public class TestController {
	
	private static final Logger logger = LoggerFactory.getLogger(TestController.class);

	/*
	 * 1. "/hello" 라는 URI로 요청이 들어오면,
	 * 2. "Hello REST!!" 라는 문자열을 StringHttpMessageConverter를 이용해 response message의 body에 담아 브라우저로 반환한다.
	 */
	@RequestMapping("/hello")
	public String hello() {
		return "Hello REST!!";
	}
	
	/*
	 * 1. "/member" 라는 URI로 요청이 들어오면,
	 * 2. memberVO 객체를 MappingJackson2HttpMessageConverter로 json 타입으로 변환, response message의 body에 담아 브라우저로 반환한다.
	 */
	@RequestMapping("/member")
	public MemberVO member() {
		MemberVO memberVO = new MemberVO();
		memberVO.setId("hong");
		memberVO.setPwd("1234");
		memberVO.setName("홍길동");
		memberVO.setEmail("hong@test.com");
		return memberVO;
	}
	
	/*
	 * 1. "/members" 라는 URI로 요청이 들어오면,
	 * 2. List<MemberVO> 객체를 MappingJackson2HttpMessageConverter로 json 타입으로 변환, response message의 body에 담아  브라우저로 반환한다.
	 */
	@RequestMapping("/members")
	public List<MemberVO> members() {
		List<MemberVO> members = new ArrayList<MemberVO>();
		
		for (int i = 0; i < 10; i++) {
			MemberVO memberVO = new MemberVO();
			memberVO.setId("hong" + i);
			memberVO.setPwd("1234" + i);
			memberVO.setName("홍길동" + i);
			memberVO.setEmail("hong" + i + "@test.com");
			members.add(memberVO);	
		}
		return members;
	}
	
	/*
	 * 1. "/membersMap" 이라는 URI로 요청이 들어오면,
	 * 2. Map<Integer, MemberVO> 객체를 MappingJackson2HttpMessageConverter로 json 타입으로 변환, response message의 body에 담아 브라우저로 반환한다.
	 */
	@RequestMapping("/membersMap")
	public Map<Integer, MemberVO> membersMap() {
		Map<Integer, MemberVO> membersMap = new HashMap<Integer, MemberVO>();
		for (int i = 0; i < 10; i++) {
			MemberVO memberVO = new MemberVO();
			memberVO.setId("hong" + i);
			memberVO.setPwd("1234" + i);
			memberVO.setName("홍길동" + i);
			memberVO.setEmail("hong" + i + "@test.com");
			membersMap.put(i, memberVO);
		}	
		return membersMap;
	}
	
	/*
	 * 1. "/notice/{num}" 이라는 URI로 요청이 들어오면,
	 * 2. @PathVariable을 사용해 경로에 포함 된 데이터를 추출한다.
	 * 3. 추출한 int값을 MappingJackson2HttpMessageConverter로 json 타입으로 변환, response message의 body에 담아 브라우저로 반환한다.
	 */
	@RequestMapping(value = "/notice/{num}", method = RequestMethod.GET)
	public int notice(@PathVariable int num) throws Exception {
		return num;
	}
	
	/*
	 * 1. "/notice/{str}" 이라는 URI로 요청이 들어오면,
	 * 2. @PathVariable을 사용해 경로에 포함 된 데이터를 추출한다.
	 * 3. 추출한 String 값을 StringHttpMessageConverter를 이용해 response message의 body에 담아 브라우저로 반환한다.
	 */
	@RequestMapping(value = "/notice2/{str}", method = RequestMethod.GET, produces = "text/html; charset=utf-8")
	public String notice2(@PathVariable String str) throws Exception {
		return str;
	}
	
	/*
	 * 1. "/info" 라는 URI로 요청이 들어오면,
	 * 2. @RequestBody를 사용해 json 형태로 전송 된 데이터를 Java의 Object 형태로 변환한다.
	 * 3. 변환한 데이터를 log를 통해 console에 출력한다.
	 */
	@RequestMapping(value = "/info", method = RequestMethod.POST)
	public void modify(@RequestBody MemberVO memberVO) {
		logger.info("memberVO: {}", memberVO);
	}	

}

3.  REST 실습_2)  ResponseEntity를 통한 response

이전 실습에서 보면 우리는 Java에서 만든 객체를 return 했을 뿐인데, @ResponseBody 덕분에 return한 객체가 json으로 변환되어 클라이언트 쪽으로 전달되었다. 

그렇다면 이 json객체는 어디에 실려 web에 전달되는 것인지 한번 알아볼 필요가 있다.

 

기본적으로 서버에서 보내는 모든 응답은 결과적으로 HttpResponseMessage 형태로 변환되어 브라우저로 전달 되는데, 해당 형식은 다음과 같다.

HttpResponseMessage

우리가 return한 json객체는, 해당 HttpResponseMessage의 Message Body부분에 실려서 브라우저로 나가게 된다.

이 HttpResponseMessaged의 Header와 Body를 Java의 객체 형식으로 만들어둔게 ResponseEntitiy로, 응답메세지의 Header나 Body 부분을 메서드 체이닝을 통해 편하게 접근해 작성할 수 있다.

 

1. ResponseEntity를 return하는 RestController

@RestController
@RequestMapping("/boards")
public class BoardController {
	
	private static final Logger logger = LoggerFactory.getLogger(TestController.class);

	/*
	 * 1. "/members2" 라는 URI로 요청이 들어오면,
	 * 2. List<MemberVO> 형태의 객체를 만들고,
	 * 3. HttpEntity를 상속받은 ResponseEntity 객체를 통해, 
	 * 4. header에 http 상태를 입력하고, body에 만든 객체를 담아 브라우저로 반환한다.
	 * 5. 이때 body 부분에 객체가 들어가 있기 때문에 MappingJackson2HttpMessageConverter가 동작한다.
	 */
	@RequestMapping("/members2")
	public ResponseEntity<?> members2() {
		List<MemberVO> members = new ArrayList<MemberVO>();
		for (int i = 0; i < 10; i++) {
			MemberVO memberVO = new MemberVO();
			memberVO.setId("hong" + i);
			memberVO.setPwd("1234" + i);
			memberVO.setName("홍길동" + i);
			memberVO.setEmail("hong" + i + "@test.com");
			members.add(memberVO);
		}
		return ResponseEntity
				.status(HttpStatus.INTERNAL_SERVER_ERROR)
				.body(members);
	}
	
	/*
	 * 1. "/res3" 라는 URI로 요청이 들어오면,
	 * 2. Http Response Message의 header 부분에 값을 입력하기 위해 HttpHeaders 객체를 생성한다.
	 * 3. 브라우저로 반환할 데이터를 작성한다.
	 * 4. ResponseEntity 객체에 (반환할 데이터, header에 입력할 데이터, http 상태코드) 를 작성해 return한다.
	 * 5. 이때 body 부분에 string 데이터가 들어가 있기 때문에 StringHttpMessageConverter가 동작한다. 
	 */
	@RequestMapping("/res3")
	public <T> ResponseEntity<T> res3() {
		HttpHeaders responseHeader = new HttpHeaders();
		responseHeader.add("Content-Type", "text/html; charset=utf-8");
		
		StringBuilder message = new StringBuilder();
		message.append("<script>");
		message.append(" alert('새 회원을 등록합니다.');");
		message.append(" location.href='/sts/test/members2'");
		message.append("</script>");
		return new ResponseEntity(message, responseHeader, HttpStatus.CREATED);	
	}

	/*
	 * 1. "/all" 이라는 URI로 요청이 들어오면,
	 * 2. List<ArticleVO> 형태의 객체를 만들고,
	 * 3. ResponseEntity 객체에 HttpStatus 설정, message body에 들어갈 객체를 담아 브라우저로 반환한다.
	 * 4. 이때 body 부분에 객체가 들어가 있기 때문에 MappingJackson2HttpMessageConverter가 동작한다.
	 */
	@RequestMapping(value = "/all", method = RequestMethod.GET)
	public ResponseEntity<?> listArticles(Model model) {
		logger.info("listArticles 메서드 호출");
		
		List<ArticleVO> articles = new ArrayList<ArticleVO>();
		for (int i = 0; i < 10; i++) {
			ArticleVO articleVO = new ArticleVO();
			articleVO.setArticleNo(i);
			articleVO.setWriter("홍길동" + i);
			articleVO.setTitle("안녕하세요" + i);
			articleVO.setContent("새 상품을 소개합니다." + i);
			articles.add(articleVO);
		}
		
		return ResponseEntity.status(HttpStatus.OK).body(articles);
	}
	
	/*
	 * 1. "/{articleNo}" 이라는 URI로 요청이 들어오면,
	 * 2. @PathVariable로 경로에 포함 된 데이터를 잡아내고, ArticleVO 객체를 만든다.
	 * 3. ResponseEntity 객체에 HttpStatus 설정, message body에 들어갈 객체를 담아 브라우저로 반환한다.
	 * 4. 이때 body 부분에 객체가 들어가 있기 때문에 MappingJackson2HttpMessageConverter가 동작한다.
	 * 5. int는 primitive type 이기 때문에 빈 데이터가 들어오면 null 값이 들어오게 된다.
	 * 6. 이러면 NullPointerException이 터질 수 있기 때문에, 이를 방지하기 위해 reference type인 Integer를 사용한다.
	 */
	@RequestMapping(value = "/{articleNo}", method = RequestMethod.GET)
	public ResponseEntity<?> findArticles(@PathVariable Integer articleNo) {
		logger.info("findArticles 메서드 호출");
		
		ArticleVO articleVO = new ArticleVO();
		articleVO.setArticleNo(articleNo);
		articleVO.setWriter("홍길동");
		articleVO.setTitle("안녕하세요");
		articleVO.setContent("홍길동 글 입니다.");
		return ResponseEntity.status(HttpStatus.OK).body(articleVO);
	}
	
	/*
	 * 1. "" 이라는 URI로 요청이 들어오면, 즉 jsp에서 AJAX를 통해 요청이 들어온다면,
	 * 2. @RequestBody로 AJAX 통해 전달 된 json 형태의 데이터를 Java의 Object로 변환한다.
	 * 3. ResponseEntity 객체에 HttpStatus 설정, message body에 들어갈 String을 담아 브라우저로 반환한다.
	 * 4. AJAX 성공 여부에 따라 success / error 중 하나가 동작한다.
	 */
	@RequestMapping(value = "", method = RequestMethod.POST)
	public ResponseEntity<?> addArticle(@RequestBody ArticleVO articleVO) {
		try {
			logger.info("addArticle 메서드 호출");
			logger.info("articleVO: {}", articleVO);
			return ResponseEntity.status(HttpStatus.OK).body("ADD_SUCCEEDED");
		} catch (Exception e) {
			return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(e.getMessage());
		}
	}
	
	/*
	 * 1. "/{articleNo}" 이라는 URI로 요청이 들어오면, 즉 jsp에서 AJAX를 통해 요청이 들어온다면,
	 * 2. @RequestBody로 AJAX 통해 전달 된 json 형태의 데이터를 Java의 Object로 변환한다.
	 * 3. ResponseEntity 객체에 HttpStatus 설정, message body에 들어갈 String을 담아 브라우저로 반환한다.
	 * 4. AJAX 성공 여부에 따라 success / error 중 하나가 동작한다.
	 */
	@RequestMapping(value = "/{articleNo}", method = RequestMethod.PUT)
	public ResponseEntity<?> modArticle(@RequestBody ArticleVO articleVO) {
		try {
			logger.info("modArticle 메서드 호출");
			logger.info("articleNo: {}", articleVO.getArticleNo());
			logger.info("articleVO: {}", articleVO);
			return ResponseEntity.status(HttpStatus.OK).body("MOD_SUCCEEDED");
		} catch (Exception e) {
			return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(e.getMessage());
		}
	}
	
	/*
	 * 1. "/{articleNo}" 이라는 URI로 요청이 들어오면, 즉 jsp에서 AJAX를 통해 요청이 들어온다면,
	 * 2. @PathVariable로 경로에 포함 된 데이터를 잡아낸다.
	 * 3. ResponseEntity 객체에 HttpStatus 설정, message body에 들어갈 String을 담아 브라우저로 반환한다.
	 * 4. AJAX 성공 여부에 따라 success / error 중 하나가 동작한다.
	 */
	@RequestMapping(value = "/{articleNo}", method = RequestMethod.DELETE)
	public ResponseEntity<?> deleteArticle(@PathVariable Integer articleNo) {
		try {
			logger.info("deleteArticle 메서드 호출");
			logger.info("articleNo: {}", articleNo);
			return ResponseEntity.status(HttpStatus.OK).body("DELETE_SUCCEEDED");
		} catch (Exception e) {
			return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(e.getMessage());
		}
	}
	
}

4. Interceptor

인터셉터를 사용하면 브라우저 요청이 있을 때, 요청 메서드 호출 전 후에 개발자가 원하는 기능을 수행할 수 있다.

즉, 요청 URL에 따라 Controller의 메서드를 실행하기 전, 후에 Interceptor를 두어 특정 작업을 수행할 수 있다.

interceptor 동작 시점

인터셉터는 필터와 비슷한 기능을 하지만 필터는 웹 애플리케이션의 특정한 위치에서만 동작하는 데 반해,

인터셉터는 좀 더 자유롭게 위치를 변경해서 기능을 수행할 수 있다. 즉, 인터셉터는 어플리케이션 내에서 적용 범위를 설정해 사용할 수 있다.

주로 쿠키(cookie) 제어, 파일 업로드 작업 등에 사용한다.

 

인터셉터를 실습하기 위해 국제화 기능을 추가해 보았고, 해당 기능을 위해 몇 가지 설정을 진행하였다.

1. web.xml

모든 설정파일을 읽기위해, <param-value>을 변경한다.

여러 xml을 읽기위해 기존에 root-context.xml 이었던 부분을 *.xml로 변경

2. servlet-context.xml

servlet-context 내에 mvc 라는 태그를 추가하기 위한 설정과, 사용할 Interceptor를 등록한다.

mvc 태그를 사용하기 위한 설정 추가

<mvc:interceptors>							
    <mvc:interceptor>
    	<!-- interceptor를 적용할 경로 설정 -->
        <mvc:mapping path="/test/*.do" />	
        <mvc:mapping path="/*/*.do" />		
        <beans:bean class="com.freeflux.sts.locale.LocaleInterceptor" />
    </mvc:interceptor>
</mvc:interceptors>

3. message-context.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"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
	
	<bean id="localeResolver"
		class="org.springframework.web.servlet.i18n.SessionLocaleResolver" />
	
	<bean id="messageSource"
		class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
		<property name="basenames">
			<!-- 국제화를 위해 설정해야하는 외국어가 여러가지기 때문에, list 태그를 통해 설정파일들을 읽는다. -->
			<list>
				<value>classpath:locale/messages</value>	
			</list>
		</property>
		<!-- File Basic Encoding. -->
		<property name="defaultEncoding" value="UTF-8" />	
		<property name="cacheSeconds" value="60" />			
	</bean>
</beans>

4. 국제화를 위한 properties 파일들

properties 파일들

1) message.properties, messages_ko.properties

해당 파일은 유니코드 문자로 번역하여 작성되어있다.

site.title=\ud68c\uc6d0\uc815\ubcf4
site.name=\uc774\ub984
site.job=\uc9c1\uc5c5
name=\ud64d\uae38\ub3d9
job=\ud504\ub85c\uadf8\ub798\uba38
btn.send=\uc804\uc1a1
btn.cancel=\ucde8\uc18c
btn.finish=\uc885\ub8cc

2) message_en.properties

site.title=Member Info
site.name=name
site.job=job
name=hong kil-dong
job=student
btn.send=send
btn.cancel=cancel
btn.finish=finish

5. Interceptor 실습_1) 국제화

인터셉터는 적용 시점을 대표적으로 3가지로 나눌 수 있다.

인터셉터 적용 시점

이 중 preHandle()을 사용하여 국제화 실습을 진행하였다.

/* 인터셉터 구현을 위해 HandlerInterceptorAdapter를 상속 */
public class LocaleInterceptor extends HandlerInterceptorAdapter { 

	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
		/* session에 저장되어 있는 지역 정보를 받아온다. */
		HttpSession session = request.getSession();
		String locale = request.getParameter("locale"); 
		
		/* 저장되어있는 지역 정보가 없다면, 기본 정보를 ko로 설정한다. */
		if (locale == null) { 
			locale = "ko";
		}

		/* 사용할 localeResolver인 SessionLocaleResolver와 locale 정보를 세션에 등록한다. */
		session.setAttribute("org.springframework.web.servlet.i18n.SessionLocaleResolver.LOCALE", new Locale(locale));
		return true;
	}

	@Override
	public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
			ModelAndView modelAndView) throws Exception {
	}

	@Override
	public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
			throws Exception {
	}
}

6. 35일차 후기

스프링을 사용하기 전 Filter는 적용할 페이지를 세세하게 고르기 어려웠던 문제가 있었다. 왜냐하면 Filter의 경우 적용 시점이 DispatcherServlet 호출 이전이기 때문에, 특정 URI에만 적용 시키기가 복잡했기 때문이다.

 

반면 Interceptor의 경우 호출 시점이 컨트롤러의 호출 직전이기 때문에, Filter에 비해 어떤 요청에 인터셉터를 적용시킬지 설정하는 것이 더 용이해졌고, 어느 시점에 적용시킬지도 선택할 수 있게되었다는 장점이 있었다.

 

개발환경이 Spring Framework만 존재하는 것이 아니기 때문에, 모바일 환경 등 다양한 환경과 데이터(정보)를 주고 받기 위해선 기존의 MVC 방식으로는 한계가 존재하게 되었다.

때문에 이를 해결하기 위해 여러 환경에서 공통으로 사용할 수 있는 형식이 필요했고, 대표적으로 JSON 형식을 사용하게 되었다. 또한, 해당 형식을 서버 내에서가 아닌 화면을 통해 전달할 수 있는 방법인 REST 방식을 사용하게 되었다.

 

웹 개발환경은 일반적으로 백엔드, 프론트엔드 환경으로 나눠지게된다. 즉, 프론트엔드 환경으로 데이터를 전송하기 위해선 REST API를 사용할 수 밖에 없다. 때문에 REST API가 무엇인지, 어떻게 사용해야할 것인지 명확히 알고있어야 겠다는 생각이 들었다.

'Bootcamp > Spring' 카테고리의 다른 글

[Spring] 8. File Up / Download, Thumbnail  (0) 2023.08.22
[Spring] 7. Log, Tiles  (0) 2023.08.21
[Spring] 6. STS  (0) 2023.08.19
[Spring] 5. Transaction, Annotation  (0) 2023.08.17
[Spring] 4. Spring Mybatis  (0) 2023.08.16