본문 바로가기
JSP/성낙현의 JSP 자바 웹 프로그래밍

서블릿(Servlet)

by k-mozzi 2023. 6. 26.
반응형
Preface

 

이번 장에선 서블릿에 대해 공부했다.

 

서블릿을 사용해 코드를 작성하니 확실히 코드 자체가 간결해지긴 했지만, 여러 파일을 연결하는 작업이 생각보다 귀찮고 까다롭다.

 

JSP의 개념 부분은 이번 장이 마지막인 것 같다.

 

책 전체에 걸쳐 꽤나 복잡한 작업들이 많이 있었지만,

 

개념 자체가 어려운 것은 아니라 여러 번 코드를 작성하다보면 자연스럽게 손에 익을 듯하다.


 

1. 서블릿이란?

 

 

- 서블릿(Servlet): JSP가 나오기 전, 자바로 웹 애플리케이션을 개발할 수 있도록 만든 기술

→ 서버 단에서 클라이언트의 요청을 받아 처리한 후 응답하는 역할

 

 

- 서블릿의 특징

1) 클라이언트의 요청에 대해 동적으로 작동하는 웹 애플리케이션 컴포넌트이다.

2) MVC 모델에서 컨트롤러 역할을 한다.

3) 모든 메서드는 스레드로 동작된다.

4) HttpServlet 클래스를 상속받는다.

 


 

2. 서블릿 컨테이너

 

 

- 서블릿 컨테이너: 서블릿을 관리하는 컨테이너

→ ex: Tomcat

 

 

- 서블릿 컨테이너의 역할

1) 통신 지원: 서버가 특정 포트로 소켓을 열고 I/O 스트림을 생성하는 등의 과정을 간단히 해주는 API를 제공한다.

2) 수명주기 관리: 서블릿을 인스턴스화한 후 초기화하고, 요청에 맞는 적절한 메서드를 호출한다.

→ 응답 후에는 가비지 컬렉션을 통해 객체를 소멸시킨다.

3) 멀티스레딩 관리: 서블릿 요청들을 스레드를 생성해 처리한다.

→ 멀티스레드 방식으로 여러 요청을 동시에 처리할 수 있다.

cf. 멀티 스레드: 여러 개의 스레드를 하나의 프로세스에서 동시 실행

cf. 멀티 프로세스: 여러 개의 프로세스를 동시 실행

4) 선언적인 보안 관리 및 JSP 지원: 보안 기능을 지원한다.

5) 처음 요청이 들어왔을 때 서블릿 객체를 생성하면 이후부턴 기존 객체를 재사용하여 처리 속도를 높인다.

 


 

3. 서블릿의 동작 방식

 

 

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

1) 모델 담당: DAO / DTO

2) 뷰를 담당: JSP

 

 

- 컨트롤러와 DAO 사이에 비즈니스 서비스 객체가 있는데, 서비스 객체는 컨트롤러가 요청을 분석한 후 호출되어 실제 비즈니스 로직을 처리한다.

→ 실습 코드에선 서블릿이 컨트롤롸 서비스 객체의 역할을 동시에 하도록 구현한다.

 


 

4. 서블릿 작성 규칙

 

 

- 서블릿 작성 규칙

1) javax.servlet, javax.servlet.http, java.io 패키지를 import한다.

2) 서블릿 클래스는 public으로 선언해야하며, HttpServlet을 상속해야 한다.

3) 사용자의 요청 처리를 위해 doGet( ) / doPost( ) 메서드를 오버라이딩해야 한다.

4) doGet( ) / doPost( ) 메서드는 ServletException, IOException 예외를 throws해야 한다.

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

→ request와 response 객체의 자료형이다.

package 패키지명;

import java.io.IOException;

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

public calss 서블릿클래스명 extends HttpServlet {
	@Override
    protected void service(HttpServletRequest req, HttpServletResponse resp)
    	throws ServletException, IOException {
        
    }
}

 


 

5. 서블릿 작성

 

 

- 서블릿은 요청명을 기준으로 이를 처리할 서블릿을 선택한다.

 

 

- 요청명: "localhost:8081/MustHaveJSP/JSP_Example_Code/ch13/MemberAuth.jsp"에서 컨텍스트 루트명인 "MustHaveJSP"를 제외한 "JSP_Example_Code/ch13/MemberAuth.jsp" 부분

→ "localhost:8081"는 호스트명이다.

 

 

- 매핑(mapping): 요청명과 서블릿을 연결해주는 작업

1) web.xml에 기술하는 방법

<servlet>
	<servlet-name>HelloServlet</servlet-name>
	<servlet-class>servlet.HelloServlet</servlet-class>
</servlet>
<servlet-mapping>
	<servlet-name>HelloServlet</servlet-name>
	<url-pattern>/JSP_Example_Code/ch13/HelloServlet.do</url-pattern>
	<!-- 컨텍스트 루트를 제외한 요청명 -->
</servlet-mapping>


<servlet>: 서블릿 클래스를 등록한다.
<servlet-name>: 서블릿을 참조할 때 사용할 이름을 입력한다.
<servlet-class>: 패키지를 포함한 서블릿 클래스명을 입력한다.
<servlet-mapping>: 매핑 정보를 등록한다.
<servlet-name>: <servlet>에서 사용한 <servlet-name>과 동일한 이름을 입력한다.
<url-pattern>: 요청명으로 사용할 경로를 입력한다. (컨텍스트 루트를 제외하고 '/'로 시작하는 경로)

 

2) @WebServlet 어노테이션을 사용해 코드에 직접 명시하는 방법

<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
	<h2>어노테이션으로 매핑하기</h2>
	<p>
		<strong>${ message }</strong>
		<br>
		<a href="<%=request.getContextPath()%>/JSP_Example_Code/ch13/AnnoMapping.do">바로가기</a>
	</p>
</body>
</html>

 

package servlet;

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;

@WebServlet("/JSP_Example_Code/ch13/AnnoMapping.do")	//컨텍스트 루트 제외
public class AnnoMapping extends HttpServlet {

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

 

 

- @WebServlet 어노테이션을 사용할 때의 단점

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

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

→ 요청명만으로 서블릿 클래스명을 바로 알 수 있도록 명확한 이름 규칙을 정해두는 것이 좋다.

 

 

- JSP 없이 서블릿에서 바로 응답 출력: 순수 데이터만 출력해야 하는 경우에 사용한다.

<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
	<h2>web.xml에서 매핑 후 Servlet에서 직접 출력하기</h2>
	<form method="post" action="DirectServletPrint.do">
		<input type="submit" value="바로가기" />
	</form>
</body>
</html>

 

<servlet>
	<servlet-name>DirectServletPrint</servlet-name>
	<servlet-class>servlet.DirectServletPrint</servlet-class>
</servlet>
<servlet-mapping>
	<servlet-name>DirectServletPrint</servlet-name>
	<url-pattern>/JSP_Example_Code/ch13/DirectServletPrint.do</url-pattern>
</servlet-mapping>

 

package servlet;

import java.io.IOException;
import java.io.PrintWriter;

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

public class DirectServletPrint extends HttpServlet {

	@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		resp.setContentType("text/html;charset=UTF-8");
		PrintWriter writer = resp.getWriter();

		writer.println("<html>");
		writer.println("<head><title>DirectServletPrint</title></head>");
		writer.println("<body>");
		writer.println("<h2>서블릿에서 직접 출력합니다.</h2>");
		writer.println("<p>jsp로 포워드하지 않습니다.</p>");
		writer.println("</body>");
		writer.println("</html>");
		writer.close();
	}
}

 

 

- 한 번의 매핑으로 여러 요청 처리

<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
	<h3>한 번의 매핑으로 여러 가지 요청 처리하기</h3>
	${ resultValue }
	<ol>
		<li>URI: ${ uri }</li>
		<li>요청명: ${ commandStr }</li>
	</ol>
	<ul>
		<li><a href="regist.one">회원가입</a></li>
		<li><a href="login.one">로그인</a></li>
		<li><a href="freeboard.one">자유게시판</a></li>
	</ul>
</body>
</html>

 

package servlet;

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;

@WebServlet("*.one")
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.one")) {
			registFunc(req);
		} else if (commandStr.equals("/login.one")) {
			loginFunc(req);
		} else if (commandStr.equals("/freeboard.one")) {
			freeboardFunc(req);
		}

		req.setAttribute("uri", uri);
		req.setAttribute("commandStr", commandStr);
		req.getRequestDispatcher("FrontController.jsp").forward(req, resp);
	}

	void registFunc(HttpServletRequest req) {
		req.setAttribute("resultValue", "<h4>회원가입</h4>");
	}

	void loginFunc(HttpServletRequest req) {
		req.setAttribute("resultValue", "<h4>로그인</h4>");
	}

	void freeboardFunc(HttpServletRequest req) {
		req.setAttribute("resultValue", "<h4>자유게시판</h4>");
	}
}

 

 

- 서블릿의 수명 주기: 클라이언트의 요청이 들어오면 서블릿 객체 생성하고, 서블릿을 초기화한 후 요청을 처리하며, 서버를 종료할 때 서블릿 객체를 소멸시키는 일련의 활동

→ 서블릿 컨테이너가 담당하며, 각 단계마다 자동으로 특정 메서드를 호출해 해당 단계에 필요한 기능을 수행한다.

 

 

- 수명주기 메서드: 각 단계마다 호출되는 콜백 메서드

1) @PostConstruct: 객체 생성 직후, init( ) 메서드를 호출하기 전 호출

→ 어노테이션을 사용하므로 메서드명은 개발자가 지정

2) init( ): 서블릿의 초기화 작업을 수행하기 위해 호출

→ 최초 요청 시 딱 한 번만 호출

3) service( ): 클라이언트의 요청을 처리하기 위해 호출

→ 전송 방식에 따라 알맞은 메서드를 호출

4) destroy( ): 서블릿이 새롭게 컴파일되거나, 서버가 종료될 때 호출

5) @PreDestroy: destroy( ) 메서드가 실행된 후 컨테이너가 서블릿 객체를 제거하는 과정에서 호출

→ 메서드명은 개발자가 지정

<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
	<script>
		function requestAction(frm, met) {
			if (met == 1) {
				frm.method = 'get';
			} else {
				frm.method = 'post';
			}
			frm.submit();
		}
	</script>

	<h2>서블릿 수명주기(Life Cycle) 메서드</h2>
	<form action="LifeCycle.do">
		<input type="button" value="Get 방식 요청하기"
			onclick="requestAction(this.form, 1)" /> 
		<input type="button" value="Post 방식 요청하기"
			onclick="requestAction(this.form, 2)" />
	</form>
</body>
</html>

 

package servlet;

import java.io.IOException;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet("/JSP_Example_Code/ch13/LifeCycle.do")
public class LifeCycle extends HttpServlet {

	@PostConstruct
	public void myPostConstruct() {
		System.out.println("call myPostConstruct()");
	}

	@Override
	public void init() throws ServletException {
		System.out.println("call init()");
	}

	@Override
	protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		System.out.println("call service()");
		super.service(req, resp);
	}

	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		System.out.println("call doGet()");
		req.getRequestDispatcher("/JSP_Example_Code/ch13/LifeCycle.jsp").forward(req, resp);
	}

	@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		System.out.println("call doPost()");
		req.getRequestDispatcher("/JSP_Example_Code/ch13/LifeCycle.jsp").forward(req, resp);
	}

	@Override
	public void destroy() {
		System.out.println("call destroy()");
	}

	@PreDestroy
	public void myPreDestroy() {
		System.out.println("call myPreDestroy()");
	}
}

 


 

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

 

 

-  뷰

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
	<h2>MVC 패턴으로 회원인증하기</h2>
	<p>
		<strong>${ authMessage }</strong><br>
		<a href="MemberAuth.mvc?id=nakja&pass=1234">회원인증(관리자)</a>
		&nbsp;&nbsp;
		<a href="MemberAuth.mvc?id=musthave&pass=1234">회원인증(회원)</a>
		&nbsp;&nbsp;
		<a href="MemberAuth.mvc?id=stranger&pass=1234">회원인증(비회원)</a>
	</p>
</body>
</html>

 

 

- 컨트롤러

<servlet>
	<servlet-name>MemberAuth</servlet-name>
	<servlet-class>servlet.MemberAuth</servlet-class>
	<init-param>
		<param-name>admin_id</param-name>
		<param-value>nakja</param-value>
	</init-param>
</servlet>
<servlet-mapping>
	<servlet-name>MemberAuth</servlet-name>
	<url-pattern>/JSP_Example_Code/ch13/MemberAuth.mvc</url-pattern>
</servlet-mapping>

 

package servlet;

import java.io.IOException;

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

import membership.*;

public class MemberAuth extends HttpServlet {
	MemberDAO dao;

	@Override
	public void init() throws ServletException {
		// application 내장 객체 얻기
		ServletContext application = this.getServletContext();

		// web.xml에서 DB 연결 정보 얻기
		String driver = application.getInitParameter("OracleDriver");
		String connectUrl = application.getInitParameter("OracleURL");
		String oId = application.getInitParameter("OracleId");
		String oPass = application.getInitParameter("OraclePwd");

		// DAO 생성
		dao = new MemberDAO(driver, connectUrl, oId, oPass);
	}

	@Override
	protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		String admin_id = this.getInitParameter("admin_id");

		String id = req.getParameter("id");
		String pass = req.getParameter("pass");

		MemberDTO memberDTO = dao.getMemberDTO(id, pass);

		String memberName = memberDTO.getName();
		if (memberName != null) {
			req.setAttribute("authMessage", memberName + "회원님 안녕하세요.");
		} else {
			if (admin_id.equals(id)) {
				req.setAttribute("authMessage", admin_id + "는 최고 관리자입니다.");
			} else {
				req.setAttribute("authMessage", "귀하는 회원이 아닙니다.");
			}
		}
		req.getRequestDispatcher("/JSP_Example_Code/ch13/MemberAuth.jsp").forward(req, resp);
	}

	@Override
	public void destroy() {
		dao.close();
	}
}

 

728x90
반응형

댓글