MVC 1: 요청 매핑 어노테이션, 리다이렉트, 요청 파라미터 접근, 커맨드 객체, 폼태그
Spring 설정파일
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>sp5</groupId>
<artifactId>sp5-chap011</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<dependencies>
<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>
<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>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.7.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>utf-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
</project>
9행: packaging의 값은 war이다. 서블릿/JSP를 이용한 웹 어플리케이션을 개발하는 경우 war를 값으로 주어야한다.
스프링, 스프링MVC관련 설정
12~17행: servlet-api
19~24행: jsp-api
26~30행: jstl
32~36행: spring-context와 spring-aop, -web 등을 포함하는 spring-webmvc 모듈 추가(spring-context의존 생략)
DB 설정
38~42행: JdbcTemplate같은 jdbc DB연동을 위한 필요한 기능 제공
44~48행: DB커넥션풀 기능 제공(DataSource)
50~54행: MySQL 연결에 필요한 JDBC 드라이버를 제공한다.
스프링 MVC가 웹 요청을 처리하려면 DispatcherServlet을 통해서 웹요청을 받아야한다. 이 DispatcherServlet을 등록하는 것이 web.xml 파일이다. src/main/webapp/WEB-INF 폴더에 web.xml파일을 작성하자.
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.1" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee">
<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>
<param-value>UTF-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
브라우저가 Java Servlet에 접근하기 위해서는 WAS에 필요한 정보를 알려줘야 해당하는 서블렛을 호출가능하다.
요청 매핑 어노테이션
HTTP 요청을 처리하기 위해서는 해당 URL대로 경로를 찾아서 일을 처리할 코드를 작성해야 한다. 이 기능을 하는 것이 요청 매핑 어노테이션이다.
@GetMapping: Get방식으로 HTTP 요청을 처리한다.
Get은 요청을 전송할 때 데이터를 Body에 담지 않고 쿼리스트링을 통해 전송한다.
(= @RequestMapping(value= "경로", method = RequestMethod.GET) )
@PostMapping: Post방식으로 HTTP요청을 처리한다.
POST는 리소스를 생성/변경하기 위해 설계 되었기 떄문에 GET과 달리 전송해야될
데이터를 HTTP 바디에 담아서 전송한다.
(= @RequestMapping(value= "경로", method = RequestMethod.POST) )
※ GET과 POST의 차이 자세한건 출처: hongsii.github.io/2017/08/02/what-is-the-difference-get-and-post/
@RequestMapping: Get과 Post방식 둘다 받을 수 있다.
우리는 이번시간에 회원가입 서비스를 만들어 볼 것이다.
step1(약관동의) -> step2(회원 정보 입력) -> step3(가입완료) 과정으로 만든다.
이렇게 여러 단계를 거쳐 하나의 기능으로 만들면 관련 요청 경로를 하나의 클래스에서 처리하는게 관리에 좋다.
Controller 파일 예제를 보면서 확인해보자.
RegisterController
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
|
package controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import spring.DuplicateMemberException;
import spring.MemberRegisterService;
import spring.RegisterRequest;
@Controller
@RequestMapping("/register")
public class RegisterController {
private MemberRegisterService memberRegisterService;
public void setMemeberRegisterService(MemberRegisterService memberRegisterService) {
this.memberRegisterService = memberRegisterService;
}
@RequestMapping("/step1")
public String handleStep1() {
return "register/step1";
}
// @PostMapping("/step2")
// public String step2(HttpServletRequest request) {
// String agreeParam = request.getParameter("agree");
// if(agreeParam == null || !agreeParam.equals("true")) {
// return "register/step1";
// }
// return "register/step2";
// }
@PostMapping("/step2")
public String handleStep2(@RequestParam(value = "agree", defaultValue = "false") Boolean agreeVal, Model model) {
if (!agreeVal) {
return "register/step1";
}
model.addAttribute("registerRequest", new RegisterRequest());
return "register/step2_2";
}
@GetMapping("/step2")
public String handleStep2Get() {
return "redirect:/register/step1"; // '/'로 시작하면 웹 어플리케이션 기준으로 이동 경로를 생성
// return "redirect:step1"; // 현재경로 기준으로 상대경로 생성
}
@PostMapping("/step3")
public String handleStep3(RegisterRequest regReq) {
try {
memberRegisterService.regist(regReq);
return "register/step3";
} catch (DuplicateMemberException ex) {
return "register/step2_2";
}
}
@GetMapping("/step3")
public String handleStep3Get() {
return "redirect:/register/step1";
}
}
|
cs |
요청 매핑 어노테이션
15행: @RequestMapping("/register")로 클래스에 경로를 지정하는 경우, 각 메서드에 공통되는 경로로 지정할 수 있다.
24행, 28행: 메서드에 경로 지정하는 경우, 공통 경로에 더한 나머지 경로이다.
요청 매핑 에노테이션은 Get방식과 Post방식 두가지로 구분해서 요청방식을 다르게 처리할 수 있다.
38~45행 그리고 47~51행 모두 "/step2" 경로에 대한 요청을 처리하고 있지만 어떤 방식으로 받느냐에 따라 다른 메소드가 호출되는 것이다.
리다이렉트
만약 step1에서 약관 동의를 거치지 않고 step2 url을 그대로 홈페이지에 입력해서 들어가는 경우(Get), POST방식으로 들어온 것이 아니기 때문에 전 단계인 다시 약관 동의를 거칠 수 있도록 step1주소로 다시 보내줘야한다. 이런 경우를 리 다이렉트라고 한다.
"redirect:경로": 현재 경로 기준으로 상대 경로 생성
"redirect:/경로": 웹 어플리케이션을 기준으로 경로 생성
요청 파라미터 접근
step1에서 약관동의를 해야지 step2 단계로 넘어갈 수 있다고 했다.
그러려면 step1에서 사용자가 체크한 체크박스의 값을 전달 받을 수 있어야한다. 이렇게 사용자가 Request 할 때 담은 값들에 접근하기 위해서는 요청 파라미터 접근 방법을 알아야한다.
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>
위와 같이 step1에서는 step2에게 post방식으로 속성이름 agee에 값을 담아서 Request를 전달했다.
요청 파라미터 접근 방법
1. HttpServletRequest
컨트롤 처리 메서드의 파라미터로 HttpServletRequest 타입을 사용하고 getParmeter()메서드로 파라미터의 값을 구하는 비교적 간단한 방법이다. RegisterController의 주석처리된 30행의 step2 메서드를 주목해보자.
getParmeter에 속성이름 agree 값을 넣어서 전달 받은 값을 String 형태로 저장한다.
2. @RequestParam
요청 파라미터가 1,2개 정도 소수인경우 이 어노테이션을 사용하면 간단하다. 39행의 handleStep2메서드를 보자.
이 어노테이션이 붙은 파라미터의 타입은 신기하게도 Boolean형이다. 스프링 MVC는 파라미터 타입에 맞게 String 값을 변환해준다. 즉 파라미터 값을 읽어와 Boolean 타입으로 변환 시켜서 agreeVal 파라미터에 전달한 것인데, 만약 타입변환 가정에서 오류가 있을경우 익셉션을 일으킨다. 예를들어, true나 false가 아닌 "cake"이런 스트링이 Boolean으로 값을 변경하러 오는 경우 등이다.
@RequestParam의 속성
- value: HTTP요청 파라미터의 이름을 지정한다.
- required: 필수 여부 지정, 기본값:true
- defaultValue: 요청 파라미터가값이 없을 때 사용할 문자열 지정, 기본값은 없다.
**주의** 기본값이 없기 때문에 요청받은 값이 없는데, faultValue값이 지정되어 있지 않는 경우 오류가 자주 발생한다 !
3. 커멘드 객체(**중요**)
커멘드 객체는 사용자가 Request하면서 전달한 값들이 많고 이 다수의 값들에 접근하는 경우 자주 사용하는 방식이다. 예를 들어 회원가입 하는 경우, 이름, 이메일, 비밀번호 등의 값들을 보내고 접근해야 하는데 이런경우 1,2번 방식으로 값을 일일히 불러오는 것은 불편하고 코드량이 많다.
여기 아래 step2의 폼이 있다. 사용자가 이곳에 4개의 값을 입력하고 가입 완료버튼을 눌러 Request를 보내면, 우리는 그 값이 유효한지 step3에서 검사 후, 값이 유효하면 step3.jsp 뷰를 보여주고, 유효하지 않으면 다시 이 폼(step2.jsp)를 보여주어야한다.
RegisterController의 54행의 메소드를 보자. 중요한 것은 RegisterRequest 타입이 파라미터에 있다는 것이다. 이 자체가 커맨드 객체로 값을 받은 것이다. !!! (비교적 간단하지 않은가!)
스프링은 요청 파라미터의 값들을 커맨드 객체에 담아주는 기능을하는데, 여기서는 그 커맨드 객체의 역할을 RegisterRequest가 하고 있는 것이다. 커맨드 객체가 되기 위해서는 필수적으로 요청 파라미터의 이름(name)들을 자신의 속성으로 갖고 있고 모두 setter메소드로 구현되어 있어야한다. 예를 들면, RegisterRequest는 email, password, confirmPassword, name 속성과 setEmail, setPassword, setName, setConfirmPassword 메소드를 가지고 있는 것이다.
자, Command객체가 다수의 요청 파라미터의 값들을 쉽게 받을 수 있음을 알았으니 54행의 메소드를 살펴보자.
Controller와 경로에 적합한 메소드를 찾았다면, 그이후의 과정은 아래와 같다.
Controller -> Service -> Dao -> DB
DB-> Dao -> Service -> Controller
우리는 값들을 가지고 MemberRegisterService 기능을 만족시켜주면된다
사용방법
- 설정파일에서 MemberRegisterService를 빈 객체로 등록한다.
- 컨트롤러에서(18행, 20행) memberRegisterService의 객체를 의존주입 받는다.
- Step3에서 회원등록과정을 처리한다. 54행 커맨드 객체인 RegisterRequest를 파라미터로 받는다. 이 객체를 값으로 넣어서 regist 메서드를 실행한다.
- register 메서드에서는 만약 이메일이 중복되면 DuplicateMemberException을 일으킬것이고, 아니면 멤버를 생성하여 DAO를 통해 DB에 멤버를 저장할 것이다.
- 익셉션이 발생하지 않으면 step3의 회원가입 완료 화면을 보여주고, 그렇지 못했다면 step2의 폼을 다시 보여준다.
커맨드 객체
(1) 뷰 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>${registerRequest.name}님의 회원가입을 축하드립니다.</p>
<p><a href="<c:url value='/main'/>">[첫 화면 이동]</a></p>
</body>
</html>
회원가입을 완료하면 보여지는 step3.jsp이다. RegisterController의 54행을 보면, 생성한 커맨드 객체의 이름은 분명 regReq이다. 하지만 여기 step3.jsp의 9행에서 사용한 속성 이름은 registerRequest로 사용했다. 이는 스프링 MVC가 자동으로 커맨드 객체 클래스의 첫글자를 소문자로 바꾼 명으로 커맨드 객체를 뷰에 전달하기 때문이다. 커맨드 객체의 속성명은 그대로 사용한다.(name을 함께 사용)
스프링 MVC가 명명한 커맨드 객체의 이름을 우리가 변경할 수 있다. 다음단계로 가보자.
(2) @ModelAttribute 애노테이션으로 커맨드 객체 속성 이름 변경
커맨드 객체에 접그할 때 사용할 속성이름을 변경하고 싶다.
@PostMapping("/step3")
public String handleStep3(@ModelAttribute("registerCommand") RegisterRequest regReq) {
try {
memberRegisterService.regist(regReq);
return "register/step3";
} catch (DuplicateMemberException ex) {
return "register/step2_2";
}
}
모델에서 사용할 속성 이름을 값으로 설정한다. 여기에서는 "registerCommand" 라고 설정했다.
(3) 커맨드 객체와 스프링 폼 연동
요청 파라미터에 접근해서 커맨드 객체를 통해 값을 편하게 받았다면, 이번엔 커맨드 객체를 통해 편하게 값을 뷰에 전달할 수 있다.
위의 예제에서 가입완료 버튼을 눌렀는데, 유효성 검사를 통과하지 못하여 익셉션이 발생해서 다시 step2의 회원가입 폼으로 돌아 왔을 경우 원래 입력했던 이메일 값들은 기억하게 해주는게 좋다. 예를 들어
<input type="text" name="email" id="email" value="${registerRequest.email}"> 이런식으로 말이다.
이 방법 말고도 스프링 폼을 활용해 보자. 스프링폼은 jstl의 일종이며, jstl은 스프링 커스텀 태그의 일종이다.
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>
<form:form> 태그를 사용하려면 커맨드 객체는 필수적으로 존재해야한다.
2행: 폼 태그 사용을 위한 taglib 디렉티브 설정
10행: <form:form>태그. modelAttribute속성은 커맨드 객체의 속성 이름을 지정한다. 기본값: "command"
13행: <form:input>태그. html의 <input type="text">와 비슷한 형태이다.
path속성은 input태그의 속성 name과 id에 path의 값을 넣고, path의 속성 프로퍼티의 값을 value에 넣는것과 같다.
학습출처: 초보 웹 개발자를 위한 스프링5 프로그래밍 입문
추가적으로 학습한 내용 정리
+스프링 설정파일이 .xml일 경우 서비스 객체 구현
방법1. 똑같이 스프링 설정파일에 서비스 객체를 생성하고 자동주입한다.
서비스 Bean 객체 생성
1
|
<beans:bean id="memberRegisterService" class="spring.MemberRegisterService"></beans:bean>
|
cs |
@Autowired
private MebmerRegisterService MRS = new MemberRegisterService();
방법2. @Repository or @Service or @Component 어노테이션을 붙이고 @Resource 어노테이션을 이용해 서비스 객체를 생성하고 주입한다.
아래처럼 서비스 기능 class에 @Repository 어노테이션을 붙인다.
1
2
3
4
5 6 |
@Repository("memberRegisterSvc")
// @Service // @Component publlic class MemberRegisterService{
}
|
cs |
이 서비스 객체를 사용할 Controller에서 MemberRegisterService 프로퍼티에 @Resource 어노테이션을 붙인다.
1
2
3 |
@Resource("memberRegisterSvc")
// @Autowired private MemberRegisterService memberRegisterService;
|
cs |
이름을 굳이 명시하지 않고 싶은 경우에는 이름을 명시하지 않아도 되고,
MemberRegisterService가 빈객체로 등록되어 있다는 가정하에 @Autowired 어노테이션을 이용하는 것도 가능한다.
servletContext.xml 변경되고 배운점
(1)
<beans> 태그 =>변경=> <beans:beans>태그
<bean> =>변경=> <beans:bean>
<beans> 태그를 <beans:beans> 태그로 변경한 이유는 spring MVC 설정 파일의 코드 중복을 줄일 수 있기 때문이란다..
(2)
<context:component-scan base-package="spring" />
=> base-package에서 명시된 "spring"패키지의 경로에 포함된 모든 하위 경로의 자바 파일들에게
@Controller, @Component, @Service, @Repository 어노테이션이 명시되어 있으면 모두 Bean으로 등록된다.
추가 개념공부
서블릿:
클라이언트의 요청을 처리하고, 그 결과를 반환하는
Servlet클래스의 구현 규칙을 지킨 자바 웹 프로그래밍 기술
client에서 HTTP Request를 보내고 처리 끝나고 HTTP Response를 보내는 역할
JSP(Java Server Page):
Java 코드가 들어가 있는 HTML 코드
<% 소스코드 %> 또는 <%= 소스코드 =%> 형태로 들어간다.
이 자바 소스코드로 작성된 부분들은 웹 브라우저로 보내는 것이 아닌 웹 서버에서 실행되는 부분을 나타냅니다. 웹 프로그래머가 소스코드를 수정할 경우에도 디자인 부분 제외하고 자바 소스코드만 수정하면 되기에 효율을 높혀준다.
JSTL(Jsp Standard Tag Library)
JSP파일에 자바 형식의 코드를 사용하면 불편할 수 있다.
해결 => EL (Expression Language) JSTL (Jsp Standard Tag Librayry)
EL은 해석 그대로의 표현 언어를 이해하고 속성 값들을 편하게 출력하기 위해
=> ${ }이렇게 사용
JSTL은 표준 액션 태그로 처리하기 힘든부분을 담당. 커스텀 태그의 일종이다.
JSTL이란 JSP의 taglib으로 커스텀하여 사용하는 편리한 외부 공식 하이브러리다. JSTL 홈페이지에서 해당 라이브러리를 다운받아 내 프로젝트 라이브러리 추가하고, 아래와 같이 taglib으로 import 해준다.
<%@ taglib prefix="c" uri="java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="form" uri="http://springframework.org/tags/form" %>
jstl/core라는 모듈을 c라는 이름으로 주로 사용하며 (prefix : c)
<c: 내가 원하는 기능> 이런식으로 사용가능하다. 다음에 <c: >를 더 심도있게 다뤄보자.
출처: