BackEnd/Spring MVC

[Spring] 스프링 MVC - 기본 기능 - 1

샤아이인 2022. 2. 26.

내가 공부한것을 올리며, 중요한 단원은 저 자신도 곱씹어 볼겸 상세히 기록하고 얕은부분들은 가겹게 포스팅 하겠습니다.

 

1. 요청 매핑

● MappingController

@RestController
public class MappingController {
    private Logger log = LoggerFactory.getLogger(getClass());

    /**
    * 기본 요청
    * 둘다 허용 /hello-basic, /hello-basic/
    * HTTP 메서드 모두 허용 GET, HEAD, POST, PUT, PATCH, DELETE 
    */
    @RequestMapping("/hello-basic")
    public String helloBasic() {
        log.info("helloBasic");
        return "ok";
    }
}
 

@RestController

- @Controller 는 반환값이 String이면 view이름으로 인식되어, viewResolver를 통하여 뷰를 찾고 랜더링 된다.

- @RestController는 반환값으로 뷰를 찾는것이 아니라, HTTP 메시지 바디에 바로 입력하여 보낸다.

따라서 위 코드 실행시 바로 "ok" 메시지를 받을 수 있다.

 

@RequestMapping("/hello-basic")

- /hello-basic URL 호출이 오면 이 메서드가 실행되도록 매핑한다.

- 대부분의 속성을 배열[] 로 제공하므로 다중 설정이 가능하다. {"/hello-basic", "/hello-go"}

 

다음 두가지 요청은 다른 URL이지만, 스프링은 다음 URL 요청들을 같은 요청으로 매핑한다.

매핑: /hello-basic

URL 요청: /hello-basic , /hello-basic/

 

● HTTP 메서드 매핑 축약

@GetMapping(value = "/mapping-get-v2")
public String mappingGetV2() {
    log.info("mapping-get-v2");
    return "ok";
}
 

GET, POST, PUT, DELETE, PATCH 등에 대한 Mapping도 있다.

 

● PathVariable(경로 변수) 사용

/**
* PathVariable 사용
* 변수명이 같으면 생략 가능
* @PathVariable("userId") String userId -> @PathVariable userId 
*/
@GetMapping("/mapping/{userId}")
public String mappingPath(@PathVariable("userId") String data) {
    log.info("mappingPath userId={}", data);
    return "ok";
}
 

최근 많이 사용되는 스타일 이다. HTTP API는 다음과 같이 리소스경로에 식별자를 넣는 스타일을 선호한다.

- /mapping/userA

- /users/1

 

@RequestMapping 은 URL 경로를 템플릿화 할 수 있는데, @PathVariable 을 사용하면 매칭 되는 부분을 편리하게 조회할 수 있다.

또한 @PathVariable 의 이름과 파라미터 이름이 같으면 생략할 수 있다.

 

예를 들어 위 코드에서 파라미터 부분을 다음과 같이 변경할수가 있다.

@GetMapping("/mapping/{userId}")
public String mappingPath(@PathVariable String userId) {
    // 생략
}

 

PathVariable은 또한 다중 경로변수를 사용할 수 있다. 다음과 같이 말이다.

@GetMapping("/mapping/users/{userId}/orders/{orderId}")
public String mappingPath(@PathVariable String userId, @PathVariable Long orderId) {
    log.info("mappingPath userId={}, orderId={}", userId, orderId);
    return "ok";
}
 

 

● 특정 파라미터 조건 매핑

@GetMapping(value = "/mapping-param", params = "mode=debug")
public String mappingParam() {
    log.info("mappingParam");
    return "ok";
}
 

http://localhost:8080/mapping-param?mode=debug 과 같이 요청한다면,

요청 파라미터에 mode=debug 가 있어야지만 해당 메서드를 실행할 수 있다.

 

● 특정 헤더 조건 매핑

/**
*특정 헤더로 추가 매핑
* headers="mode",
* headers="!mode"
* headers="mode=debug"
* headers="mode!=debug" (! = )
*/
@GetMapping(value = "/mapping-header", headers = "mode=debug")
public String mappingHeader() {
    log.info("mappingHeader");
    return "ok";
}
 

직전 파라미터 매핑과 비슷하지만, HTTP 헤더를 통하여 mode=debug 값을 지정해야만 해당 메서드를 사용할 수 있다.

 

● 미디어 타입 조건 매핑 - HTTP 요청 Content-Type, consume

/**
* Content-Type 헤더 기반 추가 매핑 Media Type 
* consumes="application/json"
* consumes="!application/json"
* consumes="application/*"
* consumes="*\/*"
* MediaType.APPLICATION_JSON_VALUE
*/
@PostMapping(value = "/mapping-consume", consumes = "application/json")
public String mappingConsumes() {
    log.info("mappingConsumes");
    return "ok";
}
 

HTTP 요청의 Content-Type 헤더를 기반으로 미디어 타입으로 매핑한다.

즉 메시지를 받는 입장인 서버에서 데이터가 application/json 형태로 넘어오는 데이터만 받겠다는 의미이다.

 

● 미디어 타입 조건 매핑 - HTTP 요청 Accept, produce

/**
* Accept 헤더 기반 Media Type 
* produces = "text/html"
* produces = "!text/html" 
* produces = "text/*"
* produces = "*\/*"
*/
@PostMapping(value = "/mapping-produce", produces = "text/html")
public String mappingProduces() {
    log.info("mappingProduces");
    return "ok";
}
 

HTTP 요청의 Accept 헤더를 기반으로 미디어 타입으로 매핑한다.

즉, 클라이언트가 메시지를 보낼때 어떤 타입의 데이터를 받을수 있는지 Accept 헤더에 기입할수 있다.

클라이언트가 나는 "text/html" 인 데이터를 받아들일 수 있다고 말했기 때문에, test/html 형태의 데이터를 반환하는 해당 메서드는 실행될수 있는것 이다.

 

2. HTTP 요청 - 기본, 헤더 조회

애노테이션 기반의 스프링 컨트롤러는 다양한 파라미터를 지원한다. 이번 시간에는 HTTP 헤더 정보를 조회하는 방법에 대하여 알아보자.

@Slf4j
@RestController
public class RequestHeaderController {

    @RequestMapping("/headers")
    public String headers(HttpServletRequest request, HttpServletResponse response,
                          HttpMethod httpMethod, Locale locale,
                          @RequestHeader MultiValueMap<String, String> headerMap,
                          @RequestHeader("host") String host,
                          @CookieValue(value="myCookie", required = false) String cookie){

        log.info("request={}", request);
        log.info("response={}", response);
        log.info("httpMethod={}", httpMethod);
        log.info("locale={}", locale);
        log.info("headerMap={}", headerMap);
        log.info("header host={}", host);
        log.info("myCookie={}", cookie);

        return "OK";
    }
}
 

결과는 다음과 같다.

- request, response 객체를 확인할 수 있다.

- HTTP method가 무엇인지 또한 확인 가능하다.

- Locale 정보를 조회하여 언어를 확인할수 있다.

- MultiValueMap은 모든 HTTP헤더를 MultiValueMap 형식으로 조회한다.

- @RequestHeader("host") String host => 특정 헤더를 조회한다. 속성으로는 값의 필수여부, 기본값 등이 있다.

- 쿠키또한 조회할수 있다.

 

● MultiValueMap

MAP과 유사한데, 하나의 키에 여러 값을 받을 수 있다.

HTTP header, HTTP 쿼리 파라미터와 같이 하나의 키에 여러 값을 받을 때 사용한다.

MultiValueMap<String, String> map = new LinkedMultiValueMap();
map.add("keyA", "value1");
map.add("keyA", "value2");

//[value1,value2]
List<String> values = map.get("keyA");
 

3. HTTP 요청 파라미터 - 쿼리 파라미터, HTML Form

클라이언트가 서버로 데이터를 전달할때는 주로 다음 3가지 방법을 사용한다.

- GET : 쿼리 파라미터

- POST : HTML Form

- HTTP message body에 데이터를 직접 담아서 전달

 

요청 파라미터 - 쿼리 파라미터, HTML Form

HttpServletRequest 의 request.getParameter() 를 사용하면 다음 두가지 요청 파라미터를 모두 조회할 수 있다.

 

1) GET 쿼리 파라미터 전송

http://localhost:8080/request-param?username=hello&age=20

 

2) POST를 통한 메시지 바디 전송

POST /request-param ...
content-type: application/x-www-form-urlencoded

username=hello&age=20
 

GET 쿼리 파리미터 전송 방식이든, POST HTML Form 전송 방식이든 둘다 형식이 같으므로 구분없이 조회할 수 있다.

둘다 getParameter() 메서드로 인자를 추출할수 있는것이다.

 

이것을 간단히 요청 파라미터(request parameter) 조회라 한다.

 

● requestParamV1

@Slf4j
@Controller
public class RequestParamController {

    @RequestMapping("/request-param-v1")
    public void requestParamV1(HttpServletRequest request, HttpServletResponse response) throws IOException {
        String username = request.getParameter("username");
        int age = Integer.parseInt(request.getParameter("age"));
        log.info("username={}, age={}", username, age);

        response.getWriter().write("OK");
    }

}
 

여기서는 단순히 HttpServletRequest가 제공하는 방식으로 요청 파라미터를 조회했다.

 

4. HTTP 요청 파라미터 - @RequestParam

스프링이 제공하는 @RequestParam 을 사용하면 요청 파라미터를 매우 편리하게 사용할 수 있다.

 

● requestParamV2

@ResponseBody
@RequestMapping("/request-param-v2")
public String requestParamV2(@RequestParam("username") String memeberName, @RequestParam("age") int memberAge){
        log.info("username={}, age={}", memeberName, memberAge);
        return "OK";
}
 

@RequestParam : 파라미터 이름으로 데이터를 추출한다.

name 속성을 통하여 사용한다. => @RequestParam("username") String memberName

 

 

@ResponseBody : 원래 String을 반환하면 Spring은 view객체를 찾아서 반환하려고 한다. 하지만 단순하게 String을 보내고싶은것 이라면 @ResponseBody를 사용하면 된다. HTTP message body에 직접 해당 내용 입력하여 전달한다.

 

● requestParamV3

@ResponseBody
@RequestMapping("/request-param-v3")
public String requestParamV3(@RequestParam String username, @RequestParam int age){
      log.info("username={}, age={}", username, age);
      return "OK";
}

 

HTTP 파라미터 이름이 변수 이름과 같으면 @RequestParam(name="xx")은 생략이 가능하다.

 

● requestParamV4

@ResponseBody
@RequestMapping("/request-param-v4")
public String requestParamV4(String username, int age){
    log.info("username={}, age={}", username, age);
    return "OK";
}
 

String , int , Integer 등의 단순 타입이면 @RequestParam 까지도 생략이 가능하다.

다만 너무 과하게 생략하면 의미전달이 명확하지 못하다는 점이 있다. 따라서 @RequestParam 정도는 적어주는것이 좋을것 같다.

 

@RequestParam 애노테이션을 생략하면 스프링 MVC는 내부에서 required=false 를 적용한다.

 

● 파라미터 필수 여부 - requestParamRequired

@ResponseBody
@RequestMapping("/request-param-required")
public String requestParamRequired(
            @RequestParam String username,
            @RequestParam(required = false) Integer age){
    log.info("username={}, age={}", username, age);
    return "OK";
}
 

@RequestParam(required = false) 는 해당 파라미터가 필수가 아니라는것을 의미한다.

username같은경우 속성 이 생략된것 이다. 생략하면 기본값인 required = true 를 갖는다. 즉 꼭 있어야 한다는 의미이다.

 

주의! - 파라미터 이름만 사용

/request-param?username= => 파라미터 이름만 있고 값이 없는 경우 빈문자로 통과하게 된다.

 

주의! - 기본형(primitive)에 null 입력

/request-param 요청

@RequestParam(required = false) int age

age변수는 int값을 받아야 하는데, 지정하지 않을경우 null이 넘어온다. 하지만 int는 null을 받을수가 없다.

따라서 null을 받을수 있는 Integer로 변경하거나, 또는 다음에 나오는 defaultValue를 사용해야 한다.

 

● 기본 값 적용 - requestParamDefault

@ResponseBody
@RequestMapping("/request-param-default")
public String requestParamDefault(
        @RequestParam(defaultValue = "guest") String username,
        @RequestParam(required = false, defaultValue = "-1") int age){
    log.info("username={}, age={}", username, age);
    return "OK";
}
 

파라미터에 값이 없는 경우 defaultValue 를 사용하면 기본 값을 적용할 수 있다. 이미 기본 값이 있기 때문에 required 는 의미가 없다.

 

예를들어 /request-param 로 요청을 보내면 기본 default값들이 로그에 찍힌다.

요청시 파리미터를 지정하지 않았기 때문에 default값이 나오는것을 확인할수 있다.

 

● 파라미터를 Map으로 조회하기 - requestParamMap

@ResponseBody
@RequestMapping("/request-param-map")
public String requestParamMap(@RequestParam Map <String, Object> paramMap) {
    log.info("username={}, age={}", paramMap.get("username"), paramMap.get("age"));
    return "ok";
}
 

파라미터를 Map, MultiValueMap으로 조회할 수 있다.

- @RequestParam Map => Map(key=value)

- @RequestParam MultiValueMap

MultiValueMap(key=[value1, value2, ...] 예시) (key=userIds, value=[id1, id2])

 

댓글