[Spring] 스프링 MVC 1 : 요청, 매핑, 커맨드 객체, 리다이렉트, 폼 태그, 모델
2023. 12. 3.
반응형

 

 

 

 

이 포스팅은 스프링 MVC에 대해서 다룹니다.

앞선 포스팅에서는 MVC를 위한 설정을 했다면 이제는 직접 MVC의 코드를 작성해 보겠습니다.

 

이전 포스팅:

https://coding-log.tistory.com/289

 

[Spring] 스프링 MVC 프레임워크 동작 구조

이 포스팅에서는 스프링 MVC의 동작 구조에 대해서 다룹니다. https://coding-log.tistory.com/286 [Spring] 스프링 MVC 시작하기 (with IntelliJ)이 포스팅에서는 IDE에서 스프링 MVC 프로젝트를 만드는 법을 다룹니

coding-log.tistory.com

 

 

pom.xml

 

		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-jdbc</artifactId>
			<version>5.0.2.RELEASE</version>
		</dependency>
		<dependency>
			<groupId>org.apache.tomcat</groupId>
			<artifactId>tomcat-jdbc</artifactId>
			<version>8.5.27</version>
		</dependency>
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>5.1.45</version>
		</dependency>

		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-api</artifactId>
			<version>1.7.25</version>
		</dependency>

		<dependency>
			<groupId>ch.qos.logback</groupId>
			<artifactId>logback-classic</artifactId>
			<version>1.2.3</version>
		</dependency>

 

 

pom.xml 파일에 위의 코드와 같이 의존성을 추가해 줍니다.

jdbc, mysql 등 DB 연동을 위한 모듈들을 추가해 줬습니다.

 

그럼 프로젝트 코드를 예제를 세팅해 보겠습니다.

이전 포스팅의 코드가 있다는 가정하에 진행합니다.

 

src/main/java 폴더에 첨부파일을 넣어줍니다.

 

spring.zip
0.00MB

 

 

MVC에서 MC에 해당하는 로직들의 예제코드입니다.

이제 이 로직들과 V를 연결해줘야 합니다.

이를 위해서는 두 가지 과정이 필요합니다.

 

1. 특정 URL을 처리할 코드 (@Controller)

2. 처리 결과를 HTML과 같은 형식으로 응답하는 코드

 

이 중 첫 번째는 @Controller 사용한 컨트롤러 클래스를 이용해서 구현합니다. 

컨트롤러 클래스는 요청 매핑 어노테이션을 사용해서 메서드가 처리할 경로를 지정합니다.

(@RequestMapping, @GetMapping, @ PostMapping 등)

 

 

 

 

반응형

 

 

 

 

 

 

회원 가입 상황을 가정해 보겠습니다. 

 

약관 동의 -> 회원 정보 입력 -> 가입 완료 

 

위와 같은 로직을 거친다면 요청 URL을 다음과 같이 정할 수 있습니다.

 

약관동의: http://localhost:8080/register/step1

 

회원 정보 입력: http://localhost:8080/register/step2

 

가입 완료: http://localhost:8080/register/step3

 

회원 가입이라는 하나의 기능이 이처럼 여러 단계를 거쳐서 완성이 되는 경우 하나의 컨트롤러에서 처리하면 코드 관리가 용의해집니다.

 

이런 경우 작성하는 방법이 두 가지가 있습니다.

 

@Controller
public class RegisterController {

  @RequestMapping("/register/step1")
  public String handleStep1() {
    return "register/step1";
  }
  
  @RequestMapping("/register/step2")
  public String handleStep2() {
    return "register/step2";
  }
  
  @RequestMapping("/register/step3")
  public String handleStep3() {
    return "register/step3";
  }
}

 

 

세 개의 기능을 요청 경로에 따라 처리하는 세 개의 메소드를 만들었습니다.

그런데 코드를 보면 요청 경로가 /register로 겹치는 것을 알 수 있습니다. 같은 컨트롤러에서 처리되기 때문입니다.

이러한 공통 경로를 @RequestMapping 어노테이션에서 처리할 수 있습니다.

 

 

@Controller
@RequestMapping("/register")
public class RegisterController {

  @RequestMapping("/step1")
  public String handleStep1() {
    return "register/step1";
  }
  
  @RequestMapping("/step2")
  public String handleStep2() {
    return "register/step2";
  }
  
  @RequestMapping("/step3")
  public String handleStep3() {
    return "register/step3";
  }
}

 

 

코드를 이렇게 수정해도 위와 같이 "http://localhost:8080/register/step1" 요청 맵핑을 처리합니다.

 

 

 

 

 

반응형

 

 

 

 

 

그렇다면 이를 요청에 따라 리턴하는 뷰를 구현해 보겠습니다.

 

step1.jsp

 

<%@ page contentType="text/html; charset=utf-8" %>
<!DOCTYPE html>
<html>
<head>
	<title>회원가입</title>
</head>
<body>
	<h2>약관</h2>
	<p>약관 내용</p>
	<form action="step2" method="post">
	<label>
		<input type="checkbox" name="agree" value="true"> 약관 동의
	</label>
	<input type="submit" value="다음 단계" />
	</form>
</body>
</html>

 

 

약관의 동의를 구하는 화면을 만들었습니다. 그럼 ControllerConfig 설정파일에 RegisterController를 빈으로 등록하겠습니다.

 

 @Bean
 public RegisterController registerController() {
    return new RegisterController();
 }

 

 

그럼 이제 프로젝트를 실행시켜 보겠습니다.

 

 

 

 

 

 

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";
  }

  @GetMapping("/register/step2")
  public String handleStep2Get() {
    return "redirect:/register/step1";
  }

 

 

같은 요청 URL 경로인 "/register/step2"를 처리합니다만, Get과 Post로 나뉘었습니다. 무슨 차이가 있을까요?

@RequestMapping은 Get, Post 상관없이 요청을 처리합니다.

만약 Post요청만 처리하고 싶다면 @PostMapping을, Get 요청만 처리하고 싶다면 @GetMapping을 사용합니다.

 

즉, 위의 예시코드는 같은 경로로 들어온 요청을 GET요청이면 @GetMapping으로, POST 요청이면 @PostMapping으로 처리하는 코드인 것입니다.

 

@RequestMapping(value = "/register/step2", method = RequestMethod.GET)
@RequestMapping(value = "/register/step2", method = RequestMethod.POST)

 

 

@RequestMapping에서도 속성을 주면 지정이 가능합니다.

 

 

<form action="step2" method="post">
    <label>
        <input type="checkbox" name="agree" value="true"> 약관 동의
    </label>
    <input type="submit" value="다음 단계" />
</form>

 

 

위는 step1.jsp 파일입니다. 폼의 요청 파라미터 'agree'의 값을 POST 방식으로 전달하게 됩니다.

이 요청 파라미터를 컨트롤러 메소드에서 사용하는 방법은 두 가지가 있습니다.

 

1. HttpServletRequest

2. @RequestParam

 

HttpServletRequest 타입을 사용하고 getParameter()를 사용해 파라미터의 값을 구합니다.

 

HttpServletRequest Ver

@PostMapping("/register/step2")
  public String handleStep2(
     	HttpServletRequest request){
    String param = request.getParameter("agree");
    if (param == null || !param.equals("agree")) {
      return "register/step1";
    }
    return "register/step2";
  }

 

 

요청 파라미터가 몇 개 안 된다면 @RequestParam을 사용해서 간단하게 값을 구할 수 있습니다.

 

@RequestParam Ver

@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";
  }

 

agree 요청 파라미터 값을 읽어와 변수에 그 값을 할당하고 요청 파라미터의 값이 없으면 false 문자열을 값으로 사용합니다.

약관에 동의하지 않았다면 다시 step1페이지로, 동의했다면 step2페이지로 이동하게 됩니다.

 

 

 

 

반응형

 

 

 

 

 

아래 표는 @RequestParam의 속성입니다.

 

속성 타입 설명
value String HTTP 요청 파라미터의 이름을 지정
required boolean 필수 여부를 지정. 이 값이 true이면서 해당 요청 파라미터에 값이 없으면 익셉션 발생. 기본 값은 true.
defaultValue String 요청 파라미터가 값이 없을 때 사용할 문자열의 값을 지정. 기본값은 없음.

 

스프링 MVC는 파라미터 타입에 맞게 String값을 변환해 줍니다. 위의 예제 코드에서도 String을 boolean으로 변환해서 사용했습니다.

boolean 이외에도 int, long 등 기본 데이터 타입과 래퍼 타입에 대한 변환을 지원합니다.

 

그럼 이제 회원 가입 단계에서 약관 동의까지 구현했으니, 회원 정보 입력을 받아보겠습니다.

 

step2.jsp

 

<%@ page contentType="text/html; charset=utf-8" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<!DOCTYPE html>
<html>
<head>
    <title>회원가입</title>
</head>
<body>
    <h2>회원 정보 입력</h2>
    
    <form action="step3" method="post">
    <p>
        <label>이메일:<br>
        <input type="text" name="email" id="email" value="${registerRequest.email}">
        </label>
    </p>
    <p>
        <label>이름:<br>
        <input type="text" name="name" id="name" value="${registerRequest.name}">
        </label>
    </p>
    <p>
        <label>비밀번호:<br>
        <input type="password" name="password" id="password">
        </label>
    </p>
    <p>
        <label>비밀번호 확인:<br>
        <input type="password" name="confirmPassword" id="confirmPassword">
        </label>
    </p>
    <input type="submit" value="가입 완료">
    </form>
</body>
</html>

 

 

이는 회원 가입 화면입니다.

 

 

 

 

아까 우리는 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";
  }

  @GetMapping("/register/step2")
  public String handleStep2Get() {
    return "redirect:/register/step1";
  }

 

그럼 우리가 바로 주소창에 회원 정보 입력의 요청 URL인 http://localhost:8080/register/step2로 접속하면 어떻게 될까요?

 

step1의 페이지로 리다이렉트 하게 됩니다. 왜 이런 처리를 해놨을까요?

바로 예외처리를 위해서입니다.

일반적인 접근 방식은 약관 동의 후 요청 파라미터의 값을 가지고 이 페이지에 PostMapping으로 들어오게 됩니다.

하지만 위와 같이 바로 요청 URL을 적어서 들어올 경우 Get방식으로 들어오기 때문에 구현이 되어있지 않다면 405 에러가 발생하게 됩니다.

그래서 GET, POST 둘 다 구현이 필요하며, 이 경우 step1의 값을 받아올 수 있도록 step1 페이지로 리다이렉트 하는 것입니다.

 

리다이렉트의 사용방법은 위의 예제 코드처럼 redirect:[주소]; 를 입력하면 됩니다.

 

step2.jsp에서는 폼을 사용하여 회원의 정보를 받게 되는 게 그 목록은 다음과 같습니다.

 

  • email
  • name
  • password
  • confirmPassword

 

폼 전송 요청을 처리하기 위해서 컨트롤러는 값을 어떻게 받아야 할까요?

아까처럼 HttpServletRequest를 쓸 수도 있겠지만 사용자의 입력 값이 많아질수록 코드가 아주 길어지게 됩니다.

스프링은 이런 불편함을 줄이기 위해 요청 파라미터의 값을 커맨드 객체에 담아주는 기능을 제공합니다. 

예를 들어 이름이 email인 요청 파라미터 값을 커맨드 객체의 setEmail() 메소드를 사용해서 커맨드 객체에 전달하는 기능을 제공합니다.

커맨드 객체는 요청 파라미터의 값을 전달받을 수 있는 세터 메서드를 포함하는 객체를 사용하면 됩니다.

 

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";
    }
  }

 

 

RegisterController 예제 코드입니다. 커맨드 객체로 RegisterRequest 클래스를 사용했습니다.

아까 첨부 파일에 있던 코드로 setter 메소드가 있습니다.

스프링은 이 메소드를 사용해서 요청 파라미터의 값을 커맨드 객체에 복사한 뒤 regReq 파라미터로 전달합니다.

 

회원가입의 완료를 위한 handleStep3메소드 입니다. MemberRegisterService를 이용해서 회원 가입을 처리하고 회원 가입에 성공하면 step3, 실패하면 step2로 뷰 이름을 리턴합니다.

 

위처럼 RegisterController는 MemberRegisterService 타입의 빈에 의존하기 때문에 ControllerConfig 파일에서 의존 주입을 설정해 주어야 합니다.

 

  @Autowired
  private MemberRegisterService memberRegSvc;

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

 

 

위의 코드에서 MemberRegisterService 타입 빈은 MemberConfig 설정 클래스에 정의되어 있습니다.

 

 

 

 

 

반응형

 

 

 

 

 

이제 회원가입에 성공했을 때 결과를 보여줄 step3.jsp를 작성해 보겠습니다.

 

step3.jsp

 

<%@ 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><strong>${registerRequest.name}님</strong>
    회원 가입을 완료했습니다.</p>
<p><a href="<c:url value='/main'/>">[첫 화면 이동]</a></p>
</body>
</html>

 

 

 

가입할 때 사용한 이름을 회원 가입 완료 화면에 보여주기 위해 커맨드 객체를 사용했습니다.

handleStep3()에서 매개변수로 RegisterRequest를 커맨드 객체로 사용했습니다.

스프링 MVC는 첫 글자를 소문자로 바꾼 클래스 이름과 동일한 속성 이름을 사용해서 커맨드 객체를 뷰에 전달합니다.

그래서 registerRequest.name을 사용할 수 있는 것입니다.

 

커맨드 객체의 이름을 변경하고 싶다면 @ModelAttribute를 사용하면 됩니다.

 

@PostMapping("/register/step3")
public String handleStep3(@ModelAttribute("formData")RegisterRequest regReq) {
	...
}

 

 

이렇게 작성하게 되면 registerRequest를 formData로 바꿀 수 있습니다.

 

 

또한 커맨드 객체와 스프링 폼을  연동할 수 있습니다. 스프링은 <form:form> 태그와 <form:input> 태그를 제공하고 있습니다.

이 두 태그를 사용하면 커맨드 객체의 값을 빈 폼에 출력할 수 있습니다.

 

step2.jsp (수정 버전)

 

<%@ page contentType="text/html; charset=utf-8" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<!DOCTYPE html>
<html>
<head>
    <title>회원가입</title>
</head>
<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>
    <p>
        <label>비밀번호 확인:<br>
            <form:password path="confirmPassword" />
        </label>
    </p>
    <input type="submit" value="가입 완료">
</form:form>
</body>
</html>

 

 

만약 중복된 이메일로 다시 폼을 입력해야 하는 경우 빈 폼을 보여주지 않고 작성했던 값을 다시 보여줄 수 있습니다.

스프링이 사용하는 폼 태그를 사용하기 위해 taglib 디렉티브를 설정했습니다.

<form:form> 태그는 HTML의 <form> 태그를 생성하고, 속성은 다음과 같습니다.

 

  • action: <form> 태그의 action 속성과 동일한 값을 사용
  • modelAttribute: 커맨드 객체의 이름 지정. 설정하지 않는 경우, "command"를 기본값으로 사용함.

 

<form:form> 태그를 사용하려면 커맨드 객체가 존재해야 합니다.

step2.jsp에서 <form:form> 태그를 사용하기 때문에 registerRequest인 객체를 모델에 넣어야 태그가 정상 작동합니다.

 

@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";
  }

 

 

이를 위해서 RegisterController에서 Model을 사용해 주었습니다. 이를 통해 입력받은 값을 addAttribute로 저장할 수 있게 되었습니다.

 

 

<a href="<c:url value='/main'/>">[첫 화면 이동]</a>

 

 

step3.jsp에 이 코드는 첫 화면으로 이동할 수 있게 해주는 하이퍼링크 코드입니다.

이 "/main"이라는 요청 URL을 처리해 보도록 하겠습니다.

원래 요청 URL을 처리하기 위해서는 Controller에서 @RequestMapping을 사용해 처리해야 합니다.

하지만 이 로직만을 처리하기 위해 컨트롤러를 만드는 일은 공수가 크기 때문에 다른 방법을 사용하겠습니다.

WebMvcConfigurer 인터페이스의 addViewControllers() 메소드를 사용하면 됩니다.

MvcConfig 파일에 다음과 같은 코드를 추가하겠습니다.

 

@Override
public void addViewControllers(ViewControllerRegistry registry) {
	registry.addViewController("/main").setViewName("main");
}

 

 

이 설정을 통해 /main 요청 경로에 대해 뷰 이름으로 main을 사용하게 되었습니다.

그럼 main 화면을 작성해 보겠습니다.

 

main.jsp

 

<%@ 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>
<p><a href="<c:url value="/register/step1" />">[회원 가입하기]</a>
</body>
</html>

 

 

 

 

 

 

 

 

반응형

 

 

 

 

 

이렇게 개발을 하다 보면 자주 만나는 에러 사례를 정리해 보겠습니다.

 

요청 매핑 어노테이션 관련 에러)

 

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

 

 

체크리스트

 

  • 요청 경로가 올바른지
  • 컨트롤러에 설정한 경로가 올바른지
  • 컨트롤러 클래스를 빈으로 등록했는지
  • 컨트롤러 클래스에 @Controller 어노테이션을 사용했는지

 

405 에러: 지원하지 않는 전송 방식을 사용한 경우

ex) post 방식만 처리한느 요청 경로를 get 방식으로 연결하면 발생

 

@RequestParam이나 커맨드 객체 관련 에러)

 

400 에러: Bad Request 

필수로 존재해야 하는 기본 값이 없는 경우

요청 파라미터의 값을 @RequestParam이 적용된 파라미터의 타입으로 변환할 수 없는 경우

요청 파라미터의 값을 커맨드 객체에 복사하는 과정에서 타입 변환이 안 되는 경우

etc...

 

정말 다양한 경우에 발생하기 때문에 log를 잘 읽어보면서 원인을 파악해야 합니다.

 

 

 

 

 

반응형

 

 

 

 

 

 

다음으로 세 개의 설문 항목과 응답자의 지역과 나이를 입력받는 설문 조사 정보를 담기 위해 코드를 작성해 보겠습니다.

 

 

Respondent.java

 

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

 

 

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;
    }

}

 

 

Respondent: 응답자 정보.

AnsweredData: 설문 항목에 대한 답변, 응답자 정보.

답변 목록을 저장하기 위해 List 타입의 response 프로퍼티 사용

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

 

AnsweredData 클래스는 앞서 커맨드 객체로 사용한 클래스와 비교하면 차이가 있습니다.

기초자료형만 사용했던 것과 다르게 리스트 타입의 프로퍼티가 존재하고, 중첩 프로퍼티를 가집니다.

 

리스트타입의 프로퍼티는 "프로퍼티이름[인덱스]" 형식으로 값 목록을 처리하고, ex) responses[1]

중첩 프로퍼티는 "프로퍼티이름.프로퍼티이름" 형식으로 값을 처리합니다. ex) res.name

 

그럼 AnsweredData 클래스를 커맨드 객체로 사용하는 컨트롤러 예제를 작성해 보겠습니다.

 

SurveyController.java

 

@Controller
@RequestMapping("/survey")
public class SurveyController {
    @GetMapping
    public String form(){
        return "survey/surveyForm";
    }

    @PostMapping
    public String submit(@ModelAttribute("ansData")AnsweredData data){
        return "survey/submitted";
    }
}

 

 

"/survey"라는 경로로 GET 요청이 들어오면 form() 메소드를, POST 요청이 들어오면 submit() 메소드를 실행시켜 요청을 처리하게 됩니다.

 

컨트롤러를 작성했으니 설정 파일에서 빈으로 등록해 줍시다.

 

ControllerConfig.java

 

@Bean
public SurveyController surveyController(){
	return new SurveyController();
}

 

 

이제 SurveyController에서 처리했던 뷰 화면을 작성해 보겠습니다.

 

 

submitted.jsp

 

<%@ 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>

 

 

surveyForm.jsp

 

<%@ 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">
  <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>

  <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>

 

 

input 태그의 name 속성은 다음과 같이 커맨드 객체의 프로퍼티로 매핑됩니다.

 

responses[n] -> responses 프로퍼티(List타입)의 n번째 값

res.x -> res 프로퍼티(Respondent 타입)의 x 프로퍼티

 

/survey를 실행하면 이런 화면이 됩니다.

 

 

 

 

값을 넣으면 get -> post 맵핑을 통해 결과화면으로 이동합니다.

 

 

 

 

 

 

 

 

반응형

 

 

 

 

 

 

그럼 이번에는 Model을 통해서 컨트롤러에서 뷰에 데이터를 전달해 보겠습니다.

 

이를 위해서는 요청 매핑 어노테이션이 적용된 메소드의 파라미터로 Model을 추가하고,

Model 파라미터의 addAttribute() 메소드로 뷰에서 사용할 데이터를 전달해야 합니다.

 

addAttribute()의 첫 번째 파라미터는 속성 이름입니다. 뷰 코드는 이 이름을 사용해서 데이터에 접근합니다.

이를 실행해 보기 위해 예제 코드를 작성해 보겠습니다.

 

Question.java

 

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();
    }

}

 

title, option에 질문 제목과 답변 옵션을 보관하고, 주관식이면 답변이 없을 수도 있기 때문에 제목만 받는 생성자를 사용해 값을 입력받습니다.

 

그리고 이를 처리하기 위해 컨트롤러를 수정해 보겠습니다.

 

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";
    }

    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";
    }

}

 

 

form() 메소드에 Model 파라미터가 추가됐고, Question 타입의 리스트를 추가했습니다.

 

 

 

SurveyController가 전달한 Question 리스트를 이용해서 폼 화면이 생성되었습니다.

 

지금까지 구현한 컨트롤러는 두 가지 특징이 있습니다.

Model을 이용해서 뷰에 전달할 데이터를 설정하고, 결과를 보여줄 뷰의 이름을 리턴하는 것입니다.

ModelAndView를 사용하면 전과는 달리 모델과 뷰 이름을 함께 제공할 수 있습니다.

 

 

@GetMapping
public ModelAndView form() {
	List<Question> questions = createQuestions();
	ModelAndView mav = new ModelAndView();
	mav.addObject("questions", questions);
	mav.setViewName("survey/surveyForm");
	return mav;
}

 

 

위의 코드와 동일하게 작동하도록 ModelAndView를 활용해 코드를 작성해 봤습니다.

String 타입 대신 ModelAndView를 리턴해 메소드를 구현할 수 있습니다.

addObject()를 통해 모델 데이터를, setViewName()을 통해 뷰 이름을 지정합니다.

 

GET방식과 POST 방식에 동일 이름 커맨드 객체를 사용할 수도 있습니다.

 

@Controller
@RequestMapping("/login")
public class LoginController {

    @GetMapping
    public String form(@ModelAttribute("login") LoginCommand loginCommand) {
    	return "login/loginForm";
    }
    
    @PostMapping
    public String form(@ModelAttribute("login") LoginCommand loginCommand) {
    	...
    }
}

 

 

이름을 명시적으로 지정하기 위해 @ModelAttribute를 사용해 줬습니다.

입력 폼과 폼 전송 처리에서 사용할 커맨드 객체의 속성 이름이 클래스와 다르다면 이처럼 커맨드 객체를 파라미터로 추가하면 됩니다.

 

 

 

 

출처: 초보 웹 개발자를 위한 스프링 5 프로그래밍 입문

반응형
myoskin