ㅁ 폴더와 파일 생성
- src/main/webapp/WEB-INF/views에 book 폴더를 만든다.
- src/main/webapp/WEB-INF/views/book 폴더에 list.jsp, detail.jsp, modify.jsp, enroll.jsp를 만든다.
ㅁ main.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<c:set var="contextPath" value="${pageContext.request.contextPath}" />
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<script src="${ contextPath }/resources/js/sample.js"></script>
<script src="${ contextPath }/assets/js/jquery-3.7.1.min.js"></script>
</head>
<body>
<!-- / 또는 /main.do라는 url mapping으로 요청시 해당 /WEB-INF/views/main.jsp가 보여지도록 한다. -->
<h1>메인페이지입니다</h1>
<h3>1. 정적 자원 확인</h3>
<img src="${ contextPath }/resources/images/1.jpeg" width="100" onclick="test();">
<img src="${ contextPath }/assets/images/2.jpg" width="100" id="img">
<script>
$(function(){
$('#img').on("click", () => {
alert("어서오세요");
})
})
</script>
<hr>
<h3>2. 응답페이지 보여지게 하는 연습 (포워딩, redirect)</h3>
<!-- <a href="${contextPath}/list.bk">도서목록페이지로 이동</a> -->
<a href="${contextPath}/book/list.do">도서목록페이지로 이동</a>
<a href="${contextPath}/book/enrollForm.do">도서등록페이지로 이동</a>
</body>
</html>
- "2. 응답페이지 보여지게 하는 연습(포워딩, redirect)" 부분을 작성한다.
※ 스프링에서 WEB-INF에 있는 페이지는 무조건 서블릿을 거쳐서 가야 한다.
- Spring MVC에서는 WEB-INF 폴더에 있는 페이지(예: JSP 파일 등)는 외부에서 직접 접근할 수 없고, 반드시 컨트롤러(서블릿)를 통해서만 접근이 가능합니다.
이는 보안상의 이유로 WEB-INF에 위치한 파일이 URL로 직접 노출되지 않게 하려는 목적이며,
Spring MVC에서는 Controller에서 요청을 처리하고 적절한 View로 포워딩하는 방식으로 이 구조를 사용합니다.
ㅁ url 경로
- "${contextPath}/list.bk" => "${contextPath}/book/list.do"
- 서비스를 앞에 한 단계 더 둬서 구분해도 된다.
ㅁ com.br.mvc.controller에 BookController 일반 클래스 생성
package com.br.mvc.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@Controller
public class BookController {
// 요청보단 응답(forward, redirect)과 관련된 내용 학습
//@RequestMapping(value="/book/list.do", method=RequestMethod.GET)
//@RequestMapping(value="book/list.do", method=RequestMethod.GET)
//@RequestMapping(value="book/list.do")
@RequestMapping("book/list.do")
public String bookList() {
// /WEB-INF/views/book/list.jsp 포워딩
return "book/list";
}
}
- a 태그는 get방식이다. redirect도 거의 get방식이다.
- value="/book/list.do"에서 맨 앞의 슬래시는 생략 가능하다.
Spring MVC의 뷰 리졸버는 경로에 있는 슬래시("/")를 자동으로 정리해주기 때문에, //처럼 슬래시가 중복되어도 문제가 발생하지 않는다.
- value 속성명도 생략하고 url mapping값만 작성시 value 속성
- method 속성 생략 가능하다. 생략시 기본값은 get, post 둘 다 허용한다.
ㅁ list.jsp 수정
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<c:set var="contextPath" value="${pageContext.request.contextPath}" />
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h3>도서 목록 페이지</h3>
<table border="1">
<thead>
<tr>
<th>번호</th>
<th>제목</th>
<th>저자</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>수학의 정석</td>
<td>나수학</td>
</tr>
<tr>
<td>2</td>
<td>자바의 정석</td>
<td>강자바</td>
</tr>
<tr>
<td>3</td>
<td>스프링의 정석</td>
<td>이봄</td>
</tr>
</tbody>
</table>
</body>
</html>
- 도서 목록페이지를 만들었다.
- 데이터는 db로부터 조회되었다고 가정한다.
- /book/list.do라는 url mapping값이 보여진다.
=========================================================================
ㅁ list.jsp 수정
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<c:set var="contextPath" value="${pageContext.request.contextPath}" />
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<script src="${contextPath}/assets/js/jquery-3.7.1.min.js"></script>
</head>
<body>
<h3>도서 목록 페이지</h3>
<table border="1">
<thead>
<tr>
<th>번호</th>
<th>제목</th>
<th>저자</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>수학의 정석</td>
<td>나수학</td>
</tr>
<tr>
<td>2</td>
<td>자바의 정석</td>
<td>강자바</td>
</tr>
<tr>
<td>3</td>
<td>스프링의 정석</td>
<td>이봄</td>
</tr>
</tbody>
</table>
<script>
$(function(){
$("tbody>tr").on("click", function(){
location.href = '${contextPath}/book/detail.do?no=' + $(this).children().eq(0).text();
})
})
</script>
</body>
</html>
- 도서 목록 페이지에서 각 행을 클릭하면 그 행의 도서 상세 페이지로 이동하게 클릭 이벤트를 건다.
- 이때 넘겨야하는 글번호는 $(this).children().eq(0).text()로 구한다.
ㅁ detail.jsp 수정
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<c:set var="contextPath" value="${pageContext.request.contextPath}" />
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h3>도서 상세 페이지</h3>
<div>
번호 : 1 <br>
제목 : 수학의 정석 <br>
저자 : 나수학 <br><br>
</div>
</body>
</html>
ㅁ BookController 수정
package com.br.mvc.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@Controller
public class BookController {
// 요청보단 응답(forward, redirect)과 관련된 내용 학습
//@RequestMapping(value="/book/list.do", method=RequestMethod.GET)
//@RequestMapping(value="book/list.do", method=RequestMethod.GET)
//@RequestMapping(value="book/list.do")
@RequestMapping("book/list.do")
public String bookList() {
// /WEB-INF/views/book/list.jsp 포워딩
return "book/list";
}
@RequestMapping("book/detail.do")
public void bookDetail() {
// /WEB-INF/views/book/detail.jsp 포워딩
// 아무것도 작성하지 않았는데도 잘 뜬다.
// return "book/detail";
}
}
- 만약 return을 했다면 return "book/detail";을 작성했을 것이다.
그런데 우리가 요청했던 url mapping값("book/detail.do")와 비슷하다.
- 응답페이지로 포워딩 할 때 반환값이 없다면 기본적으로 url mapping값으로 페이지를 찾는다.
- 스프링 MVC에서 포워딩할 페이지의 경로와 URL 매핑이 동일할 경우 메소드의 반환 타입을 void로 작성할 수 있다.
View Resolver가 URL 경로를 사용해 해당 경로의 JSP 페이지를 찾아 자동으로 포워딩합니다.
- url mapping값에서 .do는 떼고 그 앞으로만 찾는다. (.을 포함한 뒤쪽 구문은 뗀다)
==============================================================================
ㅁ detail.jsp 수정
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<c:set var="contextPath" value="${pageContext.request.contextPath}" />
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h3>도서 상세 페이지</h3>
<div>
번호 : 1 <br>
제목 : 수학의 정석 <br>
저자 : 나수학 <br><br>
<a href="${contextPath}/book/modifyForm.do">수정하기 페이지로 이동</a>
</div>
</body>
</html>
- 도서 상세 페이지에 수정하기 페이지로 이동할 수 있게 한다.
ㅁ modify.jsp 수정
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<c:set var="contextPath" value="${pageContext.request.contextPath}" />
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h3>도서 수정 페이지</h3>
<form action="${contextPath}/book/modify.do" method="post">
제목 : <input type="text" name="title" value="수학의 정석"> <br>
저자 : <input type="text" name="author" value="나수학"> <br><br>
<button type="submit">수정</button>
</form>
</body>
</html>
ㅁ BookController 수정
package com.br.mvc.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@Controller
public class BookController {
// 요청보단 응답(forward, redirect)과 관련된 내용 학습
//@RequestMapping(value="/book/list.do", method=RequestMethod.GET)
//@RequestMapping(value="book/list.do", method=RequestMethod.GET)
//@RequestMapping(value="book/list.do")
@RequestMapping("book/list.do")
public String bookList() {
// /WEB-INF/views/book/list.jsp 포워딩
return "book/list";
}
@RequestMapping("book/detail.do")
public void bookDetail() {
// /WEB-INF/views/book/detail.jsp 포워딩
// 아무것도 작성하지 않았는데도 잘 뜬다.
}
@RequestMapping("book/modifyForm.do")
public String bookModifyForm() {
// /WEB-INF/views/book/modify.jsp 포워딩
return "book/modify";
}
@RequestMapping(value="book/modify.do", method=RequestMethod.POST)
public String bookModify() {
// 수정 성공했다는 가정 하에 포워딩이 아닌
// "contextPath/book/detail.do?no=번호" redirect(url 재요청)
return "redirect:/book/detail.do?no=" + 1;
}
}
- 포워드 때와 달리 redirect: 다음에 슬래시로 시작해야만 한다.
경로가 애플리케이션의 루트 경로에서 시작하도록 하려면 /를 붙여 절대 경로로 지정한다.
- 리다이렉트 시 redirect: 접두사를 사용하면, 애플리케이션의 컨텍스트 루트에서 시작하는 경로로 인식된다.
- 리다이렉트할 때 redirect:/book/detail.do?no=1는 실제로 http://localhost:8888/mvc/book/detail.do?no=1로 요청이 전송됩니다.
- 수정 버튼을 누르면
- 다시 상세페이지로 가진다.
==============================================================================
ㅁ entroll.jsp 수정
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<c:set var="contextPath" value="${pageContext.request.contextPath}" />
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h3>도서 등록 페이지</h3>
<form action="${ contextPath }/book/enroll.do" method="post">
제목 : <input type="text" name="title"> <br>
저자 : <input type="text" name="author"> <br><br>
<button type="submit">등록</button>
</form>
</body>
</html>
ㅁ BookController 수정
package com.br.mvc.controller;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@Controller
public class BookController {
// 요청보단 응답(forward, redirect)과 관련된 내용 학습
//@RequestMapping(value="/book/list.do", method=RequestMethod.GET)
//@RequestMapping(value="book/list.do", method=RequestMethod.GET)
//@RequestMapping(value="book/list.do")
@RequestMapping("book/list.do")
public String bookList() {
// /WEB-INF/views/book/list.jsp 포워딩
return "book/list";
}
@RequestMapping("book/detail.do")
public void bookDetail() {
// /WEB-INF/views/book/detail.jsp 포워딩
// 아무것도 작성하지 않았는데도 잘 뜬다.
}
@RequestMapping("book/modifyForm.do")
public String bookModifyForm() {
// /WEB-INF/views/book/modify.jsp 포워딩
return "book/modify";
}
@RequestMapping(value="book/modify.do", method=RequestMethod.POST)
public String bookModify() {
// 수정 성공했다는 가정 하에 포워딩이 아닌
// "contextPath/book/detail.do?no=번호" redirect(url 재요청)
return "redirect:/book/detail.do?no=" + 1;
}
@RequestMapping("book/enrollForm.do")
public String bookEnrollForm() {
// /WEB-INF/views/book/enroll.jsp 포워딩
return "book/enroll";
}
// 따로 응답을 포워드나 redirect하지 않고
// 요청한 브라우저로 <script>문을 돌려줘서 흐름 제어하기
// 브라우저로 <script>문 돌려주면 그 스크립트문이 바로 실행된다.
@RequestMapping("book/enroll.do")
public void bookEnroll(HttpServletResponse response) throws IOException {
// db 연동이 안되어있어서 그냥 0 아니면 1이 돌아오게끔.
int result = Math.random() < 0.5 ? 1 : 0;
response.setContentType("text/html; charset=UTF-8");
PrintWriter out = response.getWriter();
out.println("<script>");
if(result > 0) {
// alert로 성공메세지 출력 후 목록페이지
out.println("alert('성공적으로 등록되었습니다');");
out.println("location.href='/mvc/book/list.do';");
}else {
// alert로 실패메세지 출력 후 기존에 작업중이던 작성페이지 유지
out.println("alert('등록에 실패하였습니다');");
out.println("history.back();");
}
out.println("</script>");
}
}
- response.getWriter() 부분이 서블릿을 쓸 때는 throws로 예외를 넘겼었다.
여기서도 throws를 써서 스프링이 처리하게 한다.
- script 태그가 별도의 펑션으로 묶여있지 않기 때문에 로드되면 곧바로 실행된다.
- 제목과 저자를 입력한 후 "등록" 버튼을 누르면
등록에 실패한 경우는 alert문이 뜨고 확인을 누르면 여전히 도서 등록 페이지에 있고,
등록에 성공한 경우는 alert문이 뜨고 확인을 누르면 도서 목록페이지로 이동된다.
ㅁ book 컨트롤러의 url mapping값을 보면 매번 앞에 book을 베이스로 깔고 있다.
일일이 매번 작성할 필요 없이 한번만 선언해둘 수도 있다.
package com.br.mvc.controller;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@RequestMapping("/book") // 해당 컨트롤러는 앞으로 /book으로 시작하는 모든 url 요청을 처리하는 클래스로 선언
@Controller
public class BookController {
// 요청보단 응답(forward, redirect)과 관련된 내용 학습
//@RequestMapping(value="/book/list.do", method=RequestMethod.GET)
//@RequestMapping(value="book/list.do", method=RequestMethod.GET)
//@RequestMapping(value="book/list.do")
//@RequestMapping("book/list.do")
@RequestMapping("/list.do")
public String bookList() {
// /WEB-INF/views/book/list.jsp 포워딩
return "book/list";
}
//@RequestMapping("book/detail.do")
@RequestMapping("/detail.do")
public void bookDetail() {
// /WEB-INF/views/book/detail.jsp 포워딩
// 아무것도 작성하지 않았는데도 잘 뜬다.
}
//@RequestMapping("book/modifyForm.do")
@RequestMapping("modifyForm.do")
public String bookModifyForm() {
// /WEB-INF/views/book/modify.jsp 포워딩
return "book/modify";
}
//@RequestMapping(value="book/modify.do", method=RequestMethod.POST)
@RequestMapping(value="/modify.do", method=RequestMethod.POST)
public String bookModify() {
// 수정 성공했다는 가정 하에 포워딩이 아닌
// "contextPath/book/detail.do?no=번호" redirect(url 재요청)
return "redirect:/book/detail.do?no=" + 1;
}
//@RequestMapping("book/enrollForm.do")
@RequestMapping("/enrollForm.do")
public String bookEnrollForm() {
// /WEB-INF/views/book/enroll.jsp 포워딩
return "book/enroll";
}
// 따로 응답을 포워드나 redirect하지 않고
// 요청한 브라우저로 <script>문을 돌려줘서 흐름 제어하기
// 브라우저로 <script>문 돌려주면 그 스크립트문이 바로 실행된다.
//@RequestMapping("book/enroll.do")
@RequestMapping("/enroll.do")
public void bookEnroll(HttpServletResponse response) throws IOException {
// db 연동이 안되어있어서 그냥 0 아니면 1이 돌아오게끔.
int result = Math.random() < 0.5 ? 1 : 0;
response.setContentType("text/html; charset=UTF-8");
PrintWriter out = response.getWriter();
out.println("<script>");
if(result > 0) {
// alert로 성공메세지 출력 후 목록페이지
out.println("alert('성공적으로 등록되었습니다');");
out.println("location.href='/mvc/book/list.do';");
}else {
// alert로 실패메세지 출력 후 기존에 작업중이던 작성페이지 유지
out.println("alert('등록에 실패하였습니다');");
out.println("history.back();");
}
out.println("</script>");
}
}
- 둘이 조합된다고 보면 된다. /book에 /list.do, /detail.do 등이 붙는다.
- 두 방식 중 편한 방식으로 작성하면 된다.