ch11. MVC 1: 요청 매핑/커맨드 객체/ 리다이렉트/ 폼 태그/모델

728x90
참고 도서 : 스프링5 프로그래밍 입문 - 최범균 저

ch11. MVC 1: 요청 매핑/커맨드 객체/ 리다이렉트/ 폼 태그/모델

*[이 장 학습내용] : 스프링 MVC의 기본적인 컨트롤러 구현 방법

    ▶요청 매핑 애노테이션 이용 -> 요청 경로 처리 메소드 설정 방법
    ▶커맨드 객체 사용 -> 폼에 입력한 데이터를 받아서 처리 방법 
    ▶모델을 통해 뷰가 응답 생성 시 필요한 데이터 전달하는 방법 
*스프링 MVC를 사용해서 웹 어플리케이션을 개발한다는 것 = [컨트롤러, 뷰 코드 구현한다는 것]

-어떤 컨트롤러를 이용해서 어떤 경로를 처리할지 결정
-웹브라우저의 요청에 필요한 처리 후 결과를 JSP 이용(뷰 코드) 해서 보여줌

1. [프로젝트 준비]

src/main/java : 자바코드, 설정 파일 위치
src/main/webapp : HTML/CSS/JS 위치할 폴더
src/main/webapp/WEB-INF: web.xml 파일 위치할 폴더
src/main/webapp/WEB-INF/view : 컨트롤러 결과 보여줄 JSP 파일 위치

[pom.xml] 모듈 의존 추가

-<dependency>웹 위한 모듈 / DB 연동 위한 모듈에 대한 의존 설정을 한다

-> 이클립스에 프로젝트 파일 임포트하기

<dependencies> //MVC 위한 모듈 의존 설정 추가
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>javax.servlet.jsp-api</artifactId>
<version>2.3.2-b02</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency> //DB 연동을 위한 모듈 의존 추가
<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>
</dependencies>

[설정 파일 작성]

- DB 설정 파일 (DataSource, 트랜잭션 관련설정 )
- 스프링 MVC 기본 설정 파일
- web.xml 설정 파일 (DispatcherServlet 등록)

                       <MemberConfig 클래스 작성 > : DB 연동 위한 설정 파일

@Configuration //스프링 설정 파일 
@EnableTransactionManagement //@Transactional 활성화 위해 추가 
public class MemberConfig { 
	@Bean(destroyMethod = "close") //DB 연동을 위해 빈으로 등록 
	public DataSource dataSource() {
		DataSource ds = new DataSource();
		ds.setDriverClassName("com.mysql.jdbc.Driver");
		ds.setUrl("jdbc:mysql://localhost/spring5fs?characterEncoding=utf8");
		ds.setUsername("spring5");
		ds.setPassword("spring5");
		ds.setInitialSize(2);
		ds.setMaxActive(10);
		ds.setTestWhileIdle(true);
		ds.setMinEvictableIdleTimeMillis(60000 * 3);
		ds.setTimeBetweenEvictionRunsMillis(10 * 1000);
		return ds;
	}
	@Bean
	public PlatformTransactionManager transactionManager() {
		DataSourceTransactionManager tm = new DataSourceTransactionManager();
		tm.setDataSource(dataSource());
		return tm;
	}
	@Bean
	public MemberDao memberDao() {
		return new MemberDao(dataSource());
	}
	@Bean
	public MemberRegisterService memberRegSvc() {
		return new MemberRegisterService(memberDao());
	}
	@Bean
	public ChangePasswordService changePwdSvc() {
		ChangePasswordService pwdSvc = new ChangePasswordService();
		pwdSvc.setMemberDao(memberDao());
		return pwdSvc;
	}
}

                        <MemberConfig 클래스 작성 > : DB 연동 위한 설정 파일

@Configuration
@EnableWebMvc //스프링 웹 MVC 사용위해 추가 
public class MvcConfig implements WebMvcConfigurer { //인터페이스 상속받음 
	@Override
	public void configureDefaultServletHandling(
			DefaultServletHandlerConfigurer configurer) {
		configurer.enable();
	}
	@Override
	public void configureViewResolvers(ViewResolverRegistry registry) {
		registry.jsp("/WEB-INF/view/", ".jsp");
	}
}
 

                       <web.xml> 파일 작성 : DispatcherServlet 등록

<servlet>
<servlet-name>dispatcher</servlet-name> //이름 등록
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextClass</param-name> //초기화 파라미터 설정
<param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
</init-param>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value> //스프링 설정 클래스 목록 지정
config.MemberConfig
config.MvcConfig
config.ControllerConfig
</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<filter>
<filter-name>encodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name
▷ “프로젝트 준비가 끝났다”
 

2. [요청 매핑 애노테이션 적용 메소드 이용 -> 경로 매핑]

*요청 매핑 애노테이션 : @RequestMapping, @GetMapping, @PostMapping

[경로매핑]

: 컨트롤러 클래스 안에서 요청 매핑 애노테이션 메소드르 통해 처리할 요청 경로를 지정

      -> 이 요청 경로를 해당 메소드가 처리하도록 설정
      -> 관련 요청 경로들을 묶어서 한 개의 컨트롤러 클래스 안에서 처리할 수 O
@Controller
public class HelloController{
	@GETMapping("/hello") //요청 경로 지정 (메소드)
	public String hello(Medo model, ) {
		model.addAttribute("greeting")
	}
}

<<+추가>>

공통 경로를 클래스 위에서 묶어주고, 개별 경로를 각 메소드 위에서 묶어 처리한다.

-> 스프링 MVC
클래스에 적용한 요청 매핑 애노테이션 경로와
메소드에 적용한 요청 매핑 애노테이션 경로를 합쳐서 경로를 찾기 때문
@Controller
@RequestMapping("/register") //공통 경로 
public class RegistController { //클래스 
	
	@RequestMapping("/step1") //개별 경로 
	public String handleStep1() {
		
	}
	@RequestMapping("/step2") //개별 경로
	public String handleStep2() {
		
	}
	@RequestMapping("/step3") //개별 경로
	public String handlStep3() {
		
	}
}

[약관 동의 화면 요청 처리] -> [회원정보 입력 처리] -> [가입 처리 결과 화면 보여주기]

                요청 URL 처리 코드 (컨트롤러 작성)

[약관 동의 화면 요청 처리]

이 경우, 별다른 처리 없이 약관 내용만 보여주면 되기 때문에 약관 내용 보여줄 뷰 이름만 리턴

@Controller
public class RegisterController { //(회원가입) 컨트롤러 클래스 
	//필드
	private MemberRegisterService memberRegisterService;
		
	public void setMemberRegisterService(
			MemberRegisterService memberRegisterService) {
		this.memberRegisterService = memberRegisterService;
	}
	@RequestMapping("/register/step1") //약관 동의 화면 요청 처리 매핑 
	public String handleStep1() {
		return "register/step1"; //약관 동의 화면 결과 뷰 이름 반환 
	}
}​

                처리 결과를 응답하는 코드 (뷰 코드 작성)

-컨트롤러가 리턴한 뷰 이름 코드

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

                컨트롤러 설정 클래스(파일) 작성

이 파일 안에 컨트롤러 클래스를 빈으로 등록

@Configuration
public class ControllerConfig { //컨트롤러 설정 파일 
	@Autowired //자동 의존 주입 
	private MemberRegisterService memberRegSvc;
	@Bean
	public RegisterController registerController() { //컨트롤러 클래스를 빈으로 등록 
		RegisterController controller = new RegisterController();
		controller.setMemberRegisterService(memberRegSvc); //의존 주입 
		return controller;
	}
}

3. [GET POST 구분: @GetMapping / @PostMapping ]

-스프링 MVC 는 별도 설정 없으면

기본적으로 @RequestMapping 에 지정한 경로와 일치하는 요청을 처리한다.
<요청 방식에 대한 제한 걸기>

POST 방식 요청만 처리하고 싶다면 @PostMapping 애노테이션으로 제한할 수 있다.
-> 이 경우 해당 경로에 대한 POST 방식의 요청 경로만 처리하며
                                      GET 방식의 요청 경로는 처리하지 않는다.

GET 방식 요청만 처리하고 싶다면 @GetMapping 애노테이션으로 제한할 수 있다.


같은 경로에 대해 GET POST 방식을 각각 다른 메소드가 처리하도록 설정할 수 있다.

4. [요청 파라미터 접근]

<컨트롤러 메소드에서 요청 파라미터에 접근하는 방법 >

                HttpServletRequest 직접 사용하기 : 여러 개 일 때

- 컨트롤러 처리 메소드의 파라미터로 HttpServletRequest 타입 사용하고                                                                                                           getParameter() 메소드로 파라미터의 값을 구하면 된다.

	@PostMapping("/register/step2")
	public String handleStep2(HttpServletRequest request) {
		Satring agreeParam = request.getParameter("agree");
		...
	}

                @RequestParam 사용하기 : 몇 개 안될 때

-스프링 MVC

String 타입으로 받은 요청 파라미터 값을 읽어와서 해당 타입에 대한 변환을 지원한다.

	@PostMapping("/register/step2")
	public String handleStep2(
	   @RequestParam(value = "agree", defaultValue="false") Boolean agree ) {
		...		..
	}

                커맨드(command) 객체 사용하기 (뒤에서 설명)


                요청 URL 처리 코드 (컨트롤러 작성)

[회원 정보 입력 처리]

앞선 동의 여부에 따라 다른 폼 반환하도록 처리

@Controller
public class RegisterController {
	@PostMapping("/register/step2")
	public String handleStep2( //회원 정보 입력 처리 
	@RequestParam(value = "agree", defaultValue = "false") Boolean agree,
			Model model) {
		if (!agree) { //만약 약관 동의X 
			return "register/step1"; // 다시 약관 폼 뷰 반환
		}
		model.addAttribute("registerRequest", new RegisterRequest());
		return "register/step2"; //동의 O -> 회원 정보 입력 폼 뷰 반환
	}
}

                처리 결과를 응답하는 코드 (뷰 코드 작성)

<!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>
    <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="가입 완료">

5. [리다이렉트 처리]

*잘못된 전송 방식 요청이 왔을 때 알맞은 경로로 리다이렉트 처리하기

-요청 매핑 적용 메소드가 “redirect:경로리턴한 경우.

 redirect: ‘/’로 시작하는 경로
-실제 리다이렉트 경로 = 웹 어플리케이션 경로 + 리턴한 경로 합친 경로 사용

 
redirect: ‘/;로 시작하지 않는 경로

-현재 경로 기준 -> 상대 경로를 사용

 
redirect: ‘완전한 URL’ 경로 : 완전한 URL 해당 경로 사용

 

6. [커맨드 객체 이용 -> 요청 파라미터 사용하기]

-폼이 전송한 파라미터 정보를 컨트롤러 코드에서 사용하기 위해서

<컨트롤러 메소드에서 요청 파라미터에 접근하는 방법 >

 HttpServletRequest 사용 : 여러 개 일 때
, 요청 파라미터 개수 증가할 때마다 코드 길이도 길어짐

 @RequestParam : 몇 개 안될 때

 커맨드(command) 객체 사용하기 **

             ➂ 커맨드(command) 객체 사용하기 **

-폼에 입력한 값을 커맨드 객체로 전달받아 처리
-스프링은 요청 파라미터 값을 커맨드 객체에 담아주는 기능을 제공한다.

-스프링 MVC
처리 메소드에 전달할 커맨드 객체를 생성하고
해당 객체의 세터 메소드를 이용해서 일치하는 요청 파라미터의 값을 전달하여 사용한다.

-커맨드 객체 = 요청 매핑 애노테이션 적용 메소드의 파라미터에 위치

                   요청 URL 처리 코드 (컨트롤러 작성)

[가입 처리 결과 화면 보여주기]

	@PostMapping("/register/step3") //처리 결과 매핑 
	public String handleStep3(RegisterRequest regReq) { //커맨드 객체로 전달 받음
		try { //회원 가입 성공 시,
			memberRegisterService.regist(regReq);
			return "register/step3"; //결과 폼 리턴  
		} catch (DuplicateMemberException ex) { 
		     //회원 가입 실패 시, 
			return "register/step2"; //다시 이전 폼 리턴 
		}
	}
}

                   처리 결과를 응답하는 코드 (뷰 코드 작성)

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

7. [JSP 코드에서 커맨드 객체 사용하기]

-가입할 때 입력한 정보를 회원 가입 완료 화면에서 보여줄 때 사용

-뷰 코드가 커맨드 객체에 접근하여 속성을 사용하고자 할 때,

                   (기본)

-커맨드 객체의 기본 이름은 클래스 이름을 그대로 사용하며
-뷰 코드는 클래스(첫 글자 소문자) 이름을 사용하여 객체에 접근할 수 있다.

                   (변경) @ModelAttribute 애노테이션 사용

-@ModelAttribute 애노테이션 사용하면 커맨드 객체에 접근할 속성 이름 변경 O
-커맨드 객체 파라미터 앞에 ModelAttribute를 붙여주고 그 값에 변경할 이름을 지정함

 

8. [커맨드 객체와 스프링 폼 연동] : 사전 taglib 디렉티브 설정필요

스프링 MVC 가 제공하는 커스텀 태그를 사용하면 간단하게 커맨드 객체 값을 출력 가능
커스텀 태그 사용 : 커맨드 객체의 값을 폼의 태그 생성하여 출력 O


<form:form> 태그 : HTML <form> 태그 생성
<form:input> 태그 : HTML <input> 태그 생성
<form:password> 태그 : HTML password타입의 <input> 택그 생성

9. [컨트롤러 구현 없이 단순 경로 매핑]

-특별한 처리 없이 요청 경로와 뷰 이름을 연결해주는 기능을 위해 따로 컨트롤러 작성하는 것이 번거롭다.

           -MvcConfig 클래스에서 작성 (스프링 MVC 기본 설정 위해 작성한 설정 파일)
           -WebMvcConfigurer 인터페이스의 addViewControllers()메소드 재정의하여 사용하면
                                                    별도의 컨트롤러 구현 없이도 요청 경로와 뷰 이름 연결할 수 있다
@Override
public void addViewControllers(ViewControllerRegistry registry) {
	registry.addViewController(“/main”).setViewName(“main”);
}				//요청 경로 <-> 뷰 이름  단순 경로 매핑

10. [주요 에러 발생 상황 정리] : 콘솔 로그 메시지 잘 확인할 것

1) 요청 매핑 애노테이션 관련 주요 익셉션

< 404 에러 >
-요청 경로 처리 올바른지
-컨트롤러 설정 경로 올바른지
-컨트롤러 클래스 빈 등록 여부
-컨트롤러 클래스에 @Controlloer 적용 여부
-반환한 뷰 이름에 해당하는 JSP 파일 존재 여부
< 405 에러>
-지원하지 않는 전송 방식을 사용한 경우

 

2) @RequestParam/커맨드 객체 관련 주요 익셉션

< 400 에러>
요청 파라미터 값을 적용된 파라미터 타입으로 변환 할 수 없는 경우
-커맨드 객체에 요청 파라미터 값을 복사하는 과정에서 타입 변환 불가능한 경우

 

11. [커맨드 객체 : 중첩/ 콜렉션 프로퍼티]

-스프링 MVC 는 커맨드 객체가

                ➀ 리스트 타입의 프로퍼티를 가졌거나 : 프로퍼티이름[인덱스] 형식으로 접근

                ➁ 중첩 프로퍼티를 가진 경우에도 요청 파라미터 값을 알맞게 커맨드 객체에 설정 기능 제공

                                                                     : 프로퍼티이름.프로퍼티이름 형식으로 접근

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

-< 뷰에 데이터 전달 컨트롤러 >
                      ⓐ 요청 애노테이션 메소드의 파라미터로 Model 타입 추가
                      ⓑ Model 파라미터의 addAttribute() 메소드로 뷰에 데이터 전달
                              -> 뷰 코드는 Model을 통해 전달받은 데이터를 ${_____} 이용해서 사용

13. [ModelAndView -> 한 번에 뷰 선택, 모델 전달 하기]

-지금까지 구현한 컨트롤러의 기능은

결과 보여줄 뷰 이름 리턴 / Model 이용해서 뷰에 전달할 데이터 설정 하는 것

-이 두 가지를 ModelAndView 사용하여 한 번에 처리 가능

-ModelAndView = 모델과 뷰 이름을 함께 제공

-요청 매핑 애노테이션 적용 메소드는 ModelAndView 타입으로 리턴 가능

 @GetMapping
public ModelAndView form() {
	ModelAndView mav = new ModelAndView();
	mav.addObject(“questions”, questions); //뷰에 전달할 모델 데이터
	mav.setViewName(“survey/surveyForm”); //뷰 이름 설정 
	return mav; //반환 
}

14. [주요 폼 태그]

(1) <form> 태그를 위한 커스텀 태그 : <form:form>

(2) <input> 관련 커스텀 태그:
<form:input>, <form:password>, <form:hidden>

(3) <select> 
관련 커스텀 태그 :

<form.select>, <form:options>, <form:option>

(4) 
체크 박스 관련 커스텀 태그:

<form:chechboxes>, <form:checkbox>

(5) 
라디오 버튼 관련 커스텀 태그:

<form:radiobuttons>, <form:radiobutton>

(6) <textarea> 
태그를 위한 커스텀 태그: <form:textarea>

 

728x90