금요일, 5월 3
Shadow

#025 속성과 리스너

01. 웹 애플리케이션이 되어 보자

도와주세요 ~ 초기화 파라미터
서블릿도 초기화 파라미터가 있습니다.

DD파일에서..
<init-param>
<param-name> adminEmail</param-name>
<param-value>likewecare@wike.com</param-value>
</init-param>

서블릿 코드에서 :
out.println(getServletConfig().getInitParameter(“adminEmail”));

서블릿 초기화가 된 다음에야 서브릿 초기화 파라미터를 사용할수 있습니다.
메소드가 리턴한 ServletConfig, 객체에 대한 참조를 가지고 ServletConfig의 메소드를 호출할수 잇습니다. GetInitParameter()메소드 같은것 말이죠 그러나 서블릿 생성자에서는 이 메소드를 호출할수 없습니다. 서블릿이 일생을 잘 훓어보면 너무 일찍 호출했다는 것을 알수 있을겁니다. 컨테이너가 서블릿의 init()을 호출하고 난 다음에야, 서블릿은 서블릿의 정체성을 갖기 때문입니다.

*컨테이너가 서블릿을 초기화할때, 서블릿마다 하나식 ServletConfig를 생성합니다. 컨테이너는 DD에서 서블릿 초기화 파라미터를 읽어 이 정보를 ServletConfig로 넘겨줍니다. 그다음 ServletConfig를 서블릿 init() 메소드에 제공하지요

컨테이너가 서블릿을 초기화 할때 단 한번만 서블릿 초기화 파라미터를 읽습니다.
컨테이너가 서블릿을 만들때 DD를 읽어 이름/값의 쌍으로 ServletConfig를 생성합니다. 컨테이너는 이 초기화 파라미터를 두번다시 읽지 않습니다. 이름/값의 쌍이 ServletConfig 안에 기록되면, 서블릿이 다시 배포되지 않는한 DD를 수정한다고 바뀌지 않습니다.

1. 컨테이너는 배포 서술자를 읽습니다. 물론 초기화 파라미터로 읽습니다.
2. 컨테이너는 새로운 ServletConfig 인스턴스를 만듭니다.
3. 컨테이너는 초기화 파라미터에 있는 값들을 이름/값의 쌍의 형식으로 읽어들입니다. 여기서는 하나의 쌍만 있다고 가정해 좁시다.
4. 컨테이너는 ServeltConfig 객체에 이름/값으로 된 초기화 파라미터를 설정합니다.
5. 컨테이너는 서블릿 클래스 인스턴스를 생성합니다.
6. 컨테이너는 ServletConfig,의 참조를 인자로 서블릿의 init()메소드를 호출합니다.

Reqest 객체 속성에 설정이라.. 하지만 Request 객체를 받는 JSP하고만 정보를 공유하는것 아닌가요?
– 서블릿에만 적용되는 초기화 파라미터 말고 애플리케이션에 적용되는 그런것 없나?
컨텍스트 초기화 파라미터가 답이다.
컨특스트 초기화 파라미터의 작동 방식은 서블릿 초기화 파라미터와 동일합니다. 그러나 컨텍스트 초기화 파라미터는 특정 하나의 서블릿만 사용하는 것이 아니라 모든 웹 애플리케이션에서 이용할수 있다는 차이가 있지요. 웹 애플리케이션에 있는 모든 JSP 서블릿에서 별다른 코딩없이도 컨텍스트 초기화 파라미터 정보에 접근할수 있으며, 그렇다고 모든 서블릿의 DD를 수정하는 일따위는 하지 않아도 됩니다.

DD파일
<context-param>
<param-name>adminEmail</param-name>
<param-value>clientheaderror@wick.com</param-value>
<context-param>

서블릿코드
out.println(getServletContext().getInitParameter(“adminEmail”));

ServletConfig는 서블릿 당 하나
ServletContext는 웹 애플리케이션당 하나

웹애플리케이션 초기화:
컨테이너는 DD에 있는 <context-param> 항목을 읽고 각각의 이름/값의 쌍을 만듭니다.
컨테이너는 ServletContext 객체를 하나 생성하니다.
컨테이너는 생생한 컨텍스트 초기화 파라미터 이름/값에 대한 참조 init메소드 인자로 넘깁니다.
현재 웹 애플리케이션에 있는 모든 서블릿과 JSP는 바로 이 ServletContext에 접근할 수 있습니다.

웹 애플리케이션에도 main()처럼 제일 먼저 실행되는 메소드가 있으면 얼마나 좋을까?
그들이 원하는 것은 바로 ServletContextListen이다.
서블릿도 JSP도 아닌 클래스가 필요합니다.
javax.servlet.ServletContextlistener 인터페이스를 구현하면 이 클래스를 만들수 있습니다.

다음의 기능을 가진 클래스를 원합니다.
ㅇ 컨텍스트가 초기화되는 것을 알아차릴수 있어야 합니다.
– ServletContext로 부터 컨텍스트 초기화 파라미터를 읽습니다.
– 데이터베이스를 연결하기 위하여 쵝화 파라미터 검색명을 사용합니다.
– 데이터베이스 Connection 객체를 속성(attribute)에 저장합니다.
ㅇ 컨텍스트가 종료되는걸 알아차릴수 있어야 합니다.
– 데이터베이스 연결을 닫습니다.

ServletContextListener 클래스 :
import javax.serlet.*;

public class MyServletContextListener implements ServletContextListener{
public vodi contextInitialized(ServletContextEvent event){
//여기에 데이터베이스 연결을 초기화 하는 코딩을 합니다.
//그리고 이를 컨텍스트 속성에 저장합니다.
}

public void contextDestoryed(ServletContextEvent event){
//여기에 데이터베이스 연결을 닫는 코딩을 합니다.
}
}

컨텍스트 리스너 작성 및 사용하기
웹애플리케이션을 컨테이너가 인식하기 위하여 배포 서술자 web.xml에 등록하는것과 마찬기지로 리스너도 그렇게 하면 됩니다. ServletContext 이벤트를 리스닝하기 위해서는 ServletContextListener인터페이스를 구현해야 합니다. 그다음 컴파일된 파일을 WEB-INF/classes 디렉토리에 배포하고, 배포서술자 web.xml에 <listener> 항목을 추가하여 컨테이너에게 이런놈이 있다는 것을 알려주는 겁니다.

1. 리스너 클래스 생성
2. WEB-INF/classes 에 클래스 배포
3. 배포서술자 web.xml에 <listener> 항목 추가
<listener>
<listener-class>
com.example.MyServletContextListener
</listener-class>
</listener>

3개의 클래스와 하나의 DD가 필요합니다.
1. The ServletContextLisetner
MyServletContextListener.java
ServletContextListener를 구현한 클래스 입니다. 컨텍스트 초기화 파라미터를 읽어 객체를 생성하고 이를 컨텍스트 속성에 묶어 둡니다.
2. 속성 클래스
Dog.java
평범한 자바 객체입니다.
ServletContextlistener에서 생성되어 속성값이 됩니다. ServletContext의 속성에 묶어 두면, 나중에 서블릿이 꺼내쓰겠죠
3. 서블릿
ListenerTester.java
HttpServlet클래스를 상속합니다. 컨테스트에서 Dog객체를 끄집어 내어 Getbreed()메소드를 호출하여 이 내용을 Response에 출력할것입니다. 그러면 브라우저에서 이 내용을 확인할수 있겠죠. 즉 리스너가 제대로 일했는지 간단하게 확인만 합니다.

리스너 클래스 코딩하기

package com.example;
import javax.servlet.*;

public class MyservletContextlistener implements ServletContextListener{
public void contextInitialized(ServletContextEvent event){
ServletContet sc = event.getServletContext();
String dogBreed = sc.getinitParameter(“breed”);
Dog d = new Dog(dogBreed);
sc.setAttribute(“dog”,d);
}

public void contextDestoryed(ServletContextEvent event){
// 여기서 할일은 없습니다.
}
}
속성 클래스 Dog코딩하기
package com.example;

public class Dog{
private String bread;

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

서블릿 클래스 코딩하기(ServletContextListener)

package com.example;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;

public class ListenerTester extends HttpServlet{
public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException{
response.setContentType(“text/html”);
PrintWriter out = response.getWriter();
out.println(“test Context attributes set by listener<br>”);
out.println(“<br>”);
Dog dog = (Dog) getServletContext().getAttribute(“dog”);
out.println(“DOG’s Breed is :”+dog.getBreed());
}
}

*getAttribute는 Object를 리턴합니다. 캐스트는 필수입니다.

배포서술자 작성하기
컨테이너한테 우리가 리스너를 만들었음을 알려줄 때입니다. 이작업은 <listener>요소에 클래스 이름만 기재하면 되니 간단한 작업입니다.

<context-param>
<param-name>breed</param-name>
<param-value>Greet Dane</param-value>
</context-param>

<listener>
<listener-class>
com.example.MyservetContextListener
</listener-class>
</listener>

컴파일, 배포
1. 클래스 3개를 컴파일합니다.
2. 톰캣에 새로운 웹 애플리케이션을 만듭니다.
– 톰갯 webapps 디렉토리 아래에 listenerTest라는 디렉토리를 만듭니다.
– listenerTest 디렉토리 아래에 Web-inf라는 디렉토리를 만듭니다.
– WEB-INF 디렉토리 아래로 WEB.xml 파일을 옮깁니다.
– WEB-INF 디렉토리 아래에 classes 디렉토리를 만듭니다.
– classes 디렉토리 아래 패키지 구조와 동일한 디렉토리 구조를 만듭니다. com 디렉토리 아래에 example 디렉토리..
3. 톰갯 웹 애플리케이션 디렉토리 구조 아래에 컴파일한 3개의 파일을 복사합니다.
4. WEB-INF 디렉토리 아래로 web.xml 배포 서술자 파일을 복사합니다.
5. 톰캣을 내렷다가 다시 시작함으로써 배포 작업을 완료합니다.

전체이야기를 그림으로 보면…

1. 컨테이너는 애플리케이션 배포서술자를 읽습니다. <listener>요소와 <context-param>요소도 읽겠죠
2. 컨테이너는 ServeltContext 객체를 생성합니다. 애플리케이션에서 이 객체를 공유 하겠죠
3. 컨테이너는 컨텍스트 초기화 파라미터의 이름/값 쌍을 만듭니다. 개수만큼 만들겠지만, 쉽게 설명하기 위해 여기서는 하나만 있다고 가정합니다.
4. 컨테이너는 생성한 컨텍스트 초기화 파라미터의 String 쌍을 ServletContext객체에 설정합니다.
5. 컨테이너는 MuservletContextListener 클래스 인스턴스를 만듭니다.
6. 컨테이너는 리스너의 contextInitiallized() 메소드를 호출합니다. 인자로 ServletContextEvent를 넘깁니다. 이 이벤트객체를 가지고 ServletContextEvent로 접근한 ServletContext로 컨텍스트 초기화 파라미터 값을 읽습니다.
7. 리스너가 ServletContextEvent에게 ServletContext 에 대한 참조를 요청합니다.
8. 리스너가 ServletContext에게 컨텍스트 초기화 파라미터 Breed에 대한 값을 요청합니다.
9. 리스너는 초기화 파라미터를 가지고 Dog객체를 생성합니다.
10. 리스너는 ServletContext의 속성으로 Dog를 설정합니다.
11. 컨테이너는 새로운 서블릿을 생성합니다.
12. 서블릿은 요청을 받고는 ServletContext에게 dog속성에 매핑된 객체 인스턴스를 요청합니다.
13. 서블릿은 Dog객체의 getBreed() 메소드를 호출합니다.

Context 생존 범위는 스레드 – 안전하지 못합니다.
문제상황을 슬로우 비디오로 보면
1. 서블릿 A가 컨텍스트 속성 foo의 값을 22로 설정합니다.
2. 서블릿 A가 컨텍스트 속성 bar의 값을 42로 설정합니다.
3. 서블릿 B는 실행 스레드가 됩니다. 서블릿 B가 컨텍스트속성  bar의 값을 16으로 설정하니다.
4. 스레드 A는 다시 샐행 스레드가 되어 속성 bar의 값을 읽어 이를 Response에 기록합니다.

getServletContext(0.setAttribute(“foo”,”22″);
getServletContext(0.setAttribute(“bar”,”42″);

out.println(getServletContext().getAttribute(“foo”);
out.println(getServletContext().getAttribute(“bar”);

어떻게 하면 컨텍스트 속성을 스레드-안전하게 만들수 있나?
컨텍스트에 락을 걸면 되지요
컨텍스트에 가장 먼저 접근한 객체가 컨텍스트에 락을 겁니다. 이렇게 하는것은 특정 시점에서 오직 하나의 스레드만이 컨텍스트 속성을 설정하거나 값을 읽는 것을 보장한다는 것을 의미합니다. 동일한 컨텍스트 속성을 다루는 모든 코드들이 마찬가지로 ServletContext에 대하여 락을 걸어야 이것이 작동한다는 것입니다. 만약 어떤코드는 락을 요청하지 않았다면 여전히 컨텍스트 속성에 자유롭게 접근할수 있습니다. 이를 방지하려면 웹 애플리케이션 설계시, 모든 개발자들이 속성에 접근하기 전에 강제적으로 락을 걸도록 만들어야 합니다.

public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException{
response.setContextType(“text/html”);
PirntWriter out = response.getWriter();
out.println(“test context attributes<br>”);

Synchronized(getServletContext()){
getServletContext().setAttribute(“foo”, “22”);
getServletContext().setAttribute(“bar”, “42”);

out.println(getServletContext().getAttribute(“foo”);
out.println(getServletContext().getAttribute(“bar”);
}

}

HttpSession 동기화로 세션 속성 보호하기

public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException{
response.setContextType(“text/html”);
PirntWriter out = response.getWriter();
out.println(“test context attributes<br>”);

synchronized(session){
getServletContext().setAttribute(“foo”, “22”);
getServletContext().setAttribute(“bar”, “42”);

out.println(getServletContext().getAttribute(“foo”);
out.println(getServletContext().getAttribute(“bar”);
}
}

 

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다

이 사이트는 스팸을 줄이는 아키스밋을 사용합니다. 댓글이 어떻게 처리되는지 알아보십시오.