실습 코드 참조
moonhy7/SpringFramework: Spring Framework 실습 코드 정리 (github.com)
3.1절 Model 2 아키텍처 구조
1. Model 2 아키텍처 구조
2. Model 2 아키텍처 구조 특징
- Model 1 아키텍처의 단점 : 자바 로직과 화면 디자인이 통합되어 엔터프라이즈 시스템에서 사용하기에는 유지보수가 어렵다.
- Model 2 아키텍처에서는 기존에 JSP가 담당했던 Controller 로직이 별도의 Controller 기능의 서블릿으로 옮겨졌다.
- 결과적으로 자바 개발자는 Controller와 Model만 관리하면 되고 Controller 로직이 사라진 JSP에는 View만 남기 때문에 디자이너는 JSP 파일만 관리하면 된다.
3. MVC 아키텍처 각 요소별 기능 및 개발 주체
- MVC 아키텍처에서 가장 중요한 Controller 기능은 MVC 프레임워크가 제공하는 Controller를 사용하는 것이 더 효율적이고 안정적임
- 하지만 프레임워크가 제공하는 Controller 구조가 복잡하고 어렵기 때문에 당분간은 Controller 기능 이해를 중점으로 학습
3.2절 Controller 구현하기 ( _052_BoardWeb_Model_Two_DispatcherServlet )
1. 서블릿 생성 및 등록
1) 버전 확인
- web.xml 버전과 org.eclipse.wst.common.project.facet.core.xml 버전이 모두 2.5로 되어있어야함
- web.xml
- org.eclipse.wst.common.project.facet.core.xml
- Maven Project를 업데이트하고 STS 재부팅하기
2) 서블릿 생성하기
- Controller 기능을 수행하는 서블릿 클래스를 하나 추가하여 기존의 Model 1 기반으로 개발된 게시판 프로그램을 MVC 아키텍처로 변경하는 작업
- 클래스 이름을 DispatcherServlet으로 설정하고 Next 클릭
- Name에 action을 적고 URL mappings에 /action 을 더블 클릭하여 Pattern을 *.do로 설정
3) xml 파일
- xml 파일에 자동으로 서블릿 관련 설정이 추가되는 것을 볼 수 있음
- <description>과 <display-name> 설정 제거
- 클라이언트의 모든 *.do 요청을 DispatcherServlet 클래스의 객체가 처리한다는 설정임
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee https://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5">
<servlet>
<servlet-name>action</servlet-name>
<servlet-class>com.springbook.view.controller.DispatcherServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>action</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
</web-app>
2. Controller 서블릿 구현
1) DispatcherServlet 클래스
- DispatcherServlet에는 GET 방식과 POST 방식 요청을 처리하는 메소드가 재정의되어 있다.
- 한글이 깨지지 않도록 인코딩 처리를 DispatcherServlet 클래스에 일괄적으로 해주므로 인코딩 변경이 용이하다.
- DispatcherServlet의 process() 메소드에서는 가장 먼저 클라이언트의 요청 URI로부터 path 정보를 추출한다.
- 추출된 path는 URI 문자열에서 마지막 /XXX.do 문자열이며, 문자열에 따라서 복잡한 분기 처리 로직이 실행된다.
package com.springbook.view.controller;
import java.io.IOException;
import java.util.List;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import com.springbook.biz.board.BoardVO;
import com.springbook.biz.board.impl.BoardDAO;
import com.springbook.biz.user.UserVO;
import com.springbook.biz.user.impl.UserDAO;
/**
* Servlet implementation class DispatcherServlet
*/
public class DispatcherServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
/**
* @see HttpServlet#HttpServlet()
*/
public DispatcherServlet() {
super();
// TODO Auto-generated constructor stub
}
/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
process(request, response);
}
/**
* @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
*/
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
//클라이언트에서 전송하는 데이터가 한글일 경우 깨짐 방지
request.setCharacterEncoding("UTF-8");
process(request, response);
}
private void process(HttpServletRequest request, HttpServletResponse response) throws IOException {
//1. 클라이언트 요청 path 정보를 추출한다.
String uri = request.getRequestURI();
// http://localhost:9900/biz/login.do
String path = uri.substring(uri.lastIndexOf("/"));
// /login.do
System.out.println(path);
//2. 클라이언트 요청 path에 따라 적절히 분기처리한다.
if(path.equals("/login.do")) {
System.out.println("로그인 처리");
}
} else if(path.equals("/logout.do")) {
System.out.println("로그아웃 처리");
} else if(path.equals("/insertBoard.do")) {
System.out.println("글 등록 처리");
} else if(path.equals("/updateBoard.do")) {
System.out.println("글 수정 처리");
} else if(path.equals("/deleteBoard.do")) {
System.out.println("글 삭제 처리");
} else if(path.equals("/getBoard.do")) {
System.out.println("글 상세 조회 처리");
} else if(path.equals("/getBoardList.do")) {
System.out.println("글 목록 검색 처리");
}
}
}
2) 실행 결과
- 아래 URI을 차례로 요청해보면 다음과 같은 분기 처리 결과가 출력된다.
- 이제 기존에 작성된 JSP 파일에서 각 분기 처리 로직을 추출하여 DispatcherServlet에 추가하고 기능을 구현할 것이다
3.3절 로그인 기능 구현하기
1) login.jsp
- 로그인 기능을 MVC로 변환하기 위해 login.jsp 파일의 action 속성값을 login.do로 수정
- .do 형태의 요청에 대해서만 DispatcherServlet이 동작하기 때문
<form action="login.do" method="post">
2) DispatcherServlet
- login_proc.jsp 파일에 있는 모든 자바 로직을 복사하여 DispatcherServlet에 추가할 것
- import 선언을 위해 Ctrl + Shift + O 단축키 누르기
//2. 클라이언트 요청 path에 따라 적절히 분기처리한다.
if(path.equals("/login.do")) {
System.out.println("로그인 처리");
//1. 사용자 입력 정보 추출(login.jsp에서 전송한 id, password 받기)
String id = request.getParameter("id");
String password = request.getParameter("password");
//2. DB연동 처리(UserDAO의 메소드 호출)
UserVO vo = new UserVO();
vo.setId(id);
vo.setPassword(password);
UserDAO userDAO = new UserDAO();
UserVO user = userDAO.getUser(vo);
//3. 화면 네비게이션(로그인 후 화면 이동 처리)
//로그인 성공 시 게시판 목록 조회 화면으로 이동
if(user != null) {
response.sendRedirect("getBoardList.do");
//로그인 실패 시 다시 로그인 화면으로 이동
} else {
response.sendRedirect("login.jsp");
}
} else if(path.equals("/logout.do")) {
System.out.println("로그아웃 처리");
3) 실행 화면
3.4절 글 목록 검색 기능 구현하기
1) DispatcherServlet
- 기존의 getBoardList.jsp 파일에서 Controller 로직에 해당하는 자바 코드를 DispatcherServlet으로 복사하기
} else if(path.equals("/getBoardList.do")) {
System.out.println("글 목록 검색 처리");
//1. 사용자 입력정보 추출(검색기능 나중에 구현)
//2. DB 연동 처리(BoardDAO의 메소드 호출)
BoardVO vo = new BoardVO();
BoardDAO boardDAO = new BoardDAO();
List<BoardVO> boardList = boardDAO.getBoardList(vo);
//3. 검색 결과를 세션에 저장하고 목록 화면으로 이동한다.
HttpSession session = request.getSession();
session.setAttribute("boardList", boardList);
response.sendRedirect("getBoardList.jsp");
}
- 리다이렉트되는 getBoardList.jsp 화면에서 검색 결과를 출력하기 위해 세션(HttpSession) 객체를 사용한다.
- 세션은 브라우저당 서버 메모리에 하나씩 유지되는 객체이므로 사용자가 많을 수록, 세션에 정보가 많이 저장될수록 서버 입장에서 부담스러움
- 따라서 검색 결과는 HttpServletRequest 객체에 저장하여 공유해야 한다.
- HttpServletRequest 객체는 클라이언트가 서버에 요청을 전송할 때마다 생성되며 바로 삭제되는 1회성 객체이므로 서버에서는 부담되지 않는다.
- 하지만 코드의 간결함을 유지하기위해 HttpSession을 사용할 예정
2) getBoardList.jsp
- 글 목록을 검색하는 자바 코드 대신 세션에 저장된 글 목록을 꺼내서 출력하도록 수정
- getBoardList.jsp 파일에 있었던 자바 코드는 Controller인 DispatcherServlet 클래스로 이동했기 때문에 getBoardList.jsp는 단지 세션에 저장된 글 목록을 꺼내서 출력하는 기능만 제공함
<%
// 세션에 저장되어 있는 게시글 목록을 꺼낸다.
List<BoardVO> boardList = (List)session.getAttribute("boardList");
%>
<!DOCTYPE html>
...
3) 실행 화면
- getBoardList.jsp 파일을 직접 요청하거나 login.jsp 파일을 실행하여 로그인하면 아래와 같은 오류 발생
- getBoardList.jsp 파일에서 세션에 저장된 글 목록을 꺼내려면 DispatcherServlet이 먼저 실행되어 검색 결과를 저장해야하므로 반드시 /getBoardList.do로 요청해야 한다.
4) 게시글 목록 검색 실행 순서
① DispatcherServlet이 클라이언트의 /getBoardList.do 요청을 받으면
② DispatcherServlet은 BoardDAO 객체를 이용하여 글 목록을 검색한다.
③ 검색된 글 목록을 세션에 등록하고
④ getBoardList.jsp 화면을 요청하면,
⑤ getBoardList.jsp는 세션에 저장된 글 목록을 꺼내어 목록 화면을 구성한다.
⑥ 마지막으로 이 응답 화면이 브라우저에 전송된다.
5) 로그인 성공 시 리다이렉트 .do로 변경
//3. 화면 네비게이션(로그인 후 화면 이동 처리)
//로그인 성공 시 게시판 목록 조회 화면으로 이동
if(user != null) {
response.sendRedirect("getBoardList.do");
//로그인 실패 시 다시 로그인 화면으로 이동
} else {
response.sendRedirect("login.jsp");
}
3.5절 글 상세 보기 기능 구현하기
1) getBoardList.jsp
- 글 상세 화면도 MVC 아키텍처로 변환하기위해 게시글 제목에 설정된 하이퍼링크를 수정한다.
- getBoardList.jsp를 getBoardList.do로 변경하면 된다.
<% for(BoardVO board : boardList) { %>
<tr>
<td><%= board.getSeq() %></td>
<td><a href="getBoard.do?seq=<%= board.getSeq() %>"><%=board.getTitle() %></a></td>
<td><%= board.getWriter() %></td>
<td><%= board.getRegDate() %></td>
<td><%= board.getCnt() %></td>
</tr>
<% } %>
2) DispatcherServlet
- 글 목록 검색 기능과 비슷하게 검색 결과를 getBoard.jsp 파일에 공유하기 위해 검색 결과를 세션에 저장하고 getBoard.jsp 파일을 리다이렉트한다.
} else if(path.equals("/getBoard.do")) {
System.out.println("글 상세 조회 처리");
//1. 상세 조회할 게시글 번호 추출
String seq = request.getParameter("seq");
//2. DB 연동 처리(BoardDAO의 메소드 호출)
BoardVO vo = new BoardVO();
vo.setSeq(Integer.parseInt(seq));
BoardDAO boardDAO = new BoardDAO();
BoardVO board = boardDAO.getBoard(vo);
//3. 검색 결과를 세션에 저장하고 목록 화면으로 이동한다.
HttpSession session = request.getSession();
session.setAttribute("board", board);
response.sendRedirect("getBoard.jsp");
} else if(path.equals("/getBoardList.do")) {
System.out.println("글 목록 검색 처리");
...
3) getBoard.jsp
- 세션에 저장된 검색 결과를 getBoard.jsp 파일에서 출력한다.
<%
// 세션에 저장된 게시글 내용을 꺼낸다.
BoardVO board = (BoardVO) session.getAttribute("board");
%>
3.6절 글 등록 기능 구현하기
1) insertBoard.jsp
- action 속성값을 insertBoard.do 로 수정한다.
<form action="insertBoard.do" method="post">
2) DispatcherServlet
- insertBoard_proc.jsp 파일에 있던 자바 코드를 복사하여 DispatcherServlet 클래스에 이동한다.
- 글 등록 처리 후 getBoardList.do가 아닌 getBoardList.jsp로 설정하면 글 등록 전 세션에 저장된 글 목록을 또다시 출력
- 한글 인코딩 코드는 DispatcherServlet의 doPost() 메소드가 일괄적으로 처리해주기 때문에 각 기능에서 삭제해도 된다.
} else if(path.equals("/insertBoard.do")) {
System.out.println("글 등록 처리");
//1. 사용자 입력정보 추출(insertBoard.jsp에서 전송된 정보 추출)
String title = request.getParameter("title");
String writer = request.getParameter("writer");
String content = request.getParameter("content");
//2. DB 연동 처리(BoardDAO의 메소드 호출)
BoardVO vo = new BoardVO();
vo.setTitle(title);
vo.setWriter(writer);
vo.setContent(content);
BoardDAO boardDAO = new BoardDAO();
boardDAO.insertBoard(vo);
//3. 화면 네비게이션
//글 등록 처리 후 글 목록 페이지로 이동
response.sendRedirect("getBoardList.do");
} else if(path.equals("/updateBoard.do")) {
System.out.println("글 수정 처리");
3.7절 글 수정 기능 구현하기
1) getBoard.jsp
2) DispatcherServlet
- updateBoard_proc.jsp 파일에 있던 자바 코드를 복사하여 DispatcherServlet 클래스의 /updateBoard.do 분기 처리로 이동시킴
- 한글 인코딩 코드는 DispatcherServlet의 doPost() 메소드가 일괄적으로 처리해주기 때문에 각 기능에서 삭제해도 된다.
- 글 수정 작업이 처리된 후에도 반드시 /getBoardList.do를 리다이렉트하여 세션에 저장된 글 목록을 갱신한다.
3.8절 글 삭제 기능 구현하기
1) 글 삭제 관련 링크 수정
- getBoard.jsp 파일에서 글 삭제 관련 링크를 deleteBoard.do로 수정하고 글 목록 링크도 .do로 변경한다.
2) DispatcherServlet
- deleteBoard_proc.jsp 파일에 있던 자바 코드를 복사하여 DispatcherServlet 클래스의 /deleteBoard.do 분기 처리로 이동시킴
- 글 삭제 작업이 처리된 후에도 반드시 /getBoardList.do를 호출하여 세션에 저장된 글 목록을 갱신한다.
} else if(path.equals("/deleteBoard.do")) {
System.out.println("글 삭제 처리");
//1.사용자 입력정보 추출(getBoard.jsp에서 전송된 정보 추출)
String seq = request.getParameter("seq");
//2. DB 연동 처리(BoardDAO의 메소드 호출)
BoardVO vo = new BoardVO();
vo.setSeq(Integer.parseInt(seq));
BoardDAO boardDAO = new BoardDAO();
boardDAO.deleteBoard(vo);
//3. 화면 네비게이션
//글 삭제 처리 후 글 목록 화면으로 이동
response.sendRedirect("getBoardList.do");
} else if(path.equals("/getBoard.do")) {
System.out.println("글 상세 조회 처리");
3.9절 로그아웃 기능 구현하기
1) getBoard.jsp
- 로그아웃 기능을 MVC로 변환하려면 우선 모든 페이지에서 logout_proc.jsp 라는 링크를 모두 logout.do 링크로 수정한다.
<h1>글 목록</h1>
<h3>테스트님 환영합니다...<a href="logout.do">Log-out</a></h3>
2) DispatcherServlet
- logout_proc.jsp 파일에 있던 자바 코드를 복사하여 DispatcherServlet 클래스의 /logout.do 분기 처리로 이동시킴
} else if(path.equals("/logout.do")) {
System.out.println("로그아웃 처리");
//1. 브라우저와 연결된 세션 객체를 강제 종료한다.
HttpSession session = request.getSession();
session.invalidate();
//2. 세션 종료 후, 로그인 화면으로 이동
response.sendRedirect("login.jsp");
} else if(path.equals("/insertBoard.do")) {
System.out.println("글 등록 처리");
3) MVC 구조 확인
- Model 기능의 VO, DAO 클래스는 재사용되었고, DispatcherServlet이라는 Controller 기능의 서블릿이 추가되었다.
- JSP 파일에서는 Controller 기능의 자바 로직이 DispatcherServlet 클래스로 이동되어서 View 기능만 남음
- 따라서 xxx_proc.jsp 파일들은 모두 삭제해도 된다.
- getBoard.jsp와 getBoardList.jsp 파일에서도 Controller 자바 로직은 사라진 상태다.
- Controller 로직 : 사용자 입력 정보 추출, Model을 이용한 DB 연동 처리, 화면 네비게이션에 해당하는 자바 코드
- 결과적으로 MVC 아키텍처를 적용한 결과 JSP 파일에서 Controller 로직에 해당되는 자바 코드는 모두 제거됨
댓글