mojo's Blog
서블릿의 이해 본문
서블릿의 개요
서블릿은 순수 자바 코드로 작성되며, 코드 자체만 보면 일반적인 자바 클래스와 다르지 않다.
다만 HttpServlet 클래스를 상속하여 서블릿 등록에 필요한 정보를 추가해야 하며, 서블릿 단독으로 실행할 수 없고 서블릿 컨테이너를 통해야 한다.
서블릿의 동작 과정은 다음과 같다.
1. HttpServlet을 상속받는 서블릿 클래스(MyServlet)을 구현한다.
2. 컨테이너는 url mapping 설정을 참고하여 서블릿을 등록한다.
3. 서블릿 객체 생성 및 init() 메서드를 실행한다.
4. 클라이언트 요청은 스레드로 동시 처리되며 각각 service() 메서드를 호출한다.
5. service() 메서드는 doGet(), doPost() 등 프로토콜에 따른 메서드를 호출한다.
6. 컨테이너 종료 혹은 웹 애플리케이션 종료 시점에 destroy() 메서드를 실행한다.
7. 서블릿을 등록 해제한다.
서블릿의 장점
- 자바를 기반으로 하므로 자바 API를 모두 사용 가능하다.
- 운영체제, 하드웨어의 영향을 받지 않으므로, 한번 개발된 애플리케이션은 다양한 서버 환경에서 실행할 수 있다.
- 웹 애플리케이션에서 효율적인 자료 공유 방법을 제공한다.
- 다양한 오픈소스 라이브러리와 개발도구를 활용할 수 있다.
서블릿의 단점
- HTML 응답을 위해서는 출력문으로 문자열 결합을 사용해야 한다.
- 서블릿에서 HTML을 포함할 경우 화면 수정이 어렵다.
- HTML 폼의 데이터 처리가 불편하다.
- 기본적으로 단일 요청과 응답을 처리하는 구조로 다양한 경로의 URL 접근을 하나의 클래스에서 처리하기 어렵다.
실제 자바 웹 개발에는 서블릿과 함께 다음과 같은 조합으로 구현하게 된다.
- 화면 구성을 위해 JSP와 같은 템플릿 엔진을 사용한다.
- REST API 구현을 위해서 JAX-RS를 사용한다.
- 복잡한 서비스 구현을 위해 프런트 컨트롤러 모델 등을 사용한다.
서블릿 클래스 구조와 생명 주기
서블릿 클래스의 구조를 살펴보도록 한다.
서블릿 자체는 자바로 구현하지만 서블릿 컨테이너에 해당 클래스가 서블릿임을 알려야 하며, 어떤 URL 접근에 실행해야 하는지 등록하는 과정이 필요하다.
서블릿 클래스는 javax.servlet.Servlet 인터페이스를 구현한 추상 클래스인 GenericServlet 클래스와 HttpServlet 클래스 중 하나를 상속해 구현하는 형태다.
대부분은 HTTP 프로토콜에 최적화되어 있는 HttpServlet 클래스를 상속해 구현하는 것이 좋다.
다음은 HttpServlet을 상속받아 doGet(), doPost() 메서드를 오버라이딩한 구조다.
public class HelloWorldServlet extends HttpServlet {
public doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
...
}
public doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
...
}
doGet() : HTTP GET 요청을 처리하기 위한 메서드다.
doPost() : HTTP POST 요청을 처리하기 위한 메서드로 보통 doGet() 을 호출해 동일하게 처리된다.
request, response : doGet(), doPost() 메서드에서 파라미터로 사용되며 서블릿 컨테이너가 클라이언트 요청과 응답을 처리할 수 있도록 서블릿에 제공하는 객체로 클라이언트와의 상호작용에 필요한 다양한 메서드를 제공한다.
request와 response는 session, application과 함께 JSP의 내장객체이기도 하며 속성 관리 기능을 이용해 컨트롤러와 뷰 페이지 간 데이터 전달 등의 목적에도 사용된다.
HttpServletRequest란?
HTTP 프로토콜의 request 정보를 서블릿에 전달하기 위한 목적으로 사용한다.
이때 사용되는 클래스에는 헤더 정보, 파라미터, 쿠키, URL, URI 등의 정보를 읽어들이는 메서드와 HTTP Body의 Stream을 읽어 들이는 메서드를 가지고 있다.
서블릿 컨테이너에서 생성되고 클라이언트 요청이 doGet(), doPost()로 전달될 때 인자로 함께 전달된다.
다시 말해서 서블릿에서 클라이언트와 연결되어 처리할 작업은 모두 HttpServletRequest를 통해야 한다.
HttpServletRequest에서 제공되는 주요 메서드는 다음과 같다.
메서드 | 반환 예 | 설명 |
getParameter(name) | hong1234 | name 속성으로 전달된 파라미터 값 |
getParameterValues(name) | {hong, jo, kang} | 동일한 name 속성으로 전달된 모든 파라미터 값 |
getRequestURL() | http://www.xxx.com:8080/shop/list.jsp | URL |
getRequestURI() | /shop/list.jsp | URL에서 스키마, 서버 이름, 포트 번호 제외한 나머지 주소와 파라미터 |
getScheme() | http | http, https, FTP와 같은 프로토콜 |
getServerName() | www.xxx.com | 서버 이름 |
getServerPort() | 8080 | 서버 포트 |
getContextPath() | /shop | 콘텍스트 경로 |
getMethod() | GET | GET, POST 등의 HTTP 메서드 |
isSecure() | false | SSL 보안 여부, https와 같은 보안 채널의 사용 여부 (true, false) |
getLocale() | ko_KR | 지역 정보 |
getProtocol() | HTTP/1.1 | 사용하는 프로토콜, 프로토콜/메이저 버젼. 마이너 버젼 |
getLocalAddr() | 127.0.0.1 | 서버의 로컬 IP 주소 |
getRemoteAddr() | 210.102.111.212 | 클라이언트 IP 주소 |
HttpServletResponse 란?
클라이언트와 연결된 처리가 가능하다.
다만 클라이언트에서 서버로 전달하는 것과 관련된 것이 아니라 서버에서 클라이언트로 전달하려는 목적을 위한 기능으로 구성된다.
서블릿 컨테이너는 요청 클라이언트에 응답을 보내기 위한 HttpServletResponse 객체를 생성하여 서블릿에 전달한다.
서블릿은 해당 객체를 이용하여 content type, 응답 코드, 응답 메시지 등을 전송할 수 있다.
HttpServletResponse 에서 제공하는 주요 메서드는 다음과 같다.
메서드 | 설명 | |
sendRedirect(String location) | 클라이언트에 리다이렉트 응답을 보낸 후 특정 URL로 다시 요청하게 함 | |
getWriter() | 클라이언트로 데이터를 보내기 위한 출력 스트림을 리턴 | |
setContentType(String type) | 클라이언트에 전달되는 콘텐츠 타입을 지정 | |
addCookie(Cookie cookie) | 응답에 쿠키를 추가 | |
addHeader(String name, String value) | 헤더에 name과 value를 추가 | |
encodeURL(string url) | 클라이언트가 쿠키를 지원하지 않을 때 세션 id를 포함한 특정 URL을 인코딩 | |
getHeaderNames() | 현재 응답이 헤더에 포함된 name을 얻어옴 |
request, response 전체 메소드는 https://javaee.github.io/javaee-spec/javadocs/javax/servlet/package-summary.html 를 참고하자.
서블릿 클래스만으로는 톰캣에서 실행이 불가능하기 때문에 web.xml이나 애너테이션으로 서블릿임을 선언해야 한다.
xml 파일을 사용하는 동작 방식은 다음과 같다.
<?xml version="1.0" encoding="UTF-8"?>
<web-app ...>
<servlet>
<servlet-name>HelloWorld</servlet-name> // servlet 이름
<servlet-class>jwbook.servlet.HelloServlet</servlet-class> // 서블릿 클래스 지정
</servlet>
<servlet>
<servlet-name>HelloWorld</servlet-name> //servlet name을 매핑
<url-pattern>/hello</url-pattern> // 서블릿 요청 주소 매핑
</servlet>
</web-app>
애너테이션 방식을 사용하는 방법은 다음과 같다.
@WebServlet(descrption="Hello World Servlet", urlPatterns="/hello")
public class HelloWorldServlet extends HttpServlet {
...
}
urlPatterns : 어떤 클라이언트 요청에 해당 서블릿을 실행할지 지정하는 것으로 여러 url을 등록할 수도 있다.
애너테이션은 https://www.baeldung.com/java-custom-annotation 을 참고하도록 한다.
properties 파일 사용하기 :
자바에서는 properties 파일을 사용하는 방식이 가장 단순하다.
다만 복잡한 구조 표현이 어려워서 서블릿 컨테이너나 스프링 프레임워크 같은 경우 초기에 xml을 주로 사용했고 지금은 자바 애너테이션과 yaml을 사용한 설정 파일 등을 주로 활용한다.
서블릿의 생명 주기란?
서블릿은 컨테이너에 의해 동작하므로 객체의 생성 과정과 종료 과정도 컨테이너 안에서 이뤄진다.
이와 같이 객체의 생성에서 종료에 이르는 과정을 생명 주기(Life Cycle)라고 하며, 필요에 따라 해당 생명 주기 안에서 적절한 기능을 구현할 수 있다.
또한 웹의 특성상 비연결 구조로 인해 웹 페이지 간 데이터 공유가 안 되는 문제 해결을 위해 컨테이너에 의해 생성되고 관리되는 특별한 객체인 Scope Object를 잘 활용할 수 있어야 한다.
서블릿 클래스는 기본적으로 doGet(), doPost()와 같이 HTTP 요청 메서드에 따라 필요한 메서드를 오버라이딩해 구현하며, 서블릿 컨테이너에 의해 객체의 생성, 소멸 등이 관리되므로 필요에 따라 특정 생명 주기 이벤트에 동작하는 메서드를 구현하기도 한다.
서블릿의 생성과 소멸은 다음과 같은 과정을 거친다.
1. 사용자 URL 요청에 따른 서블릿을 실행한다.
2. 이때 서블릿 인스턴스가 생성되지 않을 경우 인스턴스를 생성한 후 init() 메서드를 호출한다.
3. 이미 객체가 생성되어 있는 경우라면 각 요청별로 서블릿 컨테이너에서 스레드를 생성해 서블릿의 service() 메서드를 호출한다.
4. 사용자 요청에 따라 doGet(), doPost() 등의 메서드를 호출한다.
5. 서블릿 컨테이너의 종료를 포함해 서블릿 변경 등 기존 서블릿을 종료해야 할 때 destroy() 메서드를 호출한다.
※ 서블릿 초기화 : init() 메서드
클라이언트 요청이 들어오면 컨테이너는 해당 서블릿이 메모리에 있는지 확인한다.
해당 서블릿이 만약 메모리에 없을 경우 서블릿을 메모리에 적재해야 하는데, 이 때 init() 메서드를 통해 각종 초기화 작업을 수행한다.
처음 한번만 실행되므로 해당 서블릿에 각각의 스레드에서 공통적으로 사용하기 위해 필요한 작업이 있다면 init() 메서드를 오버라이딩해서 구현한다.
만약 실행 중에 서블릿이 변경될 경우, 기존 서블릿은 종료되고 다시 시작되면서 init() 메서드가 호출된다.
※ 요청/응답 : service() 메서드
init() 이후 요청은 스레드로 실행되며, service() 메서드를 통해 각각 doGet()이나 doPost()로 분기된다.
이때 파라미터로 HttpServletRequest와 HttpServletResponse 클래스 타입인 request, response 객체가 제공되는데, 사용자 요청 처리는 request, 응답 처리는 response 객체로 처리한다.
※ 서블릿 종료 : destroy() 메서드
컨테이너로부터 서블릿 종료 요청이 있을 때(종료 or 웹 어플리케이션을 재등록) destroy() 메서드를 호출한다.
init() 메서드와 마찬가지로 한번만 실행되며, 서블릿이 종료되면서 정리해야 할 작업이 있을 때 destroy() 메서드를 오버라이딩해서 구현하면 된다.
서블릿 소스코드의 변경이 감지될 경우?
실제 실행중인 서블릿의 소스코드가 변경되는 것을 서블릿 컨테이너가 직접 감지할 수는 없으며, 이클립스와 같은 개발도구에서 톰캣을 실행할 경우 코드 변경을 이클립스에서 감지하고 컴파일한 다음 필요한 경우 해당 웹 애플리케이션만 다시 시작하거나 톰캣을 다시 시작하게 된다.
페이지 이동과 정보 공유
페이지 이동 방법 :
컨트롤러에서 사용자 요청을 처리한 다음에 적절한 뷰로 이동할 수 있어야 한다.
이때 뷰에서 보여줄 데이터를 포함해서 이동해야 하는 경우, 그렇지 않은 경우가 있다.
※ 데이터를 포함하지 않는 경우 :
사용자 요청 처리 후 별도의 데이터를 포함하지 않는다면 해당 페이지로 바로 리다렉션할 수 있다.
혹은 세션에 데이터를 저장한 경우, 세션이 유효한 동안 모든 페이지에서 세션 정보를 참조할 수 있어 리다렉션을 통해서도 데이터 참조가 가능하다.
JSP, 서블릿 모두 response.sendRedirect()를 사용할 수 있다.
response.sendRedirect("main.jsp");
※ 데이터를 포함하는 경우 :
만일 데이터를 포함하여 이동한다면 request 속성으로 데이터를 넣은 후 원하는 페이지로 포워딩해야 한다.
데이터 활용 목적에 따라 session이나 application을 사용할 수도 있으며 여러 데이터를 포함하는 것도 가능하다.
데이터를 포함하여 포워딩할 경우, 다음과 같이 구현할 수 있다.
① JSP로 구현할 경우
<%
request.setAttribute("member", m);
pageContext.forwared("userInfo.jsp");
%>
② 서블릿으로 구현할 경우
doGet(...) {
...
request.setAttribute("member", m);
RequestDispatcher dispatcher = request.getRequestDispatcher("userInfo.jsp");
dispatcher.forward(request, response);
}
③ 스프링 프레임워크로 구현할 경우
@GetMapping("info")
public String getMemberInfo(int id, Model model) {
...
model.addAttribute("member", m);
return "userInfo";
}
인자로 전달된 모델 객체에 원하는 데이터를 저장하고 뷰 페이지 이름을 리턴하기만 하면 된다.
리턴되는 문자열 값은 뷰 페이지의 이름이며 확장자는 생략된다.
정보 공유란?
WWW은 비연결형 구조의 HTTP를 사용하기 떄문에 페이지 요청과 응답이 완료되면 연결이 유지되지 않는다.
따라서 클라이언트가 다른 페이지를 요청할 때 부가적 정보가 없다면 서버는 클라이언트의 현재 상태를 알 수 없기 때문에 처음 접속한 것과 마찬가지로 인식할 수 밖에 없다.
이러한 문제를 해결하기 위한 방법으로 URL rewriting, Cookie, Session이 있다.
URL rewriting :
HTTP의 Query String을 이용하는 방식으로 URL에 파라미터를 추가해 서버로 요청하는 방식이다.
정보 유지를 위해 파라미터를 매 페이지마다 확인하고 계속 추가해줘야 하며 복잡한 정보 유지는 어렵다는 문제점이 있다.
https://www.xxx.com/shop/productInfo?user=hong&p1=0112&p2=12021&set=11786
productInfo라는 서버 프로그램을 요청하며 현재 상품 정보를 보여주면서 사용자 정보와 지금까지 선택된 상품 정보 모두 URL에 포함시킨다.
Cookie :
클라이언트에 저장되는 작은 정보를 의미한다.
서버의 요청에 의해 브라우저가 저장하게 되며 서버가 요청할 때 제공하는 형식이다.
쿠키의 특징은 다음과 같다.
- 파일로 클라이언트의 컴퓨터에 저장되는 방식이며 보안상 문제가 있을 수 있다.
- 광고 혹은 기타 목적으로 사용자의 이용 행태 추적에 이용될 수 있으며, 이러한 목적의 경우 사용자 정보 활용 동의가 필요하다.
- 연속되는 페이지 이동에 대한 정보 저장보다는 재방문 등의 확인 용도로 많이 사용된다.
- 'name=value' 형식이며 유효 기간, 요청 경로, 도메인 지정 등의 부가 속성을 포함한다.
- 주로 자바스크립트를 통해 처리하지만 HttpOnly 설정으로 서버에서만 사용할 수 있도록 설정 가능하다.
쿠키의 동작 방법은 다음과 같다.
1. 서버에서 쿠키를 생성한다.
2. 쿠키를 응답 헤더에 넣어 클라이언트로 전송한다.
3. 웹 브라우저는 쿠키를 (a) 하드 디스크(유효 기간 설정) 혹은 (b) 브라우저 메모리(유효 기간 미설정)에 저장한다.
4. 쿠키에 저장된 경로에 요청 시 받은 쿠키를 서버에 전송한다.
여기서 2의 서블릿 코드에서 쿠키를 저장하는 방법은 다음과 같다.
response.addCookie(new Cookie("name", "hong"));
response.addCookie(new Cookie("tel", "010-1234-12345"));
response.addCookie(new Cookie("email", "hong@korea.com"));
위의 코드는 다음의 HTTP 응답으로 클라이언트에 전달된다.
HTTP/1.1 200 OK
Server: Apache-Coyote /1.1
Set-Cookie: name=hong
Set-Cookie: tel=010-1234-12345
Set-Cookie: email=hong@korea.com
Content-Type: text/html;charset=utf-8
...
... HTTP BODY ...
Session :
클라이언트가 웹 애플리케이션 서버에 접속할 때 서버 쪽에 생성되는 공간으로 내부적으로는 세션 아이디를 통해 참조된다.
즉 브라우저는 서버에 접속할 때 발급받은 세션 아이디를 기억하고 서버는 해당 세션 아이디로 할당된 영역에 접근하는 형식이다.
세션의 특징은 다음과 같다.
- 세션 유효 시간이나 브라우저 종료 전까지 유지되므로 서로 다른 페이지에서도 정보 공유가 가능하다.
- 로그인 유지, 장바구니, 컨트롤러 구현 등에서 다양하게 사용된다.
- 사용자마다 생성되는 공간으로, 동시에 많은 사용자가 세션을 통해 대량의 데이터를 관리한다면 충분한 메모리를 비롯한 세션 관리 대책이 필요하다.
효율적인 세션 관리 :
세션 관리의 효율을 위해 별도의 세션 관리 데이터베이스를 사용하는 경우도 있다.
가장 일반적인 형태로는 redis라고 하는 인메모리 데이터베이스를 사용하는 것이다.
redis는 NOSQL 형태로 단순 key-value 구조의 데이터를 빠르게 처리할 수 있어 인기가 많다.
속성 관리란?
컨테이너는 서블릿 관리를 위해 몇몇 객체를 자동으로 생성하고 유지하는데, 이러한 객체 중 속성 관리 기능을 제공하며 특정 범위 동안 유지되는 객체를 Scope Object라고 한다.
각각의 객체는 관리 목적에 따라 별도의 메서드로 구현된 기능을 가지고 있고 공통적으로 '키-값' 형태의 Map 자료구조를 가진다.
이를 활용하면 페이지 간, 사용자 간 데이터 공유가 가능하다.
JSP 역시 서블릿으로 변환되기 때문에 동일하다고 볼 수 있으며, useBean 액션의 scope에 사용되는 page, request, session, application 이 여기에 해당된다.
이러한 객체는 각각 생성, 소멸 시기가 정해져 있고 서로 다른 JSP, 서블릿 간의 데이터 전달이나 공유를 위한 용도로 활용된다.
Scope Object | 클래스 | 생성 | 소멸 | 범위 |
Request | javax.servlet.ServletRequest | 현재 페이지가 요청될 때 | 다른 페이지로 이동할 때 | 현재 페이지. 포워딩의 경우 다음 페이지까지 참조 가능 |
Session | javax.servlet.http.HttpSession | 클라이언트가 서버에 접속할 때 | 일정 시간이 지나거나 브라우저가 종료될 때 | 동일 클라이언트에 대해 다른 페이지에서도 참조 가능 |
Web Context | javax.servlet.ServletContext | 웹 애플리케이션이 시작될 떄 | 웹 애플리케이션이 종료될 때 | 모든 클라이언트에서 참조 가능 |
Request와 Session을 주로 활용하게 되며 모든 사용자가 공유하거나 웹 애플리케이션 전체에서 참조가 필요한 경우 Web Context를 사용할 수 있다.
이러한 객체는 속성을 저장하고 참조하기 위해 다음 메서드가 공통적으로 제공된다.
setAttribute(String name, Object o) // 속성 저장
Object getAttribute(String name) // 속성 참조
이때 저장하고자 하는 데이터가 Object 형태라는 점을 주의해야 한다.
Object 타입은 저장할때는 상관없지만 데이터를 가지고 오는 경우 리턴된 Object 타입을 원래 저장된 데이터 타입으로 변환해야 하기 때문이다.
String name = "홍길동";
request.setAttribute("name", name);
String rname = (String)request.getAttribute("name");
'JSP' 카테고리의 다른 글
JSP의 기초 (0) | 2021.12.29 |
---|---|
서블릿 프로그래밍 (0) | 2021.12.28 |
자바 웹 개발 개요 (0) | 2021.12.28 |
ToDo 리스트 앱 만들어보기 (0) | 2021.09.05 |
회원 가입 폼 만들어보기 (0) | 2021.09.05 |