[스프링 웹 MVC] 7. 스프링 MVC 활용 1부 (요청 맵핑하기)
핵심 기술
- 애노테이션 기반의 스프링 MVC
- 💕 요청 맵핑하기
- 💕 핸드러 메소드
- 💕 모델과 뷰
- 데이터 바인더 - 프로퍼티 에디터, 포매터, 컨버터
- 예외 처리
- 글로벌 컨트롤러 - 모든 컨트롤러에 공통으로 적용되는 컨트롤러
- 사용할 기술
- 스프링 부트
- 스프링 웹 MVC
- 타임리프
- ✨학습할 애노테이션
@RequestMapping
@GetMapping
, @PostMapping
, @PutMapping
등
@ModelAttribute
@RequestParam
, @RequestHeader
@PathVariable
, @MatrixVatiable
@SessionAttribute
, @RequestAttribute
, @CookieValue
@Valid
@RequestBody
, @ResponseBody
@ExceptionHandler
@ControllerAdvice
- 프로젝트 생성
- Spring Initializr
- 의존성 - Web, thymeleaf 추가
HTTP Method
-
HTTP Method
- GET, POST, PUT, PATCH, DELETE ...
-
SampleController
package me.jinmin.demowebmvc;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class SampleController {
//@RequestMapping(value = "/hello", method = {RequestMethod.GET, RequestMethod.PUT})
//@RequestMapping(value = "/hello", method = RequestMethod.GET)
//@GetMapping("/hello") //위와 같음
@RequestMapping("/hello") // url을 통해 맵핑
@ResponseBody // 문자를 응답으로 나타내고자 할 때.
public String hello(){
return "hello"; //해당 String에 해당하는 뷰 이름을 찾아간다.
}
}
- (1 테스트) 일반적으로 Method를 지정하지 않고
@RequestMapping
만 명시하면 모든 HTTP Method를 다 허용한다.
- (2 테스트)
@RequestMapping(value = "/hello", method = RequestMethod.GET)
: GET 방식만 허용한다
- (3)
@RequestMapping(value = "/hello", method = {RequestMethod.GET, RequestMethod.PUT})
: GET, PUT 방식을 허용한다. (테스트는 안 한다!)
-
(1) 테스트 SampleControllerTest
package me.jinmin.demowebmvc;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class SampleControllerTest {
@Autowired
MockMvc mockMvc; //스프링 부트 테스트 객체
@Test
public void helloTest() throws Exception {
mockMvc.perform(get("/hello")) //GET 방식으로 요청 (post, put, delete 등 모두 가능)
.andDo(print()) //Log 내용
.andExpect(status().isOk()) //상태 이상 체크
.andExpect(content().string("hello")); //내용에 "hello"가 있는지 유무
}
}
-
테스트 결과 (Log)
MockHttpServletRequest:
**HTTP Method = GET
Request URI = /hello**
Parameters = {}
Headers = []
Body = null
Session Attrs = {}
Handler:
Type = me.jinmin.demowebmvc.SampleController
Method = me.jinmin.demowebmvc.SampleController#hello()
Async:
Async started = false
Async result = null
Resolved Exception:
Type = null
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
Attributes = null
MockHttpServletResponse:
Status = 200
Error message = null
Headers = [Content-Type:"text/plain;charset=UTF-8", Content-Length:"5"]
Content type = text/plain;charset=UTF-8
Body = hello
Forwarded URL = null
Redirected URL = null
Cookies = []
-
(2) 테스트
package me.jinmin.demowebmvc;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class SampleControllerTest {
@Autowired
MockMvc mockMvc;
@Test
public void helloTest() throws Exception {
mockMvc.perform(post("/hello"))
.andDo(print())
.andExpect(status().isMethodNotAllowed());
}
}
-
테스트 결과 (Log)
MockHttpServletRequest:
HTTP Method = POST
Request URI = /hello
Parameters = {}
Headers = []
Body = null
Session Attrs = {}
Handler:
Type = null
Async:
Async started = false
Async result = null
Resolved Exception:
Type = org.springframework.web.HttpRequestMethodNotSupportedException
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
Attributes = null
MockHttpServletResponse:
Status = 405
Error message = Request method 'POST' not supported
Headers = [Allow:"GET"]
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
-
⚾ GET (조회)
- C → S 리소스 요청
- 캐싱 가능(조건적인 GET)
- 브라우저 기록 남음
- 북마크 가능
- 민감한 데이터 전송 x (URL에 표기)
- idempotent : 멱등, 동일한 GET 요청은 동일한 응답을 리턴
-
⚾ POST (생성)
- C → S 리소스 새로 생성, 수정
- 서버에 보내는 데이터를 POST 요청 본문에 담는다.
- 캐싱 x
- 브라우저 기록 x
- 북마크 x
- 데이터 길이 제한 x
-
⚾ PUT (수정)
- URI에 해당하는 데이터를 새로 만들거나 수정
- POST와 차이점
- POST의 URI는 보내는 데이터를 처리할 리소스를 지칭 : 같은 요청을 2번 보내면 매 번 새로운 2번의 응답 생성
- PUT의 URI는 보내는 데이터에 해당하는 리소스를 지칭 : 같은 요청을 2번 보내면 새로운 응답 1번 생성하고 그 뒤로 응답 x
- Idempotent
-
⚾ PATCH
- PUT과 비슷, 기존 엔티티와 새로운 데이터의 차이점만 보낸다
- Idempotent
-
⚾DELETE
- URI에 해당하는 리소스를 삭제
- Idempotent
URI 패턴
-
URI, URL, URN
- URI : Uniform Resource Identifier
- URL : Uniform Resource Location
- URN : Uniform Resource Name
-
SampleController
package me.jinmin.demowebmvc;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class SampleController {
@GetMapping({"/hello", "/hi"})
@ResponseBody
public String hello(){
return "hello";
}
}
-
SampleControllerTest
package me.jinmin.demowebmvc;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class SampleControllerTest {
@Autowired
MockMvc mockMvc;
@Test
public void helloTest() throws Exception {
mockMvc.perform(get("/hello"))
.andDo(print())
.andExpect(status().isOk());
mockMvc.perform(get("/hi"))
.andDo(print())
.andExpect(status().isOk());
}
}
- 테스트 성공
-
요청 식별자로 맵핑하기
@RequestMapping
은 아래와 같은 패턴 지원
?
: 한 글자 (“/author/???” ⇒ “/author/123”)
*
: 여러 글자 (“/author/*” ⇒ “/author/jinmin”)
**
: 여러 패스 (“/author/** ⇒ “/author/jinmin/book”)
-
클래스에 선언한 @RequestMapping
과 조합
-
클래스에 선언한 URI 패턴뒤에 이어 붙여서 맵핑
package me.jinmin.demowebmvc;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@RequestMapping("/hi")
public class SampleController {
@GetMapping("/hello")
@ResponseBody
public String hello(){
return "hello";
}
@GetMapping("/hello2")
@ResponseBody
public String hello2(){
return "hello2";
}
}
서버주소/hi/hello
서버주소/hi/hello2
(가능)
-
정규 표현식 맵핑 : @PathVariable
사용
-
SampleController
package me.jinmin.demowebmvc;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@RequestMapping("/hello")
public class SampleController {
@GetMapping("/{name:[a-z]+}")
@ResponseBody
public String hello(@PathVariable String name){
return "hello " + name;
}
}
-
SampleControllerTest
package me.jinmin.demowebmvc;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class SampleControllerTest {
@Autowired
MockMvc mockMvc;
@Test
public void helloTest() throws Exception {
mockMvc.perform(get("/hello/jinmin"))
.andDo(print())
.andExpect(status().isOk())
.andExpect(content().string("hello jinmin"));
}
}
- 테스트 성공
- IF,
mockMvc.perform(get("/hello/123456"))
⇒ 실패, 문자가 아닌 숫자가 왔기 때문에.
-
중복 패턴?
package me.jinmin.demowebmvc;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@RequestMapping("/hello")
public class SampleController {
@GetMapping("/jinmin")
@ResponseBody
public String helloJinmin(){
return "hello jinmin!";
}
@GetMapping("/*")
@ResponseBody
public String hello(){
return "hello jinmin";
}
}
서버 주소/hello/jinmin
확인, 결과는 "hello jinmin!"
- 가장 구체적으로 맵핑되는 핸들러를 선택
-
URI 확장자 맵핑 지원
- 권장 x (스프링 부트에서 기본적으로 해당 기능을 사용하지 않도록 설정)
- 보안 이슈 (Reflected File Download Attack)
- URI 변수, Path 매개변수, URI 인코딩 사용 시 불명확
미디어 타입
-
특정한 타입의 데이터를 담고 있는 요청만 처리하는 핸들러
-
Json을 보내는 요청만 처리하겠다. = @RequestMapping(consumes=MediaType.APPLICATION_JSON_UTF8_VALUE)
-
Content-Type
헤더로 필터링
-
오류 예제)
-
SampleController
package me.jinmin.demowebmvc;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class SampleController {
@GetMapping(value = "/hello", consumes = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public String hello(){
return "hello";
}
}
-
SampleControllerTest
package me.jinmin.demowebmvc;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class SampleControllerTest {
@Autowired
MockMvc mockMvc;
@Test
public void helloTest() throws Exception {
mockMvc.perform(get("/hello"))
.andDo(print())
.andExpect(status().isOk())
.andExpect(content().string("hello"));
}
}
-
결과
java.lang.AssertionError: Status
Expected :200
Actual :415 (Unsupported Media Type)
-
성공 예제)
-
SampleControllerTest
(테스트만 변경)
package me.jinmin.demowebmvc;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class SampleControllerTest {
@Autowired
MockMvc mockMvc;
@Test
public void helloTest() throws Exception {
mockMvc.perform(get("/hello")
.contentType(MediaType.APPLICATION_JSON_VALUE))
.andDo(print())
.andExpect(status().isOk())
.andExpect(content().string("hello"));
}
}
-
결과 : 성공
-
특정한 타입의 응답을 만드는 핸들러
-
Json만을 받는 응답 처리 = @RequestMapping(produces = "application/json")
-
Accept
헤더로 필터링 (⇒ Accept 헤더가 없더라도 테스트는 성공. Why? "어떠한 타입의 핸들러도 받아들인다" 라는 의미로 간주된다. 다만, Accept 헤더를 틀리게 명시하면 오류)
-
오류 예제)
-
SampleController
package me.jinmin.demowebmvc;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class SampleController {
@GetMapping(value = "/hello", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.TEXT_PLAIN_VALUE)
@ResponseBody
public String hello(){
return "hello";
}
}
-
SampleControllerTest
package me.jinmin.demowebmvc;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class SampleControllerTest {
@Autowired
MockMvc mockMvc;
@Test
public void helloTest() throws Exception {
mockMvc.perform(get("/hello")
.contentType(MediaType.APPLICATION_JSON_VALUE)
.accept(MediaType.APPLICATION_JSON_VALUE))
.andDo(print())
.andExpect(status().isOk())
.andExpect(content().string("hello"));
}
}
-
결과
java.lang.AssertionError: Status
Expected :200
Actual :406 (Not Acceptable)
-
성공 예제)
-
SampleControllerTest
package me.jinmin.demowebmvc;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class SampleControllerTest {
@Autowired
MockMvc mockMvc;
@Test
public void helloTest() throws Exception {
mockMvc.perform(get("/hello")
.contentType(MediaType.APPLICATION_JSON_VALUE)
.accept(MediaType.TEXT_PLAIN_VALUE))
.andDo(print())
.andExpect(status().isOk())
.andExpect(content().string("hello"));
}
}
-
클래스에 선언한 consumes
, produces
등은 메소드에서 잡아먹는다. (⇒ 메소드가 설정한 것이 실행된다. 오버라이딩)
-
🔔내 식대로 결론
Content-Type
: 요청 형식이 이렇다
Accept
: 응답 형식이 이렇다.
- 문자열 말고
MediaType
을 이용하자. (편하니까)
헤더와 매개변수
-
특정 헤더가 있는 요청을 처리하고 싶을 때 (headers
)
-
예제)
package me.jinmin.demowebmvc;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class SampleController {
@GetMapping(value = "/hello", headers = HttpHeaders.FROM)
//@GetMapping(value = "/hello", headers = HttpHeaders.AUTHORIZATION) //오류 : HttpHeaders.AUTHORIZATION 헤더가 없음
//@GetMapping(value = "/hello", headers = "!" + HttpHeaders.FROM) //오류 : HttpHeaders.FROM 헤더가 존재
//@GetMapping(value = "/hello", headers = HttpHeaders.FROM + "=" + "111") //오류 : 요청 HttpHeaders.FROM 헤더의 값이 "localhost"이기 때문에
@ResponseBody
public String hello(){
return "hello";
}
}
package me.jinmin.demowebmvc;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.HttpHeaders;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class SampleControllerTest {
@Autowired
MockMvc mockMvc;
@Test
public void helloTest() throws Exception {
mockMvc.perform(get("/hello")
.header(HttpHeaders.FROM, "localhost"))
.andDo(print())
.andExpect(status().isOk())
.andExpect(content().string("hello"));
}
}
-
특정 요청 매개변수를 갖는 요청을 처리하고 싶을 때 (params
)
-
예제)
package me.jinmin.demowebmvc;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class SampleController {
@GetMapping(value = "/hello", params = "name")
//@GetMapping(value = "/hello", params = "name=jinmin") //오류 : value 값이 맵핑이 안된다.
//@GetMapping(value = "/hello", params = "!name") //오류 : name이라는 param이 있기 때문에
@ResponseBody
public String hello(){
return "hello";
}
}
package me.jinmin.demowebmvc;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class SampleControllerTest {
@Autowired
MockMvc mockMvc;
@Test
public void helloTest() throws Exception {
mockMvc.perform(get("/hello")
.param("name","jinmin"))
.andDo(print())
.andExpect(status().isOk())
.andExpect(content().string("hello"));
}
}
HEAD와 OPTIONS
-
개발자가 구현하지 않아도 스프링 웹 MVC에서 자동으로 처리하는 HTTP Method
- HEAD
- OPTIONS
-
HEAD
-
GET과 동일
- But, 응답 본문을 받아오지 않고 응답 헤더만 받아온다.
-
공통 (SampleController
)
package me.jinmin.demowebmvc;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class SampleController {
@GetMapping("/hello")
@ResponseBody
public String hello(){
return "hello";
}
}
-
테스트 - GET
package me.jinmin.demowebmvc;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class SampleControllerTest {
@Autowired
MockMvc mockMvc;
@Test
public void helloTest() throws Exception {
mockMvc.perform(get("/hello"))
.andDo(print())
.andExpect(status().isOk())
.andExpect(content().string("hello"));
}
}
-
결과 (응답 본문)
MockHttpServletResponse:
Status = 200
Error message = null
Headers = [Content-Type:"text/plain;charset=UTF-8", Content-Length:"5"]
Content type = text/plain;charset=UTF-8
Body = hello
Forwarded URL = null
Redirected URL = null
Cookies = []
-
테스트 - HEAD
package me.jinmin.demowebmvc;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.head;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class SampleControllerTest {
@Autowired
MockMvc mockMvc;
@Test
public void helloTest() throws Exception {
mockMvc.perform(head("/hello"))
.andDo(print())
.andExpect(status().isOk());
}
}
-
결과 (응답 본문)
MockHttpServletResponse:
Status = 200
Error message = null
Headers = [Content-Type:"text/plain;charset=UTF-8", Content-Length:"5"]
Content type = text/plain;charset=UTF-8
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
-
Body
의 내용이 다르다. Why? 헤더 정보만 가져오기 때문에
-
OPTIONS
-
서버 또는 특정 리소스가 제공하는 기능을 확인할 수 있다.
-
서버는 Allow
응답 헤더에 사용할 수 있는 HTTP Method 목록을 제공해야 한다.
-
SampleController
(GET, POST 방식의 /hello
)
@Controller
public class SampleController {
@GetMapping("/hello")
@ResponseBody
public String hello(){
return "hello";
}
@PostMapping("/hello")
@ResponseBody
public String helloPost(){
return "hello";
}
}
-
SampleControllerTest
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class SampleControllerTest {
@Autowired
MockMvc mockMvc;
@Test
public void helloTest() throws Exception {
mockMvc.perform(options("/hello"))
.andDo(print())
.andExpect(status().isOk());
}
}
-
결과
MockHttpServletResponse:
Status = 200
Error message = null
Headers = [Allow:"GET,HEAD,POST,OPTIONS"]
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
- 직접 구성 : GET, POST
- 스프링 웹 MVC 기본 제공 : HEAD, OPTIONS
-
테스트 코드를 통해 특정 Header를 확인하는 여러 방법
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class SampleControllerTest {
@Autowired
MockMvc mockMvc;
@Test
public void helloTest() throws Exception {
mockMvc.perform(options("/hello"))
.andDo(print())
.andExpect(status().isOk())
.andExpect(header().exists(HttpHeaders.ALLOW))// Allow 헤더가 있는지
.andExpect(header().stringValues(HttpHeaders.ALLOW,
hasItems(
containsString("GET"),
containsString("POST"),
containsString("HEAD"),
containsString("OPTIONS")
))); //Allow 헤더에 위와 같은 값들이 들어있는지(순서 상관없이)
}
}
커스텀 애노테이션
-
메타 애노테이션
- 애노테이션에 사용 가능한 애노테이션(
@
위의 @
)
- 스프링이 제공하는 대부분의 애노테이션은 메타
@
-
조합(Composed) 애노테이션
- 한 개 혹은 여러 애노테이션의 조합
- 코드 간결화
- 구체적 의미 부여
-
예제)
-
@GetHelloMapping
package me.jinmin.demowebmvc;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@RequestMapping(method = RequestMethod.GET, value = "/hello")
public @interface GetHelloMapping {
}
-
SampleController
package me.jinmin.demowebmvc;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class SampleController {
@GetHelloMapping
@ResponseBody
public String hello(){
return "hello";
}
}
-
SampleControllerTest
package me.jinmin.demowebmvc;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class SampleControllerTest {
@Autowired
MockMvc mockMvc;
@Test
public void helloTest() throws Exception {
mockMvc.perform(get("/hello"))
.andDo(print())
.andExpect(status().isOk());
}
}
- 오류가 난다 Why??? 해결은 아래 내용부터‼
-
🎈 @Retention
-
해당 애노테이션 정보를 언제까지 유지할 것인가
Source
: 소스 코드까지만 유지. (⇒ 컴파일 시 정보가 사라짐, 그저 //
(주석))
Class
(기본값) : 컴파일 한 .class
파일에도 유지 (⇒ 런타임 시, 클래스를 메모리로 읽어오면 사라짐)
Runtime
: 클래스를 메모리로 읽어올 때까지 유지 (⇒ 코드에서 해당 정보를 바탕으로 특정 로직 수행, 애플리케이션이 구동될 때도 유지)
-
@GetHelloMapping
package me.jinmin.demowebmvc;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
@RequestMapping(method = RequestMethod.GET, value = "/hello")
public @interface GetHelloMapping {
}
- 테스트 구동 성공
-
@Target
-
해당 애노테이션을 어디에 사용할 수 있는지
package me.jinmin.demowebmvc;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD) // 배열로 여러 곳을 지정할 수 있다.
@Retention(RetentionPolicy.RUNTIME)
@RequestMapping(method = RequestMethod.GET, value = "/hello")
public @interface GetHelloMapping {
}
-
@Documented
- 해당 애노테이션을 사용한 코드의 문서에 그 애노테이션에 대한 정보를 표기할지 결정
Author And Source
이 문제에 관하여([스프링 웹 MVC] 7. 스프링 MVC 활용 1부 (요청 맵핑하기)), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다
https://velog.io/@jinmin2216/7.-스프링-MVC-활용-1부-요청-맵핑하기
저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념
(Collection and Share based on the CC Protocol.)
- 💕 요청 맵핑하기
- 💕 핸드러 메소드
- 💕 모델과 뷰
- 데이터 바인더 - 프로퍼티 에디터, 포매터, 컨버터
- 예외 처리
- 글로벌 컨트롤러 - 모든 컨트롤러에 공통으로 적용되는 컨트롤러
- 스프링 부트
- 스프링 웹 MVC
- 타임리프
@RequestMapping
@GetMapping
,@PostMapping
,@PutMapping
등
@ModelAttribute
@RequestParam
,@RequestHeader
@PathVariable
,@MatrixVatiable
@SessionAttribute
,@RequestAttribute
,@CookieValue
@Valid
@RequestBody
,@ResponseBody
@ExceptionHandler
@ControllerAdvice
- Spring Initializr
- 의존성 - Web, thymeleaf 추가
-
HTTP Method
- GET, POST, PUT, PATCH, DELETE ...
-
SampleController
package me.jinmin.demowebmvc; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; @Controller public class SampleController { //@RequestMapping(value = "/hello", method = {RequestMethod.GET, RequestMethod.PUT}) //@RequestMapping(value = "/hello", method = RequestMethod.GET) //@GetMapping("/hello") //위와 같음 @RequestMapping("/hello") // url을 통해 맵핑 @ResponseBody // 문자를 응답으로 나타내고자 할 때. public String hello(){ return "hello"; //해당 String에 해당하는 뷰 이름을 찾아간다. } }
- (1 테스트) 일반적으로 Method를 지정하지 않고
@RequestMapping
만 명시하면 모든 HTTP Method를 다 허용한다. - (2 테스트)
@RequestMapping(value = "/hello", method = RequestMethod.GET)
: GET 방식만 허용한다 - (3)
@RequestMapping(value = "/hello", method = {RequestMethod.GET, RequestMethod.PUT})
: GET, PUT 방식을 허용한다. (테스트는 안 한다!)
- (1 테스트) 일반적으로 Method를 지정하지 않고
-
(1) 테스트
SampleControllerTest
package me.jinmin.demowebmvc; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.web.servlet.MockMvc; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @RunWith(SpringRunner.class) @SpringBootTest @AutoConfigureMockMvc public class SampleControllerTest { @Autowired MockMvc mockMvc; //스프링 부트 테스트 객체 @Test public void helloTest() throws Exception { mockMvc.perform(get("/hello")) //GET 방식으로 요청 (post, put, delete 등 모두 가능) .andDo(print()) //Log 내용 .andExpect(status().isOk()) //상태 이상 체크 .andExpect(content().string("hello")); //내용에 "hello"가 있는지 유무 } }
-
테스트 결과 (Log)
MockHttpServletRequest: **HTTP Method = GET Request URI = /hello** Parameters = {} Headers = [] Body = null Session Attrs = {} Handler: Type = me.jinmin.demowebmvc.SampleController Method = me.jinmin.demowebmvc.SampleController#hello() Async: Async started = false Async result = null Resolved Exception: Type = null ModelAndView: View name = null View = null Model = null FlashMap: Attributes = null MockHttpServletResponse: Status = 200 Error message = null Headers = [Content-Type:"text/plain;charset=UTF-8", Content-Length:"5"] Content type = text/plain;charset=UTF-8 Body = hello Forwarded URL = null Redirected URL = null Cookies = []
-
-
(2) 테스트
package me.jinmin.demowebmvc; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.web.servlet.MockMvc; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @RunWith(SpringRunner.class) @SpringBootTest @AutoConfigureMockMvc public class SampleControllerTest { @Autowired MockMvc mockMvc; @Test public void helloTest() throws Exception { mockMvc.perform(post("/hello")) .andDo(print()) .andExpect(status().isMethodNotAllowed()); } }
-
테스트 결과 (Log)
MockHttpServletRequest: HTTP Method = POST Request URI = /hello Parameters = {} Headers = [] Body = null Session Attrs = {} Handler: Type = null Async: Async started = false Async result = null Resolved Exception: Type = org.springframework.web.HttpRequestMethodNotSupportedException ModelAndView: View name = null View = null Model = null FlashMap: Attributes = null MockHttpServletResponse: Status = 405 Error message = Request method 'POST' not supported Headers = [Allow:"GET"] Content type = null Body = Forwarded URL = null Redirected URL = null Cookies = []
-
-
⚾ GET (조회)
- C → S 리소스 요청
- 캐싱 가능(조건적인 GET)
- 브라우저 기록 남음
- 북마크 가능
- 민감한 데이터 전송 x (URL에 표기)
- idempotent : 멱등, 동일한 GET 요청은 동일한 응답을 리턴
-
⚾ POST (생성)
- C → S 리소스 새로 생성, 수정
- 서버에 보내는 데이터를 POST 요청 본문에 담는다.
- 캐싱 x
- 브라우저 기록 x
- 북마크 x
- 데이터 길이 제한 x
-
⚾ PUT (수정)
- URI에 해당하는 데이터를 새로 만들거나 수정
- POST와 차이점
- POST의 URI는 보내는 데이터를 처리할 리소스를 지칭 : 같은 요청을 2번 보내면 매 번 새로운 2번의 응답 생성
- PUT의 URI는 보내는 데이터에 해당하는 리소스를 지칭 : 같은 요청을 2번 보내면 새로운 응답 1번 생성하고 그 뒤로 응답 x
- Idempotent
-
⚾ PATCH
- PUT과 비슷, 기존 엔티티와 새로운 데이터의 차이점만 보낸다
- Idempotent
-
⚾DELETE
- URI에 해당하는 리소스를 삭제
- Idempotent
URI 패턴
-
URI, URL, URN
- URI : Uniform Resource Identifier
- URL : Uniform Resource Location
- URN : Uniform Resource Name
-
SampleController
package me.jinmin.demowebmvc;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class SampleController {
@GetMapping({"/hello", "/hi"})
@ResponseBody
public String hello(){
return "hello";
}
}
-
SampleControllerTest
package me.jinmin.demowebmvc;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class SampleControllerTest {
@Autowired
MockMvc mockMvc;
@Test
public void helloTest() throws Exception {
mockMvc.perform(get("/hello"))
.andDo(print())
.andExpect(status().isOk());
mockMvc.perform(get("/hi"))
.andDo(print())
.andExpect(status().isOk());
}
}
- 테스트 성공
-
요청 식별자로 맵핑하기
@RequestMapping
은 아래와 같은 패턴 지원
?
: 한 글자 (“/author/???” ⇒ “/author/123”)
*
: 여러 글자 (“/author/*” ⇒ “/author/jinmin”)
**
: 여러 패스 (“/author/** ⇒ “/author/jinmin/book”)
-
클래스에 선언한 @RequestMapping
과 조합
-
클래스에 선언한 URI 패턴뒤에 이어 붙여서 맵핑
package me.jinmin.demowebmvc;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@RequestMapping("/hi")
public class SampleController {
@GetMapping("/hello")
@ResponseBody
public String hello(){
return "hello";
}
@GetMapping("/hello2")
@ResponseBody
public String hello2(){
return "hello2";
}
}
서버주소/hi/hello
서버주소/hi/hello2
(가능)
-
정규 표현식 맵핑 : @PathVariable
사용
-
SampleController
package me.jinmin.demowebmvc;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@RequestMapping("/hello")
public class SampleController {
@GetMapping("/{name:[a-z]+}")
@ResponseBody
public String hello(@PathVariable String name){
return "hello " + name;
}
}
-
SampleControllerTest
package me.jinmin.demowebmvc;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class SampleControllerTest {
@Autowired
MockMvc mockMvc;
@Test
public void helloTest() throws Exception {
mockMvc.perform(get("/hello/jinmin"))
.andDo(print())
.andExpect(status().isOk())
.andExpect(content().string("hello jinmin"));
}
}
- 테스트 성공
- IF,
mockMvc.perform(get("/hello/123456"))
⇒ 실패, 문자가 아닌 숫자가 왔기 때문에.
-
중복 패턴?
package me.jinmin.demowebmvc;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@RequestMapping("/hello")
public class SampleController {
@GetMapping("/jinmin")
@ResponseBody
public String helloJinmin(){
return "hello jinmin!";
}
@GetMapping("/*")
@ResponseBody
public String hello(){
return "hello jinmin";
}
}
서버 주소/hello/jinmin
확인, 결과는 "hello jinmin!"
- 가장 구체적으로 맵핑되는 핸들러를 선택
-
URI 확장자 맵핑 지원
- 권장 x (스프링 부트에서 기본적으로 해당 기능을 사용하지 않도록 설정)
- 보안 이슈 (Reflected File Download Attack)
- URI 변수, Path 매개변수, URI 인코딩 사용 시 불명확
미디어 타입
-
특정한 타입의 데이터를 담고 있는 요청만 처리하는 핸들러
-
Json을 보내는 요청만 처리하겠다. = @RequestMapping(consumes=MediaType.APPLICATION_JSON_UTF8_VALUE)
-
Content-Type
헤더로 필터링
-
오류 예제)
-
SampleController
package me.jinmin.demowebmvc;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class SampleController {
@GetMapping(value = "/hello", consumes = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public String hello(){
return "hello";
}
}
-
SampleControllerTest
package me.jinmin.demowebmvc;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class SampleControllerTest {
@Autowired
MockMvc mockMvc;
@Test
public void helloTest() throws Exception {
mockMvc.perform(get("/hello"))
.andDo(print())
.andExpect(status().isOk())
.andExpect(content().string("hello"));
}
}
-
결과
java.lang.AssertionError: Status
Expected :200
Actual :415 (Unsupported Media Type)
-
성공 예제)
-
SampleControllerTest
(테스트만 변경)
package me.jinmin.demowebmvc;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class SampleControllerTest {
@Autowired
MockMvc mockMvc;
@Test
public void helloTest() throws Exception {
mockMvc.perform(get("/hello")
.contentType(MediaType.APPLICATION_JSON_VALUE))
.andDo(print())
.andExpect(status().isOk())
.andExpect(content().string("hello"));
}
}
-
결과 : 성공
-
특정한 타입의 응답을 만드는 핸들러
-
Json만을 받는 응답 처리 = @RequestMapping(produces = "application/json")
-
Accept
헤더로 필터링 (⇒ Accept 헤더가 없더라도 테스트는 성공. Why? "어떠한 타입의 핸들러도 받아들인다" 라는 의미로 간주된다. 다만, Accept 헤더를 틀리게 명시하면 오류)
-
오류 예제)
-
SampleController
package me.jinmin.demowebmvc;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class SampleController {
@GetMapping(value = "/hello", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.TEXT_PLAIN_VALUE)
@ResponseBody
public String hello(){
return "hello";
}
}
-
SampleControllerTest
package me.jinmin.demowebmvc;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class SampleControllerTest {
@Autowired
MockMvc mockMvc;
@Test
public void helloTest() throws Exception {
mockMvc.perform(get("/hello")
.contentType(MediaType.APPLICATION_JSON_VALUE)
.accept(MediaType.APPLICATION_JSON_VALUE))
.andDo(print())
.andExpect(status().isOk())
.andExpect(content().string("hello"));
}
}
-
결과
java.lang.AssertionError: Status
Expected :200
Actual :406 (Not Acceptable)
-
성공 예제)
-
SampleControllerTest
package me.jinmin.demowebmvc;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class SampleControllerTest {
@Autowired
MockMvc mockMvc;
@Test
public void helloTest() throws Exception {
mockMvc.perform(get("/hello")
.contentType(MediaType.APPLICATION_JSON_VALUE)
.accept(MediaType.TEXT_PLAIN_VALUE))
.andDo(print())
.andExpect(status().isOk())
.andExpect(content().string("hello"));
}
}
-
클래스에 선언한 consumes
, produces
등은 메소드에서 잡아먹는다. (⇒ 메소드가 설정한 것이 실행된다. 오버라이딩)
-
🔔내 식대로 결론
Content-Type
: 요청 형식이 이렇다
Accept
: 응답 형식이 이렇다.
- 문자열 말고
MediaType
을 이용하자. (편하니까)
헤더와 매개변수
-
특정 헤더가 있는 요청을 처리하고 싶을 때 (headers
)
-
예제)
package me.jinmin.demowebmvc;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class SampleController {
@GetMapping(value = "/hello", headers = HttpHeaders.FROM)
//@GetMapping(value = "/hello", headers = HttpHeaders.AUTHORIZATION) //오류 : HttpHeaders.AUTHORIZATION 헤더가 없음
//@GetMapping(value = "/hello", headers = "!" + HttpHeaders.FROM) //오류 : HttpHeaders.FROM 헤더가 존재
//@GetMapping(value = "/hello", headers = HttpHeaders.FROM + "=" + "111") //오류 : 요청 HttpHeaders.FROM 헤더의 값이 "localhost"이기 때문에
@ResponseBody
public String hello(){
return "hello";
}
}
package me.jinmin.demowebmvc;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.HttpHeaders;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class SampleControllerTest {
@Autowired
MockMvc mockMvc;
@Test
public void helloTest() throws Exception {
mockMvc.perform(get("/hello")
.header(HttpHeaders.FROM, "localhost"))
.andDo(print())
.andExpect(status().isOk())
.andExpect(content().string("hello"));
}
}
-
특정 요청 매개변수를 갖는 요청을 처리하고 싶을 때 (params
)
-
예제)
package me.jinmin.demowebmvc;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class SampleController {
@GetMapping(value = "/hello", params = "name")
//@GetMapping(value = "/hello", params = "name=jinmin") //오류 : value 값이 맵핑이 안된다.
//@GetMapping(value = "/hello", params = "!name") //오류 : name이라는 param이 있기 때문에
@ResponseBody
public String hello(){
return "hello";
}
}
package me.jinmin.demowebmvc;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class SampleControllerTest {
@Autowired
MockMvc mockMvc;
@Test
public void helloTest() throws Exception {
mockMvc.perform(get("/hello")
.param("name","jinmin"))
.andDo(print())
.andExpect(status().isOk())
.andExpect(content().string("hello"));
}
}
HEAD와 OPTIONS
-
개발자가 구현하지 않아도 스프링 웹 MVC에서 자동으로 처리하는 HTTP Method
- HEAD
- OPTIONS
-
HEAD
-
GET과 동일
- But, 응답 본문을 받아오지 않고 응답 헤더만 받아온다.
-
공통 (SampleController
)
package me.jinmin.demowebmvc;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class SampleController {
@GetMapping("/hello")
@ResponseBody
public String hello(){
return "hello";
}
}
-
테스트 - GET
package me.jinmin.demowebmvc;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class SampleControllerTest {
@Autowired
MockMvc mockMvc;
@Test
public void helloTest() throws Exception {
mockMvc.perform(get("/hello"))
.andDo(print())
.andExpect(status().isOk())
.andExpect(content().string("hello"));
}
}
-
결과 (응답 본문)
MockHttpServletResponse:
Status = 200
Error message = null
Headers = [Content-Type:"text/plain;charset=UTF-8", Content-Length:"5"]
Content type = text/plain;charset=UTF-8
Body = hello
Forwarded URL = null
Redirected URL = null
Cookies = []
-
테스트 - HEAD
package me.jinmin.demowebmvc;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.head;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class SampleControllerTest {
@Autowired
MockMvc mockMvc;
@Test
public void helloTest() throws Exception {
mockMvc.perform(head("/hello"))
.andDo(print())
.andExpect(status().isOk());
}
}
-
결과 (응답 본문)
MockHttpServletResponse:
Status = 200
Error message = null
Headers = [Content-Type:"text/plain;charset=UTF-8", Content-Length:"5"]
Content type = text/plain;charset=UTF-8
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
-
Body
의 내용이 다르다. Why? 헤더 정보만 가져오기 때문에
-
OPTIONS
-
서버 또는 특정 리소스가 제공하는 기능을 확인할 수 있다.
-
서버는 Allow
응답 헤더에 사용할 수 있는 HTTP Method 목록을 제공해야 한다.
-
SampleController
(GET, POST 방식의 /hello
)
@Controller
public class SampleController {
@GetMapping("/hello")
@ResponseBody
public String hello(){
return "hello";
}
@PostMapping("/hello")
@ResponseBody
public String helloPost(){
return "hello";
}
}
-
SampleControllerTest
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class SampleControllerTest {
@Autowired
MockMvc mockMvc;
@Test
public void helloTest() throws Exception {
mockMvc.perform(options("/hello"))
.andDo(print())
.andExpect(status().isOk());
}
}
-
결과
MockHttpServletResponse:
Status = 200
Error message = null
Headers = [Allow:"GET,HEAD,POST,OPTIONS"]
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
- 직접 구성 : GET, POST
- 스프링 웹 MVC 기본 제공 : HEAD, OPTIONS
-
테스트 코드를 통해 특정 Header를 확인하는 여러 방법
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class SampleControllerTest {
@Autowired
MockMvc mockMvc;
@Test
public void helloTest() throws Exception {
mockMvc.perform(options("/hello"))
.andDo(print())
.andExpect(status().isOk())
.andExpect(header().exists(HttpHeaders.ALLOW))// Allow 헤더가 있는지
.andExpect(header().stringValues(HttpHeaders.ALLOW,
hasItems(
containsString("GET"),
containsString("POST"),
containsString("HEAD"),
containsString("OPTIONS")
))); //Allow 헤더에 위와 같은 값들이 들어있는지(순서 상관없이)
}
}
커스텀 애노테이션
-
메타 애노테이션
- 애노테이션에 사용 가능한 애노테이션(
@
위의 @
)
- 스프링이 제공하는 대부분의 애노테이션은 메타
@
-
조합(Composed) 애노테이션
- 한 개 혹은 여러 애노테이션의 조합
- 코드 간결화
- 구체적 의미 부여
-
예제)
-
@GetHelloMapping
package me.jinmin.demowebmvc;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@RequestMapping(method = RequestMethod.GET, value = "/hello")
public @interface GetHelloMapping {
}
-
SampleController
package me.jinmin.demowebmvc;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class SampleController {
@GetHelloMapping
@ResponseBody
public String hello(){
return "hello";
}
}
-
SampleControllerTest
package me.jinmin.demowebmvc;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class SampleControllerTest {
@Autowired
MockMvc mockMvc;
@Test
public void helloTest() throws Exception {
mockMvc.perform(get("/hello"))
.andDo(print())
.andExpect(status().isOk());
}
}
- 오류가 난다 Why??? 해결은 아래 내용부터‼
-
🎈 @Retention
-
해당 애노테이션 정보를 언제까지 유지할 것인가
Source
: 소스 코드까지만 유지. (⇒ 컴파일 시 정보가 사라짐, 그저 //
(주석))
Class
(기본값) : 컴파일 한 .class
파일에도 유지 (⇒ 런타임 시, 클래스를 메모리로 읽어오면 사라짐)
Runtime
: 클래스를 메모리로 읽어올 때까지 유지 (⇒ 코드에서 해당 정보를 바탕으로 특정 로직 수행, 애플리케이션이 구동될 때도 유지)
-
@GetHelloMapping
package me.jinmin.demowebmvc;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
@RequestMapping(method = RequestMethod.GET, value = "/hello")
public @interface GetHelloMapping {
}
- 테스트 구동 성공
-
@Target
-
해당 애노테이션을 어디에 사용할 수 있는지
package me.jinmin.demowebmvc;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD) // 배열로 여러 곳을 지정할 수 있다.
@Retention(RetentionPolicy.RUNTIME)
@RequestMapping(method = RequestMethod.GET, value = "/hello")
public @interface GetHelloMapping {
}
-
@Documented
- 해당 애노테이션을 사용한 코드의 문서에 그 애노테이션에 대한 정보를 표기할지 결정
Author And Source
이 문제에 관하여([스프링 웹 MVC] 7. 스프링 MVC 활용 1부 (요청 맵핑하기)), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다
https://velog.io/@jinmin2216/7.-스프링-MVC-활용-1부-요청-맵핑하기
저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념
(Collection and Share based on the CC Protocol.)
URI, URL, URN
- URI : Uniform Resource Identifier
- URL : Uniform Resource Location
- URN : Uniform Resource Name
SampleController
package me.jinmin.demowebmvc;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class SampleController {
@GetMapping({"/hello", "/hi"})
@ResponseBody
public String hello(){
return "hello";
}
}
SampleControllerTest
package me.jinmin.demowebmvc;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class SampleControllerTest {
@Autowired
MockMvc mockMvc;
@Test
public void helloTest() throws Exception {
mockMvc.perform(get("/hello"))
.andDo(print())
.andExpect(status().isOk());
mockMvc.perform(get("/hi"))
.andDo(print())
.andExpect(status().isOk());
}
}
- 테스트 성공
요청 식별자로 맵핑하기
@RequestMapping
은 아래와 같은 패턴 지원?
: 한 글자 (“/author/???” ⇒ “/author/123”)*
: 여러 글자 (“/author/*” ⇒ “/author/jinmin”)**
: 여러 패스 (“/author/** ⇒ “/author/jinmin/book”)
클래스에 선언한 @RequestMapping
과 조합
-
클래스에 선언한 URI 패턴뒤에 이어 붙여서 맵핑
package me.jinmin.demowebmvc; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; @Controller @RequestMapping("/hi") public class SampleController { @GetMapping("/hello") @ResponseBody public String hello(){ return "hello"; } @GetMapping("/hello2") @ResponseBody public String hello2(){ return "hello2"; } }
서버주소/hi/hello 서버주소/hi/hello2 (가능)
정규 표현식 맵핑 : @PathVariable
사용
-
SampleController
package me.jinmin.demowebmvc; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; @Controller @RequestMapping("/hello") public class SampleController { @GetMapping("/{name:[a-z]+}") @ResponseBody public String hello(@PathVariable String name){ return "hello " + name; } }
-
SampleControllerTest
package me.jinmin.demowebmvc; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.web.servlet.MockMvc; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @RunWith(SpringRunner.class) @SpringBootTest @AutoConfigureMockMvc public class SampleControllerTest { @Autowired MockMvc mockMvc; @Test public void helloTest() throws Exception { mockMvc.perform(get("/hello/jinmin")) .andDo(print()) .andExpect(status().isOk()) .andExpect(content().string("hello jinmin")); } }
- 테스트 성공
- IF,
mockMvc.perform(get("/hello/123456"))
⇒ 실패, 문자가 아닌 숫자가 왔기 때문에.
중복 패턴?
package me.jinmin.demowebmvc;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@RequestMapping("/hello")
public class SampleController {
@GetMapping("/jinmin")
@ResponseBody
public String helloJinmin(){
return "hello jinmin!";
}
@GetMapping("/*")
@ResponseBody
public String hello(){
return "hello jinmin";
}
}
서버 주소/hello/jinmin
확인, 결과는 "hello jinmin!"- 가장 구체적으로 맵핑되는 핸들러를 선택
URI 확장자 맵핑 지원
- 권장 x (스프링 부트에서 기본적으로 해당 기능을 사용하지 않도록 설정)
- 보안 이슈 (Reflected File Download Attack)
- URI 변수, Path 매개변수, URI 인코딩 사용 시 불명확
-
특정한 타입의 데이터를 담고 있는 요청만 처리하는 핸들러
-
Json을 보내는 요청만 처리하겠다. =
@RequestMapping(consumes=MediaType.APPLICATION_JSON_UTF8_VALUE)
-
Content-Type
헤더로 필터링 -
오류 예제)
-
SampleController
package me.jinmin.demowebmvc; import org.springframework.http.MediaType; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.ResponseBody; @Controller public class SampleController { @GetMapping(value = "/hello", consumes = MediaType.APPLICATION_JSON_VALUE) @ResponseBody public String hello(){ return "hello"; } }
-
SampleControllerTest
package me.jinmin.demowebmvc; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.web.servlet.MockMvc; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @RunWith(SpringRunner.class) @SpringBootTest @AutoConfigureMockMvc public class SampleControllerTest { @Autowired MockMvc mockMvc; @Test public void helloTest() throws Exception { mockMvc.perform(get("/hello")) .andDo(print()) .andExpect(status().isOk()) .andExpect(content().string("hello")); } }
-
결과
java.lang.AssertionError: Status Expected :200 Actual :415 (Unsupported Media Type)
-
-
성공 예제)
-
SampleControllerTest
(테스트만 변경)package me.jinmin.demowebmvc; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.http.MediaType; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.web.servlet.MockMvc; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @RunWith(SpringRunner.class) @SpringBootTest @AutoConfigureMockMvc public class SampleControllerTest { @Autowired MockMvc mockMvc; @Test public void helloTest() throws Exception { mockMvc.perform(get("/hello") .contentType(MediaType.APPLICATION_JSON_VALUE)) .andDo(print()) .andExpect(status().isOk()) .andExpect(content().string("hello")); } }
-
결과 : 성공
-
-
-
특정한 타입의 응답을 만드는 핸들러
-
Json만을 받는 응답 처리 =
@RequestMapping(produces = "application/json")
-
Accept
헤더로 필터링 (⇒ Accept 헤더가 없더라도 테스트는 성공. Why? "어떠한 타입의 핸들러도 받아들인다" 라는 의미로 간주된다. 다만, Accept 헤더를 틀리게 명시하면 오류) -
오류 예제)
-
SampleController
package me.jinmin.demowebmvc; import org.springframework.http.MediaType; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.ResponseBody; @Controller public class SampleController { @GetMapping(value = "/hello", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.TEXT_PLAIN_VALUE) @ResponseBody public String hello(){ return "hello"; } }
-
SampleControllerTest
package me.jinmin.demowebmvc; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.http.MediaType; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.web.servlet.MockMvc; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @RunWith(SpringRunner.class) @SpringBootTest @AutoConfigureMockMvc public class SampleControllerTest { @Autowired MockMvc mockMvc; @Test public void helloTest() throws Exception { mockMvc.perform(get("/hello") .contentType(MediaType.APPLICATION_JSON_VALUE) .accept(MediaType.APPLICATION_JSON_VALUE)) .andDo(print()) .andExpect(status().isOk()) .andExpect(content().string("hello")); } }
-
결과
java.lang.AssertionError: Status Expected :200 Actual :406 (Not Acceptable)
-
-
성공 예제)
-
SampleControllerTest
package me.jinmin.demowebmvc; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.http.MediaType; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.web.servlet.MockMvc; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @RunWith(SpringRunner.class) @SpringBootTest @AutoConfigureMockMvc public class SampleControllerTest { @Autowired MockMvc mockMvc; @Test public void helloTest() throws Exception { mockMvc.perform(get("/hello") .contentType(MediaType.APPLICATION_JSON_VALUE) .accept(MediaType.TEXT_PLAIN_VALUE)) .andDo(print()) .andExpect(status().isOk()) .andExpect(content().string("hello")); } }
-
-
-
클래스에 선언한
consumes
,produces
등은 메소드에서 잡아먹는다. (⇒ 메소드가 설정한 것이 실행된다. 오버라이딩) -
🔔내 식대로 결론
Content-Type
: 요청 형식이 이렇다Accept
: 응답 형식이 이렇다.- 문자열 말고
MediaType
을 이용하자. (편하니까)
헤더와 매개변수
-
특정 헤더가 있는 요청을 처리하고 싶을 때 (headers
)
-
예제)
package me.jinmin.demowebmvc;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class SampleController {
@GetMapping(value = "/hello", headers = HttpHeaders.FROM)
//@GetMapping(value = "/hello", headers = HttpHeaders.AUTHORIZATION) //오류 : HttpHeaders.AUTHORIZATION 헤더가 없음
//@GetMapping(value = "/hello", headers = "!" + HttpHeaders.FROM) //오류 : HttpHeaders.FROM 헤더가 존재
//@GetMapping(value = "/hello", headers = HttpHeaders.FROM + "=" + "111") //오류 : 요청 HttpHeaders.FROM 헤더의 값이 "localhost"이기 때문에
@ResponseBody
public String hello(){
return "hello";
}
}
package me.jinmin.demowebmvc;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.HttpHeaders;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class SampleControllerTest {
@Autowired
MockMvc mockMvc;
@Test
public void helloTest() throws Exception {
mockMvc.perform(get("/hello")
.header(HttpHeaders.FROM, "localhost"))
.andDo(print())
.andExpect(status().isOk())
.andExpect(content().string("hello"));
}
}
-
특정 요청 매개변수를 갖는 요청을 처리하고 싶을 때 (params
)
-
예제)
package me.jinmin.demowebmvc;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class SampleController {
@GetMapping(value = "/hello", params = "name")
//@GetMapping(value = "/hello", params = "name=jinmin") //오류 : value 값이 맵핑이 안된다.
//@GetMapping(value = "/hello", params = "!name") //오류 : name이라는 param이 있기 때문에
@ResponseBody
public String hello(){
return "hello";
}
}
package me.jinmin.demowebmvc;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class SampleControllerTest {
@Autowired
MockMvc mockMvc;
@Test
public void helloTest() throws Exception {
mockMvc.perform(get("/hello")
.param("name","jinmin"))
.andDo(print())
.andExpect(status().isOk())
.andExpect(content().string("hello"));
}
}
HEAD와 OPTIONS
-
개발자가 구현하지 않아도 스프링 웹 MVC에서 자동으로 처리하는 HTTP Method
- HEAD
- OPTIONS
-
HEAD
-
GET과 동일
- But, 응답 본문을 받아오지 않고 응답 헤더만 받아온다.
-
공통 (SampleController
)
package me.jinmin.demowebmvc;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class SampleController {
@GetMapping("/hello")
@ResponseBody
public String hello(){
return "hello";
}
}
-
테스트 - GET
package me.jinmin.demowebmvc;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class SampleControllerTest {
@Autowired
MockMvc mockMvc;
@Test
public void helloTest() throws Exception {
mockMvc.perform(get("/hello"))
.andDo(print())
.andExpect(status().isOk())
.andExpect(content().string("hello"));
}
}
-
결과 (응답 본문)
MockHttpServletResponse:
Status = 200
Error message = null
Headers = [Content-Type:"text/plain;charset=UTF-8", Content-Length:"5"]
Content type = text/plain;charset=UTF-8
Body = hello
Forwarded URL = null
Redirected URL = null
Cookies = []
-
테스트 - HEAD
package me.jinmin.demowebmvc;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.head;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class SampleControllerTest {
@Autowired
MockMvc mockMvc;
@Test
public void helloTest() throws Exception {
mockMvc.perform(head("/hello"))
.andDo(print())
.andExpect(status().isOk());
}
}
-
결과 (응답 본문)
MockHttpServletResponse:
Status = 200
Error message = null
Headers = [Content-Type:"text/plain;charset=UTF-8", Content-Length:"5"]
Content type = text/plain;charset=UTF-8
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
-
Body
의 내용이 다르다. Why? 헤더 정보만 가져오기 때문에
-
OPTIONS
-
서버 또는 특정 리소스가 제공하는 기능을 확인할 수 있다.
-
서버는 Allow
응답 헤더에 사용할 수 있는 HTTP Method 목록을 제공해야 한다.
-
SampleController
(GET, POST 방식의 /hello
)
@Controller
public class SampleController {
@GetMapping("/hello")
@ResponseBody
public String hello(){
return "hello";
}
@PostMapping("/hello")
@ResponseBody
public String helloPost(){
return "hello";
}
}
-
SampleControllerTest
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class SampleControllerTest {
@Autowired
MockMvc mockMvc;
@Test
public void helloTest() throws Exception {
mockMvc.perform(options("/hello"))
.andDo(print())
.andExpect(status().isOk());
}
}
-
결과
MockHttpServletResponse:
Status = 200
Error message = null
Headers = [Allow:"GET,HEAD,POST,OPTIONS"]
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
- 직접 구성 : GET, POST
- 스프링 웹 MVC 기본 제공 : HEAD, OPTIONS
-
테스트 코드를 통해 특정 Header를 확인하는 여러 방법
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class SampleControllerTest {
@Autowired
MockMvc mockMvc;
@Test
public void helloTest() throws Exception {
mockMvc.perform(options("/hello"))
.andDo(print())
.andExpect(status().isOk())
.andExpect(header().exists(HttpHeaders.ALLOW))// Allow 헤더가 있는지
.andExpect(header().stringValues(HttpHeaders.ALLOW,
hasItems(
containsString("GET"),
containsString("POST"),
containsString("HEAD"),
containsString("OPTIONS")
))); //Allow 헤더에 위와 같은 값들이 들어있는지(순서 상관없이)
}
}
커스텀 애노테이션
-
메타 애노테이션
- 애노테이션에 사용 가능한 애노테이션(
@
위의 @
)
- 스프링이 제공하는 대부분의 애노테이션은 메타
@
-
조합(Composed) 애노테이션
- 한 개 혹은 여러 애노테이션의 조합
- 코드 간결화
- 구체적 의미 부여
-
예제)
-
@GetHelloMapping
package me.jinmin.demowebmvc;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@RequestMapping(method = RequestMethod.GET, value = "/hello")
public @interface GetHelloMapping {
}
-
SampleController
package me.jinmin.demowebmvc;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class SampleController {
@GetHelloMapping
@ResponseBody
public String hello(){
return "hello";
}
}
-
SampleControllerTest
package me.jinmin.demowebmvc;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class SampleControllerTest {
@Autowired
MockMvc mockMvc;
@Test
public void helloTest() throws Exception {
mockMvc.perform(get("/hello"))
.andDo(print())
.andExpect(status().isOk());
}
}
- 오류가 난다 Why??? 해결은 아래 내용부터‼
-
🎈 @Retention
-
해당 애노테이션 정보를 언제까지 유지할 것인가
Source
: 소스 코드까지만 유지. (⇒ 컴파일 시 정보가 사라짐, 그저 //
(주석))
Class
(기본값) : 컴파일 한 .class
파일에도 유지 (⇒ 런타임 시, 클래스를 메모리로 읽어오면 사라짐)
Runtime
: 클래스를 메모리로 읽어올 때까지 유지 (⇒ 코드에서 해당 정보를 바탕으로 특정 로직 수행, 애플리케이션이 구동될 때도 유지)
-
@GetHelloMapping
package me.jinmin.demowebmvc;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
@RequestMapping(method = RequestMethod.GET, value = "/hello")
public @interface GetHelloMapping {
}
- 테스트 구동 성공
-
@Target
-
해당 애노테이션을 어디에 사용할 수 있는지
package me.jinmin.demowebmvc;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD) // 배열로 여러 곳을 지정할 수 있다.
@Retention(RetentionPolicy.RUNTIME)
@RequestMapping(method = RequestMethod.GET, value = "/hello")
public @interface GetHelloMapping {
}
-
@Documented
- 해당 애노테이션을 사용한 코드의 문서에 그 애노테이션에 대한 정보를 표기할지 결정
Author And Source
이 문제에 관하여([스프링 웹 MVC] 7. 스프링 MVC 활용 1부 (요청 맵핑하기)), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다
https://velog.io/@jinmin2216/7.-스프링-MVC-활용-1부-요청-맵핑하기
저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념
(Collection and Share based on the CC Protocol.)
특정 헤더가 있는 요청을 처리하고 싶을 때 (headers
)
-
예제)
package me.jinmin.demowebmvc; import org.springframework.http.HttpHeaders; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.ResponseBody; @Controller public class SampleController { @GetMapping(value = "/hello", headers = HttpHeaders.FROM) //@GetMapping(value = "/hello", headers = HttpHeaders.AUTHORIZATION) //오류 : HttpHeaders.AUTHORIZATION 헤더가 없음 //@GetMapping(value = "/hello", headers = "!" + HttpHeaders.FROM) //오류 : HttpHeaders.FROM 헤더가 존재 //@GetMapping(value = "/hello", headers = HttpHeaders.FROM + "=" + "111") //오류 : 요청 HttpHeaders.FROM 헤더의 값이 "localhost"이기 때문에 @ResponseBody public String hello(){ return "hello"; } }
package me.jinmin.demowebmvc; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.http.HttpHeaders; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.web.servlet.MockMvc; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @RunWith(SpringRunner.class) @SpringBootTest @AutoConfigureMockMvc public class SampleControllerTest { @Autowired MockMvc mockMvc; @Test public void helloTest() throws Exception { mockMvc.perform(get("/hello") .header(HttpHeaders.FROM, "localhost")) .andDo(print()) .andExpect(status().isOk()) .andExpect(content().string("hello")); } }
특정 요청 매개변수를 갖는 요청을 처리하고 싶을 때 (params
)
-
예제)
package me.jinmin.demowebmvc; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.ResponseBody; @Controller public class SampleController { @GetMapping(value = "/hello", params = "name") //@GetMapping(value = "/hello", params = "name=jinmin") //오류 : value 값이 맵핑이 안된다. //@GetMapping(value = "/hello", params = "!name") //오류 : name이라는 param이 있기 때문에 @ResponseBody public String hello(){ return "hello"; } }
package me.jinmin.demowebmvc; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.web.servlet.MockMvc; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @RunWith(SpringRunner.class) @SpringBootTest @AutoConfigureMockMvc public class SampleControllerTest { @Autowired MockMvc mockMvc; @Test public void helloTest() throws Exception { mockMvc.perform(get("/hello") .param("name","jinmin")) .andDo(print()) .andExpect(status().isOk()) .andExpect(content().string("hello")); } }
-
개발자가 구현하지 않아도 스프링 웹 MVC에서 자동으로 처리하는 HTTP Method
- HEAD
- OPTIONS
-
HEAD
-
GET과 동일
- But, 응답 본문을 받아오지 않고 응답 헤더만 받아온다.
-
공통 (
SampleController
)package me.jinmin.demowebmvc; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.ResponseBody; @Controller public class SampleController { @GetMapping("/hello") @ResponseBody public String hello(){ return "hello"; } }
-
테스트 - GET
package me.jinmin.demowebmvc; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.web.servlet.MockMvc; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @RunWith(SpringRunner.class) @SpringBootTest @AutoConfigureMockMvc public class SampleControllerTest { @Autowired MockMvc mockMvc; @Test public void helloTest() throws Exception { mockMvc.perform(get("/hello")) .andDo(print()) .andExpect(status().isOk()) .andExpect(content().string("hello")); } }
-
결과 (응답 본문)
MockHttpServletResponse: Status = 200 Error message = null Headers = [Content-Type:"text/plain;charset=UTF-8", Content-Length:"5"] Content type = text/plain;charset=UTF-8 Body = hello Forwarded URL = null Redirected URL = null Cookies = []
-
-
테스트 - HEAD
package me.jinmin.demowebmvc; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.web.servlet.MockMvc; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.head; import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @RunWith(SpringRunner.class) @SpringBootTest @AutoConfigureMockMvc public class SampleControllerTest { @Autowired MockMvc mockMvc; @Test public void helloTest() throws Exception { mockMvc.perform(head("/hello")) .andDo(print()) .andExpect(status().isOk()); } }
-
결과 (응답 본문)
MockHttpServletResponse: Status = 200 Error message = null Headers = [Content-Type:"text/plain;charset=UTF-8", Content-Length:"5"] Content type = text/plain;charset=UTF-8 Body = Forwarded URL = null Redirected URL = null Cookies = []
-
Body
의 내용이 다르다. Why? 헤더 정보만 가져오기 때문에
-
-
-
OPTIONS
-
서버 또는 특정 리소스가 제공하는 기능을 확인할 수 있다.
-
서버는
Allow
응답 헤더에 사용할 수 있는 HTTP Method 목록을 제공해야 한다. -
SampleController
(GET, POST 방식의/hello
)@Controller public class SampleController { @GetMapping("/hello") @ResponseBody public String hello(){ return "hello"; } @PostMapping("/hello") @ResponseBody public String helloPost(){ return "hello"; } }
-
SampleControllerTest
@RunWith(SpringRunner.class) @SpringBootTest @AutoConfigureMockMvc public class SampleControllerTest { @Autowired MockMvc mockMvc; @Test public void helloTest() throws Exception { mockMvc.perform(options("/hello")) .andDo(print()) .andExpect(status().isOk()); } }
-
결과
MockHttpServletResponse: Status = 200 Error message = null Headers = [Allow:"GET,HEAD,POST,OPTIONS"] Content type = null Body = Forwarded URL = null Redirected URL = null Cookies = []
- 직접 구성 : GET, POST
- 스프링 웹 MVC 기본 제공 : HEAD, OPTIONS
-
-
테스트 코드를 통해 특정 Header를 확인하는 여러 방법
@RunWith(SpringRunner.class) @SpringBootTest @AutoConfigureMockMvc public class SampleControllerTest { @Autowired MockMvc mockMvc; @Test public void helloTest() throws Exception { mockMvc.perform(options("/hello")) .andDo(print()) .andExpect(status().isOk()) .andExpect(header().exists(HttpHeaders.ALLOW))// Allow 헤더가 있는지 .andExpect(header().stringValues(HttpHeaders.ALLOW, hasItems( containsString("GET"), containsString("POST"), containsString("HEAD"), containsString("OPTIONS") ))); //Allow 헤더에 위와 같은 값들이 들어있는지(순서 상관없이) } }
커스텀 애노테이션
-
메타 애노테이션
- 애노테이션에 사용 가능한 애노테이션(
@
위의 @
)
- 스프링이 제공하는 대부분의 애노테이션은 메타
@
-
조합(Composed) 애노테이션
- 한 개 혹은 여러 애노테이션의 조합
- 코드 간결화
- 구체적 의미 부여
-
예제)
-
@GetHelloMapping
package me.jinmin.demowebmvc;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@RequestMapping(method = RequestMethod.GET, value = "/hello")
public @interface GetHelloMapping {
}
-
SampleController
package me.jinmin.demowebmvc;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class SampleController {
@GetHelloMapping
@ResponseBody
public String hello(){
return "hello";
}
}
-
SampleControllerTest
package me.jinmin.demowebmvc;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class SampleControllerTest {
@Autowired
MockMvc mockMvc;
@Test
public void helloTest() throws Exception {
mockMvc.perform(get("/hello"))
.andDo(print())
.andExpect(status().isOk());
}
}
- 오류가 난다 Why??? 해결은 아래 내용부터‼
-
🎈 @Retention
-
해당 애노테이션 정보를 언제까지 유지할 것인가
Source
: 소스 코드까지만 유지. (⇒ 컴파일 시 정보가 사라짐, 그저 //
(주석))
Class
(기본값) : 컴파일 한 .class
파일에도 유지 (⇒ 런타임 시, 클래스를 메모리로 읽어오면 사라짐)
Runtime
: 클래스를 메모리로 읽어올 때까지 유지 (⇒ 코드에서 해당 정보를 바탕으로 특정 로직 수행, 애플리케이션이 구동될 때도 유지)
-
@GetHelloMapping
package me.jinmin.demowebmvc;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
@RequestMapping(method = RequestMethod.GET, value = "/hello")
public @interface GetHelloMapping {
}
- 테스트 구동 성공
-
@Target
-
해당 애노테이션을 어디에 사용할 수 있는지
package me.jinmin.demowebmvc;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD) // 배열로 여러 곳을 지정할 수 있다.
@Retention(RetentionPolicy.RUNTIME)
@RequestMapping(method = RequestMethod.GET, value = "/hello")
public @interface GetHelloMapping {
}
-
@Documented
- 해당 애노테이션을 사용한 코드의 문서에 그 애노테이션에 대한 정보를 표기할지 결정
Author And Source
이 문제에 관하여([스프링 웹 MVC] 7. 스프링 MVC 활용 1부 (요청 맵핑하기)), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다
https://velog.io/@jinmin2216/7.-스프링-MVC-활용-1부-요청-맵핑하기
저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념
(Collection and Share based on the CC Protocol.)
메타 애노테이션
- 애노테이션에 사용 가능한 애노테이션(
@
위의@
) - 스프링이 제공하는 대부분의 애노테이션은 메타
@
조합(Composed) 애노테이션
- 한 개 혹은 여러 애노테이션의 조합
- 코드 간결화
- 구체적 의미 부여
예제)
-
@GetHelloMapping
package me.jinmin.demowebmvc; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @RequestMapping(method = RequestMethod.GET, value = "/hello") public @interface GetHelloMapping { }
-
SampleController
package me.jinmin.demowebmvc; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.ResponseBody; @Controller public class SampleController { @GetHelloMapping @ResponseBody public String hello(){ return "hello"; } }
-
SampleControllerTest
package me.jinmin.demowebmvc; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.web.servlet.MockMvc; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @RunWith(SpringRunner.class) @SpringBootTest @AutoConfigureMockMvc public class SampleControllerTest { @Autowired MockMvc mockMvc; @Test public void helloTest() throws Exception { mockMvc.perform(get("/hello")) .andDo(print()) .andExpect(status().isOk()); } }
- 오류가 난다 Why??? 해결은 아래 내용부터‼
🎈 @Retention
-
해당 애노테이션 정보를 언제까지 유지할 것인가
Source
: 소스 코드까지만 유지. (⇒ 컴파일 시 정보가 사라짐, 그저//
(주석))Class
(기본값) : 컴파일 한.class
파일에도 유지 (⇒ 런타임 시, 클래스를 메모리로 읽어오면 사라짐)Runtime
: 클래스를 메모리로 읽어올 때까지 유지 (⇒ 코드에서 해당 정보를 바탕으로 특정 로직 수행, 애플리케이션이 구동될 때도 유지)
-
@GetHelloMapping
package me.jinmin.demowebmvc; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @Retention(RetentionPolicy.RUNTIME) @RequestMapping(method = RequestMethod.GET, value = "/hello") public @interface GetHelloMapping { }
- 테스트 구동 성공
@Target
-
해당 애노테이션을 어디에 사용할 수 있는지
package me.jinmin.demowebmvc; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target(ElementType.METHOD) // 배열로 여러 곳을 지정할 수 있다. @Retention(RetentionPolicy.RUNTIME) @RequestMapping(method = RequestMethod.GET, value = "/hello") public @interface GetHelloMapping { }
@Documented
- 해당 애노테이션을 사용한 코드의 문서에 그 애노테이션에 대한 정보를 표기할지 결정
Author And Source
이 문제에 관하여([스프링 웹 MVC] 7. 스프링 MVC 활용 1부 (요청 맵핑하기)), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@jinmin2216/7.-스프링-MVC-활용-1부-요청-맵핑하기저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)