- 이 글은 Head First Servlet & JSP를 기반으로 작성되었습니다.
Servlet LifeCycle
- 서블릿은 container에 의해 관리된다.
- life cycle이 어떤식으로 관리되는지 알아보겠음.
우선 요청과 응답에 대한 글은 이전 글을 참고해주세요.
이전 글
- 요청에 대한 글을 봤다면 다음과 같은 의문이 생길 것이다.
- 서블릿 클래스는 언제 load 되는가
- 언제 서블릿의 constructor가 실행되는가
- 서블릿 object는 얼마나 오래 살아있는가?
이제 알아봅시다.
State
- 서블릿의 state는 매우 간단하다.
- initialize 되지 않은 상태라면?
- initialize중이거나 아예 존재하지 않는 상태(init() 또는 constructor 부를때)
- 또는 destroy되고 있는 상태(running destroy())
Cycle
- 그럼 이제 state와 연관지어 cycle을 알아보자
- Web Container에서 Servlet class를 요청하면, Servlet.class(compile된 서블릿 클래스)를 불러온다.
- 서블릿을 인스턴스화한다. 즉, constructor를 호출한다.(아직 존재하지 않는상태)
- 이때 이 작업이 끝나면 객체로서 존재하게 됨(단, 아직 초기화된 상태 아님)
- init()을 호출해서 servlet이 service()를 할 수 있도록 준비시킨다.
- 생명주기에서 오직 한번만 불림.
- 이 작업이 끝나면 servlet이 초기화되었다고 할 수 있음.
- initialize된 서블릿에 요청이 발생하면 service() 메소드를 호출한다.
- service()에서는 요청에 맞춰 doPost, doGet등을 실행시켜준다.
- destory()를 호출해서 서블릿이 kill될 준비를 시킴.
- 예를들면 garbage collection을 준비시킨다던가 하는거.
- init()처럼 한 번만 불린다.
LifeCycle내의 중요한 부분들
1. init()
언제 불리는가?
- 서블릿 인스턴스가 생성된 이후 호출된다.
- service()를 호출하기 위해선 반드시 init()과정이 필요하다.
- 다시말하면, service()는 init()이 호출되기 전에는 호출될 수 없다.
왜 사용하는가?
- client request를 다루기 위해 필요한 servlet 초기화 과정이 여기서 일어남.
Override를 사용하는가?
- initialization code(database connection, register with other object...)가 서블릿에 있는 경우 사용한다.
2. service()
언제 불리는가?
- client request가 들어오면, container는 새로운 thread(혹은 threadpool로부터 기존 thread)를 할당해주고 이후 service() method가 호출됨.
왜 사용하는가
- request의 HTTP method를 확인한 뒤 적절한 doXXX()(i.e. doGet())를 호출한다.
Override를 사용하는가?
- 거의 안한다. doGet, doPost등은 거의 무조건 Override한다.
3. doGet(), doPost()...
언제 불리는가?
- service method가 HTTP method에 따라 doXXX()를 호출하게 됨.
왜 사용하는가
- 우리가 작성한 코드의 실제 시작점. 즉, 어떤 작업을 수행하는 시작점이다.
Override를 사용하는가?
- doGet, doPost등은 무조건 적어도 하나이상 Override한다.
간단한 예시
- ThreadA : init()메소드 호출해서 servlet instance를 생성.
- init()을 override했으면 작성한 servlet class에서 호출되지만 그렇지 않으면
GenericServlet
class에서 호출됨.
- ThreadB : client의 요청이 들어와서 thread 새로 생성됨. 이후 service() 메소드가 불리고 알맞는 doXXX()호출해서 작업 수행
- ThreadC: ThreadB와 동일.
각 요청은 다른 thread에서 처리된다!
- 내가 찾아보면서 가장 헷갈렸던 부분. 책에서 설명 잘해줬다.
- 각 servlet instance에서 돌아간다고 처음엔 생각했는데 그게 아니었다.
- servlet instance는 종류별로 하나만 있고, thread를 생성해서 동일한 servlet instance를 활용해 요청을 처리한다.
- 물론 그렇지 않은 케이스(
SingleThreadModel
도 있지만 이부분은 특이한 케이스라 제외)
정리하면, Container는 여러개의 thread를 사용해서 여러개의 request를 처리하는데 이때 사용하는 servlet instance는 오직 하나.
- 여기서 알아야 할 것.
- 요청당 하나의 스레드다! 즉, 동일한 유저가 동일한 요청해도 그냥 스레드 생성하던가 다른 스레드 할당.
- 누가 요청했는지는 별로 신경쓰지 않는다.
Loading과 Initializing에 대해
- servlet이 life를 시작하는 것은 Container가 servlet class file을 찾을 때이다.
- 이는 일반적으로 Container가 시작할 때(i.e. tomcat 시작) 발생한다.
- 왜, 그리고 어디서 servlet을 찾는지는 Deployment 관련 글을 쓰면서 설명하겠음.
- Finding class
- Loading
container가 처음 켜질때, 혹은 client가 첫 요청 날렸을 때 실행됨.
일단 이 두 과정 거친 이후 initialization 시작.
Initialization
- servlet 초기화를 함으로서 Object가 비로소 서블릿의 역할을 할 수 있게 됨.
- object에서 servlet이 되기 위해서는 특정 권한이 필요하다.
- ServletContext를 참조해서 Container로부터 정보를 얻는다던가 하는것들.
- 서블릿이 된다는 것은 무엇을 가지게 되는걸까?
- ServletConfig object
- servlet당 하나 가짐
- database 이름, enterprise bean 이름 등등의 deploy-time information을 전달할 때 사용
- ServletContext 접근할때도 사용
- Deployment Descriptor에서 parameter 설정 가능하다.
- ServletContext
- web app당 ServletContext 하나
- web app parameter 접근할 때 사용
- 메세지를 올리면 application의 다른 파트도 볼 수 있음. 일종의 게시판 역할
- 서버 정보(이름, 컨테이너 버전, API 버전)등을 볼 수 있다.
추가 잡 얘기
HTTP Method 간단설명
- HTTP method를 보며
service()
에서 doXXX()
를 호출한다고 했는데 어떤놈들인지 대충만 보자.
- Http method는 GET, POST, HEAD, TRACE, PUT, DELETE, OPTIONS, CONNECT가 있다.
- 여기서 servlet은
doCONNECT()
를 지원하지 않는다.
GET과 POST의 차이점
- Body의 유무 - POST는 있고 GET은 없다.
이로인해 GET은 bookmark가 되지만 POST는 안된다.
- 멱등성(idempotent)
GET은 어떤걸 가져온다는 의미지만 POST는 처리할 데이터를 전송한다는 의미 -> 이 의미에 맞춰서 로직 작성해줘야된다
그럼 다시 생각해보자.
GET은 동일한 요청을 계속해도 단순히 받아오는거니까 서버에 변경이 없다.
하지만 POST는 다르다. 요청이 처리되면 서버의 상태가 변화할 수 있다. 예를들어 유저를 계속해서 생성하는 요청을 했다고 하면 새로운 유저가 계속 생성된다.
요청을 처리해도 변화하지 않는 것을 멱등성(idempotent)이라고 한다.
즉 POST는 멱등성이 보장되지 않음.
- GET은 북마크 가능. POST는 북마크 불가.
JAR 다운로드 받는 코드 예시
public class CodeReturn extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException {
response.setContentType("application/jar");
ServletContext ctx = getServletContext();
InputStream is = ctx.getResourceAsStream("helloWorld.jar");
int read = 0;
byte[] bytes = new byte[1024];
OutputStream os = response.getOutputStream();
while ((read = is.read(bytes)) != -1) {
os.write(bytes, 0, read);
}
os.flush();
os.close();
}
}
Redirect vs Dispatch
- Redirect : 브라우저한테 딴곳가라고 시키는거
sendRedirect()
method를 사용한다.
sendRedirect()
사용할 때 주의할점이 response stream으로 보내고 난다음에 flush()
하고 사용하면 IllegalStateException
발생한다.
물론 flush안하고 response 보내게 해서 오류 안나게 할 수도 있지만 매우 안좋은 짓.
- Dispatch : 서버에서 그냥 딴곳까지 보내주고 일 다 한뒤에 반환해주는거.
RequestDispatcher
Class 사용