SpringMVC-2일차

박상원·2024년 5월 13일

spring

목록 보기
5/15

@RestController


@RestController = @Controller + @ResponseBody

  • REST-API 개발할 때 사용
  • View Resolver를 새용하지 않고 HttpMessageConverter에 변환된다.
    • file upload download.. byte stream
    • json
    • xml

jackson 라이브러리를 추가하면 spring이 알아서 감지해서 json을 객체로 객체를 json으로 바꿀 수 있게 된다.

@RestController
public class UserRestController {
    private final UserRepository userRepository;

    public UserRestController(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @GetMapping("/users/{userId}")
    public User getUser(@PathVariable("userId") String userId) {
        return userRepository.getUser(userId);
    }

}

이런식으로 @RestController를 사용하면 객체를 반환할 수 있다.
@RestController 대신 @Controller를 사용하고 handler method 위에 @ResponseBody를 붙여도 똑같은 역할을 한다.

컴포넌트 스캔 시 basePackage가 아니라 basePackageClasses로 지정하는게 더 좋은 이유는 컴파일 시점에서 오류를 체크할 수 있어서이다.

ModelAndView view에 의존성이 있다. 확장이 어려움
Model이 의존성이 낮아 사용하기 좋다.

핸들러는 보통 controller라고 하는데 명확히 따지면 controller 내부에 있는 메서드들을 핸들러라고 한다.
Controller는 어노테이션으로 사용할 수도 있고 implements를 해서 사용할 수도 있다.
Controller를 구현하는 클래스는 매우 다양하다. 근데 잘 쓰이지 않는다.

MappingJackson2HttpMessageConverter


  • ObjectMapper parameter로 주입받아 생성
  • ObjectMapper가 실제로 Object <--> json 사이에서 변환해주는 역할을 함.
public class MappingJackson2HttpMessageConverter extends AbstractJackson2HttpMessageConverter {
    @Nullable
    private String jsonPrefix;

    public MappingJackson2HttpMessageConverter() {
        this(Jackson2ObjectMapperBuilder.json().build());
    }

    public MappingJackson2HttpMessageConverter(ObjectMapper objectMapper) {
        super(objectMapper, new MediaType[]{MediaType.APPLICATION_JSON, new MediaType("application", "*+json")});
    }

    public void setJsonPrefix(String jsonPrefix) {
        this.jsonPrefix = jsonPrefix;
    }

    public void setPrefixJson(boolean prefixJson) {
        this.jsonPrefix = prefixJson ? ")]}', " : null;
    }

    protected void writePrefix(JsonGenerator generator, Object object) throws IOException {
        if (this.jsonPrefix != null) {
            generator.writeRaw(this.jsonPrefix);
        }
    }
}

@ResponseBody

  • handler method의 return value를 HttpMessageConvertor를 통해서 직렬화해서 response body로 전달
  • @RestController = @Controller + @ResponseBody
  • 일반 @Controller 클래스에도 method 레벨에서 @ResponseBody 가능

ResponseEntity


  • @ResponseBody + http status + response headers
@GetMapping("/something")
public ResponseEntity<String> handle() {
    String body = ... ;
    String etag = ... ;
    return ResponseEntity.ok()          // http status (200 OK)
                         .eTag(etag)    // response header (ETAG)
                         .build(body);  // response body
}
  • Spring Framework에서 제공하는 HttpEntity 클래스는 Http Request/Response에 해당하는 body와 header를 포함하는 클래스

ResponseEntity (응답)

  • headers
  • body
  • status

RequestEntity (요청)

  • body
  • headers
  • url
  • type
  • method

@RequestBody


  • Request body를 읽어와서 HttpMessageConverter를 통해 deserialize해서 객체로 전달받기 위한 용도
@PostMapping("/api/members")
@ResponseStatus(HttpStatus.CREATED)
public void createMember(@RequestBody Member member) {
    // ...
}

@RequestBody와 @ModelAttribute의 차이는 request body는 HTTP 요청의 body 부분을 자바 객체로 매핑하는 데 사용되고 model attribute는 HTTP 요청 파라미터를 자바 객체의 필드에 바인딩하는 데 사용되는 차이가 있다.

lombok에서 @Value 사용할 때 private 기본생성자가 생성되도록 설정하기

  • lombok 내부에서 어차피 객체생성은 reflection api를 이용해서 처리함으로 private로 작성되어도 큰 문제 없음

Spring MVC Components Overview

Handler


  • Controller
  • HttpRequestHandler

Handler == Controller

  • Handler는 그냥 Controller라고 생각해도 무방함

Controller

  • jar : org.springframework.spring-webmvc
    • package : org.springframework.web.servlet.mvc

public class WelcomeController implements Controller {
    @Override
    public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
        ModelAndView mav = new ModelAndView("index");
        return mav;
    }
}

HttpRequestHandler

  • org.springframework.web.HttpRequestHandler
    • Servlet과 유사함

public class WelcomeHttpRequestHandler implements HttpRequestHandler {
    @Override
    public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        RequestDispatcher rd = request.getRequestDispatcher("/WEB-INF/view/index.jsp");
        rd.forward(request,response);
    }
}

Annotation 기반의 @Controller

@Controller
public class HomeController {
    @GetMapping("/")
    public String index() {
        return "index";
    }
}
  • @Controller : 해당 클래스가 Controller임을 나타내기 위한 어노테이션
  • @RequestMapping : 요청에 대해 어떤 Controller, 어떤 메서드가 처리할지를 맵핑하기 위한 어노테이션
  • @RequestParam : Controller 메서드의 파라미터와 웹 요청 파라미터을 맵핑하기 위한 어노테이션
  • @ModelAttribute : Controller 메서드의 파라미터나 리턴값을 Model 객체와 바인딩하기 위한 어노테이션
  • @SessionAttributes : Model 객체를 세션에 저장하고 사용하기 위한 어노테이션

HandlerMapping - 5가지 전략


서버로 들어온 요청을 어느 핸들러로 전달할 지 결정하는 역할

기본 전략

  • RequestMappingHandlerMapping
    • @RequestMapping + HandlerMapping
    • 지금 우리가 사용하고 있는 기본 구조
    • @RequestMapping은 메서드 단위로 URL을 매핑해 줄 수 있어 @Controller 개수를 줄일 수 있음
    • URL, HTTP Method (GET/POST/PUT/DELETE...), Parameter, Header 맵핑이 가능함
    • 우선순위 : 0
  • BeanNameUrlHandlerMapping
    • BeanName + Url + HandlerMapping
    • 우선순위 : 1

나머지 전략

  • SimpleUrlHandlerMapping
    • Simple Url + HandlerMapping

BeanNameUrlHandlerMapping 전략을 사용해서 Controller를 찾는다면?

  • 가장 단순한 구조
  • Servlet + FrontController Pattern에서 처럼 늘어나는 Command 동일한 현상이 발생함.

HandlerAdapter


DispatcherServlet.properties

org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
	org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
	org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter,\
	org.springframework.web.servlet.function.support.HandlerFunctionAdapter

HandlerAdapter

DispatcherServlet과 실제 핸들러(Controller) 구현 사이를 이어주는 Object Adapter 역할

  • RequestMappingHandlerAdapter
    • @RequestMapping + HandlerAdapter
    • 우선순위 : 0
    • 대응 : Annotation 기반의 Controller, RequestMappingHandlerMapping
  • HttpRequestHandlerAdapter
    • HttpRequestHandler + HandlerAdapter
    • 우선순위 : 1
    • 대응 : HttpRequestHandler, Servlet 구현과 비슷함
  • SimpleControllerHandlerAdapter
    • Simple Controller + HandlerAdapter
    • 우선순위 : 2
    • 대응 : Controller interface를 구현한 클래스, SimpleUrlHandlerMapping, BeanNameUrlHandlerMapping

ViewController/RedirectViewController


설정

public class WebConfig implements WebMvcConfigurer {
    // ...

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/").setViewName("index");
        registry.addRedirectViewController("/this-is-home", "/");
    }
}

HandlerMapping/HandlerAdapter

  • 어떤 URL일 때 어떤 view로 rendering 하라(또는 redirect 하라)는 정보를 Map에 저장해서 사용
    • HandlerMapping -> SimpleUrlHandlerMapping 사용
  • ViewController, RedirectViewController는 모두 ParameterizableViewController라는 구현 클래스를 사용
    • ParameterizableViewController는 AbstractController라는 추상 클래스를 상속받아 구현
      • HandlerAdapter -> SimpleControllerHandlerAdapter 사용

ViewResolver


문자열 기반의 view 이름을 토대로 실제 View 구현을 결정하는 역할

  • InternalResourceViewResolver
    • default
    • jsp 지원
  • VelocityViewResolver
  • FreemarkerViewResolver
  • ThymeleafViewResolver

ViewResolver 관련 Components

LocaleResolver/LocaleContextResolver

  • view rendering 시 국제화 지원을 위한 Locale과 Timezone을 결정하는 역할
  • AcceptHeaderLocaleResolver : 웹 브라우저가 전송한 Accept-Language 헤더로부터 Locale을 선택한다. setLocale() 메서드를 지원하지 않는다.
  • CookieLocaleResolver : 쿠키를 이용해서 Locale 정보를 구한다. setLocale() 메서드는 쿠키에 Locale 정보를 저장한다.
  • SessionLocaleResolver : 세션으로부터 Locale 정보를 구한다. setLocale() 메서드는 세션에 Locale 정보를 저장한다.
  • FixedLocaleResolver : 웹 요청에 상관없이 특정한 Locale로 설정한다. setLocale() 메서드를 지원하지 않는다ㅏ.

ThemeResolver

  • view rendering 시 어떤 테마를 사용할 지 결정하는 역할
  • CookieThemeResolver : 테마를 사용자별로 설정할 수 있도록 해주며 사용되는 테마 정보가 클라이언트의 쿠키에 저장된다.
  • SessionThemeResolver : 테마를 사용자의 세션별로 설정할 수 있도록 한다.
  • FixedThemeResolver : 빈의 defaultThemeName 속성에 설정된 하나의 고정된 테마를 반환한다.

RequestToViewNameTranslator

  • 핸들러가 아무것도 리턴하지 않았을 대 view 이름을 결정하는 역할

HandlerExceptionResolver

요청 처리 과정에서 발생하는 예외를 제어하고자 할 때 사용

  • DefaultHandlerExceptionResolver
    • 표준 String 예외를 결정하고 예외에 해당하는 HTTP 상태 코드를 응답

DefaultHandlerExceptionResolver는 스프링 MVC에서 제공하는 예외 처리를 담당하는 클래스로, 예외가 발생했을 때 기본적인 예외 처리 로직을 수행합니다.

  • 예외에 따른 HTTP 응답 처리: 예외가 발생했을 때, HTTP 응답의 상태 코드와 메시지를 적절하게 설정하여 클라이언트에게 전달합니다.
    예를 들어, 예외가 발생하면 500 (Internal Server Error) 상태 코드를 반환하고, 예외 메시지를 응답 본문에 포함시켜 클라이언트에게 전달할 수 있습니다.

  • 예외 처리 메시지 변환: 예외 메시지를 클라이언트에게 전달하기 전에, 메시지를 변환하거나 포맷팅할 수 있습니다.
    예를 들어, 예외 메시지를 JSON 형식으로 변환하여 클라이언트에게 반환할 수 있습니다.

  • 예외 처리 로깅: 예외가 발생했을 때, 로깅을 통해 예외 정보를 기록할 수 있습니다. 이를 통해 개발자가 예외를 추적하고 디버깅할 수 있습니다.

DefaultHandlerExceptionResolver는 스프링 MVC의 예외 처리 과정에서 자동으로 적용되는 예외 처리기로 제공되기 때문에 별도의 설정이 필요하지 않습니다.
예외가 발생하면 DefaultHandlerExceptionResolver가 자동으로 동작하여 예외를 처리하고, HTTP 응답을 적절하게 설정합니다.
또한, 개발자가 커스텀 예외 처리 로직을 구현하여 사용할 수도 있습니다.
이를 위해 커스텀 ExceptionResolver를 등록하여 DefaultHandlerExceptionResolver의 동작을 오버라이딩할 수 있습니다.

기타

MultipartResolver

  • Multipart 요청을 처리하는 구현을 결정하는 역할
  • CommonsMultipartResolver
  • StandardServletMultipartResolver
    • Servlet 3.0 API 기반

FlashMapManager

  • redirect와 같이 하나의 요청에서 다른 요청으로 속성 값을 전달하는데 FlashMap을 사용할 수 있는 mechanism을 제공

RedirectAttribute vs Flashmapmanager

  • login fail시 출력했던 message -> RedirectAttribute 활용해서 구현.

RedirectAttribute와 FlashMapManager는 둘 다 Spring에서 리다이렉트 후에 데이터를 전달하기 위한 기능을 제공하는데,
그러나 둘 간에 몇 가지 차이점이 있습니다.

  • 데이터의 보관 위치: RedirectAttribute는 리다이렉트된 요청의 URL에 쿼리 매개변수로 데이터를 전달합니다. 즉, URL에 데이터가 노출되어 보안상 취약할 수 있습니다. 반면에, FlashMapManager는 서버 측에서 임시 데이터를 보관하므로 URL에 데이터가 노출되지 않습니다.
  • 데이터의 보존 기간: RedirectAttribute는 리다이렉트된 요청에서만 데이터를 보존하며, 다음 요청에서는 사용할 수 없습니다. 반면에, FlashMapManager는 다음 요청에서도 데이터를 유지할 수 있습니다. 이는 FlashMapManager가 서버 측에서 데이터를 보존하기 때문입니다.
  • 데이터의 활용 범위: RedirectAttribute는 리다이렉트된 요청 내에서만 데이터를 사용할 수 있습니다. 반면에, FlashMapManager는 다음 요청에서도 데이터를 사용할 수 있습니다. 이는 FlashMapManager가 서버 측에서 데이터를 보관하므로 다양한 요청에서 데이터를 활용할 수 있는 장점이 있습니다.
  • 데이터의 삭제 처리: RedirectAttribute는 리다이렉트된 요청에서 데이터를 사용하면 자동으로 삭제되며, 다음 요청에서는 사용할 수 없습니다. 반면에, FlashMapManager는 데이터의 삭제 처리를 개발자가 직접 관리해야 합니다. 개발자가 데이터를 수동으로 삭제하지 않으면 FlashMapManager에 계속 남아있을 수 있습니다.

종합적으로 말하면, RedirectAttribute는 리다이렉트된 요청에서만 사용 가능하고 URL에 데이터가 노출되는 단점이 있지만, 간단하게 사용할 수 있는 반면에, FlashMapManager는 서버 측에서 데이터를 보존하고 다양한 요청에서 활용할 수 있는 장점이 있지만, 개발자가 데이터의 삭제 처리를 관리해야 하는 등 좀 더 복잡한 사용 방법이 필요할 수 있습니다. 따라서 상황에 맞게 RedirectAttribute 또는 FlashMapManager를 선택하여 사용해야 합니다.

HandlerInterceptor


Filter에서는 Spring이 관리하는 Bean들을 사용할 수 없다.
Interceptor는 Spring이 관리하는 Bean들을 사용할 수 있다.
기능은 동일하지만 filter는 initialize 전이기 때문에 Spring이 관리하는 Bean을 사용할 수 없음
filter는 Servlet Container가 관리하지만 interceptor는 Spring이 관리한다.
interceptor의 scope는 WebApplicationContext이다. Container에서만 사용하면 되기 때문이다.

HandlerInterceptor

  • Servlet Filter와 유사하게
    • DispatcherServlet이 Controller를 호출하기 전 후에
    • 요청 및 응답을 참조, 가공할 수 있는 일종의 필터 역할

HandlerInterceptor interface

public interface HandlerInterceptor {
  default boolean preHandle(HttpServletRequest req, HttpServletResponse resp, Object handler) /*..*/ {
    return true;
  }

  default void postHandle(HttpServletRequest req, HttpServletResponse resp, Object handler,
                          ModelAndView mav) /*..*/ {

  default void afterCompletion(HttpServletRequest req, HttpServletResponse resp, Object handler,
                          Exception ex) /*..*/ {
  }
}

DispatcherServlet의 HandlerExecutionChain 실행

HandlerExecutionChain

  • 요청을 처리할 하나의 handler(Controller)와 이 요청에 적용될 여러 interceptor들을 아우르는 요청 처리 객체
public class HandlerExecutionChain {
  private final Object handler;
  private final List<HandlerInterceptor> interceptorList = new ArrayList<>();

  // ...
}

HandlerExecutionChain 실행

HandlerExecutionChain mappedHandler = /*..*/;
// ...

if (!mappedHandler.applyPreHandle(processedRequest, response)) {
    return;
}

// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

// ...

mappedHandler.applyPostHandle(processedRequest, response, mv);

Servlet Filter vs HandlerInterceptor

실행 시점이 다르다

  • Filter : DispatcherServlet 실행 전/후에 실행
  • HandlerInterceptor : DispatcherServlet 실행 이후에 Controller 실행 전/후에 실행

ApplicationContext 범위가 다르다

  • Filter: RootApplicationContext에 등록/관리
    • Filter는 ServletApplicationContext(MVC의 View나 @ExceptionHandler)를 이용할 수 없다.
  • HandlerInterceptor : ServletApplicationContext에 등록/관리

MessageSource


MessageSource interface

  • ApplicationContext's i18n support
public interface MessageSource {
  String getMessage(String code, Object[] args, defaultMessage, Locale locale);
  String getMessage(String code, Object[] args, Locale locale) /*..*/;
  String getMessage(MessageSourceResolvable resolvable, Locale locale) /*..*/;
}

MessageSource type Bean 등록

  • messageSource라는 이름의 MessageSource 타입의 Bean을 등록해두면
    • Spring framework에서 다국어 처리 시 이 Bean을 활용
  • RootConfig.java (Root Context)Bean 등록
@Bean
public MessageSource messageSource() {
    ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
    messageSource.setBasename("message");
//        messageSource.setBasenames("message", "error");

    return messageSource;
}

File Upload

Spring MVC에서의 File Upload

MultipartResolver를 이용

  • 멀티파트 요청을 처리하는 구현을 결정하는 역할

MultipartResolver 종류

  • StandardServletMultipartResolver
  • CommonsMultipartResolver

StandardServletMultipartResolver vs CommonsMultipartResolver

  • StandardServletMultipartResolver
    • StandardServletMultipartResolver는 스프링 3.1부터 추가된 클래스입니다.
    • 서블릿 3.0 멀티파트 요청 처리 기능을 활용하여 파일 업로드를 처리합니다.
    • 서블릿 3.0 이상의 컨테이너에서는 기본적으로 이 클래스가 사용됩니다.
    • HttpServletRequest의 getPart() 또는 getParts() 메서드를 사용하여 멀티파트 요청을 처리합니다.
  • CommonsMultipartResolver
    • CommonsMultipartResolver는 이전부터 스프링에서 사용되어 왔던 클래스로, Apache Commons FileUpload 라이브러리를 기반으로 합니다.
    • 서블릿 3.0 미만의 환경에서 사용됩니다.
    • CommonsMultipartResolver는 DiskFileItemFactory와 ServletFileUpload를 사용하여 멀티파트 요청을 처리합니다.
    • 이전 버전의 서블릿 컨테이너에서도 동작하기 때문에 더 널리 호환됩니다.

StandardServletMultipartResolver는 서블릿 3.0 이상의 환경에서 사용되는 것이 좋고, CommonsMultipartResolver는 이전 버전의 서블릿 컨테이너를 지원해야 할 때 사용됩니다. 대부분의 경우에는 StandardServletMultipartResolver를 사용하는 것이 권장됩니다.

ViewResolver


ViewResolver

  • Spring MVC에서는 view 이름을 문자열로 관리
  • 문자열 기반의 view 이름을 토대로 실제 View 구현을 결정하는 역할

ViewResolver의 종류 - View Template Engine에 다라

  • InternalResourceViewResolver - JSP 사용
  • VelocityViewResolver - velocity
  • FreemarkerViewResolver - freemarker
  • ThymeleafViewResolver - thymeleaf
  • Mustache
  • Groovy Templates

ThymeleafViewResolver를 가장 많이 사용함 - Spring에서 권장함
jsp 사용이 비효율적인 이유는 jsp는 application이 구동될 때 톰캣이 servlet으로 바꿔주는데 이 비용이 비싸다 그래서 html 그 자체로 사용되는 Thymeleaf를 사용하는 것임

Thymeleaf


  • HTML5 기반의 view template engine

Thymeleaf 문법(변수 vs 메세지)

${variable}

  • 변수
<span th:text="${greeting}" />

#{messageKey}

  • 다국어 메세지
<span th:text="#{greeting}" />

Thymeleaf 문법

  • 기본적으로 HTML5 속성(attribute)을 이용

th:text

  • 텍스트 출력 (HTML escape 됨)
<span th:text="'M<b>V</b>C'" />

html 변환 결과

<span>M&lt;b&gt;V&lt;/b&gt;C</span>

th:utext

  • 텍스트 출력 (HTML 태그가 그대로 적용)
<span th:utext="'M<b>V</b>C'" />

결과

<span>M<b>V</b>C</span>

th:each

  • 반복문
<table>
  <tr>
    <th>NAME</th>
    <th>PRICE</th>
    <th>IN STOCK</th>
  </tr>
  <tr th:each="prod : ${prods}">
    <td th:text="${prod.name}">Onions</td>
    <td th:text="${prod.price}">2.41</td>
  </tr>
</table>

th:if, th:unless

  • 조건문
<span th:if="${#lists.size(list) > 2}">more than 2</span>
<span th:if="${#lists.size(list) == 2}">two</span>

th:switch, th:case

<div th:switch="${user.role}">
  <p th:case="'admin'">User is an administrator</p>
  <p th:case="#{roles.manager}">User is a manager</p>
  <p th:case="*">User is some other thing</p>
</div>

th:with

  • 로컬 변수 선언
<div th:with="firstPer=${persons[0]}">
  <p>
    The name of the first person is <span th:text="${firstPer.name}">Julius Caesar</span>.
  </p>
</div>

Thymeleaf 문법

Expression Basic Objects

  • #locale
  • #request
  • #response
  • #session
  • #servletContext

Expression Utility Objects

  • strings
    • #strings : 문자열 객체를 위한 utility 메서드
${#strings.isEmpty(name)}
${#strings.contains(name,'ez')}
${#strings.startsWith(name,'Don')}
  • lists
    • #lists : list 객체를 위한 utility 메서드
${#lists.size(list)}
${#lists.isEmpty(list)}

0개의 댓글