티스토리 뷰

study/Spring

chap 11 - MVC 1 (2)

xoxowo 2023. 2. 10. 20:53

초보 웹 개발자를 위한 스프링 5 프로그램 입문을 보고 복습 겸 개인 학습 정리입니다. (windows 기준)

 

 

ㅎ.. 복잡복잡

 

 

이전 글에선 스프링 MVC로  main화면에서 회원 가입  → 가입 완료 후 → 메인화면으로도 돌아가는 것까지 만들어보았다.

이번 chap 11에서 서버에서 발생할 수 있는 오류들을 정리하고, 설문조사 페이지를 만들면서 모델처리를 하는 방법들에 대해 조금(?) 깊게 알아보았다. 

 

예제 프로젝트 만들기 (메이븐 기준)  예제 코드

sp5-chap11 프로젝트 폴더 생성

프로젝트(sp5-chap11) 하위 폴더로 아래 폴더 생성

- src/main/java

- src/main/java/webapp

- src/main/java/webapp/WEB-INF

- src/main/java/webapp/WEB-INF/view

+ step.jsp 파일 복붙

+ chap 08 spring 패키지의 파일 복붙하기

+ 데이터 베이스 생성

sp5-chap11 폴더 내부에 pom.xml 생성

+ pom.xml 파일에 <packaging> war 값 추가, 서블릿 3.1, JSP 2.3, JSTL 1.2 dependency 추가

 war : web application archive를 의미

이클립스에서 sp5-chap11 폴더에 생성한 메이븐 프로젝트 import

 

 

자주 발생하는 오류들

 

404  Not Found

요청 경로를 처리할 컨트롤러가 존재하지 않거나, WebMvcConfigurer를 이용한 설정이 없다면 404 error 발생

경로가 올바른지, 컨트롤러에 설정한 경로, 컨트롤러 클래스에 빈 등록, 컨트롤러 클래스에 @Controller 애노테이션 적용 했는지 확인해야 한다.

 

405 Method Not Allowed

 지원하지 않은 전송 방식(Method)을 사용한 경우 405 error 발생

요청에 맞는 전송 방식을 사용해야 한다.

 

 

400 Bad Request

@RequestRaram의 필수 파라미터가 존재하지 않을 경우 

 

RegisterController클래스의 hadleStep2 메서드에 @RequestRaram 애노테이션을 설정하고 기본값을 지정하지 않았을 때 약관 동의 버튼을 누르지 않고 다음 단계버튼을 누르게 되면  400 error 가 발생한다.

400 Bad Request

 @RequestRaram가 적용된 파라미터의 타입으로 변환할 수 없는 경우

 

step2.jsp파일의 input 태그의 value값이 "ture1"값을 boolean 타입으로 변환할 수 없기 때문에 400 error가 발생한다. 

 

커맨드 객체 

중첩과 컬렉션 프로퍼티

 

15. 설문조사 폼 추가

세 개의 설문 항목과 응답자의 지역, 나이를 입력받는 설문 조사 정보를 담기 위해 survey 패키지를 만들고 클래스를 추가로 작성하자 (코드를 짧게 보여주기 위해 줄였다..ㅎ.)

 

Respondent 클래스는 응답자의 정보를 담는다.

chap11/src/main/java/survey/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 클래스는 설문 항목에 대한 답변과 응답자 정보를 함께 담는다.

그리고 답변 목록을 저장하기 위해 List 타입의 responses 프로퍼티를 사용했고,

응답자 정보를 담기 위해 Respondent 타입의 res 프로퍼티를 사용했다.

 chap11/src/main/java/survey/AnsweredData.java

package survey;

import java.util.*;

public class AnsweredData {

	private List<String> responses;
	private 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;}
}

 

16. 컨트롤러 클래스 추가

 chap11/src/main/java/survey/SurveyController.java

package survey;

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;

@Controller
@RequestMapping("/survey")
public class SurveyController {
	@GetMapping
	public String form() {
		return "survey/surveyForm";
	}
	
	@PostMapping
	public String submit(@ModelAttribute("ansData") AnsweredData data) {
		return "survey/submitted";
	}
}

 

17. 위에서 만든 컨트롤러 클래스를 빈 객체 등록

 chap11/src/main/java/config/ControllerConfig.java

package config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import controller.RegisterController;
import spring.MemberRegisterService;
import survey.SurveyController;

@Configuration
public class ControllerConfig {

    @Autowired
    private MemberRegisterService memberRegSvc;

    @Bean
    public RegisterController registerController(){
        RegisterController controller = new RegisterController();
        controller.setMemberRegisterService(memberRegSvc);
        return controller;
    }
   
    @Bean
    public SurveyController surveyController() {
    	return new SurveyController();
    }
}

 

 

18. 설문조사 응답자에 대한 폼(submitted)과 설문조사 폼(surveyForm)에 대한 .jsp를 추가 (접은 글로 대체)

📌 surveyForm을 살펴보면 각 문항의 <input> 태그 안에 name="responses [  ]" 속성이 들어가있는데 앞서 커맨드 객체의 프로퍼티에 매핑된다.

 

 chap11/src/main/webapp/WEB-INF/view/survey/surveyForm.xml

더보기

<%@ 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>
    <h2>설문조사</h2>
    <form method="post">
    <p>
1. 당신의 역활은? <br/>
<label><input type="radio" name="responses[0]" value="서버">서버개발자</label>
<label><input type="radio" name="responses[0]" value="프론트">프론트개발자</label>
<label><input type="radio" name="responses[0]" value="풀스택">풀스택개발자</label>
</p>
    <p>
2. 가장 많이 사용하는 개발 도구는? <br/>
<label><input type="radio" name="responses[1]" value="Eclipse">Eclipse</label>
<label><input type="radio" name="responses[1]" value="Intellij">Intellij</label>
<label><input type="radio" name="responses[1]" value="VisualStudio">VisualStudio</label>
</p>

<p>
3.하고싶은 말 <br/>
<input type="text" name="responses[2]">
</p>
    <p>
        <label>응답자 위치:<br>
        <input type="text" name="res.location">
        </label>
    </p>
    <p>
        <label>응답자 나이:<br>
        <input type="text" name="res.age">
        </label>
    </p>
    <input type="submit" value="전송">
    </form>
</body>
</html>

 chap11/src/main/webapp/WEB-INF/view/survey/submitted.xml

더보기

<%@ 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>
    <ul>
        <c:forEach var="response" 
                   items="${ansData.responses}" varStatus="status">
        <li>${status.index + 1}번 문항: ${response}</li>
        </c:forEach>
    </ul>
    <p>응답자 위치: ${ansData.res.location}</p>
    <p>응답자 나이: ${ansData.res.age}</p>
</body>
</html>

 

19. 서버 실행

[Run As] → [Run on Server] → [톰캣😺 서버 실행] 

설문 조사에 대한 주소 설정을 했기 때문에 서버를 실행하고 http://localhost:8090/sp5-chap11/survey에 접속하면 아래와 같이 surveyForm 이 먼저 나오고 응답을 하고 전송을 누르면 데이터가 커맨드 객체에 알맞게 저장된 것을 확인할 수 있다.

 

20. Model을 통해 컨트롤러에서 뷰로 데이터 전달하기

이제 컨트롤러는 뷰가 응답화면을 구성하는데 필요한 데이터를 생성해서 전달해야 한다. 이때 Model을 사용한다.

뷰에 데이터를 전달하는 컨트롤러는 다음 두가지를 하면 된다.

- 요청 매핑 애노테이션이 적용된 메서드의 파라미터로 Model 추가 

- Model 파라미터의 addAttribute() 메서드로 뷰에서 사용할 데이터를 전달

 

※ chap 09의 컨트롤러 뷰에서 작성했었다.

 

이전에 작성했던 surveyForm 클래스는 하드 코딩이었기 때문에 이제 설문 항목을 컨트롤러에서 생성해서 뷰에 전달하는 방식으로 변경하기 위해 Question 클래스를 작성한다.

 

Question 클래스의 title과 options는 각 질문 제목과 답변 옵션을 보관하며,

주관식일 경우 생성자(16행  public Question(String title) {this(title, Collections.<String>emptyList());} )를 사용해서 답변 옵션이 없는 Question 객체를 생성한다.

 

→ chap11/src/main/java/survey/Question.java

package survey;

import java.util.Collections;
import java.util.List;

public class Question {

		private String title;
		private List<String> options;
		
		public Question(String title, List<String> options) {
			this.title = title;
			this.options = options;
		}
		public Question(String title) {
			this(title, Collections.<String>emptyList());
		}
		public String getTitle() {
			return title;
		}
		public List<String> getOptions(){
			return options;
		}
		public boolean isChoice() {
			return options != null&& !options.isEmpty();
		}
}

 

21. SurveyController가 Quesion 객체 목록을 생성해서 뷰에 전달하도록 구현하기

※ DB에서 정보를 읽어와 Question 목록을 생성하는 게 맞지만 예제에서는 컨트롤러에서 생성하도록 구현했다.

 

이전에 작성한 SurveyController 클래스 내용을 수정하자. (16번 코드와 비교해 보기)

 

form 메서드 파라미터에 Model model을 추가했고, addAttrivute() 메서드로 questions라는 이름으로 모델에 추가했다.  

→ chap11/src/main/java/survey/SurveyController.java

package survey;

import java.util.Arrays;
import java.util.List;

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 org.springframework.ui.Model;

@Controller
@RequestMapping("/survey")
public class SurveyController {
	@GetMapping
	public String form(Model model) {
		List<Question> questions = createQuestions();
		model.addAttribute("question", questions);
		return "survey/surveyForm";
	}
	
	private List<Question> createQuestions(){
		Question q1 = new Question("희망하는 역활은 무엇인가요?", Arrays.asList("서버","프론트","풀스택"));
		Question q2 = new Question("자주 사용하는 개발도구는 무엇인가요?", Arrays.asList("이클립스","인텔리제이","비쥬얼스튜디오"));
		Question q3 = new Question("하고싶은 말을 적어주세요.");
		return Arrays.asList(q1, q2, q3);
	}
	
	@PostMapping
	public String submit(@ModelAttribute("ansData") AnsweredData data) {
		return "survey/submitted";
	}
}

 

22.  컨트롤러가 전달한 Question 리스트를 사용해서 화면을 생성하도록 surveyForm.jsp도 수정한다

 

수정 전

<input> 태그 안에 name="responses [  ]" 속성을 사용하여 커맨드 객체의 프로퍼티에 매핑을 했다.

수정 후

Model 파라미터의 addAttribute() 메서드로 생성한 "questions"을 뷰에 사용할 데이터를 전달한다. 

 

 

23. 서버 실행

[Run As] → [Run on Server] → [톰캣😺 서버 실행] 

20~ 22번 과정을 거친 폼을 보면 외관상 큰 차이는 없어 보이지만 내부 코드에 많은 변화가 있었다

기존 하드코딩한   (19번)                                                                  설문 항목을 컨트롤러에서 생성해서 뷰에 전달 한 폼 

24. ModelAndView를 사용하여 한 번에 처리하기

20번에서 Model을 사용하여 뷰에 전달할 데이터를 설정하고 결과를 보여줄 뷰 이름을 리턴했다면, ModelAndView를 사용하면 한 번에 처리할 수 있다. 

addObject() 메서드로 뷰에 전달할 모델 데이터를 추가하고, 뷰 이름은 setViewName() 메서드를 이용해서 지정한다.

 

 

 

댓글