MVC패턴
MVC패턴은 모델(Model), 뷰(View), 컨트롤러(Controller)로 이루어져있다. 각 부분은 고유의 역할이 있다.
M: Model
어플리게이션의 정보와 데이터를 나타낸다.
DB, 상수, 초기화 값, 변수 등을 말한다.
클라이언트는 실제로 보고 만지지 못하는 데이터값을 칭한다고 보면 된다.
V: View
모델에게 request해서 데이터를 전달받는 곳이다.
클라이언트가 직접적으로 보고 상호작용하는 곳을 지칭하며, 이를 통해 내용을 변경하면 모델에게 request를 하게 된다.
C: Controller
모델과 뷰는 서로 직접적으로 연결되어있지 않기 때문에, 이 둘 사이의 다리 역할을 해주는 존재이다.
모델과 뷰가 변경사항을 서로 주고 받을때 항상 Controller를 거쳐간다고 볼 수 있다.
정리하자면 클라이언트는 Controller를 이용해서 Model을 조작하고, Model은 변경된 내용을 View에 적용시키며, View는 최종 페이지를 클라이언트에게 전달한다.
이제 MVC패턴이 어떤 방식으로 작동하는지 직접 만들어보면서 알아보자.
MVC모델 실습
새로운 프로젝트를 만들자. 이름은 webMVC.
가장 먼저 설정을 해줘야하는 것은 우리가 사용할 라이브러리를 추가하는 것이다.
DB에 접속을 할 것이고, JSTL도 사용할 것이기 때문에 일전에 다운로드 받아놓은 ojdbc11.jar과 jstl.jar, standard.jar을 webMVC/WEB-INF/lib에 복사하자.

그리고 우리가 컨트롤러를 구성할 Java Resources 밑에 패키지를 생성한다. 흔히 아는 url주소의 반대로 작성한다.

현재 가지고 있는 Oracle Database를 활용할 것이기 때문에, 미리 만들어 놓은 DBConn.java를 방금 만든 패키지에 복사시킨 후, 맨 위의 패키지명을 바꿔서 적용시키자.
마지막으로 tomcat의 설치경로에서 ROOT/WEB-INF에서 web.xml을 찾아서 프로젝트의 WEB-INF폴더 아래에 옮기자.
이렇게 기본 설정은 끝났다.
Controller 구성
이전에 서블릿으로 직접 url매핑을 해준 적이 있다. 하지만 이런 방식으로 모든 파일에 일일이 매핑해주기엔, 프로젝트의 규모가 조금만 커져도 매우 비효율적인 작업이 될 수 있다. 그래서 우리는 별도의 파일을 만들어 거기에 텍스트형식으로 매핑을 하고, 이 파일을 InputStream으로 읽어들여 적용시킬 것이다.
WEB-INF폴더 아래에 prop폴더를 생성하고, 그 아래에 urlMapping.properties라는 파일을 생성하자.

이 파일에 url매핑하는 방식은 이러하다.
url매핑주소=Command클래스
이제 HomeController.java 서블릿을 생성하고 init, doGet, doPost를 생성하자.
이제 주소창에 .do가 붙은 모든 주소는 HomeController로 연결시키고 여기서 urlMapping.properties을 읽어서 원하는 View로 이동시킬 것이다.
먼저 모든 .do주소에 대해서 HomeController로 연결되게끔 web.xml을 편집해주자.
<servlet>
<servlet-name>homeController</servlet-name>
<servlet-class>com.multi.home.HomeController</servlet-class>
<init-param>
<param-name>proConfig</param-name>
<param-value>/Users/poby/StudyWeb/JSP/webMVC/src/main/webapp/WEB-INF/prop/urlMapping.properties</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>homeController</servlet-name>
<url-pattern>*.do</url-pattern> <!-- 무조건 HomeController로 연결 -->
</servlet-mapping>
view파일명, 즉 jsp의 경로명을 담아줄 CommandService 인터페이스를 만들자. 이 인터페이스는 앞으로 생성할 모든 Command클래스에 상속시켜줄 것이며, 추상메소드 process()를 오버라이딩해서 원하는 jsp파일의 경로를 리턴해줄 것이다.
package com.multi.home;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
// 모든 Command클래스는 CommandService 인터페이스를 상속받아 생성한다.
public interface CommandService {
// view파일명을 리턴함
public String process(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException;
}
파라미터로 정해준 proConfig에 담긴 urlMapping.properties의 절대경로는 HomeController서블릿에서 사용될 것이다.
HomeController서블릿으로 이동해서 가장 먼저 실행되는 init()메소드에 urlMapping.properties파일을 읽어들여서 url매핑주소를 key로, Command클래스를 value로 가지는 HashMap에 추가할 것이다.
package com.multi.home;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Properties;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Servlet implementation class HomeController
*/
@WebServlet("/*.do")
public class HomeController extends HttpServlet {
private static final long serialVersionUID = 1L;
// url매핑주소와 Command파일명을 Hashmap에 보관
// key는 String, value는 CommandService인터페이스
HashMap<String, CommandService> map = new HashMap<String, CommandService>();
public HomeController() {
super();
}
public void init(ServletConfig config) throws ServletException {
// web.xml에서 매핑주소가 담긴.properties파일의 위치와 파일명을 얻어온다
// /Users/poby/StudyWeb/JSP/webMVC/src/main/webapp/WEB-INF/prop/urlMapping.properties
String propertiesFileName = config.getInitParameter("proConfig");
// 파일에 있는 문자열을 읽어와 properties객체로 변환해준다
Properties propObject = new Properties();
try {
FileInputStream fis = new FileInputStream(propertiesFileName);
propObject.load(fis); // '='을 기준으로 key/value를 나눈다
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
//-------------------------------------------------------------------
// properties객체의 key는 HashMap의 key로 적용하고
// properties객체의 value는 Object(CommandService)로 만들어서 HashMap의 value로 적용
try {
// properties객체에서 모든 key를 구해오기
Enumeration keyList = propObject.propertyNames(); // key목록 구하기
// key에 해당하는 Command클래스를 객체로 만들어 HashMap에 추가하기
while(keyList.hasMoreElements()) { // 객체 존재 유무
String key = (String)keyList.nextElement(); // key값 가져오기 ex)"*.do", "index.do"
String className = propObject.getProperty(key); // value값 가져오기 ex)"com.multi.home.CommandIndex"
// System.out.println(key + "=" + className);
// 패키지와 클래스명이 문자열로 되어있는 경우, Class객체로 생성할 수 있다.
Class commandClass = Class.forName(className);
// CommandService객체를 Class객체에서 얻어오기
CommandService service = (CommandService)commandClass.getDeclaredConstructors()[0].newInstance();
// key와 service를 HashMap객체에 추가
map.put(key, service);
}
}catch(Exception e) {
e.printStackTrace();
}
}
}
HashMap에 담긴 데이터를 표로 그려보자면 이렇다.
| url매핑주소(String) | Command클래스(CommandService) |
| *.do | com.multi.home.CommandIndex |
이제 주소창에 입력되는 모든 .do페이지는 HomeController로 요청할 것이다. 방식에 상관없이 doGet()은 무조건 실행되기 때문에 doGet()을 구성하자.
앞서 init()에서 모든 매핑주소와 그에 해당하는 Command클래스의 경로를 담고 있기 때문에, 요청받은 .do를 key값으로 지정하고, 그에 따른 value값인 Command클래스를 RequestDispatcher로 이동시켜준다.
package com.multi.home;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Properties;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Servlet implementation class HomeController
*/
@WebServlet("/*.do")
public class HomeController extends HttpServlet {
private static final long serialVersionUID = 1L;
// url매핑주소와 Command파일명을 Hashmap에 보관
// key는 String, value는 CommandService인터페이스
HashMap<String, CommandService> map = new HashMap<String, CommandService>();
public HomeController() {
super();
}
public void init(ServletConfig config) throws ServletException {
// web.xml에서 매핑주소가 담긴.properties파일의 위치와 파일명을 얻어온다
// /Users/poby/StudyWeb/JSP/webMVC/src/main/webapp/WEB-INF/prop/urlMapping.properties
String propertiesFileName = config.getInitParameter("proConfig");
// 파일에 있는 문자열을 읽어와 properties객체로 변환해준다
Properties propObject = new Properties();
try {
FileInputStream fis = new FileInputStream(propertiesFileName);
propObject.load(fis); // '='을 기준으로 key/value를 나눈다
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
//-------------------------------------------------------------------
// properties객체의 key는 HashMap의 key로 적용하고
// properties객체의 value는 Object(CommandService)로 만들어서 HashMap의 value로 적용
try {
// properties객체에서 모든 key를 구해오기
Enumeration keyList = propObject.propertyNames(); // key목록 구하기
// key에 해당하는 Command클래스를 객체로 만들어 HashMap에 추가하기
while(keyList.hasMoreElements()) { // 객체 존재 유무
String key = (String)keyList.nextElement(); // key값 가져오기 ex)"*.do", "index.do"
String className = propObject.getProperty(key); // value값 가져오기 ex)"com.multi.home.CommandIndex"
// System.out.println(key + "=" + className);
// 패키지와 클래스명이 문자열로 되어있는 경우, Class객체로 생성할 수 있다.
Class commandClass = Class.forName(className);
// CommandService객체를 Class객체에서 얻어오기
CommandService service = (CommandService)commandClass.getDeclaredConstructors()[0].newInstance();
// key와 service를 HashMap객체에 추가
map.put(key, service);
}
}catch(Exception e) {
e.printStackTrace();
}
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// /*.do, /index.do, /board/boardList.do, /member/login.do
// url주소를 구하여 해당 CommandService 호출
String uri = request.getRequestURI(); // /context/path/filename /webMVC/index.do
String ctxPath = request.getContextPath(); // /webMVC
// path주소(key값)
String commandKey = uri.substring(ctxPath.length()); // /index.do
String viewName = null;
try {
// commandKey의 value값: CommandService 객체
CommandService service = map.get(commandKey);
// 메소드 호출
viewName = service.process(request, response);
}catch(Exception e) {
viewName = "index.jsp"; // CommandService객체가 없을때 홈페이지로 이동
}
// View페이지로 이동
RequestDispatcher dispatcher = request.getRequestDispatcher(viewName);
dispatcher.forward(request, response);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
request.sendRedirect말고 RequestDispatcher를 쓰는 이유는 request와 response를 같이 넘겨주기 위해서이다.
이제 홈페이지로 이동시켜줄 Command클래스인 CommandIndex.java를 com.multi.home패키지 안에 생성해주자.
package com.multi.home;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class CommandIndex implements CommandService {
@Override
public String process(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
return "/index.jsp";
}
}
CommandService인터페이스를 상속했기 때문에 process메소드를 오버라이딩해줘야 한다.
반환값은 view페이지의 경로이다.
작동 순서는 이렇다.
주소창에 localhost:1024/webMVC/index.do 입력 -> web.xml에 의해서 HomeController서블릿 이동 -> urlMapping.properties를 참고해서 /index.do에 해당하는 클래스인 CommandIndex로 이동 -> 리턴된 view페이지 주소로 이동
이제 index.jsp를 만들어서 홈페이지로 이용하자.
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>webMVC/index.jsp</title>
</head>
<body>
<div>
<c:if test="${ logUsername==null || logUsername=='' }">
<a href="<%= request.getContextPath() %>/member/login.do">로그인</a>
</c:if>
<c:if test="${ logUsername!=null && logUsername!='' }">
${ logName }님 반갑습니다!<br>
<a href="<%= request.getContextPath() %>/member/logout.do">로그아웃</a>
</c:if>
<a href="<%= request.getContextPath() %>/board/boardList.do">게시판</a>
</div>
<h1>MVC패턴을 이용한 홈페이지</h1>
<ol>
<li>라이브러리 추가 : ojdbc11.jar, jstl.jar, standard.jar</li>
<li>패키지 생성 : Java Resources에 com.multi.home생성</li>
<li>DB연결 클래스 : DBConn클래스 추가</li>
<li>요청 주소와 해당 클래스 관리하는 properties파일 생성 /WEB-INF/prop/urlMapping.properties</li>
<li>톰캣서버에서 /ROOT/WEB-INF/web.xml 가져오기</li>
</ol>
</body>
</html>
이제 홈페이지에서 로그인을 해볼 것이다.
현재 세션값을 JSTL로 불러와서 로그인 정보를 확인한 후, 로그인이 되어있으면 로그아웃버튼을, 안되어있으면 로그인 버튼이 나타나도록 구성했다. 로그인 버튼을 누르면 /member/login.do로 이동하도록 하이퍼링크를 등록하고, urlMapping.properties에서 매핑을 해주자.
/*.do=com.multi.home.CommandIndex
/member/login.do=com.multi.home.member.CommandLogin
CommandService를 상속받은 CommandLogin클래스를 com.multi.home.member패키지 안에 생성하자.
package com.multi.home.member;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.multi.home.CommandService;
public class CommandLogin implements CommandService {
@Override
public String process(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
return "/member/login.jsp";
}
}
여기를 통해서 login.jsp로 이동시켜준다.
webapp폴더 아래에 member폴더를 만들어서 로그인에 대한 view페이지를 생성하자.
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>/member/login.jsp</title>
</head>
<body>
<h1>로그인form</h1>
<form method="post" action="<%= request.getContextPath() %>/member/loginOk.do">
아이디 : <input type="text" name="username"><br>
비밀번호 : <input type="password" name="password"><br>
<input type="submit" value="로그인">
</form>
</body>
</html>
form을 통해서 아이디와 비밀번호를 /member/loginOk.do로 이동시켜주면서, urlMapping.properties에 매핑해준다.
/*.do=com.multi.home.CommandIndex
/member/login.do=com.multi.home.member.CommandLogin
/member/loginOk.do=com.multi.home.member.CommandLoginOk
CommandLoginOk클래스를 만들고 이 안에서 DB를 접속해서 회원정보 테이블 member_tbl에서 데이터가 일치하는지 확인하자.
package com.multi.home.member;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import com.multi.home.CommandService;
public class CommandLoginOk implements CommandService {
MemberDAO dao = new MemberDAO();
MemberVO vo = new MemberVO();
@Override
public String process(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
vo.setUsername(req.getParameter("username"));
vo.setPassword(req.getParameter("password"));
dao.memberLogin(vo); // 회원정보 조회
// 로그인 성공 시 세션에 아이디/이름 저장
String viewName = null;
if(vo.getName()!=null) {
HttpSession session = req.getSession();
session.setAttribute("logUsername", vo.getUsername());
session.setAttribute("logName", vo.getName());
viewName = "/index.jsp";
}else {
viewName = "/member/login.jsp";
}
return viewName;
}
}
이제 DB에 접속하기위해 DAO와 VO를 구성하자. com.multi.home.member패키지 안에 생성하면 된다.
package com.multi.home.member;
public class MemberVO {
private String username;
private String password;
private String name;
private String tel;
private String email;
private String zipcode;
private String addr;
private String detailAddr;
private String regdate;
@Override
public String toString() { // 내가 원하는 데이터가 있는지 확인할때 실행
return "MemberVO [username=" + username + ", password=" + password + ", name=" + name + ", tel=" + tel
+ ", email=" + email + ", zipcode=" + zipcode + ", addr=" + addr + ", detailAddr=" + detailAddr
+ ", regdate=" + regdate + "]";
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getTel() {
return tel;
}
public void setTel(String tel) {
this.tel = tel;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getZipcode() {
return zipcode;
}
public void setZipcode(String zipcode) {
this.zipcode = zipcode;
}
public String getAddr() {
return addr;
}
public void setAddr(String addr) {
this.addr = addr;
}
public String getDetailAddr() {
return detailAddr;
}
public void setDetailAddr(String detailAddr) {
this.detailAddr = detailAddr;
}
public String getRegdate() {
return regdate;
}
public void setRegdate(String regdate) {
this.regdate = regdate;
}
}
package com.multi.home.member;
import com.multi.home.DBConn;
public class MemberDAO extends DBConn {
public void memberLogin(MemberVO vo) {
try {
dbConn();
sql = "SELECT username, name FROM member_tbl "
+ "WHERE username=? AND password=?";
pstmt = conn.prepareStatement(sql);
pstmt.setString(1, vo.getUsername());
pstmt.setString(2, vo.getPassword());
rs = pstmt.executeQuery();
if(rs.next()) {
vo.setUsername(rs.getString(1));
vo.setName(rs.getString(2));
}
}catch(Exception e){
e.printStackTrace();
}finally {
dbClose();
}
}
}
MemberDAO의 memberLogin메소드를 통해 DB에서 입력된 아이디와 비밀번호가 일치하는지 확인했다.
그리고 해당 레코드의 아이디와 회원명을 VO에 등록시키고, 이를 세션에 등록한 후 다시 index.jsp로 이동시켰다.
이제 index.jsp에서 세션값에 아이디와 회원명이 있다는 것을 감지했기 때문에 로그아웃 버튼만 표시된다.
'Spring' 카테고리의 다른 글
| 12.08.(목) Spring Framework(3): Controller 매핑 (0) | 2022.12.10 |
|---|---|
| 12.08.(목) Spring Framework(2): MVC흐름 (0) | 2022.12.10 |
| 12.08.(목) Spring Framework(1): 설치 및 세팅 (0) | 2022.12.10 |
| 12.07.(수) MVC패턴(3) (0) | 2022.12.08 |
| 12.06.(화) MVC패턴(2) (0) | 2022.12.07 |