@Controller
public class RegisterController {
@RequestMapping("/register/step1")
public String handleStep1() {
...
}
@RequestMapping("/register/step2")
public String handleStep2(
...
}
@RequestMapping("/register/step3")
public String handleStep3() {
...
}
}
@Controller
@RequestMapping("/register")
public class RegisterController {
@RequestMapping("/step1")
public String handleStep1() {
...
}
@RequestMapping("/step2")
public String handleStep2(
...
}
@RequestMapping("/step3")
public String handleStep3() {
...
}
}
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 {
@Bean
public RegisterController registerController() {
return RegisterController();
}
}
주로 폼을 전송할 때 POST방식을 사용하는데 스프링 MVC는 별도 설정이 없으면 GET과 POST 방식에 상관없이 @RequestMapping에 지정한 경로와 일치하는 요청을 처리한다.
@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";
}
@RequestMapping 어노테이션으로도 method속성을 정해줄수 있다.
@Controller
public class RegisterController {
@RequestMapping(value = "/register/step1", method = RequestMethod.GET)//get방식
public String handleStep1() {
...
}
@RequestMapping(value = "/register/step2", method = RequestMethod.POST)//post방식
public String handleStep2(
...
}
}
<form action="step2" method="post">
<label>
<input type="checkbox" name="agree" value="true"> 약관 동의
</label>
<input type="submit" value="다음 단계" />
</form>
HttpServletRequest를 직접 이용하는 것
HttpServletRequest 타입을 사용하고 HttpServletRequest의 getParameter() 메서드를 이용해서 파라미터의 값을 구하면 된다.
package controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import spring.RegisterRequest;
@Controller
public class RegisterController {
@RequestMapping("/register/step1")
public String handleStep1() {
return "register/step1";
}
@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";
}
}
package controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import spring.RegisterRequest;
@Controller
public class RegisterController {
@RequestMapping("/register/step1")
public String handleStep1() {
return "register/step1";
}
@PostMapping("/register/step2")
public String handleStep2(
@RequestParam(value = "agree", defaultValue = "false") Boolean agree) {
if (!agree) {
return "register/step1";
}
return "register/step2";
}
}
@RequestParam 어노테이션의 속성
@RequestParam 어노테이션을 사용한 코드를 보면 다음과 같이 agreeVal 파라미터의 타입이 Boolean이다.
@RequestParam(value = "agree", defaultValue = "false") Boolean agreeVal
@PostMapping("/register/step2") public String handleStep2( @RequestParam(value = "agree", defaultValue = "false") Boolean agree) { if (!agree) { return "register/step1"; } return "register/step2"; }
- 위 코드를 통해 agree에 true값이라면 "/step2"을 리턴해 회원 정보 입력 폼을 보여준다.
- fasle값이라면 "/step1"을 리턴해 다시 약관 동의 폼을 보여준다.
@GetMapping("/register/step2")
public String handleStep2Get() {
return "redirect:/register/step1";
}
@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);
...
}
스프링은 이러한 불편함을 줄이기 위해 요청 파라미터의 값을 커맨드(command) 객체에 담아주는 기능을 제공한다.
예를 들어 이름이 name인 요청 파라미터의 값을 커맨드 객체의 setName() 메서드를 사용해서 커맨드 객체에 전달하는 기능을 제공한다.
요청 파라미터의 값을 전달받을 수 있는 세터 메서드를 포함하는 객체를 커맨드 객체로 사용하면 된다.
@PostMapping("/register/step3")
public String handleStep3(RegisterRequest regReq) {
...
}
private MemberRegisterService memberRegisterService;
public void setMemberRegisterService(
MemberRegisterService memberRegisterService) {
this.memberRegisterService = memberRegisterService;
}
@PostMapping("/register/step3")
public String handleStep3(RegisterRequest regReq) {
try {
memberRegisterService.regist(regReq);
return "register/step3";
} catch (DuplicateMemberException ex) {
return "register/step2";
}
}
@Autowired
private MemberRegisterService memberRegSvc;
@Bean
public RegisterController registerController() {
RegisterController controller = new RegisterController();
controller.setMemberRegisterService(memberRegSvc);
return controller;
}
스프링 MVC는 커맨드 객체의 (첫 글자를 소문자로 바꾼) 클래스 이름과 동일한 속성 이름을 사용해서 커맨드 객체를 뷰에 전달한다.
@PostMapping("/register/step3")
public String handleStep3(RegisterRequest regReq) {
...
}
<p><strong>${registerRequest.name}님</strong>
회원 가입을 완료했습니다.</p>
@PostMapping("/register/step3")
public String handleStep3(@ModelAttribute("formData") RegisterRequest regReq) {
...
}
<input type="text" name="email" id="email" value="${registerRequest.email}">
첫 화면은 단순히 환영 문구와 회원 가입을 위한 링크만 제공한다면, 이를 위한 컨트롤러 클래스는 특별히 처리할 것이 없기 때문에 단순히 뷰 이름만 리턴하도록 구현된다.
이러한 특별한 로직이 없는 컨트롤러 클래스를 만드는것은 별로 바람직하지 못하다.
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/main").setViewName("main");
}
404 에러
요청 경로를 처리할 컨트롤러가 존재하지 않거나 Web MvcConfigurer를 이용한 설정이 없을 때 발생한다.
요청 경로가 올바른지
컨트롤러 설정한 경로가 올바른지
컨트롤러 클래스를 빈으로 등록했는지
컨트롤러 클래스에 @Controller 어노테이션을 적용했는지
뷰 이름에 해당하는 JSP파일이 없어도 404에러가 발생하지만 JSP파일 경로는 출력된다는 차이가 있다.
405 에러
지원하지 않는 전송 방식(method)를 사용한 경우 405에러가 발생한다.
EX) POST방식만 처리하는 요청 경로를 GET방식으로 연결하면 405에러가 발생한다.
400 에러
@PostMapping("/register/step2")
public String handleStep2(
//필수로 존재해야하고 기본값은 없다.
@RequestParam("agree") Boolean agree, Model model) {
...
}
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;
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;
}
}
요청 파라미터의 값을 알맞게 커맨드 객체에 설정해주는 기능을 제공, 규칙
ex) 이름이 responses이고 List 타입인 프로퍼티를 위한 요청 파라미터의 이름으로 "responses[0]", "responses[1]"을 사용하면 각각의 0번 인덱스와 1번 인덱스의 값으로 사용된다.
중첩 프로퍼티의 경우 파라미터 이름을 "res.name"으로 지정하면 commandObj.getRes().setName(request.getParameter("res.name"));과 같은 방식으로 커맨드 객체에 파라미터의 값을 설정한다.
@Controller
@RequestMapping("/survey")
public class SurveyController {
@GetMapping
public ModelAndView form() {
List<Question> questions = createQuestions();
ModelAndView mv = new ModelAndView();
mv.addObject("questions", questions);
mv.setViewName("survey/surveyForm");
return mv;
}
}