ServletContext, ServletContextListener

Byung Seon Kang·2022년 9월 29일
0

서블릿에 대해

목록 보기
4/9
  • 이 글은 Head First Servlet and JSP 책을 참고하여 작성하였습니다.

ServletContext의 필요성

Atribute란?

  • bound라 불리는 object set.
  • 우리는 scope에만 집중하면 된다.
    • scope란
      • 누가 접근가능한지
      • 얼마나 살아있는지
        종류
      1. Context Attribute
        application의 모든 것들이 접근이 가능
      2. Session Attribute
        특정 HttpSession만 접근 가능
      3. Request Attribute
        특정 ServletRequest만 접근 가능.

Attribute의 설정

  • 서블릿 init paramter는 servlet이 초기화되기전엔 사용할 수 없다.

  • container가 init()을 호출한 뒤에야 쓸 수 있음.

    • 참고
    • 가끔가다가 init(ServletConfig) 라고 servletconfig를 넘겨주는 경우가 있는데 이건 ServletConfig를 Override한 경우 사용한다.
  • 뭐 어쨌든 그 뒤에 사용을 한다 치자. 문제는 모든 servlet에 attribute를 하나하나 DD에 설정해서 넣어줘야 하나? 공통적으로 써야할 것 까지도?
    global 하게 써야 할 방법을 생각해보자.

Attribute Global하게 설정하기.

global...

  • Context Init parameter를 사용하자.
  • Context Init Parameter는 모든 web app에서 사용할 수 있다. 설정은 다음과 같이 해준다.
<context-param>
  <param-name>adminEmail</param-name>
  <param-value>google@naver.com</param-value>
</context-param>
  • 위처럼 설정해주고 나서는 아래와같이 호출해서 사용해주면 된다.
out.println(getServletContext().getInitParameter("adminEmail"));
  • 모든 서블릿은 getServletContext() 메소드를 가지고 있음을 알아두자.

참고
ServletConfig는 servlet당 하나. ServletContext는 web app당 하나다.
global한 설정은 ServletContext에 해준다는 것을 명심.

Context Parameter 관련

  • 중요한 부분이 있다.
    Context Parameter는 String만을 입력받을 수 있다.
    object등을 XML DD에 설정을 해줘도 Parameter로 받을 때 문제가 생긴다
  • 만약 DataSource Reference를 필요로 한다면?
    언제 누가 String parameter를 Datasource reference로 바꿔줄 것인가
  • 특정 서블릿에 이 기능을 담아버리면 그 서블릿이 항상 다른 서블릿보다 먼저 실행되어야 함을 보장해야 한다.
    이러면 문제가 많아짐.

해결책은 Listener!

Lister의 필요성

  • Context Initialization event를 listen한다.
    • 초기화가 되고 나면 context init parameter를 받고, 위에서 말한 필요 작업들을 모두 처리할 수 있게 된다.
    • 이렇게 하면 app이 client에게 service하기 전에 필요한 작업을 모두 끝낼 수 있음.
  • 이 작업은 servlet이 수행하지 않는다. 바로 ServletContextListener가 한다.
    • 예시
      • Context가 Initialize 되었을 때
        • ServletContext로부터 init parameter들을 얻어온다.
        • database connection을 하기 위해 init parameter lookup name을 사용한다.
        • database connection을 attribute로 저장시켜 모든 web app의 servlet에서 접근 가능하도록 해준다.
      • context가 destroy되었을 때
        • database 연결을 끊는다.
  • 코드 예시
package com.example.servlet;

import jakarta.servlet.ServletContext;
import jakarta.servlet.ServletContextEvent;
import jakarta.servlet.ServletContextListener;

public class MyServletListener  implements ServletContextListener {

    public void contextInitialized(ServletContextEvent event){
       //database 연결하는 코드
    }

    public void contextDestroyed(ServletContext event) {
		//database 연결 끊는 코드
    }
}

작업 순서

코드는 여기 참고

  • app 초기화부터 finish까지의 시나리오를 예시로 보자
  1. Container는 Deployment Descriptor를 읽는다.(<listener> 및 <context-param>등등)
  2. Container는 ServletContext를 생성한다.
    이제 이 ServletContext는 모든 servlet에서 공유하게 된다.
  3. Container는 각 context init parameter에 name/value로 구성된 한쌍의 String을 만들어준다.
  4. Container는 ServletContext Reference를 name/value parameter에 준다.
  5. Container가 MyServletListener class의 instance를 새로 만든다.
  6. Container는 listener의 contextInitialized() 메소드를 호출한다. 이때 new ServletContextEvent를 parameter로 전달해준다.
  7. Listener는 ServletContextEvent에게 ServletContext의 reference를 요청한다.
  8. Listener는 ServletContext에 init parameter "breed"를 요청한다.
  9. Listener는 init parameter를 사용해서 Dog object(instance)를 생성한다.
  10. Listener는 Dog를 ServletContext의 attribute로 설정해준다.
  11. Container는 새로운 servlet을 생성한다. (ServletConfig + init) 이해안되면 전에 쓴거 보고오기
  12. Servlet은 Request를 받고, attribute dog를 사용해서 요청 처리.

Listener 종류

  • ServletContextAttributeListener
  • HttpSessionListener
  • ServletRequestListener 등등..

헷갈릴만한 것

  • HttpSessionBindingListenerHttpSessionAttributeListener가 뭔 차이인지 모를 수 있음.
  • HttpSessionAttributeListener는 모든 타입의 attribute가 session에서 add, remove, replace될 때 체크
  • HttpSessionBindingListener는 자신이 session에서 add되거나 remove될 때만 체크 -> implement한 놈들만.
    ex)
package com.example.servlet;

public class Dog implements HttpSessionBindingListener {
    private String breed;

    public Dog(String breed){
        this.breed = breed;
    }

    public String getBreed(){
        return breed;
    }
    
    public void valueBound(HttpSessionBindingEvent event){
    	//자신이 session내에 있다는 것을 인지했을 때 실행하는 코드를 작성
    }
    
    public void valueUnbound(HttpSessionBindingEvent event){
    	// session에서 벗어나는 순간 실행하는 코드를 작성.
    }
    
}

Attribute Vs Parameters

  • 가장 큰 차이는 return type
    • Attribute는 Object
    • Parameter는 String
  • 그밖의 차이들
  1. Method to set
  • attribute는 있지만 parameter는 없음.
    • `setAttribute(String name, Object value)
    • parameter의 경우 DD에서 설정한다.
  1. Type
  • Attribute
    • Application/context
    • Request
    • Session
      -> servlet-specific attribute가 없다.
  • parameters
    • Application/context init parameters
    • Request parameters
    • Servlet init parameters
      -> session parameter같은거 없다
  1. Method to get
  • Attribute
    • getAttribute(String name)
  • Parameters
    • getInitParameter(String name)

Scope와 Thread-Safe

Context scope는 thread-safe하지 않다.

  • 모든 servlet에서 접근 가능하다는 것은 곧 모든 thread에서 접근 가능하다는 의미.
    • 하나의 servlet instance를 여러 thread에서 사용하므로
  • 이걸 어떻게 해결해야 할까
  1. synchronized를 service method에 사용한다면?
	public synchronized void doGet ...

이것으로는 context attribute를 지킬 수 없다.
이걸 써버리면, 동일한 servlet을 사용하는 요청을 다른 클라이언트가 했을 때 그 요청들이 아예 막혀버린다.
게다가, 동일하지 않은 다른 servlet 사용시 context scope의 attribute들이 수정가능하다는 큰 문제가 생김.
2. context를 lock한다.
정확히는 context object에 synchronizing한다.

public void doGet(HttpServletRequest request, HttpServletResponse response)
										throws IOException, ServletException {
	
    synchronized(getServletContext()){
    	//blabla.
    }
}

Session attribute는 Thread-safe한가?

  • 동일한 유저가 여러 브라우저에서 동시에 다양한 요청을 한다면?
    • 이또한 Thread-safe하지 않다.
    • 쇼핑카드의 물건을 추가하고 삭제하는 것을 동시에 했다고 하면, thread-safe하지 않으므로 문제가 생김.
  • 해결방법은 위와 비슷하다.
public void doGet(HttpServletRequest request, HttpServletResponse response)
										throws IOException, ServletException {
	...
    
    HttpSession session = request.getSession();
    
    synchronized(session) {
    	session.setAttribute("foo", "22");
        ...
    }
}
  • 여기서 당연히 질문이 생긴다. synchronized를 쓰면 overhead가 너무 많이 생기진 않을까?
    • 당연하다.
    • 우선 그렇기에 locking의 모든 standard rule을 기억해놓는것이 좋다.
    • lock rule중 가장 짧은 것을 사용하는 것이 도움이 될 것.
    • 항상 synchronized를 최소화 하는 방법을 고려해라.

servlet의 instance 변수는?

  • not thread safe
  • 그렇기에 만들어놓은 녀석이 있다.

SingleThreadModel

  • 근데 얘 쓰면 너무 안좋다.
  • servlet이 한번에 오직 하나의 request만 처리하도록 하는것.
    one request per servlet
  • 그냥 method 없는 interface. implements에 적기만 하면 thread가 동시에 servlet의 service method 치지 않는다.
    즉, service method에 synchronized 박는거랑 똑같음.

SingleThreadModel 구현 방식

  1. Queue all Request
    서블릿 하나로 request 끝낼 동안 queue에 쌓아놓고 대기하는 것.
  2. Send Requests through a pool
    이건 각 request가 동일한 서블릿이라도 다른 인스턴스를 거쳐 수행됨.

그냥 둘다 안좋다.
결국 좋은 서블릿 디자인은 instance 변수를 사용하지 않는 디자인.

Thread-safe한 것

  • Request attribute와 local variable만이 thread-safe하다.

Request Attribute and Dispatching

  • Request attribute는 request가 app의 일부, 또는 전부에 전달할 수 있도록 해준다.
  • 보통은 MVC app을 예로 든다.
    • controller에서 시작해서 JSP view로 마무리.
  • controller는 model과 통신하고, response에서 필요로 하는 view를 돌려주는 역할을 한다.
    • data는 일회성이기 때문에 context attribute나 session attribute에 집어넣을 이유가 없고 그래서 request scope에 집어넣는다.
  • 그렇다면 우리는 component의 다른 파트가 request를 받도록 하려면 어떻게 해야할까?
    • 여기서RequestDispatcher를 사용한다.

RequestDispatcher

//code in a doGet()
BeerExpert be = new BeerExpert();
ArrayList result = be.getBrands(c);

//request scope에 model data 집어넣는다.
request.setAttribute("styles", result);

//view JSP에 대한 dispatcher 얻음
RequestDispatcher view = request.getRequestDispatcher("result.jsp");

//JSP에게 request와 response object를 넘기겠다고 말함.
view.forward(request, response);
  • RequestDispatcher는 딱 두가지 메소드만 있다.

    • forward()
    • include()
  • RequestDispatcher를 쓸 때 주의할점은 다음과 같다.

    • response가 클라이언트에 commit을 하게 되면, 그 이후에 dispatch를 했을 때 IllegalStateException이 발생한다.
      • write(), flush()가 commit 상태로 바꾸게 됨.
      • write() 언제 commit 시키는지 정확히 모르겠다.
profile
왜 필요한지 질문하기

0개의 댓글