개발 이론/Spring

Spring MVC

jeonghoe21 2025. 2. 20. 07:11

Spring MVC 개요 및 기본 개념

1. Spring MVC 소개

1) Spring MVC란?

Spring MVC는 Spring Framework가 제공하는 Servlet API 기반의 웹 프레임워크입니다. 기존의 서블릿과 JSP(JavaServer Pages) 기반의 개발 방식보다 구조화된 웹 애플리케이션 개발을 가능하게 합니다.

Spring MVC는 MVC(Model-View-Controller) 패턴과 Front Controller 패턴을 구현하여 웹 애플리케이션을 보다 효율적으로 관리할 수 있도록 돕습니다.

2) MVC 패턴이란?

MVC 패턴(Model-View-Controller Pattern)은 애플리케이션을 Model, View, Controller의 세 가지 역할로 구분하여 코드의 복잡도를 줄이고 유지보수를 쉽게 하는 패턴입니다.

구성 요소역할

Model 비즈니스 로직을 처리하고 데이터를 저장 및 관리
View 사용자에게 데이터를 보여주는 역할 (HTML, JSON 등)
Controller 사용자의 요청을 받아 처리하고 Model과 View를 연결

JSP Model2 아키텍처와 비슷하지만, Spring MVC는 더 구조화된 방식으로 애플리케이션을 개발할 수 있도록 지원합니다.

3) Front Controller 패턴

Front Controller 패턴은 모든 웹 요청을 하나의 컨트롤러(DispatcherServlet)에서 받아서 처리하는 방식을 의미합니다.

  • 요청을 일관되게 처리할 수 있으며,
  • 공통적으로 수행해야 하는 작업(인증, 인가, 로깅 등)을 하나의 컨트롤러에서 관리할 수 있습니다.

Spring MVC에서 DispatcherServlet이 Front Controller 역할을 수행합니다.


2. Spring MVC의 핵심 구조

Spring MVC는 DispatcherServlet을 중심으로 동작합니다.

1) DispatcherServlet이란?

  • 모든 웹 요청을 처리하는 Spring MVC의 중앙 컨트롤러
  • Front Controller 패턴을 적용한 서블릿
  • 사용자의 요청을 적절한 컨트롤러로 라우팅하고, 결과를 반환하는 역할

2) Spring MVC의 요청 처리 흐름

  1. 사용자가 요청을 보냄 (예: GET /home)
  2. DispatcherServlet이 요청을 받음
  3. HandlerMapping을 통해 요청을 처리할 컨트롤러 찾음
  4. Controller가 요청을 처리하고 Model을 생성
  5. ViewResolver를 이용하여 View를 결정
  6. View에서 사용자에게 응답을 렌더링

3. Spring Boot에서의 Spring MVC

Spring Boot는 Spring MVC를 보다 쉽게 사용할 수 있도록 도와줍니다.

1) 내장 웹 서버 제공

Spring Boot는 Tomcat, Jetty, Undertow 등의 내장 WAS(Web Application Server)를 제공하여 별도의 서버 설치 없이 애플리케이션을 실행할 수 있습니다.

2) spring-boot-starter-web 의존성 추가

Spring Boot에서 Spring MVC를 사용하려면 **spring-boot-starter-web**을 추가하면 자동으로 설정됩니다.

Maven 설정

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

Gradle 설정

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
}

3) 자동 구성 (WebMvcAutoConfiguration)

Spring Boot는 자동으로 Spring MVC 설정을 구성해주며, @EnableWebMvc 없이도 웹 애플리케이션을 실행할 수 있습니다.

주요 자동 구성 클래스

클래스 역할
EnableWebMvcConfiguration RequestMappingHandlerMapping을 생성하고 설정
WebMvcConfigurer 개발자가 기본 설정을 커스터마이징할 수 있도록 제공

예제: WebMvcConfigurer 사용

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/").setViewName("index");
    }
}

4. Spring MVC : 컨트롤러 작성하기

1) 프로젝트 생성

Spring Boot 프로젝트를 생성하고 Spring Web, Thymeleaf를 선택합니다.

2) Controller 클래스 작성

Spring MVC에서는 @Controller 또는 **@RestController**를 사용하여 컨트롤러를 정의합니다.

@Controller
public class HomeController {
    @GetMapping("/")
    public String index() {
        return "index";
    }
}
  • @Controller → Spring MVC의 컨트롤러로 등록
  • @GetMapping("/") → 루트 URL(/) 요청을 처리
  • return "index";index.html 뷰를 반환

3) View 파일 작성 (Thymeleaf)

Spring Boot에서 Thymeleaf 템플릿 엔진을 사용하여 View를 작성할 수 있습니다.

src/main/resources/templates/index.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Spring MVC</title>
</head>
<body>
    <h1>Hello, Spring MVC!</h1>
</body>
</html>

4) 애플리케이션 실행

Spring Boot는 main() 메서드를 실행하면 내장 WAS(Tomcat)가 자동으로 실행됩니다.

@SpringBootApplication
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}
  • 브라우저에서 http://localhost:8080 접속 시 Hello, Spring MVC!가 출력됩니다.

5. Request Mapping (요청 매핑)

Spring MVC에서는 @RequestMapping 및 HTTP 메서드별 매핑 어노테이션(@GetMapping, @PostMapping 등)을 사용하여 클라이언트의 요청을 컨트롤러 메서드와 연결합니다.

1) @RequestMapping

  • 요청 URL과 컨트롤러 메서드를 매핑하는 어노테이션
  • HTTP 메서드(GET, POST, PUT, DELETE 등)를 지정할 수 있음
@Controller
@RequestMapping("/users") // 모든 메서드는 '/users' 경로를 기준으로 동작
public class UserController {
    
    @GetMapping("/{id}") // GET /users/{id}
    public String getUser(@PathVariable Long id, Model model) {
        model.addAttribute("userId", id);
        return "userView";
    }
    
    @PostMapping // POST /users
    public String createUser(@RequestParam String name) {
        System.out.println("User Created: " + name);
        return "redirect:/users";
    }
}

2) 경로 변수 (@PathVariable)

  • URL의 일부를 변수로 사용 가능
  • {}를 사용하여 경로 변수 지정
@GetMapping("/users/{id}")
public String getUser(@PathVariable("id") Long userId, Model model) {
    model.addAttribute("userId", userId);
    return "userView";
}

3) 요청 파라미터 (@RequestParam)

  • URL의 쿼리 파라미터를 메서드 인자로 받을 때 사용
@GetMapping("/search")
public String search(@RequestParam String keyword, Model model) {
    model.addAttribute("keyword", keyword);
    return "searchResult";
}

2. Model (데이터 전달 객체)

1) Model이란?

  • Controller에서 생성한 데이터를 View에 전달할 때 사용
  • Model, ModelMap, ModelAndView를 이용하여 데이터를 전달할 수 있음

2) Model 사용 예제

@GetMapping("/greeting")
public String greeting(Model model) {
    model.addAttribute("message", "Hello, Spring MVC!");
    return "greetingView";
}
  • model.addAttribute("message", "Hello, Spring MVC!") → View에서 message를 사용할 수 있음

3) ModelAndView 사용 예제

  • ModelAndView는 Model과 View 정보를 함께 설정할 수 있음
@GetMapping("/welcome")
public ModelAndView welcome() {
    ModelAndView mav = new ModelAndView("welcomeView");
    mav.addObject("message", "Welcome to Spring MVC");
    return mav;
}

6. ViewResolver (뷰 리졸버)

1) ViewResolver란?

  • 컨트롤러가 반환하는 뷰 이름을 실제 View 파일로 매핑하는 역할
  • 기본적으로 Thymeleaf, JSP, FreeMarker 등을 사용 가능

2) Spring Boot의 기본 설정

Spring Boot에서는 Thymeleaf가 기본적으로 설정되어 있으며, application.properties에서 경로를 수정할 수 있음.

spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html
  • prefix: 뷰 파일이 위치하는 디렉토리 설정
  • suffix: 뷰 파일의 확장자 설정

3) Thymeleaf 뷰 파일 예제 (greetingView.html)

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Greeting</title>
</head>
<body>
    <h1 th:text="${message}"></h1>
</body>
</html>
  • th:text="${message}" → Model에서 전달된 message 값을 출력

7. Interceptor (인터셉터)

1) Interceptor란?

  • Spring MVC에서 요청이 컨트롤러에 도달하기 전/후에 추가 작업을 수행하는 기능
  • 인증, 로깅, 권한 체크 등에 활용

2) 인터셉터 구현하기

public class LoggingInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("[Request] URI: " + request.getRequestURI());
        return true; // true를 반환해야 요청이 계속 진행됨
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("[Response] Status: " + response.getStatus());
    }
}

3) 인터셉터 등록

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoggingInterceptor()).addPathPatterns("/**");
    }
}
  • addInterceptor(new LoggingInterceptor()) → 인터셉터 등록
  • addPathPatterns("/**") → 모든 요청에 대해 인터셉터 적용


8. 예외 처리 (Exception Handling)

1) 예외 처리의 필요성

웹 애플리케이션에서는 예상치 못한 오류가 발생할 수 있습니다. 이를 적절하게 처리하지 않으면 사용자에게 불친절한 에러 페이지가 표시되거나, 보안적으로 취약한 정보가 노출될 수 있습니다.

Spring MVC에서는 @ExceptionHandler, @ControllerAdvice 등을 이용하여 예외를 중앙에서 처리할 수 있습니다.

2) @ExceptionHandler를 이용한 개별 컨트롤러 예외 처리

특정 컨트롤러에서 발생하는 예외를 처리할 때 사용합니다.

@Controller
public class UserController {
    @GetMapping("/users/{id}")
    public String getUser(@PathVariable Long id) {
        if (id <= 0) {
            throw new IllegalArgumentException("유효하지 않은 사용자 ID입니다.");
        }
        return "userView";
    }
    
    @ExceptionHandler(IllegalArgumentException.class)
    public String handleIllegalArgumentException(IllegalArgumentException e, Model model) {
        model.addAttribute("errorMessage", e.getMessage());
        return "errorPage";
    }
}
  • 특정 예외(IllegalArgumentException) 발생 시 errorPage.html로 이동
  • 모델에 errorMessage를 추가하여 뷰에서 출력 가능

3) @ControllerAdvice를 이용한 전역 예외 처리

모든 컨트롤러에서 발생하는 예외를 공통으로 처리할 때 사용합니다.

@ControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(Exception.class)
    public String handleException(Exception e, Model model) {
        model.addAttribute("errorMessage", "오류가 발생했습니다: " + e.getMessage());
        return "errorPage";
    }
}
  • 모든 컨트롤러에서 발생하는 Exception을 처리하는 전역 예외 핸들러

9. Form 데이터 처리

1) @ModelAttribute를 이용한 Form 데이터 바인딩

Form 데이터를 객체로 자동 매핑할 수 있도록 도와줍니다.

public class UserForm {
    private String name;
    private int age;
    
    // Getter & Setter
}
@Controller
public class UserController {
    @PostMapping("/register")
    public String registerUser(@ModelAttribute UserForm userForm) {
        System.out.println("이름: " + userForm.getName());
        System.out.println("나이: " + userForm.getAge());
        return "redirect:/success";
    }
}
  • 사용자가 제출한 form 데이터가 UserForm 객체로 자동 매핑됩니다.
  • name, age 값이 UserForm 객체의 필드에 저장됨.

10. 입력값 검증 (Validation)

1) Spring의 Bean Validation (@Valid@Validated)

Spring Boot에서는 Bean Validation (JSR-303, JSR-380)를 이용한 입력값 검증이 가능하며, @Valid 또는 @Validated를 활용합니다.

2) @Valid를 사용한 입력값 검증

import jakarta.validation.constraints.*;

public class UserForm {
    @NotBlank(message = "이름은 필수 입력 항목입니다.")
    private String name;
    
    @Min(value = 18, message = "나이는 18세 이상이어야 합니다.")
    private int age;

    // Getter & Setter
}
@Controller
public class UserController {
    @PostMapping("/register")
    public String registerUser(@Valid @ModelAttribute UserForm userForm, BindingResult result) {
        if (result.hasErrors()) {
            return "registerForm";
        }
        return "redirect:/success";
    }
}
  • @NotBlank, @Min 등의 검증 어노테이션을 이용해 입력값을 검증
  • BindingResult를 사용하여 검증 오류가 발생한 경우 다시 입력 폼을 보여줌

3) 커스텀 Validator 구현

Spring의 Validator 인터페이스를 구현하여 커스텀 검증 로직을 작성할 수도 있습니다.

import org.springframework.validation.Errors;
import org.springframework.validation.Validator;

public class UserFormValidator implements Validator {
    @Override
    public boolean supports(Class<?> clazz) {
        return UserForm.class.equals(clazz);
    }
    
    @Override
    public void validate(Object target, Errors errors) {
        UserForm userForm = (UserForm) target;
        if (userForm.getAge() < 18) {
            errors.rejectValue("age", "Invalid.age", "나이는 18세 이상이어야 합니다.");
        }
    }
}
@Controller
public class UserController {
    private final UserFormValidator validator;
    
    public UserController(UserFormValidator validator) {
        this.validator = validator;
    }
    
    @PostMapping("/register")
    public String registerUser(@ModelAttribute UserForm userForm, BindingResult result) {
        validator.validate(userForm, result);
        if (result.hasErrors()) {
            return "registerForm";
        }
        return "redirect:/success";
    }
}

11. 파일 업로드 (File Upload)

1) 파일 업로드 개요

Spring MVC에서는 사용자가 업로드한 파일을 처리할 수 있도록 MultipartResolver를 제공합니다. 파일 업로드를 처리하려면 Spring Boot에서 기본 제공하는 StandardServletMultipartResolver 또는 CommonsMultipartResolver를 사용할 수 있습니다.

2) 파일 업로드 설정 (Spring Boot)

Spring Boot에서는 application.properties 또는 application.yml에서 파일 업로드를 설정할 수 있습니다.

spring.servlet.multipart.enabled=true
spring.servlet.multipart.max-file-size=5MB
spring.servlet.multipart.max-request-size=10MB
  • max-file-size: 업로드할 수 있는 개별 파일의 최대 크기
  • max-request-size: 한 번의 요청에서 업로드할 수 있는 전체 파일 크기

3) 파일 업로드 컨트롤러 구현

@Controller
public class FileUploadController {
    @PostMapping("/upload")
    public String handleFileUpload(@RequestParam("file") MultipartFile file, Model model) {
        if (!file.isEmpty()) {
            try {
                byte[] bytes = file.getBytes();
                Path path = Paths.get("uploads/" + file.getOriginalFilename());
                Files.write(path, bytes);
                model.addAttribute("message", "파일 업로드 성공: " + file.getOriginalFilename());
            } catch (IOException e) {
                model.addAttribute("message", "파일 업로드 실패: " + e.getMessage());
            }
        } else {
            model.addAttribute("message", "업로드할 파일을 선택하세요.");
        }
        return "uploadResult";
    }
}
  • @RequestParam("file") MultipartFile file: 클라이언트가 업로드한 파일을 매개변수로 받음
  • file.getBytes(): 파일의 내용을 바이트 배열로 변환
  • Files.write(path, bytes): 지정된 경로에 파일 저장

4) 파일 업로드를 위한 HTML 폼 예제

<form method="POST" enctype="multipart/form-data" action="/upload">
    <input type="file" name="file" />
    <button type="submit">업로드</button>
</form>

12. 다국어 지원 (Internationalization, i18n)

1) 다국어 지원 개요

Spring MVC는 다국어 지원을 위해 MessageSource를 활용하며, 사용자의 언어 설정에 따라 다른 언어의 메시지를 제공할 수 있습니다.

2) messages.properties 파일 생성

src/main/resources/messages.properties에 기본 메시지 파일을 생성하고, 특정 언어에 따라 파일을 추가합니다.

# 기본 (한국어)
greeting=안녕하세요
# 영어 (messages_en.properties)
greeting=Hello
# 일본어 (messages_ja.properties)
greeting=こんにちは

3) MessageSource 설정

Spring Boot에서는 자동으로 MessageSource를 설정하지만, 커스텀 설정도 가능합니다.

@Bean
public MessageSource messageSource() {
    ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
    messageSource.setBasename("messages");
    messageSource.setDefaultEncoding("UTF-8");
    return messageSource;
}

4) 다국어 메시지 사용 방법

@Controller
public class GreetingController {
    @Autowired
    private MessageSource messageSource;
    
    @GetMapping("/greeting")
    public String getGreeting(Locale locale, Model model) {
        String greetingMessage = messageSource.getMessage("greeting", null, locale);
        model.addAttribute("message", greetingMessage);
        return "greetingView";
    }
}
  • Locale locale: 현재 요청의 언어 설정을 자동으로 감지
  • messageSource.getMessage("greeting", null, locale): 다국어 메시지 가져오기

5) 다국어 메시지를 HTML에서 사용하기 (Thymeleaf)

<p th:text="#{greeting}"></p>

13. Spring MVC 테스트 방법

1) Spring Boot 테스트 환경 설정

Spring Boot에서 컨트롤러를 테스트하려면 spring-boot-starter-test 의존성이 필요합니다.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>

2) MockMvc를 사용한 컨트롤러 테스트

MockMvc를 사용하면 실제 서버를 실행하지 않고 HTTP 요청을 테스트할 수 있습니다.

@WebMvcTest(GreetingController.class)
class GreetingControllerTest {
    @Autowired
    private MockMvc mockMvc;
    
    @Test
    void greetingShouldReturnMessage() throws Exception {
        mockMvc.perform(get("/greeting").locale(Locale.ENGLISH))
               .andExpect(status().isOk())
               .andExpect(content().string(containsString("Hello")));
    }
}
  • @WebMvcTest(GreetingController.class): GreetingController만 테스트하도록 지정
  • mockMvc.perform(get("/greeting").locale(Locale.ENGLISH)): 가상의 요청 실행
  • andExpect(status().isOk()): HTTP 상태 코드 검증
  • andExpect(content().string(containsString("Hello"))): 응답 내용 검증

3) 통합 테스트 (@SpringBootTest)

실제 애플리케이션의 동작을 확인하는 통합 테스트를 수행할 수 있습니다.

@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
class HttpRequestTest {
    @LocalServerPort
    private int port;

    @Autowired
    private TestRestTemplate restTemplate;

    @Test
    void greetingShouldReturnDefaultMessage() {
        String response = this.restTemplate.getForObject("http://localhost:" + port + "/greeting", String.class);
        assertThat(response).contains("Hello");
    }
}
  • @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT): 랜덤 포트에서 애플리케이션 실행
  • @LocalServerPort: 사용 중인 포트 정보 가져오기
  • TestRestTemplate을 사용하여 실제 HTTP 요청 테스트

'개발 이론 > Spring' 카테고리의 다른 글

SSR  (0) 2025.03.18
Spring, Spring Boot, JPA의 정의  (0) 2025.03.01
Spring Security  (0) 2025.02.20
Spring Boot란?  (0) 2025.02.20