4 minute read

서블릿 클래스 작성

@WebServlet("/MemberLoginPro")
public class MemberLoginProServlet extends HttpServlet {
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		String id = request.getParameter("id");
		String passwd = request.getParameter("passwd");
		
		MemberDTO member = new MemberDTO();
		member.setId(id);
		member.setPasswd(passwd);

		MemberDAO dao = new MemberDAO();
		boolean isLoginSuccess = dao.loginMember(member); 

		if(isLoginSuccess) {
			HttpSession session = request.getSession();
			session.setAttribute("sId", id);
            // 서블릿 -> 서블릿으로의 포워딩은 리다이렉트 방식을 사용하면 된다.
			response.sendRedirect("MemberMain");
		} else {
			response.setContentType("text/html; charset=UTF-8");
			PrintWriter out = response.getWriter();
			out.println("<script>");
			out.println("alert('로그인 실패!')");
			out.println("history.back()");
			out.println("</script>");
		}
	}
}

기존의 jsp 파일에서 수행하던 내용을 분리하여 데이터베이스 작업 관련 모든 코드들은 서블릿 클래스에서 처리하고 화면 관련 작업들은 jsp 페이지에서 처리하도록 한다.

서블릿 클래스에서 세션 객체를 다루기 위해서는 HttpServletRequest 객체로부터 세션 객체를 얻어내야한다. request 객체의 getSession() 메서드를 호출하여 HttpSession 타입 객체를 리턴받아 사용하면 된다.

서블릿 클래스에서 웹브라우저로 전송할 HTML 문서 형태의 응답 데이터를 생성할 경우(자바스크립트 alert()같은 경우) 자바의 출력 스트림 형태로 HTML 태그를 출력해야한다.

  1. 출력하는 HTML 문서에 대한 문서 타입(Content Type) 설정
    응답 데이터 타입으로 HTML 태그 문서가 전송됨을 클라이언트에게 알려주는 역할을 한다.
    response 객체의 setContentType() 메서드를 호출하여 문서 타입 문자열을 전달한다. response.setContentType("text/html; charset=UTF-8");
  2. 자바 코드를 사용하여 HTML 태그나 자바스크립트 등을 출력(전송)
    java.io.PrintWriter 객체가 필요(출력스트림과 연결되어 있는 객체)하다.
    response 객체의 getWriter() 메서드를 호출하여 객체를 리턴받는다.
    PrintWriter out = response.getWriter();
  3. PrintWriter 객체의 print() 또는 println() 메서드를 호출
    파라미터로 HTML 태그나 자바스크립트 코드를 문자열 형태로 전달하여 클라이언트(웹브라우저)에 출력할 수 있다.
    println()을 사용하면 세미콜론은 생략이 가능하다.

GET, POST 통합

<a href="MemberLoginForm">로그인</a>
<a href="MemberJoinForm">회원가입</a>

이렇게 기능을 추가하다 보면 그만큼 서블릿클래스의 수도 계속 늘어난다.

<a href="MemberLoginForm.me">로그인</a>
<a href="MemberJoinForm.me">회원가입</a>

그래서 서로 다른 서블릿 주소 요청들을 클래스 하나로 처리하도록 관리하는 것이 좋다.

@WebServlet("*.me")
public class MemberServlet extends HttpServlet {
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		doProcess(request, response);
	}

	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		doProcess(request, response);
	}
	
    // 오버라이딩 메서드가 아닌 사용자 정의 메서드
	protected void doProcess(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        request.setCharacterEncoding("UTF-8");
        ...
    }
}

이렇게 특정 패턴으로 끝나는 모든 서블릿 요청을 처리하는 서블릿 클래스를 정의한다. GET 방식 요청과 POST 방식 요청에 대해 별도로 나누어 처리하지 않고 하나의 메서드로 통합하여 처리하자. POST방식을 위해서 한글 인코딩 처리도 여기서 한다. 아니면 doPost()에서 해도 된다.

주소 추출

protected void doProcess(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    // 서블릿 주소의 "/" 기호까지 포함하여 비교해야한다.
    if(command.equals("/MemberLoginForm.me")) {
        ...
    } else if(command.equals("/MemberJoinForm.me")) {
        ...
    } else if ...
}

서로 다른 서블릿 주소 요청에 대한 하나의 서블릿 클래스를 정의했을 때 각 서블릿 주소에 따라 서로 다른 작업을 수행해야하므로 주소표시줄에 입력된 서블릿 주소를 가져와서 서블릿 주소를 구별 후 동작을 수행해야한다. 따라서, 요청 정보가 저장된 request 객체에서 서블릿 주소 추출을 통해 판별해야한다.

  • 참고) 요청 서블릿 주소(URL) 정보 전체 추출(= 가져오기)
    String requestURL = request.getRequestURL().toString();
    // http://localhost:8080/StudyServlet/MemberList.me
    

    서버마다 IP 주소(localhost 부분) 또는 포트번호(8080) 등이 달라질 수 있으므로 요청 URL 전체를 가져와서 문자열 판별 작업은 비효율적이다.

  1. 요청 주소 정보 중 URI 부분(/프로젝트명/서블릿주소) 추출
    String requestURI = request.getRequestURI();
    // /StudyServlet/MemberList.me
    
  2. 요청 주소 중 컨텍스트 경로(/프로젝트명) 추출
    String contextPath = request.getContextPath();
    // /StudyServlet
    
  3. 요청 주소 중 서블릿 주소 부분(/서블릿주소) 추출
    1번, 2번 결과물(requestURI, contextPath)을 가공하여 추출 가능하며 두 가지 방법이 있다.
    • “/서블릿주소” 부분에 해당하는 부분문자열 추출
      String command = requestURI.substring(contextPath.length());
      // /MemberList.me
      

      String 클래스의 substring() 메서드를 활용하는 방법이다.
      “/StudyServlet/MemberList.me” 주소 중 “/MemberList.me” 부분을 추출해야하므로 “/StudyServlet” 문자열의 길이(13)를 알아낸 후 해당 문자열의 길이를 substring() 메서드의 시작인덱스로 활용하여 문자열 끝까지 추출한다.

    • String 클래스의 replace() 메서드를 활용하여 컨텍스트 경로 부분을 널스트링(““)으로 치환
      String command = requestURI.replace(contextPath, "");
      // /MemberList.me
      
  4. request 객체의 getServletPath() 메서드 활용
    위의 방법들은 예전에 쓰이던 방식이고 지금은 1 ~ 3번 과정을 하나의 메서드로 통합하여 제공하는 request 객체의 getServletPath() 메서드를 사용한다.
    String command = request.getServletPath();
    // /MemberList.me
    

ActionForward 클래스

protected void doProcess(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    if(command.equals("/MemberLoginForm.me")) {
        ...
    } else if(command.equals("/MemberJoinForm.me")) {
        ...
    } else if ...
}

각각의 if문 마다 결국 포워딩은 공통적으로 하게된다. 이 중복작업을 제거하기 위해 ActionForward클래스를 작성하여 활용한다.

public class ActionForward {
	private String path; // 포워딩 주소(URL)
	private boolean isRedirect; // 포워딩 방식(true : Redirect, false : Dispatch 방식)
	
	public String getPath() {
		return path;
	}
	public void setPath(String path) {
		this.path = path;
	}
	public boolean isRedirect() {
		return isRedirect;
	}
	public void setRedirect(boolean isRedirect) {
		this.isRedirect = isRedirect;
	}
	
}

포워딩 정보(포워딩 방식과 주소)를 관리할 ActionForward 클래스를 이렇게 정의한다. DTO 클래스의 역할과 유사하다.

protected void doProcess(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    request.setCharacterEncoding("UTF-8");
    ActionForward forward = null;
    String command = request.getServletPath();

    if(command.equals("/MemberJoinForm.me")) {
        forward = new ActionForward();
        forward.setPath("test10_member/member_join_form.jsp");
        forward.setRedirect(false);
    } else if(command.equals("/MemberLoginForm.me")) {
        forward = new ActionForward();
        forward.setPath("test10_member/member_login_form.jsp");
        forward.setRedirect(false);
    } else if(command.equals("/MemberJoinPro.me")) {
        MemberDTO member = new MemberDTO();
        member.setId(request.getParameter("id"));
        member.setPasswd(request.getParameter("passwd"));

        MemberDAO dao = new MemberDAO();
        int insertCount = dao.insertMember(member);
        
        // 회원 가입 결과 판별
        if(insertCount > 0) { // 가입 성공 시
            forward = new ActionForward();
            forward.setPath("MemberMain.me");
            forward.setRedirect(true);
        } else { // 가입 실패 시
            // 자바스크립트 출력 과정에서 포워딩 작업은 일어나지 않으므로
            // ActionForward 객체 생성은 불필요하다.
            response.setContentType("text/html; charset=UTF-8");
            PrintWriter out = response.getWriter();
            out.println("<script>");
            out.println("alert('회원 가입 실패!')");
            out.println("history.back()");
            out.println("</script>");
        }

    if(forward != null) {
        if(forward.isRedirect()) { // 리다이렉트 방식
            response.sendRedirect(forward.getPath());
        } else { // 디스패치 방식
            RequestDispatcher dispatcher = request.getRequestDispatcher(forward.getPath());
            dispatcher.forward(request, response);
        }
    }
}

먼저 포워딩 정보를 관리하는 ActionForward 타입 변수를 선언한다. if 문 내에서 포워딩 정보 설정 시 ActionForward 객체를 생성하여 사용할 것이다. set메서드로 경로와 포워딩 방식을 설정해 두고 포워딩은 가장 마지막에서 처리한다.
주의할 점은 자바스크립트를 출력해야하는 경우 ActionForward 객체를 생성하지 않도록 해야한다. 자바스크립트로 인해 ActionForward 객체를 생성하지 않으면 현재 메서드가 종료되면서 자바스크립트 코드를 응답 데이터로 전송하고 ActionForward 객체 생성할 경우 해당 응답 정보(response 객체)를 응답 데이터로 전송한다.

Leave a comment