본 문서에서 다루는 뷰는 크게 다음과 같다.
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>