[boostcourse] 3.9. Spring MVC - BE (1)

1. MVC란?

- Model-View-Controller

- Model: 뷰가 렌더링 할 때 필요한 데이터. 사용자가 요청한 상품 목록이나 주문 내역 등

- View: 실제로 보여지는 부분. 모델을 사용해서 렌더링을 함.

- Controller: 사용자의 액션에 응답하는 컴포넌트. 모델을 업데이트하고 다른 액션을 수행함

 

MVC Model 1 아키텍처

- 브라우저가 요청하면, 해당 요청을 JSP가 받음

- 요청만큼 JSP 페이지가 존재해야 함

- Java Bean을 통해 DB와 연결됨

- 문제점: JSP 자체에 HTML, JAVA 코드가 섞여있음. 유지보수가 어려움

 

MVC Model 2 아키텍처

- 요청을 Servlet이 받게 함

- Servlet이 Java Bean을 통해 DB에서 데이터를 꺼내옴

- JSP를 통해 결과를 화면에 보여지게 함

- 로직과 뷰를 분리함

 

MVC Model 2 발전 아키텍처

- 클라이언트가 보내는 모든 요청을 프론트 컨트롤러라고 하는 Servlet 클래스가 받음 (프론트 컨트롤러는 딱 하나만 존재)

- 요청만 받고 실제 일은 컨트롤러가 처리함. 컨트롤러 혹은 핸들러라고 불리는 클래스에게 위임함

- 관련된 URL을 하나의 클래스에서 처리할 수 있도록 함

- 이런 컨트롤러는 자바 Bean들을 이용해 결과를 만듦

- 만든 결과를 모델에 담아 프론트 컨트롤러에게 보냄

- 그럼 프론트는 알맞은 뷰에게 모델을 전달해서 그 결과를 출력하도록 함

- 이러한 MVC 패턴을 지원하는 게 Spring Web Module

 

2. Spring MVC 패턴의 흐름

- Database 제외 파란색: Spring MVC가 제공해 줌

- 보라색: 개발자가 만들어야 함

- 녹색: Spring 제공 및 개발자가 만듦

 

1. 클라이언트가 요청을 보내면, 모든 요청을 Dispatcher Servlet이 받음

2. 요청을 처리해 줄 컨트롤러와 메소드가 무엇인지, Handler Mapping에게 물어봄

Handler Mapping이 혼자서 알아낼 수 없음. 어떤 요청에 어떤 Controller가 동작할지, XML 파일이나 Java 파일에 애노테이션으로 설정함. -> 이런 정보들을 Handler Mapping 객체들이 관리함

3. Dispatcher Servlet이 2번을 통해 요청을 처리할 컨트롤러와 메소드를 알아냈다면, Handler Adapter에게 실행을 요청함

4. 결정된 컨트롤러와 메소드가 실행됨

5. 그 결과를 Model에 받아서 Dispatcher Servlet에게 전달함. 이때 View name을 같이 보내줌

6. Dispatcher Servlet은 Controller가 리턴한 View name을 가지고 적절한 View Resolver를 통해서, 어떤 View인지 정확하게 알게 됨

7. View를 통해서

8. 응답함

 

Spring MVC를 이해한다 == DispatcherServlet이 어떻게 동작하는지 이해한다.

 

 3. Spring MVC를 이용한 웹 페이지 작성 방법

3.1. DispatcherServlet을 FrontController로 설정하기

- DispatcherServlet이 FrontController라고 설정해 주어야 함

- 여러 가지 방법이 있는데 그 중 web.xml 파일에 설정하는 두 가지 방법을 알아보자

방법 1. xml spring 설정 읽어오도록 하기

<servlet>
    <servlet-name>dispatcher</servlet-name>
    <servlet-class>
        org.springframework.web.servlet.DispatcherServlet
    </servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/spring/dispatcher-config.xml</param-value>  <!-- xml 파일명이 들어감 -->
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>

- <servlet></servlet>, <servlet-mapping></servlet-mapping> 태그에 각각의 값을 넣어주기

- 실제로 어떤 일들을 해야 되는지 여기에 저장

 

방법 2. Java config spring 설정 읽어오도록 하기

<servlet>
    <servlet-name>dispatcher</servlet-name>
    <servlet-class>
        org.springframework.web.servlet.DispatcherServlet
    </servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>com.example.mvcexam.config.DispatcherConfig</param-value>  <!-- Java 클래스 명이 들어감 -->
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>dispatcher</servlet-name>
    <url-pattern>/</url-pattern>	<!-- 모든 요청에 대해 dispatcherServlet을 수행한다 -->
</servlet-mapping>

- xml이 아니라 Java config 파일을 읽어옴

Java config spring 설정 읽어 들이도록 DispatcherServlet 설정

- 실습에서는 이 방법을 사용

 

3.2. Spring MVC 설정

- DispatcherServlet에 대한 정보는 web.xml에서 함

- DispatcherServlet이 읽어들여야 하는 설정은 별도로 해야 함 -> Java Config로 한다.

@Configuration

- Java Config 파일임을 알려줌

@EnableWebMvc

- Web에 필요한 Bean들을 대부분 자동으로 설정해 줌

- DispatcherServlet의 RequestMappingHandlerMapping, RequestMappingHandlerAdapter, ExceptionHandlerExceptionResolver, MessageConverter 등

- @EnableWebMvc가 기본으로 설정해 주는 거 이외에 설정이 필요하다면, WebMvcConfigurerAdapter를 상속받도록 Java Config 클래스를 작성한 후, 필요한 메소드를 오버라이딩 하도록 함

@ComponentScan

- @Controller, @Service, @Repository, @Component이 붙은 클래스를 찾아 스프링 컨테이너가 관리하게 됨

 

3.3. Controller(Handler) 클래스 작성하기

- @Controller를 클래스 위에 붙임

- 맵핑을 위해 @RequestMapping을 클래스나 메소드에서 사용함

(어떤 url로 들어온 요청인지 알아내서, 실제로 처리해야 하는 컨트롤러와 그 컨트롤러에서 구현하고 있는 메소드가 뭔지 알아내기 위해)

@RequestMapping

- Http 요청과 이를 다루기 위한 Controller의 메소드를 연결하는 애노테이션

- @GetMapping, @PostMapping 등

 

4.  Spring MVC를 이용한 웹 페이지 작성 실습

4.0. Spring MVC 프로젝트 생성

# pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>3.1.1</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.example</groupId>
	<artifactId>mvcexam</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>mvcexam</name>
	<description>Demo project for Spring Boot</description>
	<properties>
		<java.version>17</java.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

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

		<dependency>
			<groupId>org.apache.tomcat.embed</groupId>
			<artifactId>tomcat-embed-jasper</artifactId>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

</project>

 

4.1. Configuration 파일 작성하기

- DispatcherServlet이 읽어 들여야 하는 설정 정보 작성하기

 

# WebMvcContextConfiguration.java

package com.example.mvcexam.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.*;
import org.springframework.web.servlet.view.InternalResourceViewResolver;

@Configuration      // 이 클래스는 설정 파일임. DispatcherServlet이 실행될 때 읽어들이는 설정 파일
@EnableWebMvc       // 기본적인 설정들을 함
@ComponentScan(basePackages = {"com.example.mvcexam.controller"})       // 스캔해서 Bean들을 찾을 거임
public class WebMvcContextConfiguration implements WebMvcConfigurer {

    // 해당 서브 도메인에 대한 요청은 다음과 같이 지정한 곳에서 찾으라는 설정
    // 만약 이 부분이 없다면, 컨트롤러가 가진 RequestMapping에서 찾으려고 하면서 오류를 발생시킬 것임
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/assets/**").addResourceLocations("classpath:/META-INF/resources/webjars/").setCachePeriod(31556926);
        registry.addResourceHandler("/css/**").addResourceLocations("/css/").setCachePeriod(31556926);
        registry.addResourceHandler("/img/**").addResourceLocations("/img/").setCachePeriod(31556926);
        registry.addResourceHandler("/js/**").addResourceLocations("/js/").setCachePeriod(31556926);
    }

    // 매핑 정보가 없는 요청을 처리하기 위해, default servlet handler를 사용하게 함
//    @Override
//    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
//        configurer.enable();
//    }

    // 특정 URL에 대한 처리를 컨트롤러 클래스를 작성하지 않고 매핑할 수 있도록 해줌
    @Override
    public void addViewControllers(final ViewControllerRegistry registry) {
        System.out.println("addViewControllers가 호출됨");
        registry.addViewController("/").setViewName("main");    // "/"로 요청이 들어오면 "main"이라는 뷰를 보여주도록 함
    }

    // 뷰 이름만으로는 실제 뷰 정보를 찾을 수 없기에, ViewResolver를 사용해야 함
    @Bean
    public InternalResourceViewResolver getInternalResourceViewResolver() {
        InternalResourceViewResolver resolver = new InternalResourceViewResolver();
        // 뷰 이름 앞뒤로 붙여서 뷰 파일 경로를 찾음
        resolver.setPrefix("/WEB-INF/views/");
        resolver.setSuffix(".jsp");
        return resolver;
    }

}

 

4.2. web.xml에 DispatcherServlet을 FrontController로 설정하기

- DispatcherServlet을 FrontController로 설정함

- DispatcherServlet이 실행될 때 4.1에서 작성한 설정 정보를 담은 클래스를 읽어다가 실행하도록 함

 

# web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app>
    <display-name>Spring JavaConfig Sample</display-name>

    <servlet>
        <servlet-name>mvc</servlet-name>    <!-- 2. 해당 이름을 찾아감. 확인하니 스프링이 제공하는 DispatcherServlet을 FrontController로 할 것임을 명시하고 있음 -->
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextClass</param-name>
            <param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
        </init-param>
        <init-param>
            <param-name>contextConfigLocation</param-name>      <!-- 3. 작성한 설정 정보를 담은 클래스 등록 -->
            <param-value>com.example.mvcexam.config.WebMvcContextConfiguration</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>mvc</servlet-name>    <!-- 1. 모든 요청에 대하여 mvc라는 이름의 Servlet 클래스가 실행되어라 -->
        <url-pattern>/</url-pattern>
    </servlet-mapping>

</web-app>

 

4.3. main.jsp 생성해서 Mapping 연동 여부 확인하기

/main/webapp/WEB-INF/view/main.jsp 생성

 

# main.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
         pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>Main Page</title>
</head>
<body>
<h1>메인 페이지입니다 :)</h1>
</body>
</html>