[번역] Spring 공식문서 - Spring Web MVC

Ref. Spring Framework Documentation v.5.3.16
📌 이 글에서는 공식문서의 Spring Web MVC 부분만 다룹니다.

Spring Web MVC

Spring Web MVC는 Servlet API를 기반으로 구축된 독창적인 웹 프레임워크이며 처음부터 Spring Framework에 포함되었습니다. 정식 명칭인 "Spring Web MVC"는 해당 소스 모듈(spring-webmvc)의 이름에서 유래하지만 일반적으로 "Spring MVC"로 알려져 있습니다.

Spring Web MVC와 병행하여, Spring Framework 5.0은 "Spring WebFlux"라는 이름의 리액티브 스택 웹 프레임워크를 도입했으며, 그 이름도 해당 소스 모듈(spring-webflux)을 기반으로 합니다. 이 섹션에서는 Spring 웹 MVC를 다룹니다. 다음 섹션에서는 Spring WebFlux를 다룹니다.

Servlet 컨테이너 및 Java EE 버전 범위와의 기준 정보 및 호환성은 Spring Framework Wiki를 참조하십시오.

DispatcherServlet

다른 많은 웹 프레임워크와 마찬가지로 Spring MVC는 중앙 ServletDispatcherServlet이 요청 처리를 위한 공유 알고리즘을 제공하는 반면 실제 작업은 구성 가능한 대리자 구성 요소에 의해 수행되는 전면 컨트롤러 패턴을 중심으로 설계되었습니다. 이 모델은 유연하고 다양한 워크플로우를 지원합니다.

DispatcherServlet은 모든 Servlet과 마찬가지로 Java configuration을 사용하거나 web.xml에서 Servlet 사양에 따라 선언되고 매핑되어야 합니다. 차례로 DispatcherServlet은 Spring configuration을 사용하여 request mapping, view resolution, exception handling 등에 필요한 대리자 구성 요소를 검색합니다.

  • Java configuration
public class MyWebApplicationInitializer implements WebApplicationInitializer {

    @Override
    public void onStartup(ServletContext servletContext) {

        // Load Spring web application configuration
        AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
        context.register(AppConfig.class);

        // Create and register the DispatcherServlet
        DispatcherServlet servlet = new DispatcherServlet(context);
        ServletRegistration.Dynamic registration = servletContext.addServlet("app", servlet);
        registration.setLoadOnStartup(1);
        registration.addMapping("/app/*");
    }
}
  • web.xml configuration
<web-app>

    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/app-context.xml</param-value>
    </context-param>

    <servlet>
        <servlet-name>app</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value></param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>app</servlet-name>
        <url-pattern>/app/*</url-pattern>
    </servlet-mapping>

</web-app>

Context Hierarchy

DispatcherServlet은 자체 구성을 위해 WebApplicationContext(일반 ApplicationContext의 extension)를 필요로 합니다. WebApplicationContext에는 ServletContext 및 연관된 Servlet에 대한 링크가 있습니다. 또한 응용 프로그램이 액세스가 필요한 경우 WebApplicationContext를 조회하기 위해 RequestContextUtils의 정적 메서드를 사용할 수 있도록 ServletContext에 바인딩됩니다.

많은 애플리케이션에서 단일 WebApplicationContext를 갖는 것은 간단하고 충분합니다. 하나의 루트 WebApplicationContext가 여러 DispatcherServlet(또는 다른 Servlet) 인스턴스에서 공유되는 컨텍스트 계층을 가질 수도 있으며, 각각은 자체 자식 WebApplicationContext 구성을 가지고 있습니다. 컨텍스트 계층 기능에 대한 자세한 내용은 ApplicationContext의 추가 기능을 참조하세요.

루트 WebApplicationContext는 일반적으로 여러 Servlet 인스턴스에서 공유해야 하는 데이터 저장소 및 비즈니스 서비스와 같은 인프라 빈을 포함합니다. 이러한 빈은 효과적으로 상속되며 일반적으로 지정된 서블릿에 로컬인 빈을 포함하는 Servlet 관련 자식 WebApplicationContext에서 재정의(즉, 다시 선언)될 수 있습니다. 다음 이미지는 이 관계를 보여줍니다.

다음 예제에서는 WebApplicationContext 계층을 구성합니다.

public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class<?>[] { RootConfig.class };
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class<?>[] { App1Config.class };
    }

    @Override
    protected String[] getServletMappings() {
        return new String[] { "/app1/*" };
    }
}

다음 예는 web.xml에 해당하는 것을 보여줍니다.

<web-app>

    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/root-context.xml</param-value>
    </context-param>

    <servlet>
        <servlet-name>app1</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/app1-context.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>app1</servlet-name>
        <url-pattern>/app1/*</url-pattern>
    </servlet-mapping>

</web-app>

Annotated Controllers

Spring MVC는 @Controller@RestController 구성 요소가 어노테이션을 사용하여 요청 매핑, 요청 입력, 예외 처리 등을 표현하는 어노테이션 기반 프로그래밍 모델을 제공합니다. 어노테이션이 달린 컨트롤러는 유연한 메서드 서명을 가지고 있으며 기본 클래스를 확장하거나 특정 인터페이스를 구현할 필요가 없습니다. 다음 예는 어노테이션으로 정의된 컨트롤러를 보여줍니다.

@Controller
public class HelloController {

    @GetMapping("/hello")
    public String handle(Model model) {
        model.addAttribute("message", "Hello World!");
        return "index";
    }
}

앞의 예에서 메서드는 Model을 인자로 받고 뷰 이름을 String으로 반환합니다.

Declaration

서블릿의 WebApplicationContext에서 표준 Spring 빈 정의를 사용하여 컨트롤러 빈을 정의할 수 있습니다. @Controller 스테레오타입은 클래스 경로에서 @Component 클래스를 감지하고 이에 대한 빈 정의를 자동 등록하기 위한 Spring 일반 지원에 맞춰 자동 감지를 허용합니다. 또한 어노테이션이 달린 클래스에 대한 스테레오타입 역할을 하여 웹 구성 요소로서의 역할을 나타냅니다.

이러한 @Controller 빈의 자동 감지를 활성화하려면 다음 예제와 같이 구성 요소 스캔을 Java 구성에 추가할 수 있습니다.

@Configuration
@ComponentScan("org.example.web")
public class WebConfig {

    // ...
}

다음 예는 앞의 예와 동일한 XML 구성을 보여줍니다.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:p="http://www.springframework.org/schema/p"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="org.example.web"/>

    <!-- ... -->

</beans>

Request Mapping

@RequestMapping 어노테이션을 사용하여 요청을 컨트롤러 메서드에 매핑할 수 있습니다. URL, HTTP 메서드, 요청 매개변수, 헤더 및 미디어 유형별로 일치하는 다양한 속성이 있습니다. 클래스 수준에서 이를 사용하여 공유 매핑을 표현하거나 메서드 수준에서 특정 끝점 매핑으로 범위를 좁힐 수 있습니다.

@RequestMapping의 HTTP 메서드 특정 shortcut 변형도 있습니다.

  • @GetMapping
  • @PostMapping
  • @PutMapping
  • @DeleteMapping
  • @PatchMapping

shortcuts는 대부분의 컨트롤러 메서드가 기본적으로 모든 HTTP 메서드와 일치하는 @RequestMapping을 사용하는 대신 특정 HTTP 메서드에 매핑되어야 하기 때문에 제공되는 Custom Annotations입니다. @RequestMapping은 여전히 클래스 수준에서 공유 매핑을 표현하는 데 필요합니다.

다음 예제에는 타입 및 메서드 수준 매핑이 있습니다.

@RestController
@RequestMapping("/persons")
class PersonController {

    @GetMapping("/{id}")
    public Person getPerson(@PathVariable Long id) {
        // ...
    }

    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public void add(@RequestBody Person person) {
        // ...
    }
}

Handler Methods

@RequestMapping 핸들러 메서드는 유연한 서명을 가지고 있으며 지원되는 컨트롤러 메서드 인수 및 반환 값 범위에서 선택할 수 있습니다.

@RequestParam

@RequestParam 어노테이션을 사용하여 서블릿 요청 매개변수(즉, 쿼리 매개변수 또는 양식 데이터)를 컨트롤러의 메소드 인수에 바인딩할 수 있습니다.

다음 예에서는 그렇게 하는 방법을 보여줍니다.

@Controller
@RequestMapping("/pets")
public class EditPetForm {

    // ...

    @GetMapping
    public String setupForm(@RequestParam("petId") int petId, Model model) { 
        Pet pet = this.clinic.loadPet(petId);
        model.addAttribute("pet", pet);
        return "petForm";
    }

    // ...

}

@RequestHeader

@RequestHeader 어노테이션을 사용하여 요청 헤더를 컨트롤러의 메서드 인수에 바인딩할 수 있습니다.

헤더가 있는 다음 요청을 고려하십시오.

Host                    localhost:8080
Accept                  text/html,application/xhtml+xml,application/xml;q=0.9
Accept-Language         fr,en-gb;q=0.7,en;q=0.3
Accept-Encoding         gzip,deflate
Accept-Charset          ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive              300

다음 예는 Accept-EncodingKeep-Alive 헤더 값을 가져옵니다.

@GetMapping("/demo")
public void handle(
        @RequestHeader("Accept-Encoding") String encoding, 
        @RequestHeader("Keep-Alive") long keepAlive) { 
    //...
}

@CookieValue

@CookieValue 어노테이션을 사용하여 HTTP 쿠키 값을 컨트롤러의 메서드 인수에 바인딩할 수 있습니다.

다음 쿠키가 있는 요청을 고려하십시오.

JSESSIONID=415A4AC178C59DACE0B2C9CA727CDD84

다음 예는 쿠키 값을 가져오는 방법을 보여줍니다.

@GetMapping("/demo")
public void handle(@CookieValue("JSESSIONID") String cookie) { 
    //...
}

@ModelAttribute

메소드 인수에 @ModelAttribute 어노테이션을 사용하여 모델의 속성에 액세스하거나 존재하지 않는 경우 인스턴스화할 수 있습니다. 모델 속성은 이름이 필드 이름과 일치하는 HTTP Servlet 요청 매개변수의 값으로 겹쳐집니다. 이를 데이터 바인딩이라고 하며 개별 쿼리 매개변수 및 양식 필드의 구문 분석 및 변환을 처리하지 않아도 됩니다. 다음 예에서는 그렇게 하는 방법을 보여줍니다.

@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@ModelAttribute Pet pet) {
    // method logic...
}

위의 Pet 인스턴스는 다음 방법 중 하나로 소싱됩니다.

  • @ModelAttribute 메소드에 의해 추가되었을 수 있는 모델에서 검색됩니다.
  • 모델 속성이 클래스 수준 @SessionAttributes 어노테이션에 나열된 경우 HTTP 세션에서 검색됩니다.
  • 모델 속성 이름이 경로 변수 또는 요청 매개변수와 같은 요청 값의 이름과 일치하는 변환기를 통해 가져옵니다.
  • 기본 생성자를 사용하여 인스턴스화됩니다.
  • 서블릿 요청 매개변수와 일치하는 인수를 사용하여 "기본 생성자"를 통해 인스턴스화합니다. 인수 이름은 JavaBeans @ConstructorProperties를 통해 또는 바이트 코드에서 런타임에 유지되는 매개변수 이름을 통해 결정됩니다.

@SessionAttributes

@SessionAttributes는 요청 사이의 HTTP 서블릿 세션에 모델 속성을 저장하는 데 사용됩니다. 특정 컨트롤러에서 사용하는 세션 속성을 선언하는 유형 수준 어노테이션입니다. 여기에는 일반적으로 액세스할 후속 요청을 위해 세션에 투명하게 저장되어야 하는 모델 속성의 이름 또는 모델 속성 유형이 나열됩니다.

다음 예제에서는 @SessionAttributes 어노테이션을 사용합니다.

@Controller
@SessionAttributes("pet") 
public class EditPetForm {
    // ...
}

첫 번째 요청에서 이름이 pet인 모델 속성이 모델에 추가되면 자동으로 HTTP 서블릿 세션으로 승격되어 저장됩니다. 다음 예제와 같이 다른 컨트롤러 메서드가 SessionStatus 메서드 인수를 사용하여 저장소를 지울 때까지 그대로 유지됩니다.

@Controller
@SessionAttributes("pet") 
public class EditPetForm {

    // ...

    @PostMapping("/pets/{id}")
    public String handle(Pet pet, BindingResult errors, SessionStatus status) {
        if (errors.hasErrors) {
            // ...
        }
        status.setComplete(); 
        // ...
    }
}

@SessionAttribute

전역적으로 관리되는 기존 세션 속성에 액세스해야 하는 경우(즉, 컨트롤러 외부 — 예를 들어 필터에 의해) 존재하거나 존재하지 않을 수 있는 경우, 다음과 같이 메소드 매개변수에 @SessionAttribute 어노테이션을 사용할 수 있습니다. 다음 예는 이것을 보여줍니다.

@RequestMapping("/")
public String handle(@SessionAttribute User user) { 
    // ...
}

@RequestAttribute

@SessionAttribute와 유사하게 @RequestAttribute 어노테이션을 사용하여 이전에 생성된 기존 요청 속성(예: Servlet Filter 또는 HandlerInterceptor에 의해)에 액세스할 수 있습니다.

@GetMapping("/")
public String handle(@RequestAttribute Client client) { 
    // ...
}

Redirect Attributes

기본적으로 모든 모델 속성은 리디렉션 URL에서 URI 템플릿 변수로 노출되는 것으로 간주됩니다. 나머지 속성 중 기본 유형 또는 기본 유형의 컬렉션 또는 배열인 속성은 자동으로 쿼리 매개변수로 추가됩니다.

모델 인스턴스가 리디렉션을 위해 특별히 준비된 경우 기본 유형 속성을 쿼리 매개변수로 추가하면 원하는 결과가 될 수 있습니다. 그러나 주석이 달린 컨트롤러에서 모델은 렌더링 목적으로 추가된 추가 속성(예: 드롭다운 필드 값)을 포함할 수 있습니다. 이러한 속성이 URL에 나타날 가능성을 피하기 위해 @RequestMapping 메서드는 RedirectAttributes 유형의 인수를 선언하고 이를 사용하여 RedirectView에서 사용할 수 있도록 정확한 속성을 지정할 수 있습니다. 메서드가 리디렉션하는 경우 RedirectAttributes의 콘텐츠가 사용됩니다. 그렇지 않으면 모델의 내용이 사용됩니다.

RequestMappingHandlerAdapterignoreDefaultModelOnRedirect라는 flag를 제공합니다. 이를 사용하여 컨트롤러 메서드가 리디렉션되는 경우 기본 모델의 콘텐츠를 사용해서는 안 된다는 것을 나타낼 수 있습니다. 대신 컨트롤러 메서드는 RedirectAttributes 유형의 속성을 선언해야 하며 그렇게 하지 않는 경우 속성이 RedirectView에 전달되어서는 안 됩니다. MVC 네임스페이스와 MVC Java 구성 모두 이전 버전과의 호환성을 유지하기 위해 이 flag를 false로 설정합니다. 그러나 새 응용 프로그램의 경우 true로 설정하는 것이 좋습니다.

현재 요청의 URI 템플릿 변수는 리디렉션 URL을 확장할 때 자동으로 사용 가능하게 되며 Model 또는 RedirectAttributes를 통해 명시적으로 추가할 필요가 없습니다. 다음 예는 리디렉션을 정의하는 방법을 보여줍니다.

@PostMapping("/files/{path}")
public String upload(...) {
    // ...
    return "redirect:files/{path}";
}

Multipart

MultipartResolver가 활성화되면 multipart/form-data가 있는 POST 요청의 내용이 구문 분석되고 일반 요청 매개변수로 액세스할 수 있습니다. 다음 예는 하나의 일반 양식 필드와 하나의 업로드된 파일에 액세스합니다.

@Controller
public class FileUploadController {

    @PostMapping("/form")
    public String handleFormUpload(@RequestParam("name") String name,
            @RequestParam("file") MultipartFile file) {

        if (!file.isEmpty()) {
            byte[] bytes = file.getBytes();
            // store the bytes somewhere
            return "redirect:uploadSuccess";
        }
        return "redirect:uploadFailure";
    }
}

인수 유형을 List<MultipartFile>로 선언하면 동일한 매개변수 이름에 대해 여러 파일을 확인할 수 있습니다.

@RequestParam 어노테이션이 어노테이션에 지정된 매개변수 이름 없이 Map<String, MultipartFile> 또는 MultiValueMap<String, MultipartFile>로 선언되면 맵은 지정된 각 매개변수 이름에 대한 멀티파트 파일로 채워집니다.

명령 개체에 대한 데이터 바인딩의 일부로 멀티파트 콘텐츠를 사용할 수도 있습니다. 예를 들어, 이전 예제의 양식 필드와 파일은 다음 예제와 같이 양식 개체의 필드일 수 있습니다.

class MyForm {

    private String name;

    private MultipartFile file;

    // ...
}

@Controller
public class FileUploadController {

    @PostMapping("/form")
    public String handleFormUpload(MyForm form, BindingResult errors) {
        if (!form.getFile().isEmpty()) {
            byte[] bytes = form.getFile().getBytes();
            // store the bytes somewhere
            return "redirect:uploadSuccess";
        }
        return "redirect:uploadFailure";
    }
}

@RequestBody

@RequestBody 어노테이션을 사용하여 요청 본문을 읽고 HttpMessageConverter를 통해 Object로 역직렬화하도록 할 수 있습니다. 다음 예제에서는 @RequestBody 인수를 사용합니다.

@PostMapping("/accounts")
public void handle(@RequestBody Account account) {
    // ...
}

HttpEntity

HttpEntity@RequestBody를 사용하는 것과 거의 동일하지만 요청 헤더와 본문을 노출하는 컨테이너 개체를 기반으로 합니다. 다음 목록은 예를 보여줍니다.

@PostMapping("/accounts")
public void handle(HttpEntity<Account> entity) {
    // ...
}

@ResponseBody

메서드에 @ResponseBody 어노테이션을 사용하여 HttpMessageConverter를 통해 응답 본문에 반환을 직렬화할 수 있습니다. 다음 목록은 예를 보여줍니다.

@GetMapping("/accounts/{id}")
@ResponseBody
public Account handle() {
    // ...
}

ResponseEntity

ResponseEntity@ResponseBody와 비슷하지만 상태와 헤더가 있습니다.

@GetMapping("/something")
public ResponseEntity<String> handle() {
    String body = ... ;
    String etag = ... ;
    return ResponseEntity.ok().eTag(etag).build(body);
}

Jackson JSON

Spring은 Jackson JSON 라이브러리에 대한 지원을 제공합니다.

JSON Views

Spring MVC는 객체의 모든 필드의 하위 집합만 렌더링할 수 있는 Jackson의 직렬화 보기에 대한 내장 지원을 제공합니다. @ResponseBody 또는 ResponseEntity 컨트롤러 메서드와 함께 사용하려면 다음 예제와 같이 Jackson의 @JsonView 주석을 사용하여 직렬화 보기 클래스를 활성화할 수 있습니다.

@RestController
public class UserController {

    @GetMapping("/user")
    @JsonView(User.WithoutPasswordView.class)
    public User getUser() {
        return new User("eric", "7!jd#h23");
    }
}

public class User {

    public interface WithoutPasswordView {};
    public interface WithPasswordView extends WithoutPasswordView {};

    private String username;
    private String password;

    public User() {
    }

    public User(String username, String password) {
        this.username = username;
        this.password = password;
    }

    @JsonView(WithoutPasswordView.class)
    public String getUsername() {
        return this.username;
    }

    @JsonView(WithPasswordView.class)
    public String getPassword() {
        return this.password;
    }
}

좋은 웹페이지 즐겨찾기