본 문서에서 다루는 뷰는 크게 다음과 같다.
step1
: 약관 동의, 별다른 기능 없음step2
: 회원가입 정보입력, step1에 동의 해야만 접근 가능step3
: 회원가입 완료, 첫 화면으로 돌아가는 기능 제공surveyFrom
: 설문조사 항목을 표기submitted
: 설문조사 결과를 화면에 표기 웹 어플리케이션을 개발하는 것은 다음 코드를 작성하는 것
URL을 처리
할 코드응답
하는 코드요청 매핑 어노테이션
은 다음과 같다.
이를 이용하여 간단히 화면을 띄우는 방법은 다음 예시와 같다.
// RegisterController.java
@Controller
public class RegisterController {
...
@RequestMapping("/register/step1")
public String handleStep1() {
return "register/step1";
}
...
뷰의 이름
등록
@RequestMapping
에 지정한 경로와 일치하는 요청을 처리POST
방식의 요청만 처리하고 싶다면, @PostMapping
어노테이션을 사용해 제한GET
방식은 @GetMapping
어노테이션을 사용해 제한
// RegisterController.java
@Controller
public class RegisterController {
...
// regist/step2 경로로 들어오는 요청중 POST 방식만 처리
@PostMapping("/register/step2")
public String handleStep2(
...
}
// regist/step2 경로로 들어오는 요청중 GET 방식만 처리
@GetMapping("/register/step2")
public String handleStep2Get() {
return "redirect:/register/step1";
}
...
}
step1.jsp 코드에는 다음처럼 ‘agree’ 요청 파라미터의 값을 POST
방식으로 전송하며,
폼에서는 지정한 agree 요청 파라미터의 값
을 이용해 약관 동의 여부를 확인 가능
<!-- step1.jsp -->
<!-- 약관 동의 화면을 생성하는 코드 -->
...
<form action="step2" method="post">
<label>
<input type="checkbox" name="agree" value="true"> 약관 동의
</label>
<input type="submit" value="다음 단계" />
</form>
...
컨트롤 매서드에서 요청 파라미터를 사용하는 방법은 다음과 같이 2가지 방법이 존재
HttpServletRequest
를 직접 이용@RequestParam
어노테이션을 사용 // RegisterController.java
@Controller
public class RegisterController {
...
@PostMapping("/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
타입을 사용getPatameter()
매서드를 이용해 파라미터의 값을 구함 // RegustController.java
@Controller
public class RegisterController {
...
@PostMapping("/register/step2")
public String handleStep2(
/*
* agree 요청 파라미터의 값을 읽어, agreeVal에 할당
* 요청 파라미터의 값이 없다면, "false" 문자열을 값으로 사용
*/
@RequestParam(value = "agree", defaultValue = "false") Boolean agreeVal){
if (!agree) {
return "register/step1";
}
return "register/step2";
}
...
}
@RequestParam
어노테이션을 사용해 간단히 값을 구하는 것이 가능속성
은 다음이 존재
step2는 step1에서 약관을 동의해야만 접근 가능한 페이지이므로, URL을 직접
입력하여 접근하는 GET 방식은 허용하지 않음
따라서 다음과 같이 URL을 직접 입력하는 경우, step1로 리다이렉트
시킴
// RegisterController.java
@Controller
public class RegisterController {
...
// regist/step2 경로로 들어오는 요청중 POST 방식만 처리
@PostMapping("/register/step2")
public String handleStep2(
...
}
// regist/step2 경로로 들어오는 요청중 GET 방식만 처리, step1로 리다이렉트 시킴
@GetMapping("/register/step2")
public String handleStep2Get() {
return "redirect:/register/step1";
}
...
}
리다이렉트할 경로
를 설정하는 방법은 다음과 같이 3가지가 존재
웹 어플리케이션을 기준
으로 이동 경로를 생성:
상대 경로
를 이용:
절대 경로
를 이용:
step2.jsp가 생성하는 폼은 다음 파라미터
를 이용해 정보를 서버에 전송
@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");
...
하지만 파라미터의 개수가 훨씬 많아진다면
일일히
값을 읽어올 것인가?
커맨드 객체
에 담아주는 기능을 다음과 같이 제공
@PostMapping("/register/step3")
public String handleStep3(RegisterRequest regReq){
...
}
요청 파라미터
의 값을 전달 받을 수 있는 setter 매서드
를 포함하는 객체를 커맨드 객체로 사용
// RegisterRequest.java
public class RegisterRequest {
...
public void setEmail(String email) {
this.email = email;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
...
}
가입할 때 사용한 이메일 주소와 이름을 가입 완료 화면에서 커맨드 객체를 이용해 다음과 같이 표시 가능
...
<body>
<p><strong>${registerRequest.name}님</strong>
회원 가입을 완료했습니다.</p>
<p><a href="<c:url value='/main'/>">[첫 화면 이동]</a></p>
</body>
</html>
커맨드 객체
에 접근시 사용한 속성 이름
동일한
속성 이름을 사용해 커맨드 객체를 뷰에 전달
JSTL
c tag
는 여기를 참조@PostMapping("/register/step3") public String handleStep3(RegisterRequest regReq){ ... } // 위와 같은 RegisterRequest 클래스 이름에서 첫 글자만 소문자로 변경해 아래와 같이 전달
<p><strong>${registerRequest.name}님</strong>
회원 가입을 완료했습니다.</p>
@PostMapping("/register/step3")
// 뷰 코드에서는 "fromData"라는 이름으로 커맨드 객체에 접근 가능
public String handleStep3(@ModelAttribute("formData") RegisterRequest regReq){
...
}
@ModelAttribute
어노테이션을 사용해 변경다시 입력
해야 하는 불편함이 발생커맨드 객체
의 값을 폼에 채워주면 이런 불편함을 해소 가능커스텀 태그
를 사용해 간단한 커맨드 객체의 값 출력 가능<form:form>
태그와 <form:input>
태그를 제공
<!-- step2.jsp -->
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
...
<body>
<h2>회원 정보 입력</h2>
<form:form action="step3" modelAttribute="registerRequest">
<p>
<label>이메일:<br>
<form:input path="email" />
</label>
</p>
<p>
<label>이름:<br>
<form:input path="name" />
</label>
</p>
<p>
<label>비밀번호:<br>
<form:password path="password" />
</label>
</p>
...
커맨드 객체
가 존재해야 함객체
를 모델
에 넣어야 함
// RegisterController.java
@Controller
public class RegisterController {
...
@PostMapping("/register/step2")
public String handleStep2(
@RequestParam(value = "agree", defaultValue = "false") Boolean agree,
Model model) {
if (!agree) {
return "register/step1";
}
// 커스텀 태그 사용을 위해 커맨드 객체를 모델에 넣어줌
model.addAttribute("registerRequest", new RegisterRequest());
return "register/step2";
}
...
}
step3.jsp에 존재하는 다음과 같은 코드는 가입 완료 후 첫 화면으로 복귀하는 역할
<p><a herf="c:url value='/main/'>">[첫 화면 이동]</a></p>
이 첫 화면이 단순히 환영 문구와 회원 가입으로 이동하는 링크를 제공한다면, 이를 위해 다음과 같은 컨트롤러
를 구현해야 함
@Controller
public class MainController{
@RequestMapping("/main")
public String main(){
return "main";
}
}
낭비
발생addViewControllers()
매서드를 이용해 다음과 같이 보완 가능
// MvcConfig.java
@Configuration
@EnableWebMvc
public class MvcConfig implements WebMvcConfigurer {
...
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/main").setViewName("main");
}
}
스프링 MVC는 다음과 같은 경우에 요청 파라미터
의 값을 알맞게 커맨드 객체에 전달
리스트
타입의 프로퍼티를 가졌거나중첩
프로퍼티를 가진 경우각 경우의 규칙은 다음과 같다.
"프로퍼티이름[인덱스]"
형식이면 리스트를 처리"프로퍼티이름.프로퍼티이름"
형식이면 중첩 프로퍼티를 처리 // AnsweredData.java
public class AnsweredData{
private List<String> responses;
private Respondent res;
...
}
// Respondent.java
public class Respondent{
private int age;
private String location;
...
}
리스트
타입의 프로퍼티 responses를 가짐중첩
프로퍼티 res를 가짐 <!-- surveyForm.jsp -->
...
1. 당신의 역할은?<br/>
<label><input type = "radio" name = "response[0]" value = "서버">
서버개발자</label>
<label><input type = "radio" name = "response[0]" value = "프론트">
프론트개발자</label>
<label><input type = "radio" name = "response[0]" value = "풀스택">
풀스택개발자</label>
</p>
<p>
2. 가장 많이 사용하는 도구는?<br/>
<label><input type = "radio" name = "response[1]" value = "Eclipse">
Eclipse</label>
<label><input type = "radio" name = "response[1]" value = "Intellij">
Intellij</label>
<label><input type = "radio" name = "response[1]" value = "Sublime">
Sublime</label>
...
<label>응답자 위치:<br>
<input type="text" name="res.location">
</label>
...
컨트롤러는 뷰가 응답 화면
을 구성하는데 필요한 데이터를 생성해서 Model
을 이용하여 전달하며 다음과 같은 두 가지를 수행
Model
을 추가addAttribute()
매서드로 뷰에서 사용할 데이터 전달앞선 예제에서 JSP 파일에 설문 항목을 하드 코딩한 부분을
컨트롤러
에서 생성해 뷰에전달
하는 방법으로 변경
// SurveyController.java
@Controller
@RequestMapping("/survey")
public class SurveyController {
@GetMapping
/*
* 1. 파라미터로 Model 타입을 추가
* 2. addAttribute() 매서드로 전달
*/
public String form(Model model) {
List<Question> questions = createQuestions();
model.addAttribute("questions", questions);
return "survey/surveyForm";
}
// 설문항목
private List<Question> createQuestions() {
Question q1 = new Question("당신의 역할은 무엇입니까?",
Arrays.asList("서버", "프론트", "풀스택"));
Question q2 = new Question("많이 사용하는 개발도구는 무엇입니까?",
Arrays.asList("이클립스", "인텔리J", "서브라임"));
Question q3 = new Question("하고 싶은 말을 적어주세요.");
return Arrays.asList(q1, q2, q3);
}
@PostMapping
public String submit(@ModelAttribute("ansData") AnsweredData data) {
return "survey/submitted";
}
}
<!-- surveyForm.jsp -->
...
<body>
<h2>설문조사</h2>
<form method="post">
<c:forEach var="q" items="${questions}" varStatus="status">
<p>
${status.index + 1}. ${q.title}<br/>
<c:if test="${q.choice}">
<c:forEach var="option" items="${q.options}">
<label><input type="radio"
name="responses[${status.index}]" value="${option}">
${option}</label>
</c:forEach>
</c:if>
<c:if test="${! q.choice }">
<input type="text" name="responses[${status.index}]">
</c:if>
</p>
</c:forEach>
...
Model
을 이용해 뷰에 전달할 데이터 설정뷰 이름
을 리턴ModelAndView
를 사용하면 이 두 가지를 한번에 다음과 같이 처리 가능
// SurveyController.java
@Controller
@RequestMapping("/survey")
public class SurveyController {
/*
* 기존 코드
@GetMapping
public String form(Model model) {
List<Question> questions = createQuestions();
model.addAttribute("questions", questions);
return "survey/surveyForm";
}
*/
@GetMapping
public ModelAndiew form() {
List<Question> questions = createQuestions();
mav.addObject("questions", questions);
mav.setViewName("survey/surveyFrom");
return mav;
}
}
스프링 MVC는 <form:form>
, <form:input>
등 HTML 폼과 커맨드 객체를 연동하기 위한 JSP 태그 라이브러리
를 제공
<form>
커스텀 태그는 <form> 태그를 생성할 때 사용
<form:form>
...
<input type="submit" value="가입 완료">
</form:form>
method 속성
과 action 속성
의 기본값은 각각 post
와 현재 요청 URL
만약 요청 URL이 “/sp5-chap11/register/step2” 라면 앞선 태그는 다음의 <form> 태그를 생성 ~~~jsp
modelAttribute
속성값으로 설정
<form:form modelAttribute="loginCommand">
...
</form>
이전에 입력한 값
을 출력 가능
<form:from modelAttribute="loginCommand">
...
<input type="text" name="id" value="${loginCoomand.id}" />
...
</form:form>
이때 input을 직접 사용하기 보다는
<form:input>
등의 태그를 사용하면 편리
<form:input>
: text 타입 <input> 태그<form:password>
: passowrd 타입 <input> 태그<form:hidden>
: hidden 타입 <input> 태그<form:input>
커스텀 태그는 다음과 같이 path
속성을 사용해 연결할 커맨드 객체의 프로퍼티를 지정
<form:form modelAttribute="registerRequest" action=step3">
<p>
<label>이메일:<br/>
<form:input path="email"/>
</label>
</p>
<!-- 위 코드는 아래와 같이 HTML <input> 태그를 생성 -->
<form id="registerRequest" action=step3" method="post">
<p>
<label>이메일:<br/>
<input id="email" name="email" type="text" value=""/>
</label>
</p>
<form:select>
: <select> 태그를 생성, <option> 태그를 생성할 때 필요한 콜렉션을 전달 받을 수 있음<form:password>
: 지정한 콜렉션 객체를 이용하여 <option> 태그를 생성<form:hidden>
: <option> 태그를 한 개 생성선택
옵션을 제공할 때 주로 사용Model
을 통해 전달하면 뷰에서 다음과 같이 간단하게 태그 생성 가능
<form:form modelAttriute="login">
<p>
<label for="loginType">로그인 타입</label>
<!-- loginTypes에는 모델에서 넘어온 로그인 타입 관련 array등이 저장됨 -->
<form:select path="loginType" items="${loginTypes}" />
</p>
...
</form:form>
배열
이나 List
타입을 이용해 한 개 이상의 값을 커맨드 객체에 저장 후 HTML 폼에서는 checkbox
타입 태그 사용<form:checkboxes>
: 커맨드 객체의 특정 프로퍼티와 관련된 checkbox 타입
의 <input> 태그 목록 생성<form:checkbox>
: 커맨드 객체의 특정 프로퍼티와 관련된 한 개
의 checkbox 타입 <input> 태그 생성
<p>
<!-- favoriteOsNames에 존재 하는 값들에 대한 체크박스를 생성 -->
<label>선호 OS</label>
<form:checkboxes items="${favoriteOsNames}" paht = "favoriteOs" />
</p>
radio
타입의 <input>
태그를 사용<form:radiobuttons>
: 커맨드 객체의 특정 프로퍼티와 관련된 radio 타입
의 <input> 태그 목록 생성<form:radiobutton>
: 커맨드 객체의 특정 프로퍼티와 관련된 한 개
의 radio 타입 <input> 태그 생성items
속성에 값으로 사용할 콜렉션을 전달받고 path
속성에 커맨드 객체의 프로퍼티를 지정
<p>
<label>주로 사용하는 개발툴</label>
<form:radiobuttons items="${tools}" path="tool"/>
</p>
여러 줄
을 입력받아야 하는 경우 사용<form:textarea>
커스텀 태그를 제공
<p>
<label for="etc">기타</label>
<form:textarea path="etc" cols="20" rows="3" />
</p>