@RestController = @Controller + @ResponseBody
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를 구현하는 클래스는 매우 다양하다. 근데 잘 쓰이지 않는다.
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);
}
}
}
@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
}

@PostMapping("/api/members")
@ResponseStatus(HttpStatus.CREATED)
public void createMember(@RequestBody Member member) {
// ...
}
@RequestBody와 @ModelAttribute의 차이는 request body는 HTTP 요청의 body 부분을 자바 객체로 매핑하는 데 사용되고 model attribute는 HTTP 요청 파라미터를 자바 객체의 필드에 바인딩하는 데 사용되는 차이가 있다.


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

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);
}
}
@Controller
public class HomeController {
@GetMapping("/")
public String index() {
return "index";
}
}
서버로 들어온 요청을 어느 핸들러로 전달할 지 결정하는 역할

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
public class WebConfig implements WebMvcConfigurer {
// ...
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index");
registry.addRedirectViewController("/this-is-home", "/");
}
}
DefaultHandlerExceptionResolver는 스프링 MVC에서 제공하는 예외 처리를 담당하는 클래스로, 예외가 발생했을 때 기본적인 예외 처리 로직을 수행합니다.
예외에 따른 HTTP 응답 처리: 예외가 발생했을 때, HTTP 응답의 상태 코드와 메시지를 적절하게 설정하여 클라이언트에게 전달합니다.
예를 들어, 예외가 발생하면 500 (Internal Server Error) 상태 코드를 반환하고, 예외 메시지를 응답 본문에 포함시켜 클라이언트에게 전달할 수 있습니다.예외 처리 메시지 변환: 예외 메시지를 클라이언트에게 전달하기 전에, 메시지를 변환하거나 포맷팅할 수 있습니다.
예를 들어, 예외 메시지를 JSON 형식으로 변환하여 클라이언트에게 반환할 수 있습니다.예외 처리 로깅: 예외가 발생했을 때, 로깅을 통해 예외 정보를 기록할 수 있습니다. 이를 통해 개발자가 예외를 추적하고 디버깅할 수 있습니다.
DefaultHandlerExceptionResolver는 스프링 MVC의 예외 처리 과정에서 자동으로 적용되는 예외 처리기로 제공되기 때문에 별도의 설정이 필요하지 않습니다.
예외가 발생하면 DefaultHandlerExceptionResolver가 자동으로 동작하여 예외를 처리하고, HTTP 응답을 적절하게 설정합니다.
또한, 개발자가 커스텀 예외 처리 로직을 구현하여 사용할 수도 있습니다.
이를 위해 커스텀 ExceptionResolver를 등록하여 DefaultHandlerExceptionResolver의 동작을 오버라이딩할 수 있습니다.
RedirectAttribute와 FlashMapManager는 둘 다 Spring에서 리다이렉트 후에 데이터를 전달하기 위한 기능을 제공하는데,
그러나 둘 간에 몇 가지 차이점이 있습니다.
- 데이터의 보관 위치: RedirectAttribute는 리다이렉트된 요청의 URL에 쿼리 매개변수로 데이터를 전달합니다. 즉, URL에 데이터가 노출되어 보안상 취약할 수 있습니다. 반면에, FlashMapManager는 서버 측에서 임시 데이터를 보관하므로 URL에 데이터가 노출되지 않습니다.
- 데이터의 보존 기간: RedirectAttribute는 리다이렉트된 요청에서만 데이터를 보존하며, 다음 요청에서는 사용할 수 없습니다. 반면에, FlashMapManager는 다음 요청에서도 데이터를 유지할 수 있습니다. 이는 FlashMapManager가 서버 측에서 데이터를 보존하기 때문입니다.
- 데이터의 활용 범위: RedirectAttribute는 리다이렉트된 요청 내에서만 데이터를 사용할 수 있습니다. 반면에, FlashMapManager는 다음 요청에서도 데이터를 사용할 수 있습니다. 이는 FlashMapManager가 서버 측에서 데이터를 보관하므로 다양한 요청에서 데이터를 활용할 수 있는 장점이 있습니다.
- 데이터의 삭제 처리: RedirectAttribute는 리다이렉트된 요청에서 데이터를 사용하면 자동으로 삭제되며, 다음 요청에서는 사용할 수 없습니다. 반면에, FlashMapManager는 데이터의 삭제 처리를 개발자가 직접 관리해야 합니다. 개발자가 데이터를 수동으로 삭제하지 않으면 FlashMapManager에 계속 남아있을 수 있습니다.
종합적으로 말하면, RedirectAttribute는 리다이렉트된 요청에서만 사용 가능하고 URL에 데이터가 노출되는 단점이 있지만, 간단하게 사용할 수 있는 반면에, FlashMapManager는 서버 측에서 데이터를 보존하고 다양한 요청에서 활용할 수 있는 장점이 있지만, 개발자가 데이터의 삭제 처리를 관리해야 하는 등 좀 더 복잡한 사용 방법이 필요할 수 있습니다. 따라서 상황에 맞게 RedirectAttribute 또는 FlashMapManager를 선택하여 사용해야 합니다.
Filter에서는 Spring이 관리하는 Bean들을 사용할 수 없다.
Interceptor는 Spring이 관리하는 Bean들을 사용할 수 있다.
기능은 동일하지만 filter는 initialize 전이기 때문에 Spring이 관리하는 Bean을 사용할 수 없음
filter는 Servlet Container가 관리하지만 interceptor는 Spring이 관리한다.
interceptor의 scope는 WebApplicationContext이다. Container에서만 사용하면 되기 때문이다.

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) /*..*/ {
}
}
public class HandlerExecutionChain {
private final Object handler;
private final List<HandlerInterceptor> interceptorList = new ArrayList<>();
// ...
}
HandlerExecutionChain mappedHandler = /*..*/;
// ...
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
// ...
mappedHandler.applyPostHandle(processedRequest, response, mv);

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) /*..*/;
}
@Bean
public MessageSource messageSource() {
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
messageSource.setBasename("message");
// messageSource.setBasenames("message", "error");
return messageSource;
}
StandardServletMultipartResolver는 서블릿 3.0 이상의 환경에서 사용되는 것이 좋고, CommonsMultipartResolver는 이전 버전의 서블릿 컨테이너를 지원해야 할 때 사용됩니다. 대부분의 경우에는 StandardServletMultipartResolver를 사용하는 것이 권장됩니다.
ThymeleafViewResolver를 가장 많이 사용함 - Spring에서 권장함
jsp 사용이 비효율적인 이유는 jsp는 application이 구동될 때 톰캣이 servlet으로 바꿔주는데 이 비용이 비싸다 그래서 html 그 자체로 사용되는 Thymeleaf를 사용하는 것임
<span th:text="${greeting}" />
<span th:text="#{greeting}" />
th:text
<span th:text="'M<b>V</b>C'" />
html 변환 결과
<span>M<b>V</b>C</span>
th:utext
<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>
${#strings.isEmpty(name)}
${#strings.contains(name,'ez')}
${#strings.startsWith(name,'Don')}
${#lists.size(list)}
${#lists.isEmpty(list)}