참고 자료 : 초보 개발자를 위한 스프링5 프로그래밍 입문
<Chapter 11. MVC 1 : 요청 매핑, 커맨드 객체, 리다이렉트, 폼 태그, 모델 >
1. 프로젝트 준비
1) 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
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>sp5</groupId>
<artifactId>sp5-chap11</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>javax.servlet.jsp-api</artifactId>
<version>2.3.2-b02</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-jdbc</artifactId>
<version>8.5.27</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.45</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.7.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>utf-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
</project>
2) 요청 매핑 애노테이션을 이용한 경로 매핑
웹 애플리케이션을 개발하는 것은
다음 코드를 작성하는 것과 같다.
- 특정 요청 URL을 처리할 코드
- 처리 결과를 HTML과 같은 형식으로 응답하는 코드
요청 매핑 어노테이션 중 하나인
@GetMapping 어노테이션은
"/hello" 요청 경로를 hello() 메서드가 처리하도록 설정했다.
(10장 HelloController.java 파일 확인)
또한,
요청 매핑 어노테이션을 적용한 메서드를
2개 이상 정의할 수도 있다.
각 요청 매핑 어노테이션의 경로에서
공통되는 부분의 경로를 담은 @RequestMapping 어노테이션을
클래스에 적용하고
각 메서드는 나머지 경로를 값으로 갖는
요청 매핑 어노테이션을 적용할 수 있다.
@Controller
@RequestMapping("/register") // 각 메서드에 공통되는 경로를 지정해준다.
// 이렇게 공통된 부분은 중복으로 기재되는 걸 방지할 수 있음.
public class RegistController {
@RequestMapping("/step1") // 공통 경로를 제외한 나머지 경로를 지정해준다
public String handleStep1(){
return "register/step1";
}
@RequestMapping("/step2")
public String handleStep2(){
...
}
}
스프링 MVC는
클래스에 적용한 요청 매핑 어노테이션의 경로와 (/register)
메서드에 적용한 요청 매핑 어노테이션의 경로를 (/step1, /step2)
합쳐서 경로를 찾는다.
> 회원가입의 첫 번째 과정인
약관을 보여주는 요청 경로를 처리하는 컨트롤러 클래스
package controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class RegisterController {
@RequestMapping("/register/step1")
public String handleStep1(){
return "register/step1";
}
}
3) GET과 POST 구분 : @GetMapping, @PostMapping
스프링 MVC는 GET, POST 방식 상관없이
@RequestMapping 어노테이션에
지정한 경로와 일치하는 요청을 처리한다.
단, POST 방식 요청만 처리하고 싶다면
@PostMapping 어노테이션을 사용해서
제한이 가능하다.
4) 요청 파라미터 접근
컨트롤러 메서트에서 요청 파라미터를 사용하는 방법
1. HttpServletRequest를 직접 이용
package controller;
import org.springframework.stereotype.Controller;
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";
}
@RequestMapping("/register/step2")
public String handleStep2(HttpServletRequest request){
String agreeParam=request.getParameter("agree");
if (agreeParam==null || !agreeParam.equals("true")){
return "register/step1";
}
return "register/step2";
}
}
> 메서드의 파라미터는 HttpServletRequest 타입 사용
> 파라미터값 구하기 : HttpServletRequest의 getParameter() 메서드 사용
2.@RequestParam 어노테이션 사용
> 요청 파라미터 개수가 몇 개 안되면 간단하게 사용할 수 있음
@RequestMapping("/register/step2")
public String handleStep2(
@RequestParam(value="agree",defaultValue="false") Boolean agreeVal){
if(!agree){
return "register/step1";
}
return "register/step2";
}
}
agree 요청 파라미터의 값을 읽어와서 agreeVal 파라미터에 할당
요청 파라미터의 값이 없으면 "false" 문자열을 값으로 사용
> value : HTTP 요청 파라미터의 이름을 지정
> required : 필수 여부를 지정
> defaultValue : 요청 파라미터가 값이 없을 때 사용할 문자열 값을 지정함
agree 요청 파라미터의 값을 읽어옴
> Boolean 타입으로 변환하여 agreeVal 파라미터에 전달
> 기본 데이터 타입과 래퍼 타입에 대한 변환을 지원
5. 리다이렉트 처리
컨트롤러에서 특정 페이지로 리다이렉트 시키는 방법은
"redirect:경로"를 뷰 이름으로 리턴하면 된다.
@GetMapping("/register/step2")
public String handleStep2Get(){
return "redirect:/register/step1";
}
rediect: 뒤의 문자열이 /로 시작하면
웹 어플리케이션 기준으로 이동경로를 생성한다.
웹 어플리케이션 경로인 /sp5-chap11 과
/register/step1을 연결한
/sp5-chap11/register/step2가 된다.
6. 커맨드 객체를 이용해서 요청 파라미터 사용하기
@PostMapping("/register/step3")
public String handleStep3(HttpServletRequest request){
String email=request.getParameter("email");
String name=request.getParameter("name");
String password=request.getParameter("password");
String confirmPassword=request.getParameter("confirmPassword");
RegisterRequest regReq=new RegisterRequest();
regReq.setEmail(email);
regReq.setName(name);
regReq.setPassword(password);
regReq.setConfirmPassword(confirmPassword);
...
}
위 방식대로 진행하면
파라미터의 개수가 많아지면 그만큼 작성을 해줘야한다
스프링은 이러한 불편함을 줄이고자
요청 파라미터의 값을 커맨드 객체에 담아주는 기능을 제공함
커맨드 객체는
요청 매핑 어노테이션이 적용된 메서드의 파라미터에 위치함
@PostMapping("/register/step3")
public String handleStep3(RegisterRequest regReq){
...
}
RegisterRequest 클래스에는
setEmail, setName... 메서드가 있음
스프링은 이 메서드를 이용해서
요청 파라미터의 값을 커맨드 객체에 복사한 후
regReq에 전달함.
package controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import spring.DuplicateMemberException;
import spring.MemberRegisterService;
import spring.RegisterRequest;
@Controller
public class RegisterController {
private MemberRegisterService memberRegisterService;
public void setMemberRegisterService(MemberRegisterService memberRegisterService){
this.memberRegisterService=memberRegisterService;
}
@RequestMapping("/register/step1")
public String handleStep1(){
return "register/step1";
}
@RequestMapping("/register/step2")
public String handleStep2(
@RequestParam(value="agree", defaultValue = "false") Boolean agree) {
if (!agree) {
return "register/step1";
}
return "register/step2";
}
@GetMapping("/register/step2")
public String handleStep2Get(){
return "redirect:/register/step1";
}
// step3 폼 전송 요청을 처리하는 코드
@PostMapping("/register/step3")
public String handleStep3(RegisterRequest regReq){
try {
memberRegisterService.regist(regReq);
return "register/step3";
} catch (DuplicateMemberException ex){
return "register/step2";
}
}
}
handleStep3() 메서드는
MemberRegisterService를 이용해 회원가입을 처리함
회원가입에 성공하면 step3 뷰를 리턴하고
실패하면 step2 뷰를 리턴한다
package config;
import controller.HelloController;
import controller.RegisterController;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import spring.MemberRegisterService;
@Configuration
public class ControllerConfig {
@Autowired
private MemberRegisterService memberRegSvc;
@Bean
public HelloController helloController(){
return new HelloController();
}
@Bean
public RegisterController registerController(){
RegisterController controller =new RegisterController();
controller.setMemberRegisterService(memberRegSvc);
return controller;
}
}
RegisterController 클래스는
MemberRegisterService 타입의 빈을 의존한다.
따라서 ControllerConfig.java 파일에
의존 주입을 설정해줘야 한다.
7. 뷰 JSP 코드에서 커맨드 객체 사용하기
<%@ page contentType="text/html; charset=utf-8" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<title>회원가입</title>
</head>
<body>
<p><strong>${registerRequest.name}님</strong>
회원 가입을 완료했습니다.</p>
<p><a href="<c:url value='/main'/>">[첫 화면 이동]</a></p>
</body>
</html>
커맨드 객체를 사용해서 정보를 표시한다.
registerRequest가 커맨드 객체에 접근할 떄 사용한 속성 이름
${registerRequest.name}
8. @ModelAttribute 어노테이션으로 커맨드 객체 속성 이름 변경
> 커맨드 객체 접근 시 사용할 속성 이름 변경을 하려면
파라미터에 해당 어노테이션을 적용해주면 된다.
public String handleStep3(@ModelAttribute("formData") RegisterRequest regReq){
...
}
formData라는 이름으로 뷰 코드에서 커맨드 객체에 접근할 수 있음!
9. 커맨드 객체와 스프링 폼 연동
<input type="text" name="name" id="name" value="${registerRequest.name}">
> 다시 폼을 보여줄 때 커맨드 객체의 값을 폼에 채워주면
다시 입력해야 하는 불편함을 해소할 수 있다.
10. 컨트롤러 구현 없는 경로 매핑
WebMvcConfigurer 인터페이스의
addViewControllers() 메서드를 사용하면
컨트롤러 구현 없이 요청 경로와 뷰 이름을 연결할 수 있다.
@Override
public void addViewControllers(ViewControllerRegistry registry){
registry.addViewController("/main").setViewName("main");
}
/main 요청 경로에 대해
뷰 이름으로 Main을 사용한다고 설정하는 것임.
MvcConfig 파일에 해당 설정을 추가해준다.
main.jsp
<%@ page contentType="text/html; charset=utf-8" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<title>메인</title>
</head>
<body>
<p>환영합니다.</p>
<p><a href="<c:url value="/register/step1" />">[회원 가입하기]</a>
</body>
</html>
11. 주요 에러 발생 상황
1) 요청 매핑 어노테이션과 관련된 주요 익셉션
404 에러 발생
- 요청 경로 확인
- 컨트롤러에 설정한 경로 확인
- 컨트롤러 클래스를 빈으로 등록했는지 확인
- 컨트롤러 클래스에 @Controller 어노테이션 적용했는지 확인
- JSP 파일이 존재하지 않아도 확인
405 에러 발생
- 지원하지 않는 전송 방식을 사용한 경우
예를 들어 POST 방식만 처리하는 요청 경로를
GET 방식으로 연결하면 에러가 난다
2) @RequestParam이나 커맨드 객체와 관련된 익셉션
12. 커맨드 객체 : 중첩 , 콜렉션 프로퍼티
Respondent.java
package survey;
public class Respondent {
private int age;
private String location;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getLocation() {
return location;
}
public void setLocation(String location) {
this.location = location;
}
}
AnsweredData.java
package survey;
import java.util.List;
public class AnsweredData {
private List<String> responses; // 답변 목록을 저장하기 위해 List 타입의 responsed 프로퍼티 사용
private Respondent res; // 응답자 정보를 담기 위해 Respondent 타입의 res 프로퍼티 사용
public List<String> getResponses() {
return responses;
}
public void setResponses(List<String> responses) {
this.responses = responses;
}
public Respondent getRes() {
return res;
}
public void setRes(Respondent res) {
this.res = res;
}
}
Respondent : 응답자 정보를 담는 클래스
AnsweredData : 설문 항목에 대한 답변과 응답자 정보를 함께 담는다.
res 프로퍼티는 Respondent 타입이며
res 프로퍼티는 다시 age와 location 프로퍼티를 갖는다.
중첩 프로퍼티를 의미함
프로퍼티 : 변수와 유사하지만
일반적으로 변수보다 높은 수준의 추상화를 제공함
값을 가져오거나 설정할 때 추가적인 로직이 실행될 수 있다.
변수 : 데이터를 저장하고 참조하기 위한 메모리 위치를 나타낸다.
단순히 값을 간단히 저장하고 읽기 위한 용도로 사용됨
HTTP 요청 파라미터 이름이
프로퍼티이름[인덱스] > List 타입 프로퍼티의 값 목록을 처리
프로퍼티이름.프로퍼티이름 > 중첩 프로퍼티 값을 처리
package controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import survey.AnsweredData;
@Controller
@RequestMapping("/survey")
public class SurveyController {
@GetMapping
public String form(){
return "survey/surveyForm";
}
@PostMapping
public String submit(@ModelAttribute("ansData")AnsweredData data){
return "survey/submitted";
}
}
ControllerConfig.java
package config;
import controller.HelloController;
import controller.RegisterController;
import controller.SurveyController;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import spring.MemberRegisterService;
@Configuration
public class ControllerConfig {
@Autowired
private MemberRegisterService memberRegSvc;
@Bean
public HelloController helloController(){
return new HelloController();
}
@Bean
public RegisterController registerController(){
RegisterController controller =new RegisterController();
controller.setMemberRegisterService(memberRegSvc);
return controller;
}
@Bean
public SurveyController surveyController(){
return new SurveyController();
}
}
surveyForm.jsp > 폼 전송을 위한 뷰
submitted.jsp > 폼 전송 후 결과를 보여주기 위한 뷰
13. Model을 통해 컨트롤러에서 뷰에 데이터 전달하기
> 컨트롤러는 뷰가 응답 화면을 구성하기 위해 필요한 데이터를 생성 및 전달해야한다.
이때 사용하는 것이 Model.
- 요청 매핑 어노테이션이 적용된 메서드의 파라미터로 Model을 추가
- Model 파라미터의 addAttribute() 메서드로 뷰에서 사용할 데이터 전달
model.addAttribue("greeting","안녕하세요, " + name);
> 속성이름 (이 이름을 사용해 데이터에 접근함)
${greeting} > 이런식으로
1) ModelAndView를 통한 뷰 선택과 모델 전달
> Model을 이용해서 뷰에 전달할 데이터 설정
> 결과를 보여줄 뷰 이름 리턴
ModelAndView를 사용하면 위 2가지를 모두 한 번에 처리할 수 있다.
@GetMapping
public String form(Model model) {
List<Question> questions = createQuestions();
model.addAttribute("questions", questions);
return "survey/surveyForm";
}
>
@GetMapping
public ModelAndView form() {
List<Question> questions = createQuestions();
ModelAndView mav=new ModelAndView();
mav.addObject("questions",questions);
mav.setViewName("survey/surveyForm");
return mav;
}
'여니의 프로그래밍 study > Spring & Spring Boot' 카테고리의 다른 글
[Spring] MVC 3 : 세션, 인터셉터, 쿠키 (1) | 2023.12.26 |
---|---|
[Spring] MVC 2 : 메시지, 커맨드 객체 검증 (1) | 2023.12.08 |
[Spring] 스프링 mvc 프레임워크 동작 방식 (Chapter 10) (0) | 2023.11.25 |
[Spring] 스프링 mvc 시작하기 (Chapter 9) (0) | 2023.11.23 |
[Spring] DB 연동 - 2 (Chapter 8) (1) | 2023.10.08 |