(책) 자바 웹을 다루는 기술

Chap 10 서블릿의 필터와 리스너 기능

EunaSon 2023. 8. 23. 18:39

서블릿의 기능을 도와주는 다른 API들에 대해 알아보자

서블릿의 요청과 응답 기능을 작업하기 전에 수행하는 필터(Filter) 기능을 알아보고

서블릿의 속성과 스코프(scope) 개념에 대해 살펴보자

서블릿 API에서 특정 이벤트가 발생했을 때 이벤트를 처리할 수 있는 여러가지 리스너(Listener)에 대해서도 알아보자

 

10.1 서블릿 속성과 스코프

356p

서블릿 속성(Attribute)

ServletContext, HttpSession, HttpServletRequest 세 서블릿 API 클래스에 저장되는 객체(정보)

서블릿 API의 setAttribute(String name, Object value)로 바인딩하고,

필요할 때 getAttribute(String name)으로 바인딩된 속성을 가져오고,

removeAttribute(String name)으로 해당 속성을 서블릿 API에서 제거함

 

서블릿의 스코프(scope)

서블릿 API에 바인딩된 속성에 대한 접근범위

 

스코프의 종류

스코프 종류 해당 서블릿 API 해당 속성의 스코프
애플리케이션 스코프 ServletContext 해당 속성은 애플리케이션 전체에 대해 접근할 수 있음
세션 스코프 HttpSession 해당 속성은 브라우저에서만 접근할 수 있음
리퀘스트 스코프 HttpServletRequest 해당 속성은 해당 요청/응답 사이클에서만 접근할 수 있음

 

-각 서블릿 API에 바인딩된 속성의 스코프 실습

[ SetAttribute 클래스 ]

String ctxMesg = "context에 바인딩됩니다."; //동일하게 sesMesg, reqMesg도 만듦

 

ServletContext ctx = getServletContext(); // ServletContext 객체를 얻음

HttpSession session = request.getSession(); // HttpSession 객체를 얻음

 

ctx.setAttribute("context", ctxMesg);

session.setAttribute("session", sesMesg);

reqiest.setAttribute("request", reqMesg); // 얻은 객체에 속성을 바인딩함

 

[ GetAttribute 클래스 ]

ServletContext ctx = getServletContext();

HttpSession session = request.getSession(); // ServletContext , HttpSession 객체를 얻음

 

String ctxMesg = (String)ctx.getAttribute("context");

String sesMesg = (String)session.getAttribute("session");

String reqMesg = (String)request.getAttribute("request"); // 각 서블릿API에서 바인딩된 속성의 값을 가져옴

 

out.print("context 값 : " + ctxMesg + "<br>"); 

out.print("session 값 : " + sesMesg + "<br>");

out.print("request 값 : " + reqMesg + "<br>"); // 출력되는지 확인

 

=>

브라우저에서 /set으로 속성을 바인딩 후 /get으로 요청해보면

Context와 Session 객체에 바인딩된 속성은 같은 브라우저에서 접근하는 것이므로 값이 출력됨,

기존에 바인딩된 request 객체(/set)는 /get으로 요청하여 생성된 request 객체와 다른 객체이므로 null 출력

 

기존 크롬 브라우저가 아니라 인터넷익스플로러에서 다시 /get으로 요청해보면

크롬의 session 객체에는 접근할 수 없으므로 session은 null을 출력

 

context 객체에 바인딩된 데이터는 모든 브라우저에서 같은 결과를 출력함.

 

10.2 서블릿의 여러 가지 URL 패턴

URI 패턴

: 실제 서블릿의 매핑 이름을 말함

즉, 서블릿 매핑 시 사용되는 가상의 이름으로, 클라이언트가 브라우저에서 요청할 때 사용됨

반드시 슬래시( / )로 시작해야함

 

-실습

[ TestServlet1 클래스 ] : 정확하게 이름까지 일치하는 URI 패턴

/first/test로 요청해야 실행됨

 

* request.getContextPath() : 컨텍스트 이름을 가져옴

* request.getRequestURL().toString() : 전체 URL을 가져옴

* request.getServletPath() : 서블릿 매핑 이름을 가져옴

* reqeust.getRequestURI() : URI를 가져옴

 

[ TestServlet2 클래스 ] : 디렉터리 이름만 일치하는 URI 패턴

/first/ 디렉터리로 시작하는 요청에 대해 실행됨

 

[ TestServlet3 클래스 ] : 확장자만 일치하는 패턴

*.do = 확장자가 do이면 실행됨

 

참고) /* = 모든 요청에 대해 실행됨

* /first/base.do 로 요청시, 확장자명이 do이지만 앞의 디렉터리 이름이 우선하여 TestServlet2가 실행됨

 

10.3 Filter API

필터

365p

브라우저에서 서블릿에 요청하거나 응답할 때 미리 요청이나 응답과 관련해 여러 가지 작업을 처리하는 기능

한글 인코딩 등 각 서블릿에서 반복적으로 처리하는 공통 작업을 미리 필터에서 처리하도록 하면 편리함

 

필터는 용도에 따라 크게 요청 필터 / 응답 필터로 나뉨 366p

- 요청필터

사용자 인증 및 권한 검사

요청 시 요청 관련 로그 작업

인코딩 기능

 

- 응답필터

응답 결과에 대한 암호화 작업

서비스 시간 측정

 

필터 관련 API

javax.servlet.Filter

javax.servlet.FilterChain

javax.servlet.FilterConfig

 

-Filter 인터페이스에 선언된 메서드

destroy() : 필터 소멸 시 컨테이너에 의해 호출되어 종료 작업을 수행함

doFilter() : 요청/응답시 컨테이너에 의해 호출되어 기능을 수행함

init() : 필터 생성 시 컨테이너에 의해 호출되어 초기화 작업을 수행함

 

-FilterConfig의 메서드

getFilterName() : 필터 이름을 반환함

getInitParameter(String name) : 매개변수 name에 대한 값을 반환함

getServletContext() : 서블릿 컨텍스트 객체를 반환함

 

-사용자 정의 필터 만들기

367p

사용자 정의 필터는 반드시 Filter 인터페이스를 구현해야함

init(), doFilter(), destroy()의 추상 메서드를 구현해야함

필터를 각각의 요청에 맞게 적용하기 위해 필터 매핑을 해야함

 

필터 매핑 방법에는 애너테이션을 이용하는 방법 / web.xml에 설정하는 방법 두 가지가 있음

일반적으로 애너테이션을 이용하는 방법이 편리하여 더 많이 사용됨

 

-Filter를 이용한 한글 인코딩 실습

[ login.html ]

이름, 비밀번호를 입력한 뒤 전송하도록 함

 

[ LoginTest 클래스 ]

setCharacterEncoding("utf-8") 메서드를 주석처리함 -> 이름(한글)이 이상한 기호로 출력되는 것 확인

 

[ EncoderFilter (필터)클래스 ]

이제 필터를 이용해 한글 인코딩 기능을 구현해보자

 

패키지명에서 우클릭 > New > Filter > Class name으로 'EncodeFilter'를 입력하고 Next > Filter mappings에서 '/EncoderFilter'를 선택 후 Edit > 모든 요청에 대해 필터 기능을 수행하도록 Pattern을 ' /* ' 로 수정함 > URL Pattern에서 /*를 확인 후 Next > Finish 클릭하여 필터 클래스가 생성된 것을 확인

: 클래스 내에 애너테이션으로 필터 생성된 것 확인할 수 있음 

@WebFilter("/*") // Webfilter 애너테이션을 이용해 모든 요청이 필터를 거치게 함

public class EncoderFilter implements Filter { ... } // 사용자 정의 필터는 반드시 Filter 인터페이스를 구현해야함

 

init(), doFilter(), destroy() 메서드를 구현함

 

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ...{

// doFilter() 메서드의 매개변수로 request, response와 FilterChain 타입인 chain을 가짐

  request.setCharacterEncoding("utf-8") // 전달된 request를 이용해 한글 인코딩 작업을 함

 

chain.doFilter(request, response); // 다음 필터로 넘기는 작업을 수행함

 

=> 톰캣 재실행 후 로그인창에서 한글로 이름 입력할때, 필터를 거쳐 한글이 제대로 출력되는 것 확인.

 

응답 필터 사용

373p

서블릿에서 요청과 응답에 대한 필터 기능은 동일한 필터가 수행함

doFilter() 메서드에서 chain.doFilter(request, response)를 기준으로 위쪽은 요청필터기능, 아래쪽은 응답필터기능을 함

 

응답 필터 기능으로 작업 시간 구하기

chain.doFilter(request, response) 의 위에

long begin = System.currentTimeMillis(); // 요청필터에서 요청 처리 전의 시각을 구함

 

아래쪽에

long end = System.currentTimeMillis(); // 응답필터에서 요청 처리 후의 시각을 구함

System.out.println("작업시간 : " + (end-begin) + "ms"); // 둘을 빼서 작업수행시간을 구함

 

 

10.4 여러 가지 서블릿 관련 Listener API

376p

자바 GUI애서 마우스 클릭 등 이벤트 발생 시 여러가지 이벤트 핸들러를 이용해 화면의 기능을 구현함

마찬가지로 서블릿에서도 서블릿에서 발생하는 이벤트에 대해 적절한 처리를 해주는 여러 가지 리스너를 제공함

 

서블릿 관련 여러 가지 리스너들

서블릿 관련 Listener 추상 메서드  기능
ServletContextListener contextInitialized()
contextDestroyed()
Context 객체의 생성/소멸 이벤트 발생 시 처리함
ServletContextAttributeListener attributeAdded()
attributeRemoved()
attributeReplaced()
Context 객체에 속성 추가/제거/수정 이벤트 발생 시 처리함 
HttpSessionListener sessionCreated()
sessionDestroyed()
Session 객체의 생성/소멸 이벤트 발생 시 처리함
HttpSessionAttributeListener attributeAdded()
attributeRemoved()
attributeReplaced()
Session 객체에 속성 추가/제거/수정 이벤트 발생 시 처리함 
HttpSessionBindingListener valueBound()
valueUnbound()
Session에 바인딩/언바인딩된 객체를 알려주는 이벤트 발생 시 처리함
HttpSessionActivationListener sessionDidActivate()
sessionWillPassivate()
Session의 활성화/비활성화 이벤트 발생 시 처리함
ServletRequestListener requestInitialized()
requestDestroyed()
클라이언트의 요청 이벤트 발생 시 처리함
ServletRequestAttributeListener attributeAdded()
attributeRemoved()
attributeReplaced()
Request 객체에 속성 추가/제거/수정 이벤트 발생 시 처리함

 

-HttpSessionBindingListener 이용해 로그인 접속자수 표시

* HttpSessionBindingListener : Session에 바인딩/언바인딩된 객체를 알려주는 이벤트 발생 시 처리함

377p

[ login2.html ]

id와 비밀번호를 입력 후 전송하는 로그인 창 만듦

 

[ LoginTest 클래스 ]

HttpSession session = request.getSession(); // 세션 생성/가져옴,

... //  form으로 전달된 id, pw 저장

 

LoginImpl loginUser = new LoginImpl(user_id, user_pw); // 전송된 id, pw로 LoginImpl 인스턴스 생성

 

if (session.isNew() ) { // 처음 로그인 시도한거면

  session.setAttribute("loginUser", loginUser);

  // 세션에 LogImpl 인스턴스 바인딩함, LoginImpl은 HttpSessionBindingListener 구현했으므로 바인딩 시 valueBound() 메서드가 호출됨(콘솔에 '사용자 접속' 출력, ++total_User로 접속자수 증가시킴)

 }

 

[ LoginImpl 클래스 ]

HttpSessopmBindingListener 를 구현하여 세션에 바인딩 이벤트 발생시 처리하는 이벤트핸들러(valueBound(), valueUnbound())가 구현되어있음

세션에 바인딩시(로그인폼에서 전송>LoginTest에서 LoginImpl 객체 생성하여 id,pw 저장 후 세션에 바인딩함) valueBound() 메서드가 호출되어 LoginImpl의 static 변수인 total_user의 값을 1 증가시킴

 

* HttpSessionBindingListener를 구현한 LoginImpl 클래스는 리스너를 따로 등록할 필요 없음

 

-HttpSessionListener 이용해 로그인 접속자수 표시

* HttpSessionListener : Session 객체의 생성/소멸 이벤트 발생 시 처리함

380p

HttpSessionListener를 이용해 웹 페이지 로그인 시 접속자수와 모든 접속자 id를 표시해주는 기능을 구현해보자

 

[ LoginTest 클래스 ]

List user_list = new ArrayList(); // 로그인한 접속자 id를 저장하는 list임

 

//컨텍스트 가져오고, 세션 생성(-> HttpSession 이벤트 발생 > 서블릿 컨테이너-등록된 HttpSessionListener 구현체가 있는지 확인(LoginImple) > 해당 구현체의 sessionCreated()를 호출)

 

// form으로 전송된 id, pw 가져와서 생성한 LoginImpl 객체를 세션에 바인딩, user_list에 id 추가, 컨텍스트에 user_list 바인딩

 

out.println("<a href='logout?user_id=" + user_id + " '>로그아웃</a>");

// 로그아웃 클릭 시 LogoutTest 서블릿으로 접속자id를 전송해 로그아웃 처리함 

 

[ LogoutTest 클래스 ]

로그아웃 링크 클릭시 접속자수 1을 감소시킴, user_list에서 해당 접속자id를 삭제 후 다시 user_list를 컨텍스트 객체에 바인딩함

 

String user_id = request.getParameter("user_id"); // request에서 user_id 받아옴

session.invalidate() // 로그아웃 시 세션을 소멸시킴(-> HttpSession 이벤트 발생 > 서블릿 컨테이너-등록된 HttpSessionListener 구현체가 있는지 확인(LoginImple) > 해당 구현체의 sessionDestroyed()를 호출)

 

// 컨텍스트에 바인딩된 user_list 가져와서 리스트에서 해당 user_id 삭제함(user_list.remove(user_id) )

 

context.removeAttribute("user_list") // 컨텍스트에 바인딩된 user_list 삭제

context.setAttribute("user_list", user_list); // 로그아웃한 user_id 삭제한 user_list 다시 바인딩함

 

[ LoginImple 클래스 ]

* LoginImpl 클래스는 HttpSessionListener를 구현하여 세션 생성/소멸 시 이벤트를 처리하는 핸들러임

HttpSessionBindingListener와 달리 HttpSessionListener는 반드시 리스너를 구현한 이벤트핸들러를 애너테이션을 이용해 등록해야함

: 패키지 우클릭> New > Listener > Class name으로 'LoginImpl'을 입력하고 next > HttpSessionListener체 체크 후 next > Finish >> LoginImpl에 @WebListener 애너테이션으로 리스너가 등록된 것을 확인 > 리스너를 등록한 이벤트핸들러를 이용해서 세션을 생성 시에는 sessionCreated() 메서드로 이벤트를 처리하고, 세션을 삭제할 때는 sesisonDestroyed() 메서드로 이벤트를 삭제함 

=>

@WebListener

public class LoginImpl implements HttpSessionListener { 

...

  @Override

  public void sessionCreated(HttpSessionEvent.... // 세션 생성 시 이벤트를 처리함(접속자수 1 증가시킴)

  }

 

  @Override

  public void sessionDestroyed(... // 세션 소멸 시 이벤트를 처리함(접속자수 1 감소시킴)

  }


* HttpSessionListner로 세션 생성/종료 이벤트 처리

서블릿 컨테이너는 HttpSessionEvent가 발생한 시점에 자신에게 등록된 HttpSessionListener 구현체가 있는지 확인합니다. 만약 등록된 리스너가 있으면 세션이 생성된 시점에 해당 HttpSessionListener 리스너의 sessionCreated()를 세션이 제거된 시점에 sessionDestroyed()를 호출합니다.

출처 https://dololak.tistory.com/614