Spring MVC 개념과 패턴

    1 Spring MVC

    • Spring Framework이 직접 제공하는 Servlet API 기반의 웹 프레임워크
    • Spring MVC 는 다음 패턴의 구현체
      • MVC pattern
      • Front controller pattern

    1.1 MVC Pattern( Model-View-Controller)

    • 애플리케이션의 개발 영역을 Model, View, Controller 세 가지 역할로 구분
    • 역할을 나눔으로서 코드의 복잡도를 줄일 수 있는 장점

    • model: Bean
    • view: JSP
    • service: Servlet

    1.1.1 Controller

    • 사용자의 요청을 받아 어떻게 처리 할지 결정하고 요청을 분석
    • 주로 비즈니스 로직을 처리하고 결과를 모델에 추가

    1.1.2 Model

    • 컨트롤러가 뷰에 전달할 데이터를 보관
    • 데이터와 비즈니스 로직을 결합하여 뷰에 필요한 정보 제공

    1.1.3 View

    • 모델 데이터를 기반으로 클라이언트에게 HTML, JSON 등의 형식으로 응답을 생성

     

    1.2 JavaBeans/JSP/Servlet (JSP Model2)

     

     

    1.3 Front Controller Pattern

    A controller that handles all requests for a Web site. - Martin Fowler

    • 모든 요청을 Front controller 에서 받아서 요청에 따라 실제 처리할 컨트롤러에 위임
    • 인증, 인가 등 공통적으로 처리해야 할 부분을 Front controller에서 처리하기 용이

     

     

    1.4 DispatcherServlet

    FrontController 를 DispatcherServlet 으로 부름

    • Spring MVC Framework의 중심이 되는 Servlet
    • Controller로 향하는 모든 웹 요청의 entry point
    • Front Controller 디자인 패턴의 표현

     

    1.5 Spring boot web MVC

    • Spring boot framework 에서는 embedded WAS를 제공
    • WAS 서버를 별도로 설치하지 않고 내장된 WAS를 이용해서 애플리케이션을 단독으로 실행가능
      • Tomcat, Jetty, UnderTow 등이 있음
    • spring-boot-starter-web 의존성을 추가하는것 만으로 자동 설정된 애플리케이션을 실행 가능

    1.5.1 WebMvcAutoConfiguration

    • 자동으로 스프링 웹 애플리케이션의 주요 컴포넌트 설정
    • EnableWebMvcConfiguration과 WebMvcAutoConfigurationAdapter 클래스를 포함

    1.5.2 EnableWebMvcConfiguration

    • EnableWebMvcConfiguration 클래스는 RequestMappingHandlerMapping과 RequestMappingHandlerAdapter 컴포넌트를 생성하고 설정

    1.5.3 WebMvcAutoConfigurationAdapter

    • WebMvcConfigurer 인터페이스를 구현

    1.5.4 WebMvcConfigurer

    *개발자가 Spring MVC의 기본 설정을 커스터마이징할 수 있도록 해줌
    * Spring MVC에 필요한 기본 설정을 추상 메서드를 통해서 커스터 마이징
    * add~~ : 새로운 빈이나 오브젝트를 추가하는것
    * configure~~ : 설정작업을 하는것

     

    🌱 [실습] IntelliJ 에서 Spring Boot MVC 프로젝트 생성

    hello world 를 출력하는 application 만들어보기

    @Controller
    public class HomeControlloer {
        @GetMapping("/")
        public String home() {
            return "home";
        }
    }

    @Controller: 스프링에게 해당 클래스가 웹 요청을 처리하는 컨트롤러임을 안내

    @GetMapping(”/”) 메서드를 통해 home.html 템플릿을 찾아 렌더링

    <!DOCTYPE html>
    <html lang = "ko">
    <head>
        <meta charset="UTF-8">
        <title>Hello First Web Spring</title>
    </head>
    <body>
        <div>
            <h1>Hello First Web Spring</h1>
        </div>
    </body>
    </html>

    클라이언트가 웹 브라우저에서 루트 URL("/")에 접근하면,

    스프링 컨트롤러가 “home” 뷰 이름을 반환하고,

    템플릿 엔진이 이 HTML 템플릿을 찾아서 렌더링한 후 사용자에게 보여준다.

    view 는 thymeleaf 이므로 남는 것은 Controller


    2 Controller

    • MVC 패턴에서 Controller 역할
      • 요청 처리 및 흐름 제어 담당
    • Front Controller 패턴에서 Command interface 구현 클래스에 해당
      • 실제 웹 요청을 처리하는 역할
    @Controller                         // <-- Controller 임을 지정
    public class HomeController {
        @GetMapping("/")                // <-- HTTP Method 지정, URL 맵핑
        public String index() {
            return "index";             // <-- view 이름 지정
        }
    }

    @Controller: component scan 과정을 통해 자동으로 bean 등록

     

    2.1 Request Mapping

    요청을 Controller 메서드에 맵핑

    2.1.1 @RequestMapping을 통한 URL 맵핑

    @RequestMapping("/persons")
    @RequestMapping(value = "/persons")

    @RequestMapping 어노테이션에서 “value” 속성이 기본(default) 속성이기 때문에,

    @RequestMapping("/persons")는 내부적으로 @RequestMapping(value = "/persons")와 같은 의미로 처리된다.

    2.1.2 @RequestMapping을 통한 HTTP Method 맵핑

    @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)

    이 코드들은 같은 URI(”/persons”)에 대해 HTTP 메서드별로 각각 별도의 요청 핸들러를 정의한다.

    각 어노테이션은 해당 메서드(GET, POST, PUT 등)의 요청에만 응답하도록 만든다.

    • GET: 리소스 조회
    • POST: 새 리소스 생성 또는 특정 작업 실행
    • PUT: 리소스 전체 업데이트
    • DELETE: 리소스 삭제
    • PATCH: 리소스 부분 업데이트
    • HEAD: 헤더 정보만 반환
    • OPTIONS: 지원하는 HTTP 메서드 목록 반환
    • TRACE: 경로 추적(디버깅 용도)

    2.1.3 HTTP Method 맵핑은 줄여서

    @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)

     

    2.2 Request Mapping (w/params)

    2.1.1 request parameter와 연결하는 방법

    • 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 이용

    @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";
    }

     

    2.3 Controller Method에서 사용 가능한 method argument

     

    2.4 Controller Method에서 사용 가능한 return type


    3 Model 이용하기

    3.1 Model로 이용할 수 있는 type

    • java.util.Map interface
    • org.springframework.ui.Model interface
    • org.springframework.ui.ModelMap class

    3.1.1 실제 처리되는 내용

    • Model에 설정한 속성(attribute)이 View에 request.attribute 로 전달됨

     

    🌱 [실습] Map, Model, ModelMap 에 값을 넣어서 view에서 가져다가 출력

    git checkout model

    git checkout requestmapping

    @Controller
    @RequestMapping("/user")    // user 가 prefix 처럼 동작
    public class UserController {
        private final UserRepository userRepository;
    
        public UserController(UserRepository userRepository) {
            this.userRepository = userRepository;
        }
    
        @GetMapping("/user")    // 요청을 Controller 메서드에 맵핑
        public String getUsers(Model model) { /* TODO 1: 적절한 모델 type 넣기 */
            List<User> users = userRepository.getUsers();
            //TODO 2: user 목록을 모델의 attribute 추가
            model.addAttribute("users", users);
            return "users";
        }
    }
    @Controller
    @RequestMapping("/messenger")    // user 가 prefix 처럼 동작
    public class UserController {
        private final UserRepository userRepository;
    
        public UserController(UserRepository userRepository) {
            this.userRepository = userRepository;
        }
    
        @GetMapping("/user")    // 요청을 Controller 메서드에 맵핑
        public String getUsers(Map<String, Object> model) { /* TODO 1: 적절한 모델 type 넣기 */
            List<User> users = userRepository.getUsers();
            //TODO 2: user 목록을 모델의 attribute 추가
            model.put("users", users);
            return "users";
        }
    }

    4 ModelAndView

    4.1 ModelAndView = Model + View

    @GetMapping("/some-request")
    public ModelAndView doSomething() {
        ModelAndView mav = new ModelAndView("viewName");
        mav.addObject("name", "value");
        // ...
    
        return mav;
    }

    🌱 [실습] Map, Model, ModelMap 에 값을 넣어서 view 에서  ModelAndView 로 변경

    @Controller
    @RequestMapping("/messenger")    // user 가 prefix 처럼 동작
    public class UserController {
        private final UserRepository userRepository;
    
        public UserController(UserRepository userRepository) {
            this.userRepository = userRepository;
        }
    
        @GetMapping("/user")    // 요청을 Controller 메서드에 맵핑
        public ModelAndView getUsers() { 
            List<User> users = userRepository.getUsers();
            ModelAndView modelAndView = new ModelAndView("users");
            modelAndView.addObject("users", users);
            return modelAndView;
        }
    }

     

    4.2 요청 parameter 받아오기 (@RequestParam)

    4.2.1 @RequestParam

    • 요청 URL의 Query String을 처리하기 위한 어노테이션

    요청 URL

    GET http://localhost:8080/persons?order=-createdAt

    Controller Method

    @GetMapping("/persons")
    public List<Person> getPersons(@RequestParam(name="order") String order) {
        // ...
    }

     

    4.3 요청 URL의 가변 인자 가져오기 (@PathVariable)

    4.3.1 @PathVariable

    • 요청 URL의 Resource(Path)을 처리하기 위한 어노테이션
      • @RequestMapping 의 path 에 변수명을 입력받기 위한 place holder 가 필요함

    요청 URL

    GET http://localhost:8080/persons/99499102

    Controller Method

    @GetMapping("/persons/{personId}")
    public List<Person> getPersons(@PathVariable(name="personId", required=true) Long personId) {
        // ...
    }

    requried=true 이면 personId를 넘겨주지 않으면 에러남 

     

    🌱 [실습] request parameter 및 가변인자를 받아서 적절한 동작을 하는 API들 완성

    git checkout requestmapping

    // Spring MVC 컨트롤러임을 선언
    @Controller
    public class UserController {
    
        // UserRepository를 주입받아 사용자 데이터에 접근
        private final UserRepository userRepository;
    
        // 생성자 주입을 통해 UserRepository 의존성을 주입
        public UserController(UserRepository userRepository) {
            this.userRepository = userRepository;
        }
        
        // GET 요청 "/users"를 처리하여 모든 사용자 목록을 반환
        @GetMapping("/users")
        public String getUsers(Model model) {
            // 저장소에서 사용자 목록을 조회
            List<User> users = userRepository.getUsers();
            // 조회한 사용자 목록을 모델에 추가하여 뷰로 전달
            model.addAttribute("users", users);
            // "users.html" 뷰를 반환
            return "users";
        }
    
        // GET 요청 "/users/{userId}"를 처리하여 특정 사용자 정보를 반환
        @GetMapping("/users/{userId}")
        public String getUser(Model model,
                              @PathVariable("userId") String id) {
            // PathVariable로 전달된 id를 이용해 사용자 정보를 조회
            User user = userRepository.getUser(id);
            // 조회한 사용자 정보를 모델에 추가
            model.addAttribute("user", user);
            // "user.html" 뷰를 반환
            return "user";
        }
    
        // GET 요청 "/users"를 처리하되, 요청 파라미터 id가 있을 경우 특정 사용자 정보를 반환
        @GetMapping(value = "/users", params = "{id}")
        public String getUserByName(Model model,
                                    @RequestParam("id") String id) {
            // 요청 파라미터 id로 사용자 정보를 조회
            User user = userRepository.getUser(id);
            // 조회한 사용자 정보를 모델에 추가
            model.addAttribute("user", user);
            // "user.html" 뷰를 반환
            return "user";
        }
    }

     

     

    5.1 @CookieValue

    • HTTP 쿠키를 처리하기 위한 어노테이션
    @GetMapping("/some-request")
    public List<Person> getPersons(@CookieValue(name = "SESSION") String sessionId) {
        // ...
    }

    🌱 [실습] 로그인 만들기

    git checkout login1

    @Controller
    public class LoginController {
        private final UserRepository userRepository;
    
        public LoginController(UserRepository userRepository) {
            this.userRepository = userRepository;
        }
    
        // `SESSION` 이라는 쿠키가 있으면 로그인 완료 메세지 출력 (`loginSuccess.html`).
        //  `SESSION` 이라는 쿠키가 없으면 로그인 폼 화면 출력 (`loginForm.html`).
        @GetMapping("/login")
        public String login(@CookieValue(name = "SESSION", required = false) String session) {
            if (session != null && !session.isEmpty()) { // SESSION 쿠키가 존재하면 로그인 완료 뷰 반환, 없으면 로그인 폼 반환
                return "loginSuccess"; // loginSuccess.html 뷰
            }
            return "loginForm"; // loginForm.html 뷰
        }
    
        // `userRepository.matches(id, password)` 메서드 이용.
        // 로그인 성공 시 `SESSION` 쿠키에 session id 값 저장하고
        // 모델을 이용해서 `loginSuccess.html`에 세션 아이디 전달.
        // 로그인 실패 시 `/login`으로 redirect.
        @PostMapping("/login")
        public String doLogin(@RequestParam String id, @RequestParam String password,
                              HttpServletResponse response, Model model) {
            if (userRepository.matches(id, password)) {
                String sessionId = UUID.randomUUID().toString();  // 로그인 성공 시 session id 생성
                Cookie cookie = new Cookie("SESSION", sessionId);  // SESSION 쿠키 생성 및 설정
                cookie.setPath("/"); // 쿠키의 유효 경로를 지정
                cookie.setHttpOnly(true); // 쿠키를 HttpOnly 속성으로 설정
                response.addCookie(cookie); // 생성한 쿠키를 HTTP 응답에 추가하여, 클라이언트(브라우저)로 전송
    
                model.addAttribute("sessionId", sessionId);
                return "loginSuccess";
            }
            return "loginForm";
        }
    }

     

     

    댓글