코코무의 코딩캔버스

[JSP] MVC 패턴을 위한 서블릿(Servlet) 본문

JSP

[JSP] MVC 패턴을 위한 서블릿(Servlet)

코코무 2024. 2. 13. 13:13
서블릿(Servlet)

 

MVC 패턴을 적용한 모델2 방식의 게시판을 제작하기 위해서 필요한 기술

👉 JSP와 Servlet의 관계 https://pre-walkingdeveloper.tistory.com/93

 

 

  • 서블릿의 개념 및 특징

JSP가 나오기 전, Java로 웹 애플리케이션을 개발할 수 있도록 만든 기술이다. 서버 단에서 클라이언트의 요청을 받아 처리한 후 응답하는 역할을 한다.

Servlet이 등장한 초기에는 JSP 템플릿 기술이 없었기 때문에 모든 웹 페이지의 프레젠테이션 로직도 서블릿에서 처리해야 했다. 주로 Servlet이 HTML 코드를 직접 생성하는 방식으로 View를 처리했다.

java 클래스에서 HTML 코드를 작성할 수 있는지 오랜만에 자각하였다. 예를 들어,

response.setContentType("text/html");

response.getWriter().println("<html>");

이런 식으로 사용했었던 것이다. 이걸 감안한다면 JSP는 고급 템플릿 기술이라 말할만 하다.

 

클라이언트의 요청에 대해 동적으로 작동하는 웹 애플리케이션 컴포넌트(component)로 MVC 모델에서 컨트롤러(controller) 역할을 한다. 모든 메서드는 스레드로 동작하며 javax.servlet.http 패키지의 HttpServlet 클래스를 상속 받는다.

 

서블릿을 만들었다고 해서 스스로 작동하는 것은 아니고 서블릿을 관리하는 컨테이너가 필요하다.

Tomcat이 바로 그 서블릿 컨테이너인데 특징은 다음과 같다.

1. 서블릿의 수명주기를 관리한다.

서블릿을 인스턴스화 한 후 초기화 → 요청에 맞는 적절한 메서드 호출 → 응답 후에는 garbage 컬렉션을 통해 객체를 소멸시킨다.

2. 요청이 오면 스레드를 생성하여 처리한다.

여러 요청을 동시에 처리하는 멀티스레딩 방식으로 관리한다.

3. 클라이언트의 요청을 받아 응답을 보낼 수 있도록 통신을 지원한다.

클라이언트와 통신하려면 특정 포트, 소켓을 열고 I/O 스트림을 생성하는 등(API 제공) 선언적인 보안 관리를 하며 JSP는 별도의 보안 기능을 지원하므로 별도로 구현하지 않아도 된다.

 

 

 

  • 서블릿의 동작 방식

서블릿은 MVC 패턴에서 컨트롤러 역할을 한다.

 

[Tomcat]

1) 클라이언트의 요청을 받아

2) 분석한 후 요청을

3) 처리할 서블릿을 찾는다. 서블릿을 통해

4) 비즈니스 서비스 로직을 호출하고 모델로부터 그

5) 결과값을 받아 request나 session 영역에 저장한 후 결과값을

6) 출력할 view를 선택한다. 최종적으로 선택된

7) 뷰(jsp 페이지)의 결과 값을 출력한 후 요청

8) 클라이언트에게 응답한다.

 

동작 예시:

 

  • 서블릿 작성 규칙

기본적으로 javax.servlet, javax.servlet.http, java.io 패키지를 import 한다.

1. 클래스는 반드시 public으로 선언하고 HttpServlet을 상속 받는다.

2. 사용자의 요청을 처리하기 위해 doGet(), doPost() 메서드를 반드시 오버라이딩 해야 한다.

3. doGet(), doPost() 메서드는 ServletException과 IOException 예외를 던지도록 선언한다(throws).

4. doGet(), doPost() 메서드를 호출할 때 매개변수는 HttpServletRequest와 HttpServletResponse를 사용한다.

 

작성 방법은 1. 클래스 추가 시 servlet을 사용, 2. 클래스를 일반적으로 선언하고 상속과 오버라이드를 진행 두 가지가 있다.

 

 

  • 서블릿 작성

서블릿 작성은 클라이언트의 요청을 전달할 요청명을 결정하는 일부터 시작된다.

jsp에서는 클라이언트 요청을 jsp가 직접 받아 처리하지만, 서블릿은 요청명을 기준으로 이를 처리할 서블릿을 선택하게 된다.

요청명과 서블릿을 연결해주는 작업을 매핑(mapping)이라고 하며 2가지의 방식으로 처리된다.

 

1. web.xml에 기술

<servlet> <!-- 서블릿 등록 -->
	<servlet-name>서블릿명</servlet-name> <!-- 서블릿 매핑을 위한 서블릿명 -->
    <servlet-class>서블릿 패키지명.서블릿 클래스 파일명</servlet-class>
</servlet>
<servlet-mapping> <!-- 서블릿과 요청명 매핑 -->
	<servlet-name>서블릿명</servlet-name>
    <url-pattern>/jsp 폴더명/서블릿명.mit></url-pattern> <!-- 컨텍스트 루트를 제외한 요청명 적기(보통 .do 사용) -->

 

 

.java 형식의 클래스를 만든다.

doGet을 오버라이딩하여 서블릿을 진행한다.

public class HelloServlet extends HttpServlet{
	
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
    	throws ServletException, IOException {
        
        req.setAttribute("message", "Hello Servlet");
        req.getRequestDispatcher("/13Servlet/HelloServlet.jsp").forward(req, resp);
	}
}

request 영역에 message라는 속성으로 데이터를 저장하고 HelloServlet.jsp로 포워드한다.

req는 doGet() 메서드의 매개변수로 전달 받은 request 내장 객체(request 영역에 저장된 데이터를 포워드된 페이지까지 공유가 되므로 해당 속성을 출력함)

<%= request.getAttribute("message") %>
<br>
<a href="./HelloServlet.mit">바로가기</a>

HelloServlet.jsp를 바로 실행하면 서블릿을 거치지 않기 때문에 java 코드가 실행되지 않는다. 하지만 '바로가기'를 클릭하여 HelloServlet.mit로 요청을 하면 서블릿을 통해 구현되기 때문에 web.xml에 있는 내용을 거쳐 주소 표시줄에 HelloServlet.mit로 출력되면서 req, resp가 정상 작동된다.

 

서블릿은 요청명을 통해 서블릿이 직접 요청을 처리(jsp) → 데이터를 영역에 저장(request) → 결과를 출력할 jsp를 선택(web.xml) → 영역을 통해 공유된 데이터를 출력하는 형식(HelloServlet.mit)

 

 

2. @WebServlet 애너테이션을 사용하여 코드에 직접 명시하는 방법

${ message }
<br>
<a href="<%= request.getContextPath() %>/13Servlet/AnnoMapping.mit>바로가기</a>

EL을 이용하여 request 영역에 저장된 데이터를 출력한다.

컨텍스트 루트 경로를 반환(/MustHaveJSP 반환), 요청명이 합쳐져서 바로가기 링크를 완성한다.

 

@WebServlet("/13Servlet/AnnoMapping.mit")
public class AnnoMapping extends HttpServlet{

	@Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
    	throws ServletException, IOException{
        
        req.setAttribute("message", "@WebServlet으로 매핑");
        req.getRequestDispatcher("/13Servlet/AnnoMapping.jsp").forward(req, resp);

애너테이션을 이용하여 이 서블릿 요청이 /13Servlet/AnnoMapping.mit를 처리한다. web.xml에서 <url-pattern>에 입력한 값과 똑같이 컨텍스트 루트를 제외하고 작성한다.

HttpServlet을 상속하고 doGet() 메서드를 오버라이딩해서 request 영역에 데이터를 저장한 후, AnnoMapping.jsp로 포워드한다.

 

 

 

  • web.xml과 애너테이션 비교 분석

애너테이션은

1. 요청명(url-pattern)이 변경된다면 해당 서블릿 클래스를 수정한 후 다시 컴파일 해야 한다.

2. 서블릿 개수가 많아지면 특정 요청명을 처리하는 클래스를 찾기가 어려워진다.

3. web.xml을 사용하면 요청명만으로 클래스를 쉽게 찾을 수 있지만 @WebServlet 방식에서는 검색할 방법이 마땅히 없다. 그래서 요청명과 클래스명을 매핑해주는 일정한 패턴이 없다면 유지보수 하기가 힘들다.

4. 서비스를 개편하면서 이 요청명에 해당하는 동작을 수정해야 한다. web.xml은 파일을 열어 Ctrl+F(찾기) 해서 요청명을 입력하면 즉시 찾을 수 있다.

5. @WebServlet을 사용하면 study.do를 사용하여 검색하는 방법이 전혀 없다(클래스를 일일히 열어 확인 해야 함).

 

3. JSP 없이 서블릿에서 바로 응답 출력

위 두 가지 방식은 매핑 방식이 달랐으나 공통적으로 View를 담당하는 JSP를 사용하여 응답 내용을 출력했다. 비동기 방식으로 통신할 때 xml이나 json을 사용하는 경우가 있다. 순수 데이터만 출력해야 하는 경우에는 서블릿에서 직접 출력하는 것이 편리할 때가 있다.

 

4. 한 번의 매핑으로 여러 가지 요청 처리

요청명이 추가되면 이에 따른 매핑도 함께 추가해야 한다. 

 

1) 회원가입, 로그인, 자유게시판의 경로를 전달 받아 처리하는 코드

${ resultValue }
<ol>
	<li>URI : ${ uri }</li>
    <li>요청명: ${ commandStr }</li>
</ol>
<ul>
	<li><a href="../13Servlet/regist.mit">회원가입</a>
    <li><a href="../13Servlet/login.mit">로그인</a>
    <li><a href="../13Servlet/freeBoard.mit">자유게시판</a>
</ul>

 

2) mit로 끝나는 모든 요청을 서블릿 하나로 처리할 수 있다.

@WebServlet("*.mit")
public class FrontController extends HttpServlet{

	@Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
    	throws ServletException, IOException {
        
        String uri = req.getRequestURI();
        int lastSlash = uri.lastIndexOf("/");
        String commandStr = uri.substring(lastSlash);
        
        if(commandStr.equals("/regist.mit"))
        	registFunc(req);
        else if(commandStr.equals("/login.mit"))
        	loginFunc(req);
        else if(commandStr.equals("/freeBoard.mit"))
        	freeBoardFunc(req);
        
        req.setAttribute("uri", uri);
        req.setAttribute("commandStr", commandStr);
        req.getRequestDispatcher("/13Servlet/FrontController.jsp").forward(req, resp);
	}
    
    //페이지별 처리 메서드
    void registFunc(HttpServletRequest req){
    	req.setAttribute("resultValue", "회원가입");
    }
    
    void loginFunc(HttpServletRequest req){
    	req.setAttribute("resultValue", "로그인");
    }
    
    void freeBoardFunc(HttpServletRequest req){
    	req.setAttribute("resultValue", "자유게시판");
    }

 

 

 

  • MVC 패턴을 적용한 회원인증 구현

회원인증 구성도

M(Java): MemberDAO.java / MemberDTO.java → DB와 연동V(JSP): MemberAuth.jspC(Servlet): MemberAuth.java

 

${ authMessage }
<br>
<a href="./MemberAuth.mvc?id=kkw&pass=1234">회원인증(회원)</a>
<a href="./MemberAuth.mvc?id=ez304&pass=1234">회원인증(관리자)</a>
<a href="./MemberAuth.mvc?id=kkk&pass=1234">회원인증(비회원)</a>

회원인증 처리 후에 결과 메시지를 출력한다.

이전에 만든 member 테이블에 존재하는 아이디 여부를 확인 검증한다.

 

<servlet>
	<servlet-name>MemberAuth</servlet-name>
    <servlet-class>servlet.MemberAuth</servlet-class>
    <init-param>
    	<param-name>admin_id</param-name>
        <param-value>ez304</param-value>
    </init-param>
</servlet>
<servlet-mapping>
	<servlet-name>MemberAuth</servlet-name>
    <url-pattern>/13Servlet/MemberAuth.mvc</url-pattern>
</servlet-mapping>