Ch11. MVC 1 : 요청 매핑
스프링 MVC를 사용해 웹 어플리케이션을 만든 다는 것은 결국 컨트롤러와 뷰 코드를 구현한다는 것을 뜻한다.
예제 프로젝트를 만들어가보자.
우선 설정 파일들이다.
sp5-chap11/src/main/java/config/MemberConfig.java
package config;
import org.apache.tomcat.jdbc.pool.DataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import spring.ChangePasswordService;
import spring.MemberDao;
import spring.MemberRegisterService;
// DataSource, 트랜잭션, 서비스 클래스, DAO 클래스 설정 파일
@Configuration
@EnableTransactionManagement
public class MemberConfig
{
@Bean(destroyMethod="close")
public DataSource dataSource()
{
DataSource ds = new DataSource();
ds.setDriverClassName("com.mysql.cj.jdbc.Driver");
ds.setUrl("jdbc:mysql://localhost:3306/spring5fs?characterEncoding=utf8");
ds.setUsername("spring5");
ds.setPassword("1234");
ds.setInitialSize(2); // 커넥션풀을 2개 만들어 놓는다
ds.setMaxActive(10); // 활성 상태 가능한 최대 커넥션 개수 10
ds.setMaxIdle(10);
// 10초 주기로 유휴 커넥션이 유효한지 여부 검사, 최소 유휴 시간 3분으로 지정
ds.setTestWhileIdle(true); // 유휴 커넥션 검사 true
ds.setMinEvictableIdleTimeMillis(1000 * 60 * 3); // 최소 유휴 시간 3분 설정
ds.setTimeBetweenEvictionRunsMillis(10 * 1000); // 10초 주기로
return ds;
}
@Bean
public PlatformTransactionManager transactionManager()
{
DataSourceTransactionManager tm = new DataSourceTransactionManager();
tm.setDataSource(dataSource()); // DataSource를 이용해 트랜잭션 연동에 사용할 DataSource를 지정함.
return tm;
}
@Bean
public MemberDao memberDao()
{
return new MemberDao(dataSource());
}
@Bean
public MemberRegisterService memberRegSvc()
{
return new MemberRegisterService(memberDao());
}
@Bean
public ChangePasswordService changePwdSvc()
{
ChangePasswordService pwdSvc = new ChangePasswordService();
pwdSvc.setMemberDao(memberDao()); // 주입
return pwdSvc;
}
}
MemberConfig는 빈 설정 클래스다.
내용을 보면 db 관련 설정들, DAO 클래스 관련 빈들이 있다.
sp5-chap11/src/main/java/config/MvcConfig.java
package config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ViewResolverRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
// Spring MVC 설정
@Configuration
@EnableWebMvc
public class MvcConfig implements WebMvcConfigurer
{
// @Controller로 등록되지 않은 경로를 처리
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer)
{
configurer.enable();
}
@Override
public void configureViewResolvers(ViewResolverRegistry registry)
{
// JSP 를 이용해 컨트롤러의 실행 결과를 보여주도록 함
registry.jsp("/WEB-INF/view/", ".jsp");
}
}
MvcConfig는 spring MVC 설정 클래스다.
@EnableWebMvc 애노테이션으로 필요한 설정들을 설정하고 (RequestMappingHandlerMapping, RequestmappingHandlerAdapter 생성도한다). configureViewResolvers를 오버라이딩해 jsp 경로를 등록하고 있다.
sp5-chap11/src/main/webapp/WEB-INF/web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1" metadata-complete="true">
<servlet>
<servlet-name>dispatcher</servlet-name>
<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>
<param-value>
config.MemberConfig
config.MvcConfig
config.ControllerConfig
</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<filter>
<filter-name>encodingFilter</filter-name>
<filter-class>
org.springframework.web.filter.CharacterEncodingFilter
</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
ContextConfigLocation에 MemberConfig, MvcConfig, ControllerConfig를 추가하고 있다.
@RequestMapping, @GetMapping, @PostMapping
스프링 MVC는 별도 설정이 없으면 GET, POST (html 태그 <form>의 메서드) 방식에 상관없이 @RequestMapping에 지정한 경로와 일치하는 요청을 처리한다.
따라서 POST 방식 요청만 처리하고 싶다면 @PostMapping을, GET 방식 요청만 처리하고 싶다면 @GetMapping을 사용한다.
또한 이 두 애노테이션을 사용하면 같은 경로에 대하여 GET, POST 방식을 각각 다른 메서드가 처리하도록 할 수 도 있다.
참고로 url에 직접 주소를 입력해서 접근하는것은 GET 방식 요청이다.
요청 매핑 애노테이션을 이용한 경로 매핑
회원가입 과정을 구현해보자.
일반적으로 회원 가입은 약관 동의 -> 회원 정보 입력 -> 가입 완료 절차를 거친다.
각 과정을 위한 경로는 다음과 같다.
약관 동의 화면 요청 처리 : http://localhost:8080/sp5-chap11/register/step1
회원 정보 입력 화면 : http://localhost:8080/sp5-chap11/register/step2
가입 처리 결과 화면 : http://localhost:8080/sp5-chap11/register/step3
sp5-chap11/src/main/java/controller/RegisterController.java
package controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.servlet.http.HttpServletRequest;
@Controller
public class RegisterController
{
@RequestMapping("/register/step1")
public String handleStep1()
{
return "register/step1";
}
@PostMapping("/register/step2")
public String handleStep2(HttpServletRequest request)
{
// agree 파라미터의 value
String agreeParam = request.getParameter("agree");
if(agreeParam == null || !agreeParam.equals("true"))
{
return "register/step1";
}
return "register/step2";
}
}
@Controller가 붙어 있으니 RegisterController는 컨트롤러로서 사용할 클래스다.
handleStep1은 가입 첫번째 단계인 약관 동의 화면 처리다.
약관 동의는 그냥 약관 내용을 보여줄 뷰의 이름을 리턴하고 있다.
해당하는 JSP 파일인 step1.jsp는 다음과 같다.
<%@ page contentType="text/html; charset=utf-8" %>
<!DOCTYPE html>
<html>
<head>
<title>회원가입</title>
</head>
<body>
<h2>약관</h2>
<p>약관 내용</p>
<form action="step2" method="post">
<label>
<input type="checkbox" name="agree" value="true"> 약관 동의
</label>
<input type="submit" value="다음 단계" />
</form>
</body>
</html>
step1.jsp를 보면 <form action="step2" method="post"> 태그 내부에 <input type="checkbox" name="agree" value="true"> 가 있다.
이는 약관에 동의할 경우 값이 "true"인 "agree" 요청 파라미터의 값을 POST 방식으로 전달함을 의미한다. (전달할 url은 step2).
즉
<input type="submit">에서 <form>의 데이터를 url이 "register/step2" 이면서 POST 방식인 컨트롤러와 매핑하겠다는 것이다.
다시 RegisterController 클래스를 보면 handleStep2 메소드가 있다.
이 메소드는 @PostMapping("register/step2") 애노테이션이 있으므로 POST 방식 요청을 처리할 것이다.
요청 파라미터에 접근하는 방법은 HttpServletRequest 를 사용하는 것인데, 보이는 것처럼 HttpServletRequest의 getParameter 메서드로 파라미터의 값을 받을수 있다.
여기서는 "agree" 파라미터의 값을 받고 있고 그 값은 "true" 이다.
/register/step2.jsp
<%-- 입력 폼 --%>
<%@ page contentType="text/html; charset=utf-8" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<!DOCTYPE html>
<html>
<head>
<title>회원가입</title>
</head>
<body>
<h2>회원 정보 입력</h2>
<form action="step3" method="post">
<p>
<label>이메일:<br>
<input type="text" name="email" id="email">
</label>
</p>
<p>
<label>이름:<br>
<input type="text" name="name" id="name">
</label>
</p>
<p>
<label>비밀번호:<br>
<input type="password" name="password" id="password">
</label>
</p>
<p>
<label>비밀번호 확인:<br>
<input type="password" name="confirmPassword" id="confirmPassword">
</label>
</p>
<input type="submit" value="가입 완료">
</form>
</body>
</html>
지금까지의 흐름
지금까지의 흐름을 정리해 보자.
우선 설정 파일들을 /config 폴더에(패키지) 만들었다.
/config/MvcConfig.java // Spring Mvc 설정
/config/ControllerConfig.java // 컨트롤러 설정 ( 컨트롤러들 빈 객체로 등록)
/config/MemberConfig.java // DataSource, 트랜잭션, Dao 클래스 설정
그리고 컨트롤러를 담당할 클래스 파일을 만들었다.
/controller/RegisterController.java // handleStep1(), handleStep2()
마지막으로 뷰로서 기능할 jsp 파일들을 만들었다.
/webapp/WEB-INF/view/register/step1.jsp // 약관 내용
/webapp/WEB-INF/view/register/step2.jsp // 회원 가입 입력 폼
이제 순서대로 따라가보자.
처음에 step1 창인 약관창이 step1.jsp가 보여줄것이다.
여기서 약관 동의를 체크하지 않고 다음 단계를 누른다.
그렇다면 step1.jsp의 form 태그에서 "agree" 파라미터의 값인 "false" 이다.
이 정보가 form 에 저장된다.
다음 단계를 누름으로서 "submit"에 의해 <form>에 저장된 데이터들이 전송된다.
<form> 의 action="step2" method="post"에 의해서 매핑 주소가 "/step2" 이면서 POST 방식을 처리하는 컨트롤러를 찾을 것이다.
그 컨트롤러는 바로 RegisterController.java의 handleStep2() 다.
해당 컨트롤러에 의해 "agree" 파라미터의 값이 "true"가 아니므로 "register/step1" 을 뷰 이름으로서 리턴한다 즉 다시 약관 동의 창을 보여준다.
만약 "agree" 파라미터의 값이 "true"였다면 "register/step2"를 뷰이름으로서 리턴해 약관 동의 폼을 보여줬을 것이다.
정리
1. 웹의 요청 ("agree" 파라미터의 값이 "ture", 해당 데이터가 담긴 form을 매핑 주소가 "/step2"이면서 POST 방식을 처리하는 컨트롤러에게 요청함)
2. 요청을 받은 DispatcherServlet이 컨트롤러 찾음 -> 요청에 맞는 컨트롤러는 RegisterController.java의 handleStep2()
3. 컨트롤러의 내용에 따라 얻은 데이터 처리하고, 웹에 표시해줄 뷰 리턴.
출처 : 스프링5 프로그래밍 입문 (최범균 저)