상세 컨텐츠

본문 제목

[스프링2] 11장. MVC 1 : 요청, 매핑, 커맨드 객체, 리다이렉트, 폼 태그, 모델 (2)

22-23/22-23 Spring 2

by dev otcroz 2022. 12. 22. 10:00

본문

728x90

 


11장 (2)는 목록 8 - 14까지 다루고 있습니다. (초보 웹 개발자를 위한 스프링5 프로그래밍 입문 286p~319p)


11 - (2)장의 키워드

# @ModelAttribute 애노테이션

# 커스텀 태그

# ModelAndView

 

8. @ModelAttribute 애노테이션으로 커맨드 객체 속성 이름 변경

  • @ModelAttribute : 커맨드 객체 속성 이름을 변경하고 싶을때 사용한다.
@PostMappint("/register/step3")
public String handleStep3(@ModelAttribute("formData") RegisterRequest regReq) {
	...		//"RegisterRequest -> formData 라는 이름으로 커맨드 객체에 접근 가능
}

 

9. 커맨드 객체와 스프링 폼 연동

  • 입력한 값을 계속 보여주기 위한 설정하기
  • value = "${커맨드명.값}"
//입력한 email 나오게하기
<input type="text" name="email" id="email" value="${registerRequest.email}">
//입력한 name 나오게하기
<input type="text" name="name" id="name" value="${registerRequest.name}">

 

스프링 MVC가 제공하는 커스텀 태그 사용 방법 287p

  • <form> → <form:form>, <input> <form:input>
  1. taglib 디렉티브 설정 하기
    <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %> 추가하기
  2. 폼 태그 수정하기
    <form:from action="step3" modelAttribute="registerRequest">
    <form:input path="email"/>
    <form:password path="password" />
  • action : <form>태그의 action 속성과 동일한 값을 사용한다.
    <form> 태그의 action 속성은 폼 데이터를 서버로 보낼때 해당 데이터가 도착할 URL을 명시한다.
  • modelAttribute : 커맨드 객체의 속성 이름을 지정한다. 설정하지 않는 경우 "commnad"를 기본값으로 사용한다. 예제에서 커맨드 객체의 속성 이름은 "registerRequest"이므로 이 이름을 modelAttribute 속성값으로 설정했다.
<form:form action="step3" modelAttribute="registerRequest">
  • 폼 데이터를 서버로 보내면 페이지를 step3으로 이동해라.
  • 커맨드 객체 속성 이름을 registerRequest로 설정한다.
  • path : 태그의 value로 설정한다
<input id="name" name="name" type="text" value="${registerRequest.name}" />

//커스텀 태그 사용 후
<form:input path="name" /> 
<input id="name" name="name" type="text" value="스프링" /> //만약 폼에 입력한 name이 "스프링"이라면
  • 만약 폼에 입력한 name이 "스프링"이라면 <input> 태그의 value값에 "스프링"이 입력된다.

3. 커맨드 객체를 모델에 넣기

  • <form:form> 태그로 step1에서 step2로 넘어가는 단계에서
  • 커맨드 객체인 "registerRequest" 모델을 넣어야 정상적으로 작동한다.
  • 뷰에 커맨드 객체를 전달해주어야 한다.
//RegisterController.java

import org.stringframework.ui.Model;

@Controller
public class RegisterController{
	...
	@PostMapping("/register/step2") //step2일때
	public String handleStep2(
		@RequestParam(value="agree", defaultValue="false") Boolean agree, Model model){//모델추가
			if(!agree) { //true가 아니면 [약관동의폼]인 "register/step1" 뷰를 리턴
				return "register/step1";
			}
			model.addAttribute("registerRequest", new RegisterRequest()); //새로운 커맨드 객체를 registerRequest 로 만들고, 객체를 모델에 넣어라
			return "register/step2"; //true이면 [입력 폼]인 "register/step2" 뷰를 리턴
		}
	...
}
  • [가입 완료] 버튼을 누르면, registerRequest를 커맨드 객체로 설정하여 다시 폼을 보여줄때 기존에 입력한 이메일과 이름이 폼에 채워진 채로 보일 것이다.

 

10. 컨트롤러 구현 없는 경로 매핑

  • 화면 이동만을 위한 컨트롤러 클래스는 특별한 처리 없이 단순한 뷰 이름만 리턴하도록 구현한다.
//step3.jsp
<p><a href="<c:url value='/main'/>">[첫 화면 이동]</a></p>

//컨트롤러
@Controller
public class MainController{
	@RequestMapping("/main")
	public String main(){
		return "main"; //main뷰로 이동해라. 라는 처리만 한다.
	}
}
  • step3.jsp에서 회원가입 완료 후 첫 화면으로 이동할 수 있는 링크를 제공한다.
  • 특별한 역할이 없고, 연결을 위한 컨트롤러는 필요가 없다.

※ 문제 해결 : WebMvcConfigurer 인터페이스의 addViewControllers() 메서드를 사용한다.

  • 메서드를 재정의하면 컨트롤러 구현 없이 간단한 코드로 요청 경로와 뷰 이름을 연결할 수 있다.
//MvcConfig 파일에 추가

import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;

@Override
public void addViewControllers(ViewControllerRegistry regisry) {
	registry.addViewController("/main").setViewName("main"); // "/main"경로는 main뷰로 이동해라.
}
  • /main 요청 경로에 대해 뷰 이름으로 main을 사용한다고 설정한다.

11. 주요 에러 발생 상황

11.1 요청 매핑 애노테이션과 관련된 주요 익셉션

404 오류 발생

  • 요청 경로를 처리할 컨트롤러가 없을때
  • WebMvcConfigurer를 이용한 설정이 없을때 
  • 뷰 이름에 해당하는 JSP 파일이 없을때
HTTP Status 404 - Not Found

다음 사항을 확인해야한다.

  • 요청 경로가 올바른지
  • 컨트롤러에 저장한 경로가 올바른지
  • 컨트롤러 클래스를 빈으로 등록했는지
  • 컨트롤러 클래스에 @Controller 애노테이션을 적용 했는지
  • 리턴하는 뷰 이름에 해당하는 JSP 파일이 존재하는지

405 오류 발생

  • 지원하지 않는 전송 방식을 사용한 경우 (POST 방색만 처리하는 요청 경로를 GET으로 연결한 경우)
HTTP Status 405 - Method Not Allowed

 

11.2 @RequestParam이나 커맨드 객체와 관련된 주요 익셉션

404 오류 발생

  • @RequestParam 애노테이션을 필수로 설정하고, 기본값을 지정하지 않은 경우
@PostMappint("/register/step2")
public String handleStep2(
	//필수로 존재해야하고 기본값이 없음
	@RequestParam("agree")Boolean agree, Model model){
	...
}
  • 약관 동의 페이지에서 동의롤 하지 않고, [다음 단계]로 넘어온 경우
  • <input> 요소가 선택되지 않아 파라미터로 아무 값도 전송되지 않아 agree 파라미터 값이 존재하지 않는다.
HTTP Status 400 - Bad Request

  • value 값을 Boolean 타입으로 변환할 수 없는 경우
<input type="checkbox" name="agree" value="true1"> 약관동의
  • value의 true1은 Boolean 타입으로 변경할 수 없다.
HTTP Status 400 - BadRequest

 

12. 커맨드 객체 : 중첩 · 콜렉션 프로퍼티

public class Responded {
		
	private int age;
	private String location;

	public int getAge() {
		retrun age;
	}

	public void setAge(int age) {
		this.age = age;
	}

	public String getLocation() {
		return location;
	}

	public void setLocation(String location) {
		this.location = location;
	}
}
public class AnsweredData {
	private List<String> responses; //리스트 프로퍼티
	private Respondent res; //중첩프로퍼티를 가짐

	public List<Stirng> 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;
	}
}
  • 리스트 프로퍼티 : responses는 String 타입의 값을 갖는 List 콜랙션이다.
  • 중첩 프로퍼티 : res 프로퍼티는 Repondent 차입이며 age와 location 프로퍼티를 가진다.
  • 중첩된 형식으로 표현 : res.age, res.location으로 표현

스프링 MVC의 리스트, 중첩 프로퍼티 처리

  • 요청 파라미터의 값을 알맞게 커맨드 객체에 설정해주는 기능을 제공한다.

규칙

  • HTTP 요청 파라미터 이름이 "파라미터이름[인덱스]" 형식이면 List  타입 프로퍼티의 값 목록으로 처리한다.
  • HTTP 요청 파라미터 이름이 "프로퍼티이름 프로퍼티이름"과 같은 형식이면 중첩 프로퍼티 값을 처리한다.

ex) List 타입 프로퍼티를 위한 파라미터 이름 : responses[0](0번 인덱스), responses[1] (1번 인덱스)

중첩 프로퍼티의 경우 파라미터 이름 : res.name

다음과 유사한 방식으로 커맨드 객체에 파라미터 값을 설정한다.

commandObj.getRes().setName(request.getParameter("res.name"));

  • 컨트롤러 코드 작성
@Controller
@RequestMappint("/survey")
public class SurveyController {

	@GetMapping
	public String form() { //메서드 처리 경로 "/survey" - Get방식 요청 처리
		return "survey/surveyForm";
	}

	@PostMapping //메서드 처리 경로 "/survey" - POST방식 요청 처리
	public String submit(@ModelAttribute("ansData") AnsweredData data) { //커맨드 객체로 AnsweredData 사용
		return "survey/submitted";
	}
}
  • 컨트롤러 클래스를 빈으로 추가
import survey.SurveyController;

@Controller
public class ControllerConfig {

	...

	@Bean
	public SurveyController surveyController() {
		return new SurveyController();
	}
}
  • forn()의 뷰 : survey/surveyForm
  • submit() 의 뷰 : survey/submitted
//surveyFrom.jsp

<label><input type="radio" name="responses[0]" value="서버">서버 개발자</lavbel>

<label><input type="radio" name="responses[0]" value="프로트">프론트 개발자</lavbel>

<label><input type="radio" name="responses[0]" value="풀스택">풀스택개발자</lavbel>


<label><input type="radio" name="responses[1]" value="Eclips">Eclips</lavbel>

<label><input type="radio" name="responses[2]" value="Intellij">Intellij</lavbel>

<label><input type="radio" name="responses[1]" value="Sublime">Sublime</lavbel>

<input type="text" name="responses[2]">

<input type="text" name="res.location">

<input type="text" name="res.age">
  • responses[0] -> responses 프로퍼티(List 타입)의 첫번째 값
  • responses[1] -> responses 프로퍼티(List 타입)의 두번째 값
  • responses[2] -> responses 프로퍼티(List 타입)의 세번째 값
  • res.location -> res 프로퍼티(Repondent 타입)의 location 프로퍼티
  • res.age -> res 프로퍼티(Repondent 타입)의 age 프로퍼티

[전송] 버튼을 누르고 난 페이지

  • 폼에서 전송한 데이터가 커맨드 객체에 알맞게 저장된 것을 확인할 수 있다.

 

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

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

컨트롤러가 해야할 두가지

  • 요청 매핑 애노테이션이 적용된 메서드의 파라미터로 Model을 추가
  • Model 파라미터의 addAttribute() 메서드로 뷰에서 사용할 데이터 전달

SurveyController을 설문 항복을 컨트롤러에서 생성해서 뷰에 전달하는 방식으로 변경

1. Question 객체 생성 : 제목과 답변 정보를 보관한다.

Question.java

2. 컨트롤러 수정

SurveyController.java

3. JSP 코드 수정

serveyForm.jsp

4. SurveyController에서 Model을 통해 전달한 Question 리스트를 이용하여 설문 폼이 생성

설문폼

 

13.1 ModelAndView를 통한 뷰 선택과 모델 전달

컨트롤러의 특징

  • Model을 이용하여 뷰에 전달할 데이터 설정
  • 결과를 보여줄 뷰 이름을 리턴

ModelAndView를 이용하면 이 두 가지를 한번에 처리 가능하다.

import org.springframework.web.suvel.ModelAndView;

@Controller
@RequestMappint("/survey")
public class SurveyController {

	@GetMapping
	public ModelAndView form(){
		List<Question> questions = createQuestions(); //Question 리스트 생성
		ModelAndView mav = new ModelAndView(); //ModelAndView 객체 생성
		mav.addObject("questions", questions); //question 이란 이름으로 모델데이터 추가
		mav.setViewName("survey/surveyForm"); //뷰 이름 작성
		return mav;
	}

 

13.2 GET 방식과 POST 방식에 동일 이름 커맨드 객체 사용하기

  • Model 직접 추가 → 커맨드 객체를 파라미터로 추가
@Controller
public class RegisterController{
	...
	@PostMapping("/register/step2") //step2일때
	public String handleStep2(
		@RequestParam(value="agree", defaultValue="false") Boolean agree,
		Model model){//모델 직접 추가
			if(!agree) { //true가 아니면 [약관동의폼]인 "register/step1" 뷰를 리턴
				return "register/step1";
			}
			model.addAttribute("registerRequest", new RegisterRequest()); //새로운 커맨드 객체를 registerRequest 로 만들고, 객체를 모델에 넣어라
			return "register/step2"; //true이면 [입력 폼]인 "register/step2" 뷰를 리턴
		}
}

변경 : GET, POST에서 사용할 커맨드 객체의 속성이 같을때

@Controller
public class RegisterController{
	...
	@PostMapping("/register/step2") //step2일때
	public String handleStep2(
		@RequestParam(value="agree", defaultValue="false") Boolean agree, 
		RegisterRequest registerRequest){//커맨드 객체 추가
			if(!agree) {
				return "register/step1";
			}
			return "register/step2";
		}

변경 : GET, POST에서 사용할 커맨드 객체의 속성이 다를때("/login" 요청 경로일때)

@Controller
@RequestMappint("/login")
public class LoginController {
	
	@GetMapping
	public String form(@ModelAttribute("login") LoginCommand loginCommand) { //@ModelAttribute로 커맨드 객체를 파라미터로 추가
		return "login/loginForm";
	}

	@PostMapping
	public String form(@ModelAttribute("login") LoginCommand loginCommand) {
		...
	}
}

 

14. 주요 폼 태그 설명

14.1 <form> 태그를 위한 커스텀 태그 : <form:form>

  • <form> 태그를 생성할 때 사용된다.
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %> 
...
<form:form>
...
<input ...>
</form:form>
  • method 속성 : defaut는 "post"
  • action 속성 : default는 현재 요청 URL로 설정
  • ex) 요청 URL이 "/sp5-chap22/register/step2"
<form id="command" action="/sp5-chap22/register/step2" method="post"> ...
  • <form:form> 태그는 위와 같은 <form> 태그를 생성한다
  • id값 : 커맨드 객체 이름(default는 "command") / modelAttribute로 커맨드 객체의 이름을 지정한다.
<form:form modelAttribute="loginCommand"> //커맨드 객체 이름을 loginCommand로 지정
...
</form:form>

<form:form> 태그와 관련된 속성 추가

  • action - 폼 데이터를 전송할 URL 입력
  • enctype - 전송될 데이터의 인코딩 타입
  • method - 전송 방식

커맨드 객체 값을 사용하여 이전에 입력한 값을 출력하기

<input type="text" name="id" value="${loginCommand.id}" /> //이전에 입력한 id값 가져오기

 

14.2 <input> 관련 커스텀 태그 : <form:input>, <form:password>, <form:hidden>

  • <form:input> - text 타입의 <input> 태그
  • <form:password> - password 타입의 <input> 태그
  • <form:hidden> - hidden 타입의 <input> 태그

1. <form:input> 태그

<form:input path="email" />

<input id="email" name="email" type="text" value="abc@naver.com" />
  • path 속성: 연결할 커맨드 객체의 프로퍼티를 지정한다.
  • 만약 이메일을 abc@naver.com 이라고 했다면 value 값에 abc@naver.com이 들어간다.

2. <form:password> 태그와 <form:hidden> 태그

<form:from modelAttribute="loginCommand">
	<form:hidden path="defaultSecurityLevel" /> //속성을 이용하여연결할 커맨드 객체의 프로퍼티를 지정
	...
	<form:password path="password" /> //속성을 이용하여연결할 커맨드 객체의 프로퍼티를 지정
</form:form>

14.3 <select> 관련 커스텀 태그 : <form:select>, <forn:options>, <form:option>

  • <form:select> - <select> 태그를 생성한다. <option< 태그를 생성할때 필요한 콜렉션을 전달받을 수도 있다.
  • <form:options> - 지정한 콜렉션 객체를 이용하여 <option> 태그를 생성한다.
  • <form:option> - <option> 태그 한 개를 생성한다.

1. <form:select> 태그

  • <select> 태그에서 사용할 옵션 목록을 Model을 통해서 전달한다.
@GetMapping("/login")
public String form(Model model){
	List<String> loginTypes = new ArrayList<>();
	loginTypes.add("일반회원");
	loginTypes.add("기업회원");
	loginTypes.add("헤드헌터회원");
	model.addAttribute("loginTypes", loginTypes);
	return "login/form";
}
  • path : 커맨드 객체의 프로퍼티 이름을 입력
  • items 속성 : 태그를 생성할때 사용할 콜렉션 객체를 지정

태그의 value 속성 : <option> 태그의 value 값 지정

태그의 label 속성 : 그 값의 텍스트로 사용

<form:option value="헤드헌터회원" label="헤드헌터" />
<option value="헤드헌터회원"> 헤드헌터</option>

Q&A - 빈칸 채우기

1. 커맨드 객체에 접근하여 속성 이름을 변경하고 싶으면 (@ModelAttribute 애노테이션)을 사용한다.

2. 스프링 MVC가 제공하는 태그는 (커스텀 태그)이다. 예시로는 <input>을 <form:input>으로 작성한다.

3. WebMvcConfigurer 인터페이스의 (addViewControllers()) 메서드는 요청 경로와 뷰 이름을 연결해주는 역할을 한다.

4. 컨트롤러의 두 가지 역할은 (Model을 이용하여 뷰에 전달할 데이터 생성), (결과를 보여줄 뷰 이름을 리턴)하는 것이다.

5. 4번의 컨트롤러 역할을 모두 해주는 것은 (ModelAndView)이다.

6. <form:form>태그의 속성 추가는 (action), (encytpe), (method)이다.

7.컨트롤러의 파라미터에 (커맨드 객체)를 추가하면 GET 방식과 POST 방식에 동일한 커맨드 객체를 사용할 수 있다.

 

Q&A - 주관식 문제

1. 특별한 역할이 없는 컨트롤러를 요청 경로를 매핑하여 뷰로만 연결해주려고 한다.

"/main"의 요청경로와 main 뷰 이름을 연결해서 작성하려고 할때, 다음 빈칸을 채우시오.

import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;

@Override
public void addViewControllers(ViewControllerRegistry regisry) {
	registry.__________________("______").setViewName("_____"); // "/main"경로는 main뷰로 이동해라.
}

정답: addViewControllers, /main, main

 

2. 아래와 같은 두가지일을 한번에 처리하려고 한다. 다음 빈칸을 채우시오.

  • Model을 이용하여 뷰에 전달할 데이터 설정
  • 결과를 보여줄 뷰 이름을 리턴
import org.springframework.web.suvel._______________;

@Controller
@RequestMappint("/survey")
public class SurveyController {

	@GetMapping
	public ModelAndView form(){
		List<Question> questions = createQuestions(); //Question 리스트 생성
		______________ mav = new _____________(); //ModelAndView 객체 생성
		mav.addObject("questions", _________); //question 이란 이름으로 모델데이터 추가
		mav.____________("survey/surveyForm"); //뷰 이름 작성
		return mav;
	}

createQuestions() 메서드

정답: ModelAndView, ModelAndView, ModelAndView, questions, setViewName

728x90

관련글 더보기