본문 바로가기

Web Sever 개발과 CS 기초/스프링

Spring MVC 구조와 사용법

Spring MVC 구조

@RestController
public class testController{

    @PostMapping("/test")
    public ResponseEntity<AdminSentenceSuccess> addSentence(
       //비지니스 로직
    }
}

해당 컨트롤러 메소드는 GET /test 요청이 왔을 때 실행되는 코드를 담고 있다. 단순하게 어노테이션 작성만으로 HTTP 요청을 처리할 메소드를 지정할 수 있는 것은 Spring이 제공하는 MVC 구조 덕분이다. MVC 구조의 어떤 과정으로 HTTP 요청이 각 메소드로 전달되어 비지니스 로직을 처리할 수 있는 지 알아보자.

가장 기본적인 흐름은 HTTP 요청은 먼저 DispathcerServlet으로 전달된다. DispatcherServlet은 프론트 컨트롤러로서 모든 HTTP 요청을 먼저 받아서 요청에 해당하는 컨트롤러로 HTTP 요청 정보(requestBody, param, method 등)를 객체로 만들어서 전달하는 역할을 한다.

DispathcerServlet의 동작 과정은 핸들러를 실행할 수 있는 핸들러 어댑터를 찾아 실행하고 실행된 핸들러 어댑터는 실제 핸들러를 실행한다.

RequestMapping 핸들러 어댑터

가장 많이 사용하는 @RequestMapping 을 처리하는 핸들러 어댑터인 RequestMappingHandlerAdapter가 있다.

RequestMappingHandlerAdapter 는 바로 ArgumentResolver 를 호출해서 컨트롤러(핸들러)가 필요로 하는 다양한 파라미터의 값(객체)을 생성한다. HttpServletRequest , Model 은 물론이고, @RequestParam , @ModelAttribute 같은 애노테이션 그리고 @RequestBody , HttpEntity 같은 HTTP 메시지를 처리할 수 있다.

추가 서블릿 필터와 인터셉터 구조 과정

  • 서블릿 필터

서블릿 필터는 ServletDispathcer 전에 실행되어 모든 HTTP 요청에 적용할 동작을 지정할 수 있다. 필터 체인을 사용하여 서블릿 필터를 여러 개 설정할 수 있다.

  • 인터셉터

인터셉터는 컨트롤러 호출 전( preHandle ),호출 후( postHandle ), 요청 완료 이후( afterCompletion )와 같이 단계적으로 잘 세분화 되어 있다. 서블릿 필터의 경우 단순히 request , response 만 제공했지만, 인터셉터는 어떤 컨트롤러( handler )가 호출되는지 호출 정보도 받을 수 있다.

Spring MVC 기본 기능

@RestController, @RequestMapping("/")

@RestController
public class MappingController {

    @RequestMapping(value = "/hello-basic")
    public String helloBasic() {
        return "ok";
    }
}

@RestController는 반환 값으로 뷰를 찾는 것이 아니라, 바로 HTTP 메시지 바디로 입력한다.

@RequestMapping(”/”) “/” 에 입련된 URL에 호출하는 메소드를 지정한다. 값 안에 배열[]을 사용하여 다중 URL 설정이 가능하다({"/hello-basic", "/hello-go"})

@RequestMapping 에 method 속성으로 HTTP 메서드를 지정하지 않으면 HTTP 메서드와 무관하게 호출된다.(모두 허용 GET, HEAD, POST, PUT, PATCH, DELETE)

method를 직접 지정하거나 @GetMapping, @PostMapping 어노테이션을 지정할 수 있다.

PathVariable(경로 변수) 사용

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

@PathVariable을 메소드 파라미터로 지정하여 편리하게 조회할 수 있다.

@PathVariable 의 이름과 파라미터 이름이 같으면 생략할 수 있다. “@PathVariable String userId”

특정 조건 조회하기

  • 특정 파라미터 조건 매핑하기

http://localhost:8080/mapping-param?mode=debug

@GetMapping(value = "/mapping-param", params = "mode=debug")
public String mappingParam() {
    return "ok";
}
  • 특정 헤더 조건 매핑하기
/**
 * 특정 헤더로 추가 매핑
 * headers="mode",
 * headers="!mode"
 * headers="mode=debug"
 * headers="mode!=debug" (! = )
*/
@GetMapping(value = "/mapping-header", headers = "mode=debug")
public String mappingHeader() 
    return "ok";
}
  • 미디어 타입 조건 매핑 - 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() {
    return "ok";
}
  • 미디어 타입 조건 매핑 - 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() {
    return "ok";
}

기본 헤더와 쿠키 조회

@RestController
public class RequestHeaderController {

    @RequestMapping("/headers")
    public String headers(HttpMethod httpMethod,
                Locale locale,
                @RequestHeader MultiValueMap<String, String> headerMap,
                @RequestHeader("host") String host,
                @CookieValue(value = "myCookie", required = false) String cookie) {
        //메소드 정보를 조회
        log.info("httpMethod={}", httpMethod);
        //Locale 정보를 조회(언어, 지역 설정, 출력 형식 등)
        log.info("locale={}", locale);
        //모든 HTTP 헤더를 MultiValueMap 형식으로 조회
        //하나의 키에 여러 값을 받을 수 있다.
        log.info("headerMap={}", headerMap);
        //특정 HTTP 헤더를 조회 필수 값 여부: required
        log.info("header host={}", host);
        //특정 쿠키를 조회 필수 값 여부: required
        log.info("myCookie={}", cookie);
        return "ok";
    }
}

HTTP 요청 파라미터 - @RequestParam

요청 파라미터를 매우 편리하게 사용할 수 있다.

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

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

@RequestParam String username,
@RequestParam int age

“@RequestParam(required = true, defaultValue = "guest") String username”

지정을 통해 필수 값과 값이 들어오지 않았을 때 기본 값을 설정할 수 있다.

HTTP 요청 메시지 @RequestBody (messae body정보)

@RequestBody를 사용하여 Request Message Body 정보를 사용할 수 있다.

@Controller
public class RequestBodyController {
		
    //이런식으로 문자열 messageBody도 받을 수 있고
    @ResponseBody
    @PostMapping("/request-body-string")
    public String requestBodyStringV4(@RequestBody String messageBody) {
        log.info("messageBody={}", messageBody);
        return "ok";
    }
	
    //Json 데이터를 객체로 반환하여 바로 사용할 수 있다.
    @ResponseBody
    @PostMapping("/request-body-json")
    public HelloData requestBodyJsonV5(@RequestBody HelloData data) {
        log.info("username={}, age={}", data.getUsername(), data.getAge());
        return data;
    }
}

HTTP 응답 - HTTP API, 메시지 바디에 직접 입력

HTML이 아니라 HTTP API를 제공하는 경우 Response Message Body에 Json 데이터를 담아서 전송한다. 그 방법을 알아보자. @Controller 대신에 @RestController 애노테이션을 사용하면, 해당 컨트롤러에 모두@ResponseBody 가 적용된다.

@RestController
public class ResponseBodyController {
		
    //ResponseEntity 를 반환한다. 
    //HTTP 메시지 컨버터를 통해서 JSON 형식으로 변환되어서 반환된다.
    @GetMapping("/response-body-json-v1")
    public ResponseEntity<HelloData> responseBodyJsonV1() {
        HelloData helloData = new HelloData();
        helloData.setUsername("userA");
        helloData.setAge(20);
        return new ResponseEntity<>(helloData, HttpStatus.OK);
    }
		
    //@ResponseStatus(HttpStatus.OK) 애노테이션을 사용하면 응답 코드도 설정할 수 있다.
    @ResponseStatus(HttpStatus.OK)
    @GetMapping("/response-body-json-v2")
    public HelloData responseBodyJsonV2() {
        HelloData helloData = new HelloData();
        helloData.setUsername("userA");
        helloData.setAge(20);
        return helloData;
    }

}