[필독][기초] / 서블릿 / servlet [part 1]

2016. 1. 5. 14:47language/jsp

서블릿


서블릿이란?
게시판과 같은 프로그램을 만들기 위한 자바측 기술 중 하나이다.
java.sql 팩키지를 JDBC 라고 부르는 것처럼, javax.servlet 과 javax.servlet.http 팩키지를 서블릿이라 부른다.


서블릿은 네트워크 프로토콜과 무관하게 설계되었지만, 대부분 HTTP 프로토콜을 사용하는, 웹환경에서 동적인 컨텐츠를 만들 때에 사용된다.
참고로, JSP는 서블릿 이후에, 서블릿 기술을 기반으로 탄생했는데, 서블릿보다 동적인 웹 페이지 화면을 쉽게 만들 수 있다.

서블릿을 학습할 때는 javax.servlet, javax.servlet.http 팩키지에서 서블릿의 기본골격을 먼저 공략하는 것이 현명한 학습방법이다.


서블릿의 기본 골격
서블릿 기본골격은, 모든 서블릿이 구현해야 하는 javax.servlet.Servlet 인터페이스, 대부분의 모든 서블릿이 상속해야 하는 javax.servlet.GenericServlet 추상클래스, HTTP 프로토콜을 사용하는 서블릿이 상속해야 하는 javax.servlet.http.HttpServlet 클래스로 구성된다.
아래 그림처럼 GenericServlet 은 프로그래머가 사용하기 편하도록 javax.servlet.ServletConfig 인터페이스를 구현하고 있다.

 




Servlet 인터페이스
javax.servlet.Servlet 인터페이스는 서블릿 아키텍처의 핵심이다.
모든 서블릿은 Servlet 인터페이스를 구현해야 한다.
이 인터페이스에는 서블릿의 라이프 사이클 메소드가 선언되어 있다.
          •init() : 서블릿 초기화
          •service() : 클라이언트 요청에 대한 서비스
          •destroy() : 서비스 중지, 자원반납


init() 메소드
서블릿 컨테이너는 서블릿 객체가 생성된 후, 단 한번 init() 메소드를 호출한다.
서블릿은 init() 메소드가 완료되어야 서비스할 수 있다. init() 메소드 완료 전의 요청은 블록킹된다.
init() 메소드가 호출될 때 ServletConfig 인터페이스 타입의 객체를 매개변수로 전달받는데, 만약 web.xml 에서 서블릿 초기화 파라미터를 설정했다면, 전달받은 ServletConfig 에는 web.xml에서 설정했던 서블릿 초기화 파라미터 정보를 가지고 있게 된다.
초기화 파라미터가 있다면 init() 메소드에 서블릿의 초기화 작업을 수행하는 코드가 있어야 할 것이다.


void init(ServletConfig config) throws ServletException;



service() 메소드
클라이언트가 서블릿에 요청을 보낼때마다, 서블릿 컨테이너는 서블릿의 service() 메소드를 호출한다.
전달받은 ServletRequest 타입의 객체를 통해서 요청정보와 클라이언트가 전달한 데이터를 읽을 수 있으며, 전달받은 ServletResponse 타입의 객체를 사용하여 클라이언트에게 응답할 수 있다.
여기서 주목해야 할 점은, 서블릿 컨테이너는 새로운 쓰레드에서 service()메소드를 실행한다는 것이다.


즉, 요청시마다 새로운 쓰레드에서 service() 메소드를 동시에 실행한다.
가각의 스레드에서 동시에 실행되기에 수많은 클라이언트의 요청에 대해 지체없이 응답을 할 수 있지만, 서블릿에서 공유되는 자원(파일이나 네트워크 커넥션, static 변수, 인스턴스 변수)은 "자바기초의 스레드"에서 보았던 문제가 발생할 수 있다.
문제가 발생하지 않도록 하려면, 서블릿에서 문제가 될 수 있는 static 이나 인스턴스 변수를 만들지 않는 것이 좋다.
서블릿이 공유하는 자원을 모두 동기화하는 것은 대부분의 경우에 옳은 코딩이 아니다.


void service(ServletRequest req, ServletResponse res)
          throws ServletException, IOException;



destroy() 메소드
서블릿이 더이상 서비스를 하지 말아야 할 때 서블릿 컨테이너에 의해 호출된다.
 이 메소드는 프로그래머가 호출하는게 아니다.
따라서 destroy() 를 예제로 확인하려면 "톰캣 매니저"를 사용하여 애플리케이션을 언로드하거나 톰캣을 셧다운시켜야 한다.
참고로 톰캣매니저는 http://localhost:port/manager로 접근할 수 있는 웹 애플리케이션으로 웹 애플리케이션을 관리할 용도로 제공된다.
톰캣을 설치할 때 정해준 관리자와 관리자 비밀번호를 사용하여 로그인을 해야 화면을 볼 수 있다.
관리자와 관리자 비밀번호가 생각나지 않으면 {톰캣홈}/conf/tomcat-users.xml 파일을 열어보면 알 수 있다.


void destroy();



GenericServlet 추상클래스
GenericServlet 클래스는 부모 클래스로 쓰인다.
GenericServlet 클래스는 편의를 위해서 ServletConfig 인터페이스를 구현한다.
GenericServlet 는 Servlet 인터페이스를 불완전하게 구현한다.
Servlet 인터페이스의 service() 메소드를 구현하지 않았기 때문에, GenericServlet 의 service() 메소드는 abstract 가 붙어야 하고, 그래서 GenericServlet 은 추상클래스이다.
GenericServlet 를 상속하는 자식 클래스는 service() 메소드를 구현해야 한다.


public abstract void service(ServletRequest req, ServletResponse res)
          throws ServletException, IOException;


init(ServletConfig config) 메소드는 다음과 같이 GenericServlt 에 구현되었다.


public void init(ServletConfig config) throws ServletException {
          this.config = config;
          this.init();
}


init(ServletConfig config) 구현부에 마지막에 매개변수가 없는 init() 메소드를 호출하고 있다.
매개변수가 없는 init() 메소드는 편의를 위해 GenericServlet 에 추가되었다.
자식 클래스에서 init(ServletConfig config) 메소드를 오버라이딩하는 것보다 이 메소드를 오버라이딩하는 것이 편리하다.


왜냐하면 ServletConfig 객체를 저장해야 한다는 걱정을 하지 않아도 되기 때문이다.
init(ServletConfig config) 메소드의 경우 자식 클래스에서 오버라이딩하려면 구현부에 super(config); 코드가 반드시 있어야 한다.


이 코드가 없으면 서블릿이 ServletConfig 객체를 저장하지 못하게 된다.
매개변수가 없는 init() 메소드는 다음과 같이 아무일도 하지 않는 것으로 구현되어 있다.


public void init() throws ServletException {

}


Servlet 인터페이스의 getServletConfig() 메소드는, init(ServletConfig config) 메소드에서 전달받아서 인스턴스 변수 config 에 저장된 ServletConfig 객체를 반환하도록 구현되어 있다.


public ServletConfig getServletConfig() {
          return config;
}


ServletConfig 인터페이스의 getServletContext() 메소드는 ServletContext 타입의 객체를 반환한다.


public ServletContext getServletContext() {
          return getServletConfig().getServletContext();
}


ServletConfig 인터페이스의 getInitParameter() 와 getInitParameterNames() 메소드는 GenericServlet 에서 다음과 같이 구현되어 있다.


public String getInitParameter(String name) {
          return getServletConfig().getInitParameter(name);
}

public Enumeration getInitParameterNames() {
          return getServletConfig().getInitParameterNames();
}  


편의를 위해서 GenericServlet이 ServletConfig를 구현했다는 말은 이런 것이다.
서블릿에서 ServletContext에 대한 레퍼런스를 얻기 위해서 this.getServletConfig().getServletContext(); 란 코드도 되지만 this.getServletContext(); 를 쓰는 것이 편리할 것이다.
초기화 파라미터 정보를 얻기 위해서 String driver = this.getServletConfig().getInitParameter("driver"); 와 같이 쓰기 보다는 String driver = this.getInitParameter("driver"); 쓰는 것이 편리할 것이다.



HttpServlet 클래스
HTTP 요청을 서비스하는 서블릿을 작성하는 경우에는 HttpServlet을 상속한다.
GenericServlet 추상 클래스를 상속하는 HttpServlet 클래스는 HTTP 프로토콜에 특화된 서블릿이다.
HttpServlet 클래스는 HTTP 요청을 처리하는 메소드를 제공한다.
클라이언트의 요청은 HttpServletRequest 객체 타입으로 서블릿에 전달되며, 서버는 HttpServletResponse 객체 타입으로 사용하여 응답한다.
HttpServlet 클래스는 GenericServlet 의 service() 추상 메소드를 구현했는데 구현 내용은 단지 클라이언트의 요청을 protected void service(HttpServletRequest req, HttpServletResponse resp) 메소드에 전달하는 것이다.


public void service(ServletRequest req, ServletResponse res)
          throws ServletException, IOException {
         
          HttpServletRequest  request;
          HttpServletResponse response;
         
          try {
                    request = (HttpServletRequest) req;
                    response = (HttpServletResponse) res;
          } catch (ClassCastException e) {
                    throw new ServletException("non-HTTP request or response");
          }
         
          service(request, response);
}


결국, 다음 메소드가 HTTP 요청을 처리한다.


protected void service(HttpServletRequest req, HttpServletResponse resp)
          throws ServletException, IOException {
         
          String method = req.getMethod();
         
          if (method.equals(METHOD_GET)) {
                    long lastModified = getLastModified(req);
                    if (lastModified == -1) {
                              // servlet doesn't support if-modified-since, no reason
                              // to go through further expensive logic
                              doGet(req, resp);
                    } else {
                              long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
                              if (ifModifiedSince < (lastModified / 1000 * 1000)) {
                                        // If the servlet mod time is later, call doGet()
                                        // Round down to the nearest second for a proper compare
                                        // A ifModifiedSince of -1 will always be less
                                        maybeSetLastModified(resp, lastModified);
                                        doGet(req, resp);
                              } else {
                                        resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
                              }
                    }
          } else if (method.equals(METHOD_HEAD)) {
                    long lastModified = getLastModified(req);
                    maybeSetLastModified(resp, lastModified);
                    doHead(req, resp);
          } else if (method.equals(METHOD_POST)) {
                    doPost(req, resp);
          } else if (method.equals(METHOD_PUT)) {
                    doPut(req, resp);
          } else if (method.equals(METHOD_DELETE)) {
                    doDelete(req, resp);
          } else if (method.equals(METHOD_OPTIONS)) {
                    doOptions(req,resp);
          } else if (method.equals(METHOD_TRACE)) {
                    doTrace(req,resp);
          } else {
                    //
                    // Note that this means NO servlet supports whatever
                    // method was requested, anywhere on this server.
                    //
                   
                       String errMsg = lStrings.getString("http.method_not_implemented");
                       Object[] errArgs = new Object[1];
                       errArgs[0] = method;
                       errMsg = MessageFormat.format(errMsg, errArgs);
                      
                    resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
          }
}


HttpServlet 의 +service()는 단지 #service()메소드에게 제어권을 넘기는 역할만 한다.
HttpServlet 클래스의 #service() 메소드가 호출되면 이 메소드는 요청객체(HttpServletRequest타입의 객체)안에서 HTTP 메소드(대표적으로 POST, GET이 있다.)를 읽어내고 이 값에 따라 매칭되는 메소드를 호출한다.


이를 테면 HTTP 메소드가 "GET"이면 doGet(), "POST"이면 doPost() 메소드를 호출한다.
doGet(), doPost() 와 같은 메소드가 우리가 오버라이딩 해야 하는 메소드이다.


HttpServletRequest 인터페이스는 ServletRequest 인터페이스를 상속하고, HttpServletResponse 인터페이스는 ServletResponse 인터페이스를 상속한다.
서블릿 컨테이너는 클라이언트의 요청이 오면 HttpServletRequest타입의 객체와 HttpServletResponse 타입의 객체를 만들어서 서블릿의 +service(req:ServletRequest,res:ServletResponse)메소드를 호출하면서 전달한다.
HttpServletRequest, HttpServletResponse 인터페이스를 구현한 클래스는 서블릿 컨테이너를 제작하는 벤더의 몫이다.



서블릿 클래스, 인터페이스 요약

                                                                                         

Servlet 인터페이스

init (config:ServletConfig)

service(req:ServletRequest, res:ServletResponse)

destroy()

getServletConfig() : ServletConfig

          서블릿 초기화에 관련된 변수를 가지고 있는 ServletConfig 객체를 리턴

getServletInfo() : String

          서블릿에 대한 간단한 정보를 리턴

                                                                                         
ServletConfig 인터페이스

getInitParameter(name:String) : String

          name에 해당하는 초기화 파라미터 값을 리턴

getInitParameterNames() : Enumeration

          서블릿의 초기화 파라미터 이름들을 Enumeration 타입으로 리턴

getServletContext() : ServletContext

          ServletContext 를 리턴

getServletName() : String

          서블릿 인스턴스의 이름 리턴

                                                                                         
+GenericServlet 추상 클래스

     프로토콜에 무관한 기본적인 서비스 기능을 제공하는 클래스로 Servlet, ServletConfig 인터페이스를 구현

+init()

          서블릿 초기화 메소드로, GenericServlet의 init(config:ServletConfig) 메소드의 의해 호출됨

<<abstract>> + service (req:ServletRequest, res:ServletResponse)

          Servlet 인터페이스의 service() 메소드는 여전히 추상 메소드

                                                                                         
HttpServlet 추상 클래스

     GenericServlet 추상 클래스 상속

#doGet (req:HttpServletRequest, res:HttpServletResponse)

          HTTP의 GET 요청을 처리하기 위한 메소드

#doPost (req:HttpServletRequest, res:HttpServletResponse)

          HTTP의 POST 요청을 처리하기 위한 메소드

+service (req:ServletRequest, res:ServletResponse)

          GenericServlet 추상클래스의 추상 메소드 service() 구현함. 구현내용은 아래 service() 메소드에 호출하는 것이 전부

#service (req:HttpServletRequest, res:HttpServletResponse)

          클라이어트의 요청에 따라 doGet(), doPost() 메소드 호출

                                                                                         
ServletContext 인터페이스

     서블릿이 서블릿 컨테이너와 통신하기 위해서 사용하는 메소드 제공한다.

     파일의 MIME 타입, 파일의 전체경로, RequestDispatcher 레퍼런스를 얻거나 로그파일에 기록할 수 있다.

     ServletContext 객체는 웹 애플리케이션마다 하나씩 존재하며, 그래서 웹 애플리케이션 전체의 공동 저장소를 역할을 한다.

     ServletContext 에 저장된 데이터는 같은 웹 애플리케이션내의 서블릿이나 JSP 에서 자유롭게 접근할 수 있다.
setAttribute (name:Strng, value:Object)

          데이터를 이름과 값의 쌍으로 저장한다.

getAttribute (name:String) : Object

          이름을 이용해서 저장된 데이터를 리턴

removeAttribute(name:String)

          name에 해당하는 데이터를 삭제

getInitParameter(name:String) : String

          웹 애플리케이션 전영역에 대한 초기화 파라미터 이름에 해당하는 값 반환

getRequestDispatcher(path:String) : RequestDispatcher

          주어진 Path 를 위한 RequestDispatcher 를 리턴

getRealPath(path:String) : String

          주어진 가상 Path의 실제 Path를 리턴

getResource(path:String) : URL

          주어진 Path 에 해당되는 자원의 URL을 리턴

log(msg:String)

          로그에 기록

log(String message, Throwable throwable)

          로그에 기록

                                                                                         
RequestDispatcher 인터페이스

     클라이언트의 요청을 다른 자원(서블릿, JSP)으로 전달하거나 다른 자원의 내용을 응답에 포함하기 위해 사용한다.

forward(req:ServletRequest, res:ServletResponse)

          클라이언트의 요청을 다른 자원으로 전달

include(req:ServletRequest, res:ServletResponse)

          다른 자원의 내용을 응답에 포함

                                                                                         
ServletRequest 인터페이스

     서블릿에 대한 클라이언트의 요청 정보을 담고 있음.

setAttribute(name:String,o:Object)

          객체를 주어진 이름으로 저장

getAttribute(name:String) : Object

          주어진 이름의 저장된 데이터를 리턴

removeAttribute(name:String)

          주어진 이름의 데이터를 삭제

getInputStream() : ServletInputStream

          클라이언트 요청의 바디에 있는 바이너리 테이터를 읽기 위한 입력 스트림 리턴

getParameter(name:String) : String

          클라이언트의 요청에 포함되어 있는 파라미터 이름에 해당하는 값 리턴

getParameterNames() : Enumeration

          클라이언트의 요청에 포함되어 있는 모든 파라미터 이름을 Enumeration 타입으로 리턴

getParameterValues(name:String) : String[]

          파라미터 name에 해당하는 모든 값을 String 배열로 리턴.
          체크박스나 다중 선택 리스트와 같이 값이 여러 개 있을 때 이 메소드를 사용한다.

getServletPath() : String

          "/" 로 시작하는 경로를 리턴. 경로에 쿼리스트링은 포함되지 않음.

getRemoteAddr() : String

          클라이언트의 IP 주소를 리턴

                                                                                         
HttpServletRequest 인터페이스

     ServletReqeust 상속

getCookies() : Cookie[]

          브라우저가 전달한 쿠키 배열을 리턴

getSession() : HttpSession

          현재 세션(HttpSession)을 리턴

getSession(created:boolean) : HttpSession

          현재 세션을 리턴, 만약 세션이 없는 경우 created 가 true 이면 세션을 생성후 리턴하고 created 가 false 면 null 리턴

getContextPath() : String

          요청 URI 에서 컨텍스트를 지시하는 부분을 리턴한다.
          http://localhost:port/ContextPath/board/list.do?curPage=1를 요청하면 /ContextPath 를 리턴한다.

getRequestURI() : String

          http://localhost:port/ContextPath/board/list.do?curPage=1를 요청하면
          /ContextPath/board/list.do 를 리턴한다.

getQueryString() : String

          http://localhost:port/ContextPath/board/list.do?curPage=1를 요청하면
          curPage=1 를 리턴한다.

                                                                                         
ServletResponse 인터페이스

     클라이언트에 응답을 보내기 위해 사용

getOutputStream() : ServletOutputStream

          응답으로 바이너리 데이터를 전송하기 위한 출력 스트림 리턴

getWriter() : PrintWriter

          응답으로 문자 데이터를 전송하기 위한 출력 스트림 리턴

setContentType(type:String)

          응답 데이터의 MIME 타입을 설정.
          HTML은 text/html, 일반 텍스트는 text/plain, 바이너리 데이터는 application/octet-stream 으로 설정
          이 메소드는 getWriter() 메소드 전에 호출되어야 한다.

getContentType() : String

          setContentType()메소드에서 지정한 MIME 타입 리턴
           만약 지정하지 않았다면 null 리턴

setCharacterEncoding(charset:String)

          응답 데이터의 캐릭터셋을 설정
          UTF-8로 설정하려면 setCharacterEncoding("UTF-8");
          이것은 setContentType("text/html; charset=UTF-8"); 에서의 캐릭터셋 설정과 동일하다.
          getWrite()메소드가 실행되기 전에 호출되어야 한다.

getCharacterEncoding() : String

          설정된 응답 데이터의 캐릭터셋 리턴
           캐릭터셋이 지정되지 않았다면 "ISO-8859-1" 을 리턴

setContentLength(length:int)

          응답 데이터의 크기를 int형 값으로 설정
          이 메소드는 클라이언트측에서 서버로부터의 응답 데이터를 어느 정도 다운로딩하고 있는지 표시하는데 응용될 수 있다.

                                                                                         
HttpServletResponse 인터페이스

     ServletResponse 인터페이스를 상속, HTTP 응답을 클라이언트에 보내기 위해 사용

addCookie(cookie:Cookie)

          응답에 쿠키를 전송

sendRedirect(location:String)

          주어진 URL로 리다이렉트

                                                                                         
HttpSession 인터페이스

     세션유지에 필요한 사용자의 정보를 서버측에서 저장할 때 사용한다.

getAttribute(name:String) : Object

setAttribute(name:String, value:Object)

removeAttribute(name:String)

invalidate()

                                                                                         
Cookie

     서블릿에서 만들어져서 웹브라우저로 보내는 작은 정보로 브라우저에 저장된 후에 다시 서버로 보내어진다.

     이말은 세션 유지에 필요한 정보를 클라이언트측에서 저장한다는 것이다.

     쿠키는 name 과 name 에 대해 하나의 value 를 가지며 경로, 도메인, 유효기간, 보안에 대한 옵션값을 가지기도 한다.

     서블릿에서 쿠키를 만들면 쿠키에 대한 일정한 형식의 문자열이 응답헤더에 추가된다.

     쿠키가 포함된 응답을 받은 웹브라우저는 이후 쿠키 정보를 모든 요청시 같이 보내게 된다.

     서버 요소로 전달된 쿠키 정보는 HttpServletRequest.getCookie() 메소드를 이용하면 전송된 모든 쿠키의 배열을 얻을 수 있

     다.

     쿠키나 세션은 응답후 접속을 끊는 HTTP 프로토콜의 한계를 극복하기 위한 기술이다.
+ Cookie(name:String, value:String)

+ getName() : String

+ getValue() : String

+ setValue(newValue:String)

+ getPath() : String

+ setPath(uri:String)

+ getDomain() : String

+ setDomain(pattern:String)

+ getMaxAge() : int

+ setMaxAge(expiry:int)

+ getSecure() : boolean

+ setSecure(flag:boolean)

                                                                                         


참고
http://docs.oracle.com/javaee/7/api/index.html?overview-summary.html
http://www.mkyong.com/servlet/a-simple-httpsessionlistener-example-active-sessions-counter/
http://commons.apache.org/proper/commons-fileupload/download_fileupload.cgi
http://commons.apache.org/proper/commons-io/download_io.cgi
http://commons.apache.org/proper/commons-fileupload/using.html
http://www.albumbang.com/board/board_view.jsp?board_name=free&no=292
http://www.docjar.com/docs/api/javax/servlet/GenericServlet.html
http://www.java-school.net/jsp/Servlet