스프링 MVC 모델의 구조와 동작원리

마라탕천재 ㅣ 2024. 8. 12. 23:16

1. MVC 모델이란

스프링 MVC는 자바 웹 애플리케이션 개발에 있어 가장 널리 사용되는 소프트웨어 디자인 패턴 중 하나이다.

MVC 패턴은 애플리케이션을 세 가지 핵심 구성 요소로 분리한다.

 

1. Model 

  • 데이터와 비즈니스 로직을 담당
  • 데이터베이스와 연동하여 데이터를 저장하고 불러오는 등의 작업을 수행

2. View

  • 사용자 인터페이스를 표현
  • 사용자가 보는 화면과 버튼, 폼 등을 디자인하고 구현

3. Controller

  • 사용자 입력을 처리하고 Model과 View 사이의 상호작용을 조정
  • 사용자의 입력을 받아 Model에 전달하고, Model의 결과를 바탕으로 View를 업데이트

 

스프링 프레임워크는 MVC 패턴을 효과적으로 구현하여, 개발자가 각 구성 요소를 독립적으로 개발하고 유지보수할 수 있게 해준다.

 

 

2. 서블릿이란

서블릿은 자바 웹 프로그래밍에서 클라이언트의 요청을 처리하고 그 결과를 반환하는 서버 측 프로그램이다.

좀 더 기술적으로 말하자면, 서블릿은 Java EE(Enterprise Edition) 스펙의 일부로, 동적 웹 컨텐츠를 생성하기 위한 서버 측 자바 프로그램을 말한다.

서블릿의 기본 구조는 다음과 같다.

public class MyServlet extends HttpServlet {
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // GET 요청 처리 로직
    }

    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // POST 요청 처리 로직
    }
}

다음 이미지는 컨테이너(서블릿 컨테이너)에서 서블릿의 동작 방식을 보여준다. 클라이언트의 요청이 서블릿 컨테이너에 의해 처리되고, 적절한 서블릿에 의해 비즈니스 로직이 실행되어 응답이 생성되는 과정이다.

  1. Request: 클라이언트가 웹 서버에 HTTP 요청을 보낸다.
  2. Web Container (Servlet): 웹 애플리케이션 서버의 일부로, 서블릿의 생명주기를 관리한다.
  3. 객체 생성 (HttpServletRequest, HttpServletResponse):웹 컨테이너는 클라이언트의 요청을 받아 HttpServletRequest와 HttpServletResponse 객체를 생성한다. 이 객체들은 요청 정보를 담고 있으며, 응답을 생성하는 데 사용된다.
  4. Servlet 분석 (web.xml): web.xml 파일(배포 서술자)을 분석하여 어떤 서블릿이 요청을 처리해야 하는지 결정한다.
  5. 찾은 Servlet: web.xml 분석 결과에 따라 적절한 서블릿이 선택된다.
  6. service(), doGet(), doPost() 등: HTTP 메서드에 따라 doGet(), doPost() 등이 호출된다.
  7. Response: 서블릿이 처리한 결과가 HttpServletResponse 객체를 통해 클라이언트에게 반환된다.

 

 

3. DispatcherServlet의 역할

DispatcherServlet은 스프링 MVC의 핵심 컴포넌트로, 프론트 컨트롤러 패턴을 구현한 서블릿이다.

이 서블릿의 주요 역할은 클라이언트의 모든 요청을 중앙에서 처리하고, 요청을 적절한 핸들러(컨트롤러)에게 분배하는 것이다.

이미지는 스프링 MVC의 DispatcherServlet을 중심으로 한 요청 처리 흐름을 보여준다.

 

  1. Request: 클라이언트로부터 HTTP 요청이 DispatcherServlet에 도착한다.
  2. Handler mapping: DispatcherServlet은 Handler mapping을 통해 요청을 처리할 적절한 컨트롤러를 찾는다.
  3. Controller: 선택된 컨트롤러로 요청이 전달되어 비즈니스 로직이 실행된다.
  4. Model and logical view name: 컨트롤러는 처리 결과를 Model 객체에 담고, 뷰 이름을 반환한다.
  5. ViewResolver: DispatcherServlet은 ViewResolver를 사용하여 논리적 뷰 이름을 실제 View 객체로 변환한다.
  6. View: ViewResolver로부터 받은 View 객체를 사용하여 결과를 렌더링한다.
  7. Response: 최종적으로 렌더링된 결과가 HTTP 응답으로 클라이언트에게 반환된다.
[Handler mapping의 예시]
GET /api/hello → HelloController 의 hello() 함수
GET /user/login → UserController 의 login() 함수
GET /user/signup → UserController 의 signup() 함수
POST /user/signup → UserController 의 registerUser() 함수

 

 

4. 주요 어노테이션

@Controller

  • 클래스를 스프링 MVC 컨트롤러로 지정
  • 컴포넌트 스캔의 대상이 되어 자동으로 빈으로 등록

 

@RequestMapping

  • 요청 URL을 특정 메서드나 클래스에 매핑
  • HTTP 메서드, 파라미터, 헤더 등 다양한 조건으로 매핑 가능

 

@RequestParam

  • 쿼리 파라미터나 폼 데이터를 가져올 때 사용.
  • 쉽게 말해 URL에 붙어 오는 데이터들을 받아오는 역할을 해줌
@GetMapping("/greet")

public String greet(@RequestParam String name) {

    return "Hello, " + name;

}

만약 사용자가 http://yourapp.com/greet?name=Alice 이런 식으로 요청하면, @RequestParam이 name이라는 파라미터의 값을 받아서 Alice로 처리해줌.

 

@ModelAttribute

  • 폼 데이터를 객체에 자동으로 바인딩 해줌.
  • 주로 폼 데이터를 Model 객체로 바인딩할 때 사용
@PostMapping("/submitForm") public String submitForm(@ModelAttribute User user) { 

    // User 객체에 폼 데이터가 자동으로 바인딩됨 

    return "Form submitted by " + user.getName(); 

}

폼에 name, age 같은 데이터가 있으면, User 객체의 name, age 필드에 자동으로 값이 들어가게 된다.

 

@RequestBody

  • HTTP 요청 본문에 있는 데이터를 자바 객체로 변경해줌.
  • 주로 JSON 같은 포맷의 데이터를 처리할 때 사용.
@PostMapping("/createUser")

public String createUser(@RequestBody User user) {

    // 요청 본문의 JSON 데이터가 User 객체로 변환됨

    return "User created with name " + user.getName();

}

 

클라이언트가 JSON 형식으로 {"name": "Alice", "age": 25} 이런 데이터를 POST 요청으로 보내면, @RequestBody JSON User 객체로 변환해줌

 

 

5. 사용 예시

@Controller
@RequestMapping("/users")
public class UserController {

    @Autowired
    private UserService userService;

    @GetMapping("/{id}")
    public String getUser(@PathVariable Long id, Model model) {
        User user = userService.findById(id);
        model.addAttribute("user", user);
        return "userDetails";
    }

    @PostMapping
    public String createUser(@ModelAttribute User user) {
        userService.save(user);
        return "redirect:/users";
    }
}

 

    // [Request sample]
    // GET http://localhost:8080/hello/request/star/Robbie/age/95
    @GetMapping("/star/{name}/age/{age}")
    @ResponseBody
    public String helloRequestPath(@PathVariable String name, @PathVariable int age) {
        return String.format("Hello, @PathVariable.<br> name = %s, age = %d", name, age);
    }

    // [Request sample]
    // GET http://localhost:8080/hello/request/form/param?name=Robbie&age=95
    @GetMapping("/form/param")
    @ResponseBody
    public String helloGetRequestParam(@RequestParam String name, @RequestParam int age) {
        return String.format("Hello, @RequestParam.<br> name = %s, age = %d", name, age);
    }