k-mozzi 2023. 6. 26. 18:56
반응형
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
반응형