Servlet/JSP로 사용자 관리 시스템 구현하기 과정 - 4
JSP/Servlet과JSP로 사용자 관리 시스템 구현

Servlet/JSP로 사용자 관리 시스템 구현하기 과정 - 4

반응형

Servlet/JSP로 사용자 관리 시스템 구현하기 과정 


강의 링크 : https://www.slipp.net/wiki/pages/viewpage.action?pageId=25526852



지금까지, 홈페이지 상에서 회원가입과 로그인 구현을 진행했습니다. 이번 시간에는 조금 더 효율적인 코드를 구성해보는 것에 대해 포스팅하려고 합니다.


우리는 지금 jsp파일을 통해 로그인과 회원가입에서 작성한 사용자 데이터들을 받아 서버가 돌아가는 동안 DB에 저장하고 이를 활용하고 있습니다.

이를 구현하기 위해서 jsp파일 안에 <% ~%> 구문을 통해 자바 코드를 넣고 있는데요. 이는 장기적인 측면에서 좋은 구현 방법이 아닙니다.


jsp로만 작성하면 발생할 수 있는 문제점은 여러가지가 있습니다. 우선, 여러 import 과정을 거치면서 오류가 발생할 수 있고 소스코드를 뿌릴 때 자바코드로 작성된 내부 Logic까지 노출되는 문제가 발생합니다. 이는 jsp 파일 내의 복잡도를 증가시키고 가독성이 떨어지는 영향을 가져다주기도 합니다.


이러한 문제를 해결하기 위해서는, 각자의 역할을 나눌 필요가 있습니다.


jsp가 너무 많은 일을 하지 않도록, jsp에게는 비즈니스 로직을 구현하고나서 받은 결과를 출력하는 용도로 사용해야 합니다.

작업 코드가 많아지면 구현과 테스트가 힘들어지고 가독성에도 불편함을 가져다주기 때문입니다.


그렇다면, jsp에서 자바코드로 진행하던 데이터 처리과정은 어디에서 진행해야할까요? 우리가 배웠던 Servlet을 활용하도록 합시다!



이 과정에서 MVC 패턴이 생겨나게 되었는데요. Model, View, Controller의 약자입니다. 사용자의 요청을 각자 역할을 분담해서 처리하는 것을 말합니다.


우리는 앞으로 jsp를 데이터를 출력해서 사용자에게 보여주는 view 용도로만 사용할 것입니다. 사용자의 데이터를 받아 처리하고, 결과에 따라 이동할 페이지를 결정하는 곳을 servlet으로 옮기고 이는 controller의 역할이 되겠습니다. model은 현재 우리 프로젝트에서 User 클래스와 같이 핵심적인 비즈니스 로직에 포함될 것입니다.


이처럼 모델을 사용해서 사용자가 입력한 데이터를 받고, 모델과 인터페이스 과정을 거쳐 결과에 따라 어디에 이동할지 결정하고 예외처리를 합니다. 이 과정이 정상적으로 처리가 된다면, 로직을 뿌려 사용자에게 보여주는 jsp를 활용하는 것이 MVC 형태라고 말할 수 있습니다.



실제로 우리가 jsp로 구현한 코드 중에서 servlet으로 역할을 나누도록 하겠습니다. 현재 form_action.jsp, login_action.jsp, logout.jsp가 controller가 담당해야 할 부분이 되겠습니다. 이곳에 자바 코드로 작성된 부분을 복사한 후, User 패키지 안에 아래 3개의 java코드로 작성된 servlet을 만듭니다.



SaveUserServlet.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package net.slipp.user;
 
import java.io.IOException;
 
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
 
import net.slipp.db.Database;
 
@WebServlet("/users/save"//유저 기능 사용자 저장
public class SaveUserServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) 
            throws ServletException, IOException {
        
        request.setCharacterEncoding("UTF-8");
        response.setContentType("text/html;charset=UTF-8"); // 한글 깨지지 않도록
 
        String userId = request.getParameter("userId");
        String password = request.getParameter("password");
        String name = request.getParameter("name");
        String email = request.getParameter("email");
 
        User user = new User(userId, password, name, email);
        Database.addUser(user);
        
        response.sendRedirect("/"); // 메인페이지로 다시 이동
    }
}
 
cs


LoginServlet.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package net.slipp.user;
 
import java.io.IOException;
 
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
 
@WebServlet("/users/login")
public class LoginServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) 
            throws ServletException, IOException {
        
        request.setCharacterEncoding("UTF-8");
        response.setContentType("text/html;charset=UTF-8"); // 한글 깨지지 않도록
 
        String userId = request.getParameter("userId");
        String password = request.getParameter("password");
        
        try{
            User.login(userId, password);
            HttpSession session = request.getSession();
            session.setAttribute("userId", userId);
            
            response.sendRedirect("/"); // 메인페이지로 다시 이동
        } catch(UserNotFoundException e){
            forwardJSP(request, response, "존재하지 않는 사용자입니다. 다시 로그인 하세요.");
        } catch(PasswordMismatchException e){
            forwardJSP(request, response, "비밀번호가 틀렸습니다. 다시 로그인 하세요.");
        }
    }
 
    private void forwardJSP(HttpServletRequest request, 
            HttpServletResponse response, String errorMessage)
            throws ServletException, IOException {
        request.setAttribute("errorMessage", errorMessage);
        RequestDispatcher rd = request.getRequestDispatcher("/login.jsp");
        rd.forward(request, response);
    }
}
 
cs


LogoutServlet.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package net.slipp.user;
 
import java.io.IOException;
 
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
 
@WebServlet("/users/logout")
public class LogoutServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) 
            throws ServletException, IOException {
        
        HttpSession session = request.getSession();
        session.removeAttribute("userId");
        response.sendRedirect("/");
    }
}
 
cs



annotation을 통해 경로를 /users로 잡아주고, 각 구현에 해당하는 이름의 경로로 생성해줍니다.

이제 form.jsp나 login.jsp가 action을 통해 이동하는 경로를 위에 설정한 경로로 수정해주면 잘 실행되는 것을 볼 수 있습니다.

(서블릿으로 동작하기 때문에, 할 때마다 서버를 재시작해줘야 합니다.)


logout 기능 또한 _top.jspf에서 로그아웃의 하이퍼링크를 수정해주면 되겠죠?


<li><a href="/users/logout">로그아웃</a></li>



이제 우리는 form_action.jsp, login_action.jsp, logout.jsp가 필요없게 되었습니다. 과감히 삭제합시다ㅎㅎ

어디서 오류가 발생하고, 실수를 했을지 모르기 때문에 이런 과정을 진행하면서 꾸준히 테스트를 시행해줘야 합니다. 테스트 과정에서 발생하는 오류를 그때그때 잡아낼 수 있도록 합시다.






redirect와 forward의 차이



위가 redirect, 아래가 forward로 클라이언트와 서버 사이의 request, response 과정을 나타내고 있습니다.


redirect는 클라이언트가 a.jsp에게 요청를 보내면 서버 안에 존재하는 a.jsp가 실행되면서 내부에 존재하는 b.jsp를 redirect 과정을 통해 다시 클라이언트로 답하는 모습을 볼 수 있습니다.  이를 받은 클라이언트는 다시 b.jsp의 결과를 받기 위해 서버에게 요청을 보내게 되는데요. 서버는 b.jsp의 결과를 클라이어늩에게 응답하면서 로직이 종료됩니다.


forward 방식은 차이점이 존재합니다. 클라이언트가 a.jsp에 요청을 보내면 서버 안에서 a.jsp가 실행됩니다. 이때 forward 방식은 다시 클라이언트에게 가지않고 바로 b.jsp로 이동하게 됩니다. 따라서 a.jsp가 갖고있던 요청을 바로 b.jsp로 전송이 가능합니다.


즉, forward 구조에서는 클라이언트 브라우저에서 a.jsp로 요청을 보내면, 이후에 서버는 a.jsp에서 b.jsp로 이동할 때 서버 내부에서만 이루어지므로 브라우저가 알 수 없게 됩니다. 그냥 결과만 받아보게 되는 것이죠!


따라서 우리가 현재 로그인 시 아이디와 비밀번호를 입력하고 완료하면, login.jsp로 url이 바뀌지 않는 것입니다. 단순히 응답결과만 login.jsp로 동작하는 것을 볼 수 있습니다.



웹 애플리케이션을 개발하면서 많이 헷갈리는 부분 중 하나입니다. 분명 한쪽 jsp에서 다른쪽 jsp로 페이지 이동을 통해 데이터를 전달했는데 출력 결과가 null로 나오는 문제가 발생할 때가 있습니다.

이 경우는, 데이터를 전달할 때 redirect로 하지 않았나 확인해보도록 합니다. redirect를 통해 페이지 이동을 하면 네트워크 상에서 데이터가 유출되는 문제가 발생하게 됩니다. 따라서 앞으로 서블릿 간의 이동을 할 때 데이터를 전달하고 싶으면 반드시 forward 방식을 사용해서 페이지 이동을 할 수 있도록 합시다! (굳이 데이터를 전달할 필요가 없으면 redirect를 통해 이동해도 됩니당)






jstl과 expression language



아직까지도 login.jsp에서 errorMessage를 불러오는 것과 같이 자바코드를 사용하는 곳들이 존재합니다. 물론 자바코드를 사용해도 좋긴 하지만, jsp코드에서 자바 코드를 사용하고 싶지 않는 경우도 있습니다. 또한 로그인 홈페이지 화면에서 if 조건문을 통해 메뉴바를 다르게 만들도록 구성했는데, 이 또한 가독성이 그리 좋지는 못한 상황이기에 이를 해결할 수 있는 방법에 대해서 알아보도록 하겠습니다.


바로 jstl과 expression language를 활용할 것입니다.


우선 jstl을 활용할 파일부터 다운로드를 해야합니다.

링크 : https://mvnrepository.com/artifact/javax.servlet/jstl/1.2

이 곳에서 jar파일을 다운받은 후에, 프로젝트에서 webapp/WEB-INF/lib 폴더 안에다가 복사를 해줍시다.
기존에 jar파일을 폴더에 옮겨왔을 때처럼, Java Build Path를 통해서 라이브러리에 jar파일을 add해주면 됩니다.

이제 우리는 jstl을 사용할 수 있게 되었습니다! jstl을 필요로 하는 jsp파일에서 위에 한줄로 선언만 해주면 됩니다.


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


이제 우리는 c라는 단어를 통해 jstl을 사용할 수 있게 됩니다.


_top.jspf에서 우리는 아래와 같이 java코드를 사용하고 있는 것을 볼 수 있습니다.



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
 <%
     Object userId = session.getAttribute("userId");
 %>
              <li><a href="/">Home</a></li>
<%
    if (userId == null) {
%>
              <li><a href="/login.jsp">로그인</a></li>
              <li><a href="/form.jsp">회원가입</a></li>
<%
    } else {
%>                               
              <li><a href="/logout.jsp">로그아웃</a></li>
              <li><a href="">개인정보수정</a></li>   
<%
    }
%>    
cs


이제 jstl을 활용해서 이 java코드를 없애보도록 합시다.


jstl로 나타낸 코드는 아래와 같습니다.


1
2
3
4
5
6
7
8
9
10
<c:choose>
<c:when test="${not empty userId}">
  <li><a href="/users/logout">로그아웃</a></li>
  <li><a href="/modify.jsp">개인정보수정</a></li>
</c:when>
<c:otherwise>
  <li><a href="/login.jsp">로그인</a></li>
  <li><a href="/form.jsp">회원가입</a></li>
</c:otherwise>
</c:choose>
cs


jstl에 이용되는 것은 상당히 많기 때문에 필요할 때 검색을 통해 알아보도록 합시다. 지금 우리는, when과 otherwise라는 기능을 통해 if-else문 대신 사용할 수 있습니다.


이렇게 되면, _top.jspf에서 자바코드 없이도 작성이 가능하게 됩니다. 그런데 코드 중에 눈에 띄는 것이 한가지 있는데요.


${not empty userId}


이는 expression language로 jsp에서 값들을 비교하거나 처리할 때 사용하기 편하도록 지원하는 역할을 합니다.

또한 request로 데이터를 받아오지 않아도 데이터의 존재유무를 판단해서 처리해주는 기능까지 갖추고 있어서 더욱 간결한 코드로 구현하는 것이 가능합니다. 따라서 Object userId = session.getAttribute("userId");와 같은 코드도 써주지 않아도 expression language가 스스로 찾아서 판단해줍니다. 잘만 사용하면 아주 효율적일 것 같네요.


이를 활용해 login.jsp에서 출력하는 errorMessage 또한 jstl과 expression language로 자바코드를 없앨 수 있습니다.


1
2
3
4
5
<c:if test="${not empty errorMessage}">
    <div class="control-group">
    <label class="error">${errorMessage}</label>
    </div>
</c:if>
cs


반응형