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>
<a href="MemberAuth.mvc?id=musthave&pass=1234">회원인증(회원)</a>
<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();
}
}
'JSP > 성낙현의 JSP 자바 웹 프로그래밍' 카테고리의 다른 글
웹소켓으로 채팅 프로그램 만들기 (0) | 2023.06.30 |
---|---|
JSP, Oracle을 사용한 자료실형 게시판(MVC 패턴) (0) | 2023.06.29 |
파일 업로드 및 다운로드 (0) | 2023.06.23 |
JSP 표준 태그 라이브러리(JSTL) (0) | 2023.06.21 |
표현 언어(EL: Expression Language) (0) | 2023.06.19 |
댓글