모델 2

모델 2 방식으로 게시판을 변경하려면 먼저 EL과 JSTL에 대한 학습이 필요하다.
EL과 JSTL은 뷰에 관한 기술이다.

EL

ELExpression Language은 JSP에서 사용할 수 있는 데이터에 쉽게 접근하기 위해 만들어진 표현식이다.
EL은 JSP 스펙에 포함되어 있다.
EL을 사용하면 자바빈즈의 속성값에 보다 쉽게 접근할 수 있다.
${user.email }은 User 객체의 getEmail() 메소드 호출과 같다.
User 객체가 어느 스코프에 있는지 정확히 몰라도 괜찮다.
${user.email }에서의 User 객체는, pageScope - requestScope - sessionScope - applicationScope 순으로 검색된다.

다음은 EL에서 사용할 수 있는 내재 객체 리스트다.

  • pageScope
  • requestScope
  • sessionScope
  • applicationScope
  • param
  • paramValues
  • header
  • headerValues
  • initParam
  • cookie
  • pageContext

searchWord 파라미터의 값에 접근할 때는 ${param.searchWord }와 같이 내재 객체를 사용한다.
String searchWord = request.getParameter("searchWord");와 <%= searchWord %>과 달리 ${param.searchWord }는 searchWord 파라미터가 넘어오지 않더라도 null이 아니라 ""를 출력한다.

모델 2에서 뷰는 전달받은 데이터를 보여주는 역할만을 수행한다.
데이터를 생산하는 역할은 모델이 담당하도록 해야 한다.
따라서 모델 2의 뷰는 스크립틀릿 사용을 자제해야 한다.

JSTL

태그 라이브러리(Tag Library)는 자바 코드로 바뀌는 태그를 만드는 기술이다.
태그는 디자이너에게 거부감이 덜하므로 이 기술은 디자이너와의 협업에 도움이 된다.
하지만 너무 많이 만들어져 부작용이 부각되었다.
JSTL은 JSP Standard Tag Library의 약자로 태그 라이브러리의 표준을 제공한다.
JSTL은 자바 프로그래머로 하여금 새로운 태그 라이브러리를 만들어야 한 개연성을 줄여준다.
예를 들어, 당신이 게시판 목록을 출력하기 위한 태그 라이브러리를 생각하고 있다면, 먼저 JSTL의 forEach 반복자를 고려해 봐야 한다.
(이 경우 JSTL의 forEach 반복자를 사용하는 게 재사용과 유지보수 측면 모두에서 유리하다) JSTL은 JSP 스펙에 포함되어 있지 않다.
따라서 JSTL 사용하려면 라이브러리를 내려받아야 한다.
다음 주소에서 가장 최신 버전인 JSTL 1.1.2를 내려받는다.
http://archive.apache.org/dist/jakarta/taglibs/standard/binaries/ 압축을 풀면 생기는 lib 폴더에서 jstl.jar와 standard.jar를 웹 애플리케이션의 WEB-INF/lib에 복사한다.
JSTL 중 core가 가장 많이 사용되는데 core를 사용하려면 taglib 지시어를 아래처럼 JSP에 추가한다.

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>

다음은 JSTL를 테스트하기 위한 JSP이다.
도큐먼트 베이스에 만들고 테스트한다.

/jstl.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ page import="java.util.*" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%
int firstPage = 21;
int lastPage = 30;
ArrayList<Integer> a = new ArrayList<Integer>();
a.add(1);a.add(2);a.add(3);
a.add(4);a.add(5);a.add(6);
a.add(7);a.add(8);a.add(9);a.add(10);
%>
<c:set var="a" value="<%=a %>" />
<c:set var="c" value="" />
<c:set var="firstPage" value="<%=firstPage %>" />
<c:set var="lastPage" value="<%=lastPage %>" />
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<title>JSTL core 예제</title>
</head>
<body>
<h1>if 문</h1>

<c:if test="${empty b }">
    <h4>b 리스트는 비었다.</h4>
</c:if>

<c:if test="${not empty c }">
    <h4>c 는 null 이거나 ""가 아니다.</h4>
</c:if>

<c:choose>
    <c:when test="${empty a }">
        <h4>리스트는 비었다.</h4>
    </c:when>
</c:choose>

<h1>if ~ else 문</h1>
<c:choose>
    <c:when test="${empty a }">
        <h4>리스트는 비었다.</h4>
    </c:when>
    <c:otherwise>
        <h4>리스트에 뭔가 있다.</h4>
    </c:otherwise>
</c:choose>

<h1>for 문</h1>
<c:forEach var="i" begin="${firstPage }" end="${lastPage }">
    [${i }]
</c:forEach>

<h1>for 문</h1>
<table>
<c:forEach var="i" items="${a }" varStatus="status">
    <tr>
        <td>${status.index }</td><td>${i }</td>
    </tr>
</c:forEach>
</table>
</body>
</html>

위 예제는 JSTL에서 가장 중요한 core 사용법을 간단하게 소개하고 있다.
이제는 모델 1 게시판을 모델 2 게시판으로 변경할 준비가 되었다.
주요 변경 사항은 다음과 같다.

  • 모든 JSP는 EL과 JSTL를 사용한다.
  • JSP는 결과를 받아서 보여주는 일만 하게 한다.
  • 비즈니스 로직을 담당하는 액션 클래스를 만든다.
  • 확장자가 *.do인 요청은 일단 컨트롤러에게 전달되도록 한다.
  • 페이지 번호를 담는 파라미터 이름 curPage를 page로 바꾼다.

JSP 화면 디자인 수정

EL과 JSTL를 사용하여 코드를 수정함과 동시에, 인클루드 지시어에 사용된 jsp를 제외한 모든 .jsp를 .do로 바꾼다.

header.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<h1 style="float: left;width: 150px;"><a href="/"><img src="/images/ci.gif" alt="java-school" /></a></h1>
<div id="memberMenu" style="float: right;position: relative;top: 7px;">
<c:choose>
    <c:when test="${empty user}">
        <input type="button" value="로그인" onclick="location.href='/users/login.do'" />
        <input type="button" value="회원 가입" onclick="location.href='/users/signUp.do'" />
    </c:when>
    <c:otherwise>
        <input type="button" value="로그아웃" onclick="location.href='/users/logout.do'" />
        <input type="button" value="내 정보 수정" onclick="location.href='/users/editAccount.do'" />
    </c:otherwise>
</c:choose>
</div>
bbs-sub.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<h1>게시판</h1>
<ul>
    <li>
        <ul>
            <li><a href="list.do?boardCd=chat&page=1">자유 게시판</a></li>
            <li><a href="list.do?boardCd=qna&page=1">QnA게시판</a></li>
            <li><a href="list.do?boardCd=data&page=1">자료실</a></li>
        </ul>
    </li>
</ul>

main-menu.jsp 역시 위를 참조하여 수정한다.
위 JSP 파일은 간단하여 프로그램 로직이 필요 없었다.
목록을 보여주는 JSP에는 프로그램 로직이 필요하다.
따라서 JSTL core를 사용하기 위한 태그 라이브러리 지시어를 추가한다.
loginCheck.jsp를 인클루드하는 부분은 없앤다.
logincheck.jsp가 담당하는 로직은 액션 클래스에서 담당하는 게 맞다.

list.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 lang="ko">
<head>
<meta charset="UTF-8" />
<meta name="Keywords" content="게시판 목록" />
<meta name="Description" content="게시판 목록" />
<title>자유 게시판</title>
<link rel="stylesheet" href="/css/screen.css" type="text/css"  />
<script>
function goList(page) {
    var form = document.getElementById("listForm");
    form.page.value = page;
    form.submit();
}
function goView(articleNo) {
    var form = document.getElementById("viewForm");
    form.articleNo.value = articleNo;
    form.submit();
}
function goWrite() {
    var form = document.getElementById("writeForm");
    form.submit();
}
</script>           
</head>
<body>

<div id="wrap">

    <div id="header">
        <%@ include file="../inc/header.jsp" %>
    </div>

    <div id="main-menu">
        <%@ include file="../inc/main-menu.jsp" %>
    </div>

    <div id="container">
        <div id="content">
<!-- 본문 시작 -->
<div id="content-categories">자유 게시판</div>
<!--  게시판 목록 머리말 -->
<table class="bbs-table">
<tr>
    <th style="width: 60px">NO</th>
    <th>TITLE</th>
    <th style="width: 84px;">DATE</th>
    <th style="width: 60px;">HIT</th>
</tr>
<!--  반복 구간 시작 -->
<c:forEach var="article" items="${list }" varStatus="status">
<tr>
    <td style="text-align: center;">${listItemNo - status.index }</td>
    <td>
        <a href="javascript:goView('${article.articeNo }')">${article.title }</a>
        <c:if test="${article.attachFileNum > 0 }">
        <img src="/images/attach.png" alt="첨부 파일" style="vertical-align: middle;" />
        </c:if>
        <c:if test="${article.commentNum > 0 }">
        <span class="bbs-strong">[${article.commentNum }]</span>
        </c:if>
    </td>
    <td style="text-align: center;">${article.regdate }</td>
    <td style="text-align: center;">${article.hit }</td>
</tr>
</c:forEach>
<!--  반복 구간 끝 -->
</table>
<div id="paging">
    <c:if test="${prevPage > 0 }">
        <a href="javascript:goList('${prevPage }')">[이전]</a>
    </c:if>
    <c:forEach var="i" begin="${firstPage }" end="${lastPage }">
        <c:choose>
            <c:when test="${param.page == i }">
            <span class="bbs-strong">${i }</span>
            </c:when>
            <c:otherwise>
            <a href="javascript:goList('${i }')">${i }</a>
            </c:otherwise>
        </c:choose>
    </c:forEach>
    <c:if test="${nextPage > 0 }">
        <a href="javascript:goList('${nextPage }')">[다음]</a>
    </c:if>
</div>
<div id="list-menu">
    <input type="button" value="새 글쓰기" onclick="goWrite()" />
</div>
<div id="search">
    <form action="list.do" method="get" style="margin: 0;padding: 0;">
        <input type="hidden" name="page" value="1" />
        <input type="hidden" name="boardCd" value="${param.boardCd }" />
        <div>
        <input type="text" name="searchWord" size="15" maxlength="30" />
        <input type="submit" value="검색" />
        </div>
    </form>
</div>
<!--  본문 끝 -->
        </div><!-- content 끝 -->
    </div><!--  container 끝 -->
    
    <div id="sidebar">
        <%@ include file="bbs-sub.jsp" %>
    </div>
    
    <div id="extra">
        <%@ include file="../inc/extra.jsp" %>
    </div>

    <div id="footer">
        <%@ include file="../inc/footer.jsp" %>
    </div>

</div>

<div id="form-group" style="display: none">
    <form id="listForm" action="list.do" method="get">
        <input type="hidden" name="boardCd" value="${param.boardCd }" />
        <input type="hidden" name="page" />
        <input type="hidden" name="searchWord" value="${param.searchWord }" />
    </form>
    <form id="viewForm" action="view.do" method="get">
        <input type="hidden" name="articleNo" />
        <input type="hidden" name="boardCd" value="${param.boardCd }" />
        <input type="hidden" name="page" value="${param.page }" />
        <input type="hidden" name="searchWord" value="${param.searchWord }" />
    </form>
    <form id="writeForm" action="write_form.do" method="get">
        <input type="hidden" name="boardCd" value="${param.boardCd }" />
        <input type="hidden" name="page" value="${param.page }" />
        <input type="hidden" name="searchWord" value="${param.searchWord }" />
    </form>
</div>

</body>
</html>

list.jsp 수정을 완료했다.
액션은 다음 데이터를 만들어서 list.jsp에 전달해야 한다.

  • boardNm: 게시판 이름
  • list: 게시글이 담긴 ArrayList
  • listItemNo: 목록 아이템에 붙는 번호
  • prevPage: [이전] 링크를 위한 페이지 번호
  • firstPage: 현재 블록에 해당하는 첫 번째 페이지 번호
  • lastPage: 현재 블록에 해당하는 마지막 페이지 번호
  • nextPage: [다음] 링크를 위한 페이지 번호
view.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 lang="ko">
<head>
<meta charset="UTF-8" />
<meta name="Keywords" content="게시판 상세보기" />
<meta name="Description" content="게시판 상세보기" />
<title>${boardNm }</title>
<link rel="stylesheet" href="/css/screen.css" type="text/css" />
<script>
function modifyCommentToggle(articleNo) {
    var p_id = "comment" + articleNo;
    var form_id = "modifyCommentForm" + articleNo;
    var p = document.getElementById(p_id);
    var form = document.getElementById(form_id);
    
    var p_display;
    var form_display;
    
    if (p.style.display) {
        p_display = '';
        form_display = 'none';
    } else {
        p_display = 'none';
        form_display = '';
    }
    
    p.style.display = p_display;
    form.style.display = form_display;
}
function goList(page) {
    var form = document.getElementById("listForm");
    form.page.value = page;
    form.submit();
}
function goView(articleNo) {
    var form = document.getElementById("viewForm");
    form.articleNo.value = articleNo;
    form.submit();
}
function goWrite() {
    var form = document.getElementById("writeForm");
    form.submit();
}
function goModify() {
    var form = document.getElementById("modifyForm");
    form.submit();
}
function goDelete() {
    var check = confirm("정말로 삭제하시겠습니까?");
    if (check) {
        var form = document.getElementById("delForm");
        form.submit();
    }
}
function deleteAttachFile(attachFileNo) {
    var check = confirm("첨부 파일을 정말로 삭제하시겠습니까?");
    if (check) {
        var form = document.getElementById("deleteAttachFileForm");
        form.attachFileNo.value = attachFileNo;
        form.submit();
    }
}
function deleteComment(commentNo) {
    var check = confirm("댓글을 정말로 삭제하시겠습니까?");
    if (check) {
        var form = document.getElementById("deleteCommentForm");
        form.commentNo.value = commentNo;
        form.submit();
    }
}
</script>           
</head>
<body>

<div id="wrap">

    <div id="header">
        <%@ include file="../inc/header.jsp" %>
    </div>

    <div id="main-menu">
        <%@ include file="../inc/main-menu.jsp" %>
    </div>

    <div id="container">
        <div id="content">
<!-- 본문 시작 -->
<div id="content-categories">${boardNm }</div>
<div class="view-menu" style="margin-top: 15px;margin-bottom: 5px;">
    <c:if test="${user.email == email }">
    <div class="fl">
        <input type="button" value="수정" onclick="goModify()" />
        <input type="button" value="삭제" onclick="goDelete()"/>
    </div>
    </c:if>
    <div class="fr">
        <c:if test="${nextArticle != null }">
        <input type="button" value="다음 글" onclick="goView('${nextArticle.articleNo }')" />
        </c:if>
        <c:if test="${prevArticle != null }">
        <input type="button" value="이전 글" onclick="goView('${prevArticle.articleNo }')" />
        </c:if>
        <input type="button" value="목록" onclick="goList('${param.page }')" />
        <input type="button" value="새 글쓰기" onclick="goWrite()" />
    </div>
</div>
<table class="bbs-table">
<tr>
    <th style="width: 47px;text-align: left;vertical-align: top;font-size: 1em;">TITLE</th>
    <th style="text-align: left;color: #555;font-size: 1em;">${title }</th>
</tr> 
</table>
<div id="detail">
    <div id="date-writer-hit">edited ${regdate } by ${name } hit ${hit }</div>
    <div id="article-content">${content }</div>
    <div id="file-list" style="text-align: right">
    <c:forEach var="file" items="${attachFileList }" varStatus="status">
        <div id="attach-file">
            <a href="${uploadPath }${file.filename }">${file.filename }</a>
            <c:if test="${user.email == file.email }">
            <a href="javascript:deleteAttachFile('${file.attachFileNo }')">x</a>
            </c:if>
         </div>
    </c:forEach>
    </p>
</div>
<form id="addCommentForm" action="addComment.do" method="post">
    <input type="hidden" name="articleNo" value="${param.articleNo }"/>
    <input type="hidden" name="boardCd" value="${param.boardCd }" />
    <input type="hidden" name="page" value="${param.page }" />
    <input type="hidden" name="searchWord" value="${param.searchWord }" />       
    <div id="addComment">
        <textarea name="memo" rows="7" cols="50"></textarea>
    </div>
    <div style="text-align: right;">
        <input type="submit" value="댓글 남기기" />
    </div>
</form>
<!--  댓글 반복 시작 -->
<c:forEach var="comment" items="${commentList }" varStatus="status">
<div class="comments">
    <span class="writer">${comment.name }</span>
    <span class="date">${comment.regdate }</span>
    <c:if test="${user.email == comment.email }">
    <span class="modify-del">
        <a href="javascript:modifyCommentToggle('${comment.commentNo }')">수정</a>
         | <a href="javascript:deleteComment('${comment.commentNo }')">삭제</a>
    </span>
    </c:if>
    <p id="comment${comment.commentNo }">${comment.memo }</p>
    <form id="modifyCommentForm${comment.commentNo }" class="comment-form" action="updateComment.do" method="post" style="display: none;">
        <input type="hidden" name="commentNo" value="${comment.commentNo }" />
        <input type="hidden" name="boardCd" value="${param.boardCd }" />
        <input type="hidden" name="articleNo" value="${param.articleNo }" />
        <input type="hidden" name="page" value="${param.page }" />
        <input type="hidden" name="searchWord" value="${param.searchWord }" />
	    <div class="fr">
	            <a href="javascript:document.forms.modifyCommentForm${comment.commentNo }.submit()">수정하기</a>
	            | <a href="javascript:modifyCommentToggle('${comment.commentNo }')">취소</a>
	    </div>
	    <div>
	        <textarea class="comment-textarea" name="memo" rows="7" cols="50">${comment.memo }</textarea>
	    </div>
    </form>
</div>
</c:forEach>
<!--  댓글 반복 끝 -->
<div id="next-prev">
    <c:if test="${nextArticle != null }">
    <p>다음 글 : <a href="javascript:goView('${nextArticle.articleNo }')">${nextArticle.title }</a></p>
    </c:if>
    <c:if test="${prevArticle != null }">
    <p>이전 글 : <a href="javascript:goView('${prevArticle.articleNo }')">${prevArticle.title }</a></p>
    </c:if>
</div>
<div class="view-menu">
    <c:if test="${user.email == email }">
    <div class="fl">
        <input type="button" value="수정" onclick="goModify()" />
        <input type="button" value="삭제" onclick="goDelete()"/>
    </div>
    </c:if>
    <div class="fr">
        <c:if test="${nextArticle != null }">
        <input type="button" value="다음 글" onclick="goView('${nextArticle.articleNo }')" />
        </c:if>
        <c:if test="${prevArticle != null }">
        <input type="button" value="이전 글" onclick="goView('${prevArticle.articleNo }')" />
        </c:if>
        <input type="button" value="목록" onclick="goList('${param.page }')" />
        <input type="button" value="새 글쓰기" onclick="goWrite()" />
    </div>
</div>
<!-- 목록 -->
<table id="list-table" class="bbs-table">
<tr>
    <th style="width: 60px">NO</th>
    <th>TITLE</th>
    <th style="width: 84px;">DATE</th>
    <th style="width: 60px;">HIT</th>
</tr>
<c:forEach var="article" items="${list }" varStatus="status">
<tr>
    <td style="text-align: center;">
    <c:choose>
        <c:when test="${param.articleNo == article.articleNo }">
        <img src="../images/arrow.gif" alt="현재 글" />
        </c:when>
        <c:otherwise>
        ${listItemNo - status.index }
        </c:otherwise>
    </c:choose>
    </td>
    <td>
        <a href="javascript:goView('${article.articleNo }')">${article.title }</a>
        <c:if test="${article.attachFileNum > 0 }">
        <img src="/images/attach.png" alt="첨부 파일" style="vertical-align: middle;" />
        </c:if>
        <c:if test="${article.commentNum > 0 }">
        <span class="bbs-strong">[${article.commentNum }]</span>
        </c:if>
    </td>
    <td style="text-align: center;">${article.regdate }</td>
    <td style="text-align: center;">${article.hit }</td>
</tr>
</c:forEach>
</table>
<div id="paging">
    <c:if test="${prevPage > 0 }">
        <a href="javascript:goList('${prevPage }')">[이전]</a>
    </c:if>
    <c:forEach var="i" begin="${firstPage }" end="${lastPage }">
        <c:choose>
            <c:when test="${param.page == i }">
                <span class="bbs-strong">${i }</span>
            </c:when>
            <c:otherwise>
                <a href="javascript:goList('${i }')">${i }</a>
            </c:otherwise>
        </c:choose>  
    </c:forEach>
    <c:if test="${nextPage > 0 }">
        <a href="javascript:goList('${nextPage }')">[다음]</a>
    </c:if>
</div>
<div id="list-menu">
    <input type="button" value="새 글쓰기" onclick="goWrite()" />
</div>
<div id="search">
    <form action="list.do" method="get">
        <input type="hidden" name="page" value="1" />
        <input type="hidden" name="boardCd" value="${param.boardCd }" />
        <div>
            <input type="text" name="searchWord" size="15" maxlength="30" />
            <input type="submit" value="검색" />
        </div>
    </form>
</div>
<!--  본문 끝 -->
        </div><!-- content 끝 -->
    </div><!--  container 끝 -->
    
    <div id="sidebar">
        <%@ include file="bbs-sub.jsp" %>
    </div>
    
    <div id="extra">
        <%@ include file="../inc/extra.jsp" %>
    </div>

    <div id="footer">
        <%@ include file="../inc/footer.jsp" %>
    </div>

</div>

<div id="form-group" style="display: none">
    <form id="listForm" action="list.do" method="get">
        <input type="hidden" name="boardCd" value="${param.boardCd }" />
        <input type="hidden" name="page" />
        <input type="hidden" name="searchWord" value="${param.searchWord }" />
    </form>
    <form id="viewForm" action="view.do" method="get">
        <input type="hidden" name="articleNo" />
        <input type="hidden" name="boardCd" value="${param.boardCd }" />
        <input type="hidden" name="page" value="${param.page }" />
        <input type="hidden" name="searchWord" value="${param.searchWord }" />
    </form>
    <form id="writeForm" action="write_form.do" method="get">
        <input type="hidden" name="articleNo" value="${param.articleNo }" />
        <input type="hidden" name="boardCd" value="${param.boardCd }" />
        <input type="hidden" name="page" value="${param.page }" />
        <input type="hidden" name="searchWord" value="${param.searchWord }" />
    </form>
    <form id="modifyForm" action="modify_form.do" method="get">
        <input type="hidden" name="articleNo" value="${param.articleNo }" />
        <input type="hidden" name="boardCd" value="${param.boardCd }" />
        <input type="hidden" name="page" value="${param.page }" />
        <input type="hidden" name="searchWord" value="${param.searchWord }" />
    </form>
    <form id="delForm" action="del.do" method="post">
        <input type="hidden" name="articleNo" value="${param.articleNo }" />
        <input type="hidden" name="boardCd" value="${param.boardCd }" />
        <input type="hidden" name="page" value="${param.page }" />
        <input type="hidden" name="searchWord" value="${param.searchWord }" />
    </form>
    <form id="deleteCommentForm" action="deleteComment.do" method="post">
        <input type="hidden" name="commentNo" />
        <input type="hidden" name="articleNo" value="${param.articleNo }" />
        <input type="hidden" name="boardCd" value="${param.boardCd }" />
        <input type="hidden" name="page" value="${param.page }" />
        <input type="hidden" name="searchWord" value="${param.searchWord }" />
    </form>   
    <form id="deleteAttachFileForm" action="deleteAttachFile.do" method="post">
        <input type="hidden" name="attachFileNo" />
        <input type="hidden" name="articleNo" value="${param.articleNo }" />
        <input type="hidden" name="boardCd" value="${param.boardCd }" />
        <input type="hidden" name="page" value="${param.page }" />
        <input type="hidden" name="searchWord" value="${param.searchWord }" />
    </form>       
</div>

</body>
</html>

view.jsp로 액션이 만들어서 전달해야 하는 데이터는 다음과 같다.

  • boardNm: 게시판 이름
  • title: 상세보기 대상 게시글의 제목
  • regdate: 상세보기 대상 게시글의 등록일
  • name: 상세보기 대상 게시글의 소유자 이름
  • hit: 상세보기 대상 게시글의 조회 수
  • content: 상세보기 대상 게시글의 내용
  • attachFileList: 상세보기 대상 게시글의 첨부 파일 리스트
  • commentList: 상세보기 대상 게시글의 댓글 리스트
  • nextArticle: 다음 게시글 객체
  • prevArticle: 이전 게시글 객체
  • -- 이하는 목록과 같음 --
  • list: 목록
  • listItemNo: 목록 아이템에 붙일 번호
  • prevPage: [이전] 링크를 위한 페이지 번호
  • firstPage: 현재 블록에 속한 첫 번째 페이지 번호
  • lastPage: 현재 블록에 속한 마지막 페이지 번호
  • nextPage: [다음] 링크를 위한 페이지 번호

모델 1의 write_form.jsp의 내용과 파일명을 아래와 같이 수정한다.

write.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 lang="ko">
<head>
<meta charset="UTF-8" />
<meta name="Keywords" content="글쓰기 화면" />
<meta name="Description" content="글쓰기 화면" />
<title>${boardNm }</title>
<link rel="stylesheet" href="/css/screen.css" type="text/css" />
<script>
function goList() {
    var form = document.getElementById("listForm");
    form.submit();
}
function goView() {
    var form = document.getElementById("viewForm");
    form.submit();
}
</script>           
</head>
<body>

<div id="wrap">

    <div id="header">
        <%@ include file="../inc/header.jsp" %>
    </div>

    <div id="main-menu">
        <%@ include file="../inc/main-menu.jsp" %>
    </div>

    <div id="container">
        <div id="content">
<!-- 본문 시작 -->
<div id="content-categories">${boardNm }</div>
<h3>새 글쓰기</h3>
<form id="writeForm" action="write.do" method="post" enctype="multipart/form-data">
<input type="hidden" name="boardCd" value="${param.boardCd }" />
<table id="write-form" class="bbs-table">
<tr>
    <td>제목</td>
    <td><input type="text" name="title" style="width: 90%;" /></td>
</tr>
<tr>
    <td colspan="2">
        <textarea name="content" rows="17" cols="50"></textarea>
    </td>
</tr>
<tr>
    <td>첨부 파일</td>
    <td><input type="file" name="attachFile" /></td>
</tr>
</table>
<div style="text-align: center;padding-bottom: 15px;">
    <input type="submit" value="전송" />
    <input type="button" value="목록" onclick="goList()" />
    <c:if test="${not empty param.articleNo }">
    <input type="button" value="상세보기" onclick="goView()" />
    </c:if>
</div>
</form>
<!-- 본문 끝 -->

        </div>
    </div>
    
    <div id="sidebar">
        <%@ include file="bbs-sub.jsp" %>
    </div>
    
    <div id="extra">
        <%@ include file="../inc/extra.jsp" %>
    </div>

    <div id="footer">
        <%@ include file="../inc/footer.jsp" %>   
    </div>

</div>

<div id="form-group" style="display: none">
    <form id="viewForm" action="view.do" method="get">
        <input type="hidden" name="articleNo" value="${param.articleNo }" />
        <input type="hidden" name="boardCd" value="${param.boardCd }" />
        <input type="hidden" name="page" value="${param.page }" />
        <input type="hidden" name="searchWord" value="${param.searchWord }" />
    </form>
    <form id="listForm" action="list.do" method="get">
        <input type="hidden" name="boardCd" value="${param.boardCd }" />
        <input type="hidden" name="page" value="${param.page }" />
        <input type="hidden" name="searchWord" value="${param.searchWord }" />
    </form>   
</div>

</body>
</html>

write.jsp에 액션이 만들어 전달할 데이터는 boardNm(게시판 이름)이다.

모델 1의 modify_form.jsp를 내용과 파일명을 아래와 같이 수정한다.

modify.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8" %>
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<meta name="Keywords" content="게시판 수정하기 폼" />
<meta name="Description" content="게시판 수정하기 폼" />
<title>${boardNm }</title>
<link rel="stylesheet" href="/css/screen.css" type="text/css"  />
<script>
function check() {
    //var form = document.getElementById("modifyForm");
    //유효성 검사로직 추가
    return true;
}
function goView() {
    var form = document.getElementById("viewForm");
    form.submit();
}
</script>           
</head>
<body>

<div id="wrap">

    <div id="header">
        <%@ include file="../inc/header.jsp" %>
    </div>

    <div id="main-menu">
        <%@ include file="../inc/main-menu.jsp" %>
    </div>

    <div id="container">
        <div id="content">
<!-- 본문 시작 -->
<div id="content-categories">${boardNm }</div>
<h3>수정</h3>
<form id="modifyForm" action="modify.do" method="post" enctype="multipart/form-data" onsubmit="return check();">
<input type="hidden" name="articleNo" value="${param.articleNo }" />
<input type="hidden" name="boardCd" value="${param.boardCd }" />
<input type="hidden" name="page" value="${param.page }" />
<input type="hidden" name="searchWord" value="${param.searchWord }" />
<table id="write-form" class="bbs-table">
<tr>
    <td>제목</td>
    <td><input type="text" name="title" style="width: 90%;" value="${title }" /></td>
</tr>
<tr>
    <td colspan="2">
        <textarea name="content" rows="17" cols="50">${content }</textarea>
    </td>
</tr>
<tr>
    <td>첨부 파일</td>
    <td><input type="file" name="attachFile" /></td>
</tr>
</table>
<div style="text-align: center;padding-bottom: 15px;">
    <input type="submit" value="전송" />
    <input type="button" value="상세보기" onclick="goView()" />
</div>
</form>
<!--  본문 끝 -->
        </div><!-- content 끝 -->
    </div><!--  container 끝 -->
    
    <div id="sidebar">
        <%@ include file="bbs-sub.jsp" %>
    </div>
    
    <div id="extra">
        <%@ include file="../inc/extra.jsp" %>
    </div>

    <div id="footer">
        <%@ include file="../inc/footer.jsp" %>
    </div>

</div>

<div id="form-group" style="display: none">
    <form id="viewForm" action="view.do" method="get">
        <input type="hidden" name="articleNo" value="${param.articleNo }" />
        <input type="hidden" name="boardCd" value="${param.boardCd }" />
        <input type="hidden" name="page" value="${param.page }" />
        <input type="hidden" name="searchWord" value="${param.searchWord }" />
    </form>
</div>

</body>
</html>

modify.jsp에 액션이 만들어서 전달할 데이터는 다음과 같다.

  • boardNm: 게시판 이름
  • title: 제목
  • content: 내용

다음으로 회원 모듈에서의 화면 디자인을 수정한다.
회원 모듈에서의 디자인은 전달해야 할 데이터가 없으므로 수정이 쉽다.
인클루드 지시어에 쓰인 jsp를 제외한 모든 .jsp를 .do로 바꾼다.

user-sub.jsp
<h1><회원</h1>
<ul>
  <c:choose>
      <c:when test="${empty check}">
        <li><a href="/users/login">로그인</a></li>
        <li><a href="/users/signUp">회원 가입</a></li>
        <li><a href="#">ID 찾기</a></li>
        <li><a href="#">비밀번호 찾기</a></li>
      </c:when>
      <c:otherwise>
        <li><a href="/users/editAccount">내 정보 수정</a></li>
        <li><a href="/users/changePasswd">비빌번호 변경</a></li>
        <li><a href="/users/bye">탈퇴</a></li>
      </c:otherwise>
  </c:choose>
</ul>
login.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 lang="ko">
<head>
<meta charset="UTF-8" />
<meta name="Keywords" content="로그인" />
<meta name="Description" content="로그인" />
<title>로그인</title>
<link rel="stylesheet" href="/css/screen.css" type="text/css" />
<script type="text/javascript">
function check() {
    //var form = document.getElementById("loginForm");
    //TODO 유효성 검사
    return true;
}
</script>           
</head>
<body>

<div id="wrap">

    <div id="header">

    </div>

    <div id="main-menu">
        <%@ include file="../inc/main-menu.jsp" %>
    </div>

    <div id="container">
        <div id="content">
<!-- 본문 시작 -->
<div id="content-categories">회원</div>
<h2>로그인</h2>
<c:if test="${not empty param.msg }">
<p style="color: red;">로그인에 실패했습니다.</p>
</c:if>      

<form id="loginForm" action="login.do" method="post" onsubmit="return check()">
<p style="margin: 0; padding: 0;">
<input type="hidden" name="url" value="${param.url }" />
</p>
<table>
<tr>
    <td style="width: 200px;">Email</td>
    <td style="width: 390px"><input type="text" name="email" style="width: 99%;" /></td>
</tr>
<tr>
    <td>비밀번호(Password)</td>
    <td><input type="password" name="passwd" style="width: 99%;" /></td>
</tr>
</table>
<div style="text-align: center;padding: 15px 0;">
    <input type="submit" value="전송" />
    <input type="button" value="회원 가입" onclick="location.href='signUp.do'" />
</div>
</form>
<!--  본문 끝 -->
        </div><!-- content 끝 -->
    </div><!--  container 끝 -->
    
    <div id="sidebar">
        <%@ include file="user-sub.jsp" %>
    </div>
    
    <div id="extra">
        <%@ include file="../inc/extra.jsp" %>
    </div>

    <div id="footer">
        <%@ include file="../inc/footer.jsp" %>
    </div>

</div>

</body>
</html>
signUp.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<meta name="Keywords" content="회원 가입" />
<meta name="Description" content="회원 가입" />
<title>회원 가입</title>
<link rel="stylesheet" href="/css/screen.css" type="text/css" />
<script type="text/javascript">
function check() {
    //var form = document.getElementById("signUpForm");
    //TODO 유효성 검사
    return true;
}
</script>           
</head>
<body>

<div id="wrap">

    <div id="header">
        <%@ include file="../inc/header.jsp" %>
    </div>

    <div id="main-menu">
        <%@ include file="../inc/main-menu.jsp" %>
    </div>

    <div id="container">
        <div id="content">
<!-- 본문 시작 -->
<h2>회원 가입</h2>
<form id="signUpForm" action="signUp.do" method="post" onsubmit="return check()">
<table>
<tr>
    <td style="width: 200px;">이름(Full Name)</td>
    <td style="width: 390px"><input type="text" name="name" style="width: 99%;" /></td>
</tr>
<tr>
    <td>비밀번호(Password)</td>
    <td><input type="password" name="passwd" style="width: 99%;" /></td>
</tr>
<tr>
    <td colspan="2" style="text-align: center;font-weight: bold;">
    Email이 아이디로 쓰이므로 비밀번호는 Email 계정 비밀번호와 같게 하지 마세요.
    </td>
</tr>
<tr>
    <td>비밀번호 확인(Confirm)</td>
    <td><input type="password" name="confirm" style="width: 99%;" /></td>
</tr>
<tr>
    <td>Email</td>
    <td><input type="text" name="email" style="width: 99%;" /></td>
</tr>
<tr>
    <td>이동전화(Mobile)</td>
    <td><input type="text" name="mobile" style="width: 99%;" /></td>
</tr>
</table>
<div style="text-align: center;padding-bottom: 15px;">
    <input type="submit" value="전송" />
</div>
</form>
<!--  본문 끝 -->
        </div><!-- content 끝 -->
    </div><!--  container 끝 -->
    
    <div id="sidebar">
        <%@ include file="user-sub.jsp" %>
    </div>
    
    <div id="extra">
        <%@ include file="../inc/extra.jsp" %>
    </div>

    <div id="footer">
        <%@ include file="../inc/footer.jsp" %>
    </div>

</div>

</body>
</html>
welcome.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<meta name="Keywords" content="회원 가입 환영" />
<meta name="Description" content="회원 가입 환영" />
<title>회원 가입이 완료되었습니다.</title>
<link rel="stylesheet" href="/css/screen.css" type="text/css"  />
</head>
<body>

<div id="wrap">

    <div id="header">
        <%@ include file="../inc/header.jsp" %>
    </div>

    <div id="main-menu">
        <%@ include file="../inc/main-menu.jsp" %>
    </div>

    <div id="container">
        <div id="content">
<!-- 본문 시작 -->
<div id="content-categories">회원</div>
<h2>환영합니다.</h2>
회원 가입시 입력한 Email이 아이디로 사용됩니다.<br />
<input type="button" value="로그인" onclick="javascript:location.href='login.do'" />
<!--  본문 끝 -->
        </div><!-- content 끝 -->
    </div><!--  container 끝 -->
    
    <div id="sidebar">
        <h1>Welcome</h1>
    </div>
    
    <div id="extra">
        <%@ include file="../inc/extra.jsp" %>
    </div>

    <div id="footer">
        <%@ include file="../inc/footer.jsp" %>
    </div>

</div>

</body>
</html>
editAccount.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<meta name="Keywords" content="내 정보 수정" />
<meta name="Description" content="내 정보 수정" />
<title>내 정보 수정</title>
<link rel="stylesheet" href="/css/screen.css" type="text/css"  />
<script type="text/javascript">
function check() {
    //var form = document.getElementById("editAccountForm");
    //TODO 유효성 검사
    return true;
}
</script>
</head>
<body>

<div id="wrap">

    <div id="header">
        <%@ include file="../inc/header.jsp" %>
    </div>

    <div id="main-menu">
        <%@ include file="../inc/main-menu.jsp" %>
    </div>

    <div id="container">
        <div id="content">
<!-- 본문 시작 -->
<h2>내 정보 수정</h2>
<p>
비밀번호외의 자신의 계정 정보를 수정할 수 있습니다.<br />
비밀번호는 <a href="changePasswd.do">비밀번호 변경</a>메뉴를 이용하세요.<br />
</p>
<form id="editAccountForm" action="editAccount.do" method="post" onsubmit="return check()">
<table>
<tr>
    <td>이름(Full Name)</td>
    <td><input type="text" name="name" value="${user.name }" /></td>
</tr>
<tr>
    <td>이동전화(Mobile)</td>
    <td><input type="text" name="mobile" value="${user.mobile }" /></td>
</tr>
<tr>
    <td>현재 비밀번호(Password)</td>
    <td><input type="password" name="passwd" /></td>
</tr>
<tr>
    <td colspan="2"><input type="submit" value="전송" /></td>
</tr>
</table>
</form>
<!--  본문 끝 -->
        </div><!-- content 끝 -->
    </div><!--  container 끝 -->
    
    <div id="sidebar">
        <%@ include file="user-sub.jsp" %>
    </div>
    
    <div id="extra">
        <%@ include file="../inc/extra.jsp" %>
    </div>

    <div id="footer">
        <%@ include file="../inc/footer.jsp" %>
    </div>

</div>

</body>
</html>
changePasswd.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 lang="ko">
<head>
<meta charset="UTF-8" />
<meta name="Keywords" content="비밀번호 변경" />
<meta name="Description" content="비빌번호 변경" />
<title>비밀번호 변경</title>
<link rel="stylesheet" href="/css/screen.css" type="text/css"  />
<script type="text/javascript">
function check() {
    var form = document.getElementById("changePasswordForm");
    if (form.newPasswd.value == form.confirm.value) {
        return true;    
    } else {
        alert("[변경 비밀번호]와 [변경 비밀번호 확인]값이 같지 않습니다.");
        return false;
    }
}
</script>           
</head>
<body>

<div id="wrap">

    <div id="header">
        <%@ include file="../inc/header.jsp" %>
    </div>

    <div id="main-menu">
        <%@ include file="../inc/main-menu.jsp" %>
    </div>

    <div id="container">
        <div id="content">
<!-- 본문 시작 -->
<h2>비밀번호 변경</h2>
${user.name }<br />
${user.mobile }<br />
<form id="changePasswordForm" action="changePasswd.do" method="post" onsubmit="return check()">
<table>
<tr>
    <td>현재 비밀번호</td>
    <td><input type="password" name="currentPasswd" /></td>   
</tr>
<tr>
    <td>변경 비밀번호</td>
    <td><input type="password" name="newPasswd" /></td>
</tr>
<tr>
    <td>변경 비밀번호 확인</td>
    <td><input type="password" name="confirm" /></td>
</tr>
<tr>
    <td colspan="2"><input type="submit" value="전송" /></td>
</tr>
</table>
</form>
<!--  본문 끝 -->
        </div><!-- content 끝 -->
    </div><!--  container 끝 -->

    <div id="sidebar">
        <%@ include file="user-sub.jsp" %>
    </div>

    <div id="extra">
        <%@ include file="../inc/extra.jsp" %>
    </div>

    <div id="footer">
        <%@ include file="../inc/footer.jsp" %>
    </div>

</div>

</body>
</html>
changePasswd_confirm.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<meta name="Keywords" content="비밀번호 변경 확인" />
<meta name="Description" content="비밀번호 변경 확인" />
<title>비밀번호 변경 확인</title>
<link rel="stylesheet" href="/css/screen.css" type="text/css"  />
</head>
<body>

<div id="wrap">

    <div id="header">
        <%@ include file="../inc/header.jsp" %>
    </div>

    <div id="main-menu">
        <%@ include file="../inc/main-menu.jsp" %>
    </div>

    <div id="container">
        <div id="content">
<!-- 본문 시작 -->
<h2>비밀번호가 변경되었습니다.</h2>
변경된 비밀번호로 다시 로그인하실 수 있습니다.<br />
<input type="button" value="로그인" onclick="javascript:location.href='login.do'" />
<!--  본문 끝 -->
        </div><!-- content 끝 -->
    </div><!--  container 끝 -->
    
    <div id="sidebar">
        <%@ include file="user-sub.jsp" %>
    </div>
    
    <div id="extra">
        <%@ include file="../inc/extra.jsp" %>
    </div>

    <div id="footer">
        <%@ include file="../inc/footer.jsp" %>
    </div>

</div>

</body>
</html>
bye.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<meta name="Keywords" content="탈퇴" />
<meta name="Description" content="탈퇴" />
<title>탈퇴</title>
<link rel="stylesheet" href="/css/screen.css" type="text/css" />
<script type="text/javascript">
function check() {
    //var form = document.getElementById("byeForm");
    //유효성 검사
    return true;
}
</script>           
</head>
<body>

<div id="wrap">

    <div id="header">
        <%@ include file="../inc/header.jsp" %>
    </div>

    <div id="main-menu">
        <%@ include file="../inc/main-menu.jsp" %>
    </div>

    <div id="container">
        <div id="content">

<!-- 본문 시작 -->
<div id="content-categories">회원</div>
<h2>탈퇴</h2>
<form id="byeForm" action="bye.do" method="post" onsubmit="return check()">
<table>
<tr>
    <td>이메일</td>
    <td><input type="text" name="email" /></td>   
</tr>
<tr>
    <td>비밀번호</td>
    <td><input type="password" name="passwd" /></td>  
</tr>
<tr>
    <td colspan="2"><input type="submit" value="전송" /></td>
</tr>
</table>
</form>
<!--  본문 끝 -->
        </div><!-- content 끝 -->
    </div><!--  container 끝 -->

    <div id="sidebar">
        <%@ include file="user-sub.jsp" %>
    </div>

    <div id="extra">
        <%@ include file="../inc/extra.jsp" %>
    </div>

    <div id="footer">
        <%@ include file="../inc/footer.jsp" %>
    </div>

</div>

</body>
</html>

에러 페이지 수정

하나의 에러 페이지로 모든 에러를 처리하기로 했으므로 굳이 JSTL로 수정할 필요가 없다.
기존의 스크립틀릿 코드를 그대로 사용한다.
인클루드 지시어에 쓰인 jsp를 제외한 모든 .jsp를 .do로 수정한다.

/error.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ page import="net.java_school.user.User" %>
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<meta name="Keywords" content="Error" />
<meta name="Description" content="Error" />
<title>Error</title>
<link rel="stylesheet" href="/css/screen.css" type="text/css" />
</head>
<body>
<%
//Analyze the servlet exception
Throwable throwable = (Throwable) request.getAttribute("javax.servlet.error.exception");
Integer statusCode = (Integer) request.getAttribute("javax.servlet.error.status_code");
String servletName = (String) request.getAttribute("javax.servlet.error.servlet_name");

if (servletName == null) {
    servletName = "Unknown";
}

String requestUri = (String) request.getAttribute("javax.servlet.error.request_uri");

if (requestUri == null) {
    requestUri = "Unknown";
}
%>
<div id="wrap">

    <div id="header">
        <h1 style="float: left; width: 150px;"><a href="/"><img src="/images/ci.gif" alt="java-school" /></a></h1>
        <div id="memberMenu" style="float: right; position: relative; top: 7px;">
<%
User loginUser = (User) session.getAttribute("user");
if (loginUser == null) {
%>
    <input type="button" value="로그인" onclick="location.href='/users/login.do'" />
    <input type="button" value="회원 가입" onclick="location.href='/users/signUp.do'" />
<%
} else {
%>   
    <input type="button" value="로그아웃" onclick="location.href='/users/logout.do'" />
    <input type="button" value="내 정보 수정" onclick="location.href='/users/editAccount.do'" />
<%
}
%>
        </div>
    </div>

    <div id="main-menu">
        <ul id="nav">
            <li><a href="/java/">Java</a></li>
            <li><a href="/jdbc/">JDBC</a></li>
            <li><a href="/jsp/">JSP</a></li>
            <li><a href="/css-layout/">CSS Layout</a></li>
            <li><a href="/jsp-pjt/">JSP Project</a></li>
            <li><a href="/spring/">Spring</a></li>
            <li><a href="/javascript/">JavaScript</a></li>
            <li><a href="/bbs/list.do?boardCd=chat&page=1">BBS</a></li>
        </ul>
    </div>

    <div id="container">
        <div id="content">
<!-- 본문 시작 -->
<div id="content-categories">Error</div>
<h2>Error Page</h2>
<%
if (statusCode != null && statusCode != 500) {
  out.write("<h3>Error Details</h3>");
  out.write("<strong>Status Code</strong>:" + statusCode + "<br>");
  out.write("<strong>Requested URI</strong>:"+requestUri);
}
if (throwable != null) {
  out.write("<h3>Exception Details</h3>");
  out.write("<ul><li>Servlet Name:" + servletName + "</li>");
  out.write("<li>Exception Name:" + throwable.getClass().getName() + "</li>");
  out.write("<li>Requested URI:" + requestUri + "</li>");
  out.write("<li>Exception Message:" + throwable.getMessage() + "</li>");
  out.write("</ul>");
}
%>
<!--  본문 끝 -->
        </div><!-- content 끝 -->
    </div><!--  container 끝 -->
    
    <div id="sidebar">
        <h1>Error</h1>
    </div>
    
    <div id="extra">
        <a href="http://www.youtube.com"><img src="/images/youtube.png" alt="youtube.com" /></a>
        <a href="http://www.twitter.com"><img src="/images/twitter.png" alt="twitter.com" /></a>
        <a href="http://www.facebook.com"><img src="/images/facebook.png" alt="facebook.com" /></a>
        <a href="http://www.gmail.com"><img src="/images/gmail.png" alt="gmail.com" /></a>
        <a href="http://www.java-school.net"><img src="/images/java-school.png" alt="java-school.net" /></a>
    </div>

    <div id="footer">
        <%@ include file="./inc/footer.jsp" %>
    </div>

</div>

</body>
</html>

홈페이지 역시 인클루드 지시어에 쓰인 jsp를 제외한 모든 .jsp를 .do로 바꾼다.

구현

MVC 뼈대를 이루는 클래스부터 작성한다.
아래 ActionForward 클래스는 액션 클래스의 execute() 메소드가 반환하는 타입이다.
ActionForward 객체에는 리다이렉트 여부와 이동하게 될 주소 정보만 저장된다.
리다이렉트 여부가 false면 설정된 주소로 포워딩된다.
포워딩이나 리다이렉트되는 주소는 액션 클래스에서 만들고 ActionForward에 담겨 컨트롤러에 반환된다.

ActionForward.java
package net.java_school.action;

public class ActionForward {
    private boolean isRedirect;
    private String view;
    
    public boolean isRedirect() {
        return isRedirect;
    }
    public void setRedirect(boolean isRedirect) {
        this.isRedirect = isRedirect;
    }
    public String getView() {
        return view;
    }
    public void setView(String view) {
        this.view = view;
    }
    
}

Action 인터페이스는 모든 액션 클래스가 구현해야 한다.

Action.java
package net.java_school.action;

import java.io.IOException;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public interface Action {
    public ActionForward execute(HttpServletRequest req, 
        HttpServletResponse resp) throws IOException;
}

인증이 실패했을 때 발생시킬 사용자 정의 예외 클래스이다.
런타임 예외를 상속하여 만든다.

AuthenticationException.java
package net.java_school.exception;

public class AuthenticationException extends RuntimeException {

    private static final long serialVersionUID = -2916142666133028059L;

    public AuthenticationException() {
        super();
    }

    public AuthenticationException(String message, Throwable cause,
            boolean enableSuppression, boolean writableStackTrace) {
        super(message, cause, enableSuppression, writableStackTrace);
    }

    public AuthenticationException(String message, Throwable cause) {
        super(message, cause);
    }

    public AuthenticationException(String message) {
        super(message);
    }

    public AuthenticationException(Throwable cause) {
        super(cause);
    }
    
}

*.do 요청을 처리할 컨트롤러를 만든다.

Controller.java
package net.java_school.controller;

import java.io.IOException;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import net.java_school.action.Action;
import net.java_school.action.ActionForward;
import net.java_school.exception.AuthenticationException;

public class Controller extends HttpServlet {

    private static final long serialVersionUID = 4024375917229853991L;
    
    @Override
    public void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        doPost(req,resp);
    }
    
    @Override
    public void doPost(HttpServletRequest req, HttpServletResponse resp) 
            throws ServletException, IOException {
            
        req.setCharacterEncoding("UTF-8");
        
        String uri = req.getRequestURI();
        String contextPath = req.getContextPath();
        String command = uri.substring(contextPath.length());
        
        String view = null;
        Action action = null;
        ActionForward forward = null;
        
        try {
            if (command.equals("/bbs/list.do") && req.getMethod().equals("GET")) {
                //action = new ListAction();
                forward = action.execute(req, resp);
            } else if (command.equals("/bbs/view.do") && req.getMethod().equals("GET")) {
                //action = new ViewAction();
                forward = action.execute(req, resp);
            } else if (command.equals("/bbs/write.do") && req.getMethod().equals("GET")) {
                //action = new WriteFormAction();
                forward = action.execute(req, resp);
            } else if (command.equals("/bbs/write.do") && req.getMethod().equals("POST")) {
                //action = new WriteAction();
                forward = action.execute(req, resp);
            } else if (command.equals("/bbs/addComment.do") && req.getMethod().equals("POST")) {
                //action = new AddCommentAction();
                forward = action.execute(req, resp);
            } else if (command.equals("/bbs/updateComment.do") && req.getMethod().equals("POST")) {
                //action = new UpdateCommentAction();
                forward = action.execute(req, resp);
            } else if (command.equals("/bbs/deleteComment.do") && req.getMethod().equals("POST")) {
                //action = new DeleteCommentAction();
                forward = action.execute(req, resp);
            } else if (command.equals("/bbs/deleteAttachFile.do") && req.getMethod().equals("POST")) {
                //action = new DeleteAttachFileAction();
                forward = action.execute(req, resp);                
            } else if (command.equals("/bbs/modify.do") && req.getMethod().equals("GET")) {
                //action = new ModifyFormAction();
                forward = action.execute(req, resp);
            } else if (command.equals("/bbs/modify.do") && req.getMethod().equals("POST")) {
                //action = new ModifyAction();
                forward = action.execute(req, resp);
            } else if (command.equals("/bbs/del.do") && req.getMethod().equals("POST")) {
                //action = new DeleteAction();
                forward = action.execute(req, resp);
            } else if (command.equals("/users/changePasswd.do") && req.getMethod().equals("GET")) {
                //action = new ChangePasswdFormAction();
                forward = action.execute(req, resp);
            } else if (command.equals("/users/changePasswd.do") && req.getMethod().equals("POST")) {
                //action = new ChangePasswdAction();
                forward = action.execute(req, resp);
            } else if (command.equals("/users/changePasswd_confirm.do") && req.getMethod().equals("GET")) {
                forward = new ActionForward(); //단순 포워딩
                forward.setView("/users/changePasswd_confirm.jsp");             
            } else if (command.equals("/users/signUp.do") && req.getMethod().equals("GET")) {
                forward = new ActionForward(); //단순 포워딩
                forward.setView("/users/signUp.jsp");
            } else if (command.equals("/users/signUp.do") && req.getMethod().equals("POST")) {
                //action = new SignUpAction();
                forward = action.execute(req, resp);
            } else if (command.equals("/users/welcome.do") && req.getMethod().equals("GET")) {
                forward = new ActionForward(); //단순 포워딩
                forward.setView("/users/welcome.jsp");              
            } else if (command.equals("/users/editAccount.do") && req.getMethod().equals("GET")) {
                //action = new EditAccountFormAction();
                forward = action.execute(req, resp);
            } else if (command.equals("/users/editAccount.do") && req.getMethod().equals("POST")) {
                //action = new EditAccountAction();
                forward = action.execute(req, resp);
            } else if (command.equals("/users/bye.do") && req.getMethod().equals("GET")) {
                //action = new ByeFormAction();
                forward = action.execute(req, resp);
            } else if (command.equals("/users/bye.do") && req.getMethod().equals("POST")) {
                //action = new ByeAction();
                forward = action.execute(req, resp);
            } else if (command.equals("/users/login.do") && req.getMethod().equals("GET")) {
                forward = new ActionForward();//단순 포워딩
                forward.setView("/users/login.jsp");
            } else if (command.equals("/users/login.do") && req.getMethod().equals("POST")) {
                //action = new LoginAction();
                forward = action.execute(req, resp);
            } else if (command.equals("/users/logout.do") && req.getMethod().equals("POST")) {
                //action = new LogoutAction();
                forward = action.execute(req, resp);
            }
            
            view = forward.getView();
            
            if (forward.isRedirect() == false) {
                RequestDispatcher rd = this.getServletContext().getRequestDispatcher(view);
                rd.forward(req, resp);
            } else {
                resp.sendRedirect(view);
            }
        } catch (AuthenticationException e) {
            //컨트롤러에 인증 실패 예외가 전달되면, 컨트롤러는 에러 메시지를 컨테이너에 전달한다.
            resp.sendError(HttpServletResponse.SC_FORBIDDEN, e.getMessage());
        }
        
    }
    
}

컨트롤러 서블릿을 web.xml에 선언하고 URL 매핑한다.

web.xml
<servlet>
   <servlet-name>controller</servlet-name>
   <servlet-class>net.java_school.controller.Controller</servlet-class>
   <load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
   <servlet-name>controller</servlet-name>
   <url-pattern>*.do</url-pattern>
</servlet-mapping>

웹 애플리케이션에서 공통으로 사용할 상수를 추가한다.

WebContants.java
package net.java_school.commons;

public class WebContants {
    //Session key
    public final static String USER_KEY = "user";
    //Error Message
    public final static String NOT_LOGIN = "Not Login";
    public final static String AUTHENTICATION_FAILED = "Authentication Failed";
    //Line Separator
    public final static String LINE_SEPARATOR = System.getProperty("line.separator");
    
}

게시판은 로그인 사용자만 이용할 수 있다고 했다.
따라서 로그인을 첫 번째로 구현한다.
모델 1의 login_proc.jsp의 구현 내용을 참조하여 로그인을 처리하는 액션 클래스를 만든다.
이 클래스는 login.do POST 요청이 왔을 때 작동한다.
로그인이 실패하면 로그인 페이지로 되돌아간다.

LoginAction.java
package net.java_school.user.action;

import java.io.IOException;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import net.java_school.action.Action;
import net.java_school.action.ActionForward;
import net.java_school.commons.WebContants;
import net.java_school.user.User;
import net.java_school.user.UserService;

public class LoginAction implements Action {

    @Override
    public ActionForward execute(HttpServletRequest req, 
        HttpServletResponse resp) throws IOException {
        
        String url = req.getParameter("url");
        
        String email = req.getParameter("email");
        String passwd = req.getParameter("passwd");

        UserService service = new UserService();
        User user = service.login(email, passwd);

        ActionForward forward = new ActionForward();
         
        String contextPath = req.getContextPath();
        
        if (user == null) {
            forward.setView(contextPath + "/users/login.do?url=" + url +"&msg=Login-Failed");
            forward.setRedirect(true);
        } else {
            HttpSession session = req.getSession();
            session.setAttribute(WebContants.USER_KEY, user);
            if (url != null && !url.equals("")) {
                forward.setView(url);
                forward.setRedirect(true);
            } else {
                forward.setView(contextPath + "/bbs/list.do?boardCd=chat&page=1");
                forward.setRedirect(true);
            }
        }
        
        return forward;
    }

}

로그인 화면으로 리다이렉트하는 코드에서 컨텍스트 패스를 구한 이유는 디렉터리의 깊이가 다른 어떤 URL에서라도 로그인이 작동하도록 하기 위해서다.
리다이렉트를 위한 주소와 포워딩을 위한 주소는 달리 구해야 한다.
포워딩을 위한 주소는 컨텍스트 패스 다음에 컨텍스트 패스를 제외한 /부터 시작하는 주소를 사용한다.
리다이렉트의 경우 리다이렉트 URL를 보내는 페이지의 위치를 기준으로 상대 경로를 구하거나 컨텍스트 패스를 포함하는 주소를 사용한다.
리다이렉트 시 포워딩하기 위한 URL를 사용해선 안 된다.

로그아웃하면 홈페이지로 이동한다.

LogoutAction.java
package net.java_school.user.action;

import java.io.IOException;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import net.java_school.action.Action;
import net.java_school.action.ActionForward;
import net.java_school.commons.WebContants;

public class LogoutAction implements Action {

    @Override
    public ActionForward execute(HttpServletRequest req,
            HttpServletResponse resp) throws IOException {
            
        ActionForward forward = new ActionForward();
                
        HttpSession session = req.getSession();
        session.removeAttribute(WebContants.USER_KEY);

        String contextPath = req.getContextPath();
        forward.setView(contextPath);
        forward.setRedirect(true);

        return forward;
    }

}

SignUpAction 액션 클래스는 회원 가입을 처리한다.
회원 가입이 완료되면 환영 페이지로 리다이렉트한다.
모델 1에서 signUp_proc.jsp의 구현 내용을 참조한다.

SignUpAction.java
package net.java_school.user.action;

import java.io.IOException;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import net.java_school.action.Action;
import net.java_school.action.ActionForward;
import net.java_school.user.User;
import net.java_school.user.UserService;

public class SignUpAction implements Action {

    @Override
    public ActionForward execute(HttpServletRequest req,
            HttpServletResponse resp) throws IOException {

        ActionForward forward = new ActionForward();

        //email,passwd,name,mobile 파라미터 전달
        String email = req.getParameter("email");
        String passwd = req.getParameter("passwd");
        String name = req.getParameter("name");
        String mobile = req.getParameter("mobile");

        User user = new User(email, passwd, name, mobile);

        UserService service = new UserService();
        service.addUser(user);

        forward.setView("welcome.do");
        forward.setRedirect(true);

        return forward;
    }

}

EditAccountFormAction 액션 클래스는 내 정보 수정 버튼을 클릭하여 /users/editAccount.do 요청이 컨트롤러에 전달되면 작동한다.
로그인되어 있지 않으면 로그인 페이지로 이동한다.
이때 로그인 후 되돌아올 수 있도록 요청을 전달한다.
이 부분은 모델 1의 loginCheck.jsp 파일을 참조한다.

EditAccountFormAction.java
package net.java_school.user.action;

import java.io.IOException;
import java.net.URLEncoder;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import net.java_school.action.Action;
import net.java_school.action.ActionForward;
import net.java_school.commons.WebContants;
import net.java_school.user.User;

public class EditAccountFormAction implements Action {

    @Override
    public ActionForward execute(HttpServletRequest req,
            HttpServletResponse resp) throws IOException {

        ActionForward forward = new ActionForward();

        HttpSession session = req.getSession();
        User user = (User) session.getAttribute(WebContants.USER_KEY);

        //loginCheck.jsp와 같은 로직
        if (user == null) {
            String url = req.getRequestURI();
            String query = req.getQueryString();
            if (query != null) url += "?" + query;
            url = URLEncoder.encode(url, "UTF-8");
            String contextPath = req.getContextPath();
            forward.setRedirect(true);
            forward.setView(contextPath + "/users/login.do?url=" + url);

            return forward;
        }

        forward.setView("/users/editAccount.jsp");

        return forward;
    }

}

EditAccountAction 액션 클래스는 /users/editAccount.do POST 요청에 대해 작동하여 회원정보 수정을 처리한다.

EditAccountAction.java
package net.java_school.user.action;

import java.io.IOException;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import net.java_school.action.Action;
import net.java_school.action.ActionForward;
import net.java_school.commons.WebContants;
import net.java_school.exception.AuthenticationException;
import net.java_school.user.User;
import net.java_school.user.UserService;

public class EditAccountAction implements Action {

    @Override
    public ActionForward execute(HttpServletRequest req,
            HttpServletResponse resp) throws IOException {
            
        ActionForward forward = new ActionForward();
        
        HttpSession session = req.getSession();
        User user = (User) session.getAttribute(WebContants.USER_KEY);

        //name, mobile, passwd 파라미터 전달
        String name = req.getParameter("name");
        String mobile = req.getParameter("mobile");
        String passwd = req.getParameter("passwd");
        
        if (user == null) {
            throw new AuthenticationException(WebContants.NOT_LOGIN);
        }
        
        String email = user.getEmail();
        
        UserService service = new UserService();
        
        //user.setEmail(email);
        user.setPasswd(passwd);
        user.setName(name);
        user.setMobile(mobile);
        
        int check = service.editAccount(user);
        
        if (check > 0) {
            session.setAttribute(WebContants.USER_KEY, user);
            forward.setView("changePasswd.do");
            forward.setRedirect(true);
        } else {
            throw new AuthenticationException(WebContants.AUTHENTICATION_FAILED);
        }
        
        return forward;
    }

}

ChangePasswdFormAction 액션 클래스는 /users/changePasswd.do 요청에 작동한다.

ChangePasswdFormAction.java
package net.java_school.user.action;

import java.io.IOException;
import java.net.URLEncoder;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import net.java_school.action.Action;
import net.java_school.action.ActionForward;
import net.java_school.commons.WebContants;
import net.java_school.user.User;

public class ChangePasswdFormAction implements Action {
    
    @Override
    public ActionForward execute(HttpServletRequest req, 
        HttpServletResponse resp) throws IOException {
        
        ActionForward forward = new ActionForward();
        
        HttpSession session = req.getSession();
        User user = (User) session.getAttribute(WebContants.USER_KEY);
        
        if (user == null) {
            String url = req.getRequestURI();
            String query = req.getQueryString();
            if (query != null) url += "?" + query;
            url = URLEncoder.encode(url, "UTF-8");
            String contextPath = req.getContextPath();
            forward.setView(contextPath + "/users/login.do?url=" + url);
            forward.setRedirect(true);
            
            return forward;
        }

        forward.setView("/users/changePasswd.jsp");
        
        return forward;
    }
}

ChangePasswdAction 액션 클래스는 /users/changePasswd.do POST 요청에 작동하여 비밀번호 변경을 처리한다.

ChangePasswdAction.java
package net.java_school.user.action;

import java.io.IOException;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import net.java_school.action.Action;
import net.java_school.action.ActionForward;
import net.java_school.commons.WebContants;
import net.java_school.exception.AuthenticationException;
import net.java_school.user.User;
import net.java_school.user.UserService;

public class ChangePasswdAction implements Action {
    
    @Override
    public ActionForward execute(HttpServletRequest req,
    		HttpServletResponse resp) throws IOException {
       
        ActionForward forward = new ActionForward();
        
        HttpSession session = req.getSession();
        User user = (User) session.getAttribute(WebContants.USER_KEY);
        
        if (user == null) {
            throw new AuthenticationException(WebContants.NOT_LOGIN);
        }

        //currentPasswd(현재 비밀번호),newPasswd(변경 비밀번호)
        String currentPasswd = req.getParameter("currentPasswd");
        String newPasswd = req.getParameter("newPasswd");
        String email = user.getEmail();

        UserService service = new UserService();
        int check = service.changePasswd(currentPasswd, newPasswd, email);
                
        if (check < 1) {
            throw new AuthenticationException(WebContants.AUTHENTICATION_FAILED);
        }
        
        forward.setView("changePasswd_confirm.do");
        forward.setRedirect(true);
        
        return forward;
    }
}

ByeFormAction 액션 클래스는 /users/bye.do GET 요청에 작동한다.

ByeFormAction.java
package net.java_school.user.action;

import java.io.IOException;
import java.net.URLEncoder;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import net.java_school.action.Action;
import net.java_school.action.ActionForward;
import net.java_school.user.User;

public class ByeFormAction implements Action {

    @Override
    public ActionForward execute(HttpServletRequest req,
            HttpServletResponse resp) throws IOException {

        ActionForward forward = new ActionForward();

        HttpSession session = req.getSession();
        User user = (User) session.getAttribute(WebContants.USER_KEY);
        
        if (user == null) {
            String url = req.getRequestURI();
            String query = req.getQueryString();
            if (query != null) url += "?" + query;
            url = URLEncoder.encode(url, "UTF-8");
            String contextPath = req.getContextPath();
            forward.setView(contextPath + "/users/login.do?url=" + url);
            forward.setRedirect(true);
 
            return forward;
        }

        forward.setView("/users/bye.jsp");
        
        return forward;
    }

}

ByeAction 액션 클래스는 /users/bye.do POST 요청에 작동하여 회원 탈퇴를 처리한다.

ByeAction.java
package net.java_school.user.action;

import java.io.IOException;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import net.java_school.action.Action;
import net.java_school.action.ActionForward;
import net.java_school.commons.WebContants;
import net.java_school.exception.AuthenticationException;
import net.java_school.user.User;
import net.java_school.user.UserService;

public class ByeAction implements Action {

    @Override
    public ActionForward execute(HttpServletRequest req,
            HttpServletResponse resp) throws IOException {

        ActionForward forward = new ActionForward();
        
        HttpSession session = req.getSession();
        User user = (User) session.getAttribute(WebContants.USER_KEY);

        if (user == null) {
            throw new AuthenticationException(WebContants.NOT_LOGIN); 
        }

        String email = req.getParameter("email");
        String passwd = req.getParameter("passwd");

        UserService service = new UserService();

        service.bye(email, passwd);
        session.removeAttribute(WebContants.USER_KEY);

        forward.setView("/users/bye_confirm.jsp");
        
        return forward;
    }

}

ListAction 액션 클래스는 /bbs/list.do GET 요청에 작동한다.
목록 화면을 디자인할 때 화면에 전달된다고 가정했던 데이터를 이 액션 객체가 만들고 목록 화면에 전달해야 한다.

ListAction.java
package net.java_school.board.action;

import java.io.IOException;
import java.net.URLEncoder;
import java.util.List;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import net.java_school.action.Action;
import net.java_school.action.ActionForward;
import net.java_school.board.Article;
import net.java_school.board.BoardService;
import net.java_school.commons.WebContants;
import net.java_school.user.User;

public class ListAction implements Action {
    public NumbersForPaging getNumbersForPaging(int totalRecord, int page, int numPerPage, int pagePerBlock) {
        int totalPage = totalRecord / numPerPage;
        if (totalRecord % numPerPage != 0) totalPage++;
        int totalBlock = totalPage / pagePerBlock;
        if (totalPage % pagePerBlock != 0) totalBlock++;
        int block = page / pagePerBlock;
        if (page % pagePerBlock != 0) block++;
        int firstPage = (block - 1) * pagePerBlock + 1;
        int lastPage = block * pagePerBlock;
        int prevPage = 0;
        if (block > 1) {
            prevPage = firstPage - 1;
        }
        int nextPage = 0;
        if (block < totalBlock) {
            nextPage = lastPage + 1;
        }
        if (block >= totalBlock) {
            lastPage = totalPage;
        }
        int listItemNo = totalRecord - (page - 1) * numPerPage;
        
        NumbersForPaging numbers = new NumbersForPaging();
        
        numbers.setTotalPage(totalPage);
        numbers.setFirstPage(firstPage);
        numbers.setLastPage(lastPage);
        numbers.setPrevBlock(prevPage);
        numbers.setNextBlock(nextPage);
        numbers.setListItemNo(listItemNo);
        
        return numbers;
    }    
    @Override
    public ActionForward execute(HttpServletRequest req, HttpServletResponse resp) throws IOException {

        ActionForward forward = new ActionForward();
        
        HttpSession session = req.getSession();
        User user = (User) session.getAttribute(WebContants.USER_KEY);
        
        if (user == null) {
            String url = req.getRequestURI();
            String query = req.getQueryString();
            if (query != null) url += "?" + query;
            url = URLEncoder.encode(url, "UTF-8");
            String contextPath = req.getContextPath();
            forward.setView(contextPath + "/users/login.do?url=" + url);
            forward.setRedirect(true);
            
            return forward;
        }
        
        String boardCd = req.getParameter("boardCd");
        int page = Integer.parseInt(req.getParameter("page"));
        String searchWord = req.getParameter("searchWord");
        
        BoardService service = new BoardService();
           
        int numPerPage = 10;
        int pagePerBlock = 10;
                 
        int totalRecord = service.getTotalRecord(boardCd, searchWord);
        
        int startRecord = (page - 1) * numPerPage + 1;
        int endRecord = page * numPerPage;
        
        List<Article> list = service.getArticleList(boardCd, searchWord, startRecord, endRecord);
        
        NumbersForPaging numbers = this.getNumbersForPaging(totalRecord, page, numPerPage, pagePerBlock);
        
        String boardNm = service.getBoardNm(boardCd);
        
        req.setAttribute("list", list);
        req.setAttribute("listItemNo", numbers.getListItemNo());
        req.setAttribute("prevPage", numbers.getPrevPage());
        req.setAttribute("firstPage", numbers.getFirstPage());
        req.setAttribute("lastPage", numbers.getLastPage());
        req.setAttribute("nextPage", numbers.getNextPage());
        req.setAttribute("boardNm", boardNm);
        
        forward.setView("/bbs/list.jsp");
        
        return forward;
        
    }
}

ViewAction 액션 클래스는 /bbs/view.do GET 요청에 작동한다. 상세보기 페이지에서 필요한 데이터를 만들고 전달한다.

ViewAction.java
package net.java_school.board.action;

import java.io.IOException;
import java.net.URLEncoder;
import java.util.List;
import java.util.Date;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import net.java_school.action.Action;
import net.java_school.action.ActionForward;
import net.java_school.board.Article;
import net.java_school.board.AttachFile;
import net.java_school.board.BoardService;
import net.java_school.board.Comment;
import net.java_school.commons.NumbersForPaging;
import net.java_school.commons.WebContants;
import net.java_school.user.User;

public class ViewAction implements Action {
    public NumbersForPaging getNumbersForPaging(int totalRecord, int page, int numPerPage, int pagePerBlock) {
        int totalPage = totalRecord / numPerPage;
        if (totalRecord % numPerPage != 0) totalPage++;
        int totalBlock = totalPage / pagePerBlock;
        if (totalPage % pagePerBlock != 0) totalBlock++;
        int block = page / pagePerBlock;
        if (page % pagePerBlock != 0) block++;
        int firstPage = (block - 1) * pagePerBlock + 1;
        int lastPage = block * pagePerBlock;
        int prevPage = 0;
        if (block > 1) {
            prevPage = firstPage - 1;
        }
        int nextPage = 0;
        if (block < totalBlock) {
            nextPage = lastPage + 1;
        }
        if (block >= totalBlock) {
            lastPage = totalPage;
        }
        int listItemNo = totalRecord - (page - 1) * numPerPage;
        
        NumbersForPaging numbers = new NumbersForPaging();
        
        numbers.setTotalPage(totalPage);
        numbers.setFirstPage(firstPage);
        numbers.setLastPage(lastPage);
        numbers.setPrevBlock(prevPage);
        numbers.setNextBlock(nextPage);
        numbers.setListItemNo(listItemNo);
        
        return numbers;
    }    

    @Override
    public ActionForward execute(HttpServletRequest req, 
        HttpServletResponse resp) throws IOException {

        ActionForward forward = new ActionForward();
        
        HttpSession session = req.getSession();
        User user = (User) session.getAttribute(WebContants.USER_KEY);
        
        if (user == null) {
            String url = req.getRequestURI();
            String query = req.getQueryString();
            if (query != null) url += "?" + query;
            url = URLEncoder.encode(url, "UTF-8");
            String contextPath = req.getContextPath();
            forward.setView(contextPath + "/users/login.do?url=" + url);
            forward.setRedirect(true);
                        
            return forward;
        }

        int articleNo = Integer.parseInt(req.getParameter("articleNo"));
        String boardCd = req.getParameter("boardCd");
        int page = Integer.parseInt(req.getParameter("page"));
        String searchWord = req.getParameter("searchWord");

        BoardService service = new BoardService();
        
        service.increaseHit(articleNo);

        int numPerPage = 10;
        int pagePerBlock = 10;
        
        int startRecord = (page - 1) * numPerPage + 1;
        int endRecord = page * numPerPage;        
        
        Article article = service.getArticle(articleNo);//상세보기 게시글
        List<AttachFile> attachFileList = service.getAttachFileList(articleNo);//첨부 파일 리스트
        Article nextArticle = service.getNextArticle(articleNo, boardCd, searchWord);//다음 글
        Article prevArticle = service.getPrevArticle(articleNo, boardCd, searchWord);//이전 클
        List<Article> list = service.getArticleList(boardCd, searchWord, startRecord, endRecord);//목록
        List<Comment> commentList = service.getCommentList(articleNo);//댓글 리스트
        String boardNm = service.getBoardNm(boardCd);

        //상세보기 게시글
        String title = article.getTitle();//제목
        String content = article.getContent();//내용
        content = content.replaceAll(WebContants.LINE_SEPARATOR, "<br />");
        int hit = article.getHit();//조회 수
        String name = article.getName();//작성자 이름
        String email = article.getEmail();//작성자 ID
        Date regdate = article.getRegdate();//작성일
        String contextPath = req.getContextPath();
        String uploadPath = contextPath + "/upload/";//첨부 파일 경로

        int totalRecord = service.getTotalRecord(boardCd, searchWord);
        NumbersForPaging numbers = this.getNumbersForPaging(totalRecord, page, numPerPage, pagePerBlock);
        
        req.setAttribute("title", title);
        req.setAttribute("content", content);
        req.setAttribute("hit", hit);
        req.setAttribute("name", name);
        req.setAttribute("email", email);
        req.setAttribute("regdate", regdate);
        req.setAttribute("uploadPath", uploadPath);
        req.setAttribute("attachFileList", attachFileList);
        req.setAttribute("nextArticle", nextArticle);
        req.setAttribute("prevArticle", prevArticle);
        req.setAttribute("commentList", commentList);
        
        req.setAttribute("list", list);
        req.setAttribute("listItemNo", numbers.getListItemNo());
        req.setAttribute("prevPage", numbers.getPrevPage());
        req.setAttribute("firstPage", numbers.getFirstPage());
        req.setAttribute("lastPage", numbers.getLastPage());
        req.setAttribute("nextPage", numbers.getNextPage());
        req.setAttribute("boardNm", boardNm);
        
        forward.setView("/bbs/view.jsp");
        
        return forward;
    }
}

WriteFormAction 액션 클래스는 /bbs/write.do GET 요청에 작동한다.

WriteFormAction.java
package net.java_school.board.action;

import java.io.IOException;
import java.net.URLEncoder;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import net.java_school.action.Action;
import net.java_school.action.ActionForward;
import net.java_school.board.BoardService;
import net.java_school.user.User;

public class WriteFormAction implements Action {
    
    @Override
    public ActionForward execute(HttpServletRequest req, 
        HttpServletResponse resp) throws IOException {

        ActionForward forward = new ActionForward();
        
        HttpSession session = req.getSession();
        User user = (User) session.getAttribute(WebContants.USER_KEY);
        
        if (user == null) {
            String url = req.getRequestURI();
            String query = req.getQueryString();
            if (query != null) url += "?" + query;
            url = URLEncoder.encode(url, "UTF-8");
            String contextPath = req.getContextPath();
            forward.setView(contextPath + "/users/login.do?url=" + url);
            forward.setRedirect(true);
            
            return forward;
        }

        String boardCd = req.getParameter("boardCd");

        BoardService service = new BoardService();
        String boardNm = service.getBoardNm(boardCd);
        
        req.setAttribute("boardNm", boardNm);
        
        forward.setView("/bbs/write.jsp");
        
        return forward;
    }
}

WriteAction 액션 클래스는 /bbs/write.do POST 요청에 작동하여 글쓰기를 처리한다.

WriteAction.java
package net.java_school.board.action;

import java.io.File;
import java.io.IOException;

import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import com.oreilly.servlet.MultipartRequest;
import com.oreilly.servlet.multipart.DefaultFileRenamePolicy;

import net.java_school.action.Action;
import net.java_school.action.ActionForward;
import net.java_school.board.Article;
import net.java_school.board.AttachFile;
import net.java_school.board.BoardService;
import net.java_school.commons.WebContants;
import net.java_school.exception.AuthenticationException;
import net.java_school.user.User;

public class WriteAction implements Action {
    
    @Override
    public ActionForward execute(HttpServletRequest req, 
        HttpServletResponse resp) throws IOException {

        ActionForward forward = new ActionForward();
        
        HttpSession session = req.getSession();
        User user = (User) session.getAttribute(WebContants.USER_KEY);
        
        if (user == null) {
            throw new AuthenticationException(WebContants.NOT_LOGIN);
        }
        
        ServletContext servletContext = req.getServletContext();
        String dir = servletContext.getRealPath("/upload");

        MultipartRequest multi = new MultipartRequest(
                req,
                dir,
                5*1024*1024,
                "UTF-8",
                new DefaultFileRenamePolicy());

        String title = multi.getParameter("title");
        String content = multi.getParameter("content");
        String filename = multi.getFilesystemName("attachFile");
        String filetype = multi.getContentType("attachFile");

        File f = multi.getFile("attachFile");
        long filesize = 0L;
        AttachFile attachFile = null;

        if (f != null) {
            filesize = f.length();
            attachFile = new AttachFile();
            attachFile.setFilename(filename);
            attachFile.setFiletype(filetype);
            attachFile.setFilesize(filesize);
            attachFile.setEmail(user.getEmail());
        }

        String boardCd = multi.getParameter("boardCd");

        Article article = new Article();
        article.setEmail(user.getEmail());
        article.setTitle(title);
        article.setContent(content);
        article.setBoardCd(boardCd);

        BoardService service = new BoardService();
        service.addArticle(article, attachFile);
        
        forward.setView("list.do?boardCd=" + boardCd + "&page=1");
        forward.setRedirect(true);
        
        return forward;
    }
}

ModifyFormAction 액션 클래스는 /bbs/modify.do GET 요청에 작동한다.

ModifyFormAction.java
package net.java_school.board.action;

import java.io.IOException;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import net.java_school.action.Action;
import net.java_school.action.ActionForward;
import net.java_school.board.Article;
import net.java_school.board.BoardService;
import net.java_school.commons.WebContants;
import net.java_school.exception.AuthenticationException;
import net.java_school.user.User;

public class ModifyFormAction implements Action {
    
    @Override
    public ActionForward execute(HttpServletRequest req, 
        HttpServletResponse resp) throws IOException {

        ActionForward forward = new ActionForward();
        
        int articleNo = Integer.parseInt(req.getParameter("articleNo"));
        String boardCd = req.getParameter("boardCd");

        HttpSession session = req.getSession();
        User user = (User) session.getAttribute(WebContants.USER_KEY);

        BoardService service = new BoardService();
        Article article = service.getArticle(articleNo);

        if (user == null || !user.getEmail().equals(article.getEmail())) {
            throw new AuthenticationException(WebContants.AUTHENTICATION_FAILED);
        }

        String title = article.getTitle();
        String content = article.getContent();
        String boardNm = service.getBoardNm(boardCd);

        req.setAttribute("title", title);
        req.setAttribute("content", content);
        req.setAttribute("boardNm", boardNm);
        
        forward.setView("/bbs/modify.jsp");
        
        return forward;
    }
}

ModifyAction 액션 클래스는 /bbs/modify.do POST 요청에 작동하여 글 수정을 처리한다.

ModifyAction.java
package net.java_school.board.action;

import java.io.File;
import java.io.IOException;
import java.net.URLEncoder;

import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import com.oreilly.servlet.MultipartRequest;
import com.oreilly.servlet.multipart.DefaultFileRenamePolicy;

import net.java_school.action.Action;
import net.java_school.action.ActionForward;
import net.java_school.board.Article;
import net.java_school.board.AttachFile;
import net.java_school.board.BoardService;
import net.java_school.commons.WebContants;
import net.java_school.exception.AuthenticationException;
import net.java_school.user.User;

public class ModifyAction implements Action {
    
    @Override
    public ActionForward execute(HttpServletRequest req, 
        HttpServletResponse resp) throws IOException {

        ActionForward forward = new ActionForward();
        
        HttpSession session = req.getSession();
        User user = (User) session.getAttribute(WebContants.USER_KEY);

        ServletContext servletContext = req.getServletContext();
        String dir = servletContext.getRealPath("/upload");

        MultipartRequest multi = new MultipartRequest(
                req,
                dir,
                5*1024*1024,
                "UTF-8",
                new DefaultFileRenamePolicy());

        BoardService service = new BoardService();
        int articleNo = Integer.parseInt(multi.getParameter("articleNo"));

        if (!service.getArticle(articleNo).getEmail().equals(user.getEmail())) {
            throw new AuthenticationException(WebContants.AUTHENTICATION_FAILED);
        }

        String boardCd = multi.getParameter("boardCd");
        int page = Integer.parseInt(multi.getParameter("page"));
        String searchWord = multi.getParameter("searchWord");
        searchWord = java.net.URLEncoder.encode(searchWord, "UTF-8");

        String title = multi.getParameter("title");
        String content = multi.getParameter("content");
        String filename = multi.getFilesystemName("attachFile");
        String filetype = multi.getContentType("attachFile");

        File f = multi.getFile("attachFile");
        long filesize = 0L;
        AttachFile attachFile = null;

        if (f != null) {
            filesize = f.length();
            attachFile = new AttachFile();
            attachFile.setFilename(filename);
            attachFile.setFiletype(filetype);
            attachFile.setFilesize(filesize);
            attachFile.setEmail(user.getEmail());
            attachFile.setArticleNo(articleNo);
        }

        Article article = new Article();
        article.setArticleNo(articleNo);
        article.setBoardCd(boardCd);
        article.setTitle(title);
        article.setContent(content);
        article.setEmail(user.getEmail());

        service.modifyArticle(article, attachFile);

        forward.setView("view.do?articleNo="+ articleNo + "&boardCd=" + boardCd + "&page=" + page + "&searchWord=" + searchWord);
        forward.setRedirect(true);
        
        return forward;
    }
}

DeleteAction 액션 클래스는 /bbs/del.do POST 요청에 작동하여 게시글 삭제를 처리한다.

DeleteAction.java
package net.java_school.board.action;

import java.io.IOException;
import java.net.URLEncoder;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import net.java_school.action.Action;
import net.java_school.action.ActionForward;
import net.java_school.board.Article;
import net.java_school.board.BoardService;
import net.java_school.commons.WebContants;
import net.java_school.exception.AuthenticationException;
import net.java_school.user.User;

public class DeleteAction implements Action {
    
    @Override
    public ActionForward execute(HttpServletRequest req, 
        HttpServletResponse resp) throws IOException {

        ActionForward forward = new ActionForward();
        
        HttpSession session = req.getSession();
        User user = (User) session.getAttribute(WebContants.USER_KEY);

        int articleNo = Integer.parseInt(req.getParameter("articleNo"));

        BoardService service = new BoardService();
        Article article = service.getArticle(articleNo);
        
        if (user == null || !user.getEmail().equals(article.getEmail())) {
            throw new AuthenticationException(WebContants.AUTHENTICATION_FAILED);
        }

        service.removeArticle(articleNo);

        String boardCd = req.getParameter("boardCd");
        String page = req.getParameter("page");
        String searchWord = req.getParameter("searchWord");

        searchWord = URLEncoder.encode(searchWord, "UTF-8");
        
        forward.setView("list.do?boardCd=" + boardCd + "&page=" + page + "&searchWord=" + searchWord);
        forward.setRedirect(true);
        
        return forward;
    }
}

AddCommentAction 액션 클래스는 /bbs/addComment.do POST 요청에 작동하여 댓글 쓰기를 처리한다.

AddCommentAction.java
package net.java_school.board.action;

import java.io.IOException;
import java.net.URLEncoder;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import net.java_school.action.Action;
import net.java_school.action.ActionForward;
import net.java_school.board.BoardService;
import net.java_school.board.Comment;
import net.java_school.commons.WebContants;
import net.java_school.exception.AuthenticationException;
import net.java_school.user.User;

public class AddCommentAction implements Action {
    
    @Override
    public ActionForward execute(HttpServletRequest req, 
        HttpServletResponse resp) throws IOException {

        ActionForward forward = new ActionForward();
        
        HttpSession session = req.getSession();
        User user = (User) session.getAttribute(WebContants.USER_KEY);
        
        if (user == null) {
            throw new AuthenticationException(WebContants.NOT_LOGIN);
        }

        int articleNo = Integer.parseInt(req.getParameter("articleNo"));
        String boardCd = req.getParameter("boardCd");
        int page = Integer.parseInt(req.getParameter("page"));
        String searchWord = req.getParameter("searchWord");
        String memo = req.getParameter("memo");

        Comment comment = new Comment();
        comment.setArticleNo(articleNo);
        comment.setEmail(user.getEmail());
        comment.setMemo(memo);

        BoardService service = new BoardService();
        service.addComment(comment);

        searchWord = URLEncoder.encode(searchWord, "UTF-8");
        forward.setView("view.do?articleNo=" + articleNo + "&boardCd=" + boardCd + "&page=" + page + "&searchWord=" + searchWord);
        forward.setRedirect(true);
        
        return forward;
    }
}

UpdateCommentAction 액션 클래스는 /bbs/updateComment.do POST 요청에 작동하여 댓글 수정을 처리한다.

UpdateCommentAction.java
package net.java_school.board.action;

import java.io.IOException;
import java.net.URLEncoder;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import net.java_school.action.Action;
import net.java_school.action.ActionForward;
import net.java_school.board.BoardService;
import net.java_school.board.Comment;
import net.java_school.commons.WebContants;
import net.java_school.exception.AuthenticationException;
import net.java_school.user.User;

public class UpdateCommentAction implements Action {

    @Override
    public ActionForward execute(HttpServletRequest req,
            HttpServletResponse resp) throws IOException {

        ActionForward forward = new ActionForward();
        
        HttpSession session = req.getSession();
        User user = (User) session.getAttribute(WebContants.USER_KEY);

        int commentNo = Integer.parseInt(req.getParameter("commentNo"));

        BoardService service = new BoardService();
        Comment comment = service.getComment(commentNo);
        
        if (user == null || !user.getEmail().equals(comment.getEmail())) {
            throw new AuthenticationException(WebContants.AUTHENTICATION_FAILED);
        }

        int articleNo = Integer.parseInt(req.getParameter("articleNo"));
        String boardCd = req.getParameter("boardCd");
        int page = Integer.parseInt(req.getParameter("page"));
        String searchWord = req.getParameter("searchWord");

        String memo = req.getParameter("memo");

        comment.setCommentNo(commentNo);
        comment.setArticleNo(articleNo);
        comment.setEmail(user.getEmail());
        comment.setMemo(memo);

        service.modifyComment(comment);

        searchWord = URLEncoder.encode(searchWord, "UTF-8");
        
        forward.setView("view.do?articleNo="+ articleNo + "&boardCd=" + boardCd + "&page=" + page + "&searchWord=" + searchWord);
        forward.setRedirect(true);
        
        return forward;
    }

}

DeleteCommentAction 액션 클래스는 /bbs/deleteComment.do POST 요청에 작동하여 댓글 삭제를 처리한다.

DeleteCommentAction.java
package net.java_school.board.action;

import java.io.IOException;
import java.net.URLEncoder;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import net.java_school.action.Action;
import net.java_school.action.ActionForward;
import net.java_school.board.BoardService;
import net.java_school.board.Comment;
import net.java_school.commons.WebContants;
import net.java_school.exception.AuthenticationException;
import net.java_school.user.User;

public class DeleteCommentAction implements Action {
    
    @Override
    public ActionForward execute(HttpServletRequest req, 
        HttpServletResponse resp) throws IOException {

        ActionForward forward = new ActionForward();
                
        HttpSession session = req.getSession();
        User user = (User) session.getAttribute(WebContants.USER_KEY);

        int commentNo = Integer.parseInt(req.getParameter("commentNo"));

        BoardService service = new BoardService();
        Comment comment = service.getComment(commentNo);
        if (user == null || !user.getEmail().equals(comment.getEmail())) {
            throw new AuthenticationException(WebContants.AUTHENTICATION_FAILED);
        }

        int articleNo = Integer.parseInt(req.getParameter("articleNo"));
        String boardCd = req.getParameter("boardCd");
        int page = Integer.parseInt(req.getParameter("page"));
        String searchWord = req.getParameter("searchWord");

        service.removeComment(commentNo);

        searchWord = URLEncoder.encode(searchWord, "UTF-8");
        forward.setView("view.do?articleNo=" + articleNo + "&boardCd=" + boardCd + "&page=" + page + "&searchWord=" + searchWord);
        forward.setRedirect(true);
        
        return forward;
    }
}

DeleteAttachFileAction 액션 클래스는 /bbs/deleteAttachFile.do POST 요청에 작동하여 첨부 파일 삭제를 처리한다.

DeleteAttachFileAction.java
package net.java_school.board.action;

import java.io.IOException;
import java.net.URLEncoder;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import net.java_school.action.Action;
import net.java_school.action.ActionForward;
import net.java_school.board.AttachFile;
import net.java_school.board.BoardService;
import net.java_school.commons.WebContants;
import net.java_school.exception.AuthenticationException;
import net.java_school.user.User;

public class DeleteAttachFileAction implements Action {

    @Override
    public ActionForward execute(HttpServletRequest req,
            HttpServletResponse resp) throws IOException {

        ActionForward forward = new ActionForward();
        
        HttpSession session = req.getSession();
        User user = (User) session.getAttribute(WebContants.USER_KEY);

        int attachFileNo = Integer.parseInt(req.getParameter("attachFileNo"));

        BoardService service = new BoardService();
        AttachFile attachFile = service.getAttachFile(attachFileNo);
        
        if (user == null || !user.getEmail().equals(attachFile.getEmail())) {
            throw new AuthenticationException(WebContants.AUTHENTICATION_FAILED);
        }

        int articleNo = Integer.parseInt(req.getParameter("articleNo"));
        String boardCd = req.getParameter("boardCd");
        int page = Integer.parseInt(req.getParameter("page"));
        String searchWord = req.getParameter("searchWord");

        service.removeAttachFile(attachFileNo);

        searchWord = URLEncoder.encode(searchWord, "UTF-8");
        
        forward.setView("view.do?articleNo=" + articleNo + "&boardCd=" + boardCd + "&page=" + page + "&searchWord=" + searchWord);
        forward.setRedirect(true);
        
        return forward;
    }

}

모델 2 개발 방식 정리

  • 모델 뷰 컨트롤러로 구성된다.
  • 모든 요청은 컨트롤러로 전달된다.
  • 컨트롤러는 모델에 작업을 위임한다.
  • 모델은 비즈니스 로직을 수행하고 결과 데이터를 뷰에 전달한다.

뷰는 프로젝트에서 JSP가 담당한다.
모델은 프로젝트에서 액션 클래스이며 비즈니스 로직을 담당한다.
비즈니스 로직이란 업무에 필요한 데이터를 처리하는 로직이다.
비즈니스 로직을 뷰에서 보일 데이터를 생산하는 로직이라 생각해도 된다.
비즈니스 로직은 뷰에서 떼어 내어 모델에 구현해야 한다.
뷰에 비즈니스 로직이 있다면 좋은 코드가 아니다.

모델 2 게시판 소스: https://github.com/kimjonghoon/model2