MVC Pattern (Model - View - Controller)
JavaBeans/JSP/Servlet (JSP Model2)
Servlet -> Controller
JavaBeans -> Model
JSP -> View
Front Controller Pattern
Front Controller -> 요청을 앞에서 우선적으로 처리한다
Spring MVC에서 Front Controller
Dispatcher Servlet

Root ApplicationContext - implements ServletContextListener
ServletContextListener 를 구현한 구현체
Servlet Context를 관리함
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener></listener-class>
</listener>
<context-param>
<param-name>contextClass</param-name>
<!-- ... -->
</context-param>
<context-param>
<param-name>contextConfigLocation</param-name>
<!-- ... -->
</context-param>

Servlet Application Context - Dispatcher Servlet : Spring MVC의 default Servlet
dispatcher servlet을 관리한다
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextClass</param-name>
<!-- ... -->
</init-param>
<init-param>
<param-name>contextConfigLocation</param-name>
<!-- ... -->
</init-param>
</servlet>

ApplicationContext vs WebApplicationContext
WebApplicationContext = ApplicationContext + ServletContext
기존 Application Context에다가 Dispatcher Servlet의 기능을 추가한게 WebApplication Context라고 보면 된다.
Configuration
@EnableWebMvc 태그를 달아줘야 한다
Customizing with @EnableWebMvc
Customizing without @EnableWebMvc
WebApplicationInitializer

SpringServletContainerInitializer - SpringContainer 기반의 코드구성을 해준다
@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
@Override
public void onStartup(Set<Class<?>> set, ServletContext servletContext) {
// ...
}
}
위 클래스가 수행되면 onStartUp()에 구현체가 전달되어서 초기화 과정을 거치게 된다
WebApplicationInitializer
public interface WebApplicationInitializer {
void onStartup(ServletContext servletContext) throws ServletException;
}
Web.xml 파일 없이 구성을 하려 하면, 두가지 방법이 존재한다
1) WebApplicationInitializer을 직접 구현
2) AbstractAnnotationConfigDispatcherServletInitializer을 상속받아 필요한 것 사용하기
필요한 방법대로 선택하여 수행하면 된다
Spring MVC 그림 다시 보니...
FrontController 의 역할은 Dispatcher Servlet이 한다.
Model : Spring Bean (Pojo)
View : JSP
Controller : ?
구현
@Controller // <-- Controller 임을 지정
public class HomeController {
@GetMapping("/") // <-- HTTP Method 지정, URL 맵핑
public String index() {
return "index"; // <-- view 이름 지정
}
}
@RestController : View가 아니라 응답값을 반환(Response)
@RestController
@RequestMapping("/persons")
public class PersonController {
/*
GET /persons/12345
HTTP/1.1 200 OK
Content-type: application/json;charset=UTF-8
{
"name": "dongmyo",
"age": 19,
"address": "Ulsan, Korea"
}
*/
@GetMapping("/{id}")
public Person getPerson(@PathVariable Long id) {
// ...
}
/*
POST /persons
{
"name": "dongmyo",
"age": 19,
"address": "Ulsan, Korea"
}
HTTP/1.1 201 Created
*/
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public void add(@RequestBody Person person) {
// ...
}
}
Request Mapping
@RequestMapping : 요청을 Controller method에 Mapping한다
@RequestMapping("/persons") = @RequestMapping(value = "/persons")
@RequestMapping을 통한 HTTP Method Mapping
@RequestMapping(value = "/persons", method=RequestMethod.GET)
@RequestMapping(value = "/persons", method=RequestMethod.POST)
@RequestMapping(value = "/persons", method=RequestMethod.PUT)
@RequestMapping(value = "/persons", method=RequestMethod.DELETE)
@RequestMapping(value = "/persons", method=RequestMethod.PATCH)
@RequestMapping(value = "/persons", method=RequestMethod.HEAD)
@RequestMapping(value = "/persons", method=RequestMethod.OPTIONS)
@RequestMapping(value = "/persons", method=RequestMethod.TRACE)
으로 쓸 수 있고, 또 줄여서 쓸 수 있다
@GetMapping == @RequestMapping(method=RequestMethod.GET)
@PostMapping == @RequestMapping(method=RequestMethod.POST)
@PutMapping == @RequestMapping(method=RequestMethod.PUT)
@DeleteMapping == @RequestMapping(method=RequestMethod.DELETE)
@PatchMapping == @RequestMapping(method=RequestMethod.PATCH)
Request Mapping (w/params)
id parameter가 있는 경우에만
@RequestMapping(method = RequestMethod.GET, params = { "id" })
id parameter가 없는 경우에만
@GetMapping(params = { "!id" })
type parameter 값이 raw인 경우에만
@GetMapping(params = "type=raw")
type parameter 값이 raw가 아닌 경우에만
@GetMapping(params = "type!=raw")
Controller Method
`java @GetMapping("/") public String index() { // return type: String, method argument: 없음 return "index"; } @GetMapping("/{id}") public Person getPerson(@PathVariable Long id) { // return type: Person // ... // method argument: @PathVariable return person; } @PostMapping public String doLogin(Member loginInfo, HttpSession session) { // return: redirect:'
// ... // method argument: HttpSession
return "redirect:/login";
}
Controller Method에서 사용 가능한 method argument 및 return type
return type:
Model로 이용할 수 있는 타입:
Model에 설정한 속성(Attribute)이 View에 req.attribute로 전달된다
ModelAndView : Model + View
@GetMapping("/some-request")
public ModelAndView doSomething() {
ModelAndView mav = new ModelAndView("viewName");
mav.addObject("name", "value");
// ...
return mav;
}
// 생성자에 ViewName을 넣어주면 return 할때 해당 View로 이동한다.
// 또한 ModelAndView에 Attribute를 넣어주면 JSP에서 꺼내 쓸 수 있다.
@RequestParam : 요청 Parameter 받아오기
//요청 URL
GET http://localhost:8080/persons?order=-createdAt
//Controller Method
@GetMapping("/persons")
public List<Person> getPersons(@RequestParam(name="order") String order) {
// ...
}
@PathVariable : 요청 URL의 가변인자 가져오기
//요청 URL
GET http://localhost:8080/persons/99499102
//Controller Method
@GetMapping("/persons/{personId}") //{} <- placeholder
public List<Person> getPersons(@PathVariable(name="personId", required=true) Long personId) {
// ...
}
@RequestHeader : 요청의 HTTP헤더를 처리하기 위한 Annotation. 요청 Header 값 읽기
//요청
GET /some-request HTTP/1.1
Host: localhost:8080
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36
//Controller Method
@GetMapping("/some-request")
public List<User> getUsers(@RequestHeader(name = "User-Agent") String userAgent) {
// ...
}
@CookieValue : HTTP Cookie 값 읽기
@GetMapping("/some-request")
public List<Person> getPersons(@CookieValue(name = "SESSION") String sessionId) {
// ...
}
URL Pattern

@ModelAttribute
메서드 인자로 선언된 경우 : 모델에서 속성값을 추출할 때 사용
@PostMapping("/user/register")
public String registerUser(@ModelAttribute UserRegisterRequest userRequest) {
// ...
}
메서드에 선언된 경우 : 모든 @RequestMapping에 앞서 호출되어 공통 속성을 제공한다
@ModelAttribute("user")
public User getUser(@PathVariable("userId") String userId) {
return userRepository.getUser(userId);
}
예외 처리(Exception Handling)
@ExceptionHandler
@ExceptionHandler({UserNotFoundException.class}) // <-- 어떤 예외를 처리할 것인지 선언
// --> method argument로 여러 객체들을 선언할 수 있다.
public String handleException(UserNotFoundException ex, HttpServletResponse response) {
// ...
// --> method return value로 여러 객체들을 반환할 수 있다.
return "error";
}
@ExceptionHandler에서 사용 가능한 Method Argument와 Return type
Controller 기반 예외처리
@ExceptionHandler + @ResponseStatus
@ExceptionHandler(UserNotFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public void notFound() {
// nothing to do
}
@ExceptionHandler + View
@ExceptionHandler(UserNotFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public String notFound(UserNotFoundException ex, Model model) {
model.addAttribute("exception", ex);
return "error";
}
@ExceptionHandler + @ResponseBody + HttpMessageConverter
@ExceptionHandler(Exception.class)
@ResponseBody
public ErrorDto handleException(Exception ex) {
// ...
// 예외를 처리하고 에러 메세지를 담은 `ErrorDto` 객체를 생성해서 반환
return errorDto;
}
@ControllerAdvice : @Component의 일종으로 component scanning 과정에서 자동으로 Bean을 등록한다
@ControllerAdvice
public class WebControllerAdvice {
@ExceptionHandler({ UserNotFoundException.class, PostNotFoundException.class })
public String handleException(Exception ex, Model model) {
log.error("resource not found", ex);
model.addAttribute("exception", ex);
return "error";
}
}
입력 값 검증 : Validation
Bean Validation - Java SE,EE에서 사용되는 JavaBeans에 대한 검증용 Java API
API and Implementation
API
<dependency>
<groupId>jakarta.validation</groupId>
<artifactId>jakarta.validation-api</artifactId>
<version>2.0.2</version>
</dependency>
IMPL
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.2.3.Final</version>
</dependency>
Bean Validation Annotation
사용예시
@Value
public class UserModifyRequest {
@NotNull
@Size(max = 50)
String name;
@Min(0)
int age;
}
Bean Validation 적용
public String modifyUser(@Valid @ModelAttribute UserModifyRequest userRequest,
BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
throw new ValidationFailedException(bindingResult);
}
// ...
}
Spring Validation
Validator Interface
public interface Validator {
boolean supports(Class<?> clazz);
void validate(Object target, Errors errors);
}