관리 메뉴

거니의 velog

(24) 서블릿의 필터와 리스너 기능 1 본문

Java_Servlet

(24) 서블릿의 필터와 리스너 기능 1

Unlimited00 2023. 8. 30. 20:31

* 이번에는 서블릿의 기능을 도와주는 다른 API들에 대해 알아보자. 우선 서블릿의 요청과 응답 작업하기 전에 수행하는 필터(Filter) 기능을 알아본 다음 서블릿의 속성과 스코프(Scope) 개념에 대해 살펴보자. 그리고 서블릿 관련 API에 특정 이벤트가 발생했을 때 이벤트를 처리할 수 있는 여러 가지 리스터(Listener)에 대해서도 알아보자. 이번에 배울 기능을 알아두면 좀 더 고급 기능을 구현할 수 있다.


1. 서블릿 속성과 스코프

* 서블릿 속성(attribute)이란 다음 세 가지 서블릿 API 클래스에 저장되는 객체(정보)라고 보면 된다.

- ServletContext

- HttpSession

- HttpServletRequest

* 각 속성은 앞에서 이미 사용해 봤다. 서블릿 API의 setAttribute(String name, Object value)로 바인딩하고, 필요할 때 getAttribute(String name)으로 바인딩된 속성을 가져오면 된다. 또한 removeAttribute(String name)을 이용해 서블릿 API에서 제거할 수도 있다.

* 서블릿의 스코프(Scope)는 서블릿 API에 바인딩된 속성에 대한 접근 범위를 의미한다.

* 앞에서도 사용해 봤듯이 ServletContext에 바인딩된 속성은 애플리케이션 전체에서 접근할 수 있으므로 애플리케이션 스코프를 갖는다. HttpSession에 바인딩된 속성은 그 HttpSession에 해당하는 브라우저에만 접근할 수 있으므로 세션 스코프를 갖는다. HttpServletRequest는 해당 요청/응답에 대해서만 접근하므로 리퀘스트 스코프를 갖는다.

* 스코프의 기능은 다음과 같다.

- 로그인 상태 유지 기능

- 장바구니 기능

- MVC의 Model과 View의 데이터 전달 기능

<스코프의 종류와 특징>

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

* 각 서블릿 API에 바인딩된 속성의 스코프를 알아보자.

1. 다음과 같이 GetAttribute, SetAttribute 클래스 파일을 준비한다.

2. SetAttribute 클래스를 다음과 같이 작성한다. ServletContext, HttpSession, HttpServletRequest 객체의 setAttribute() 메서드를 이용해 속성을 바인딩한다.

package sec01.ex01;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;


@WebServlet("/set")
public class SetAttribute extends HttpServlet {
	
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		response.setContentType("text/html;charset=utf-8");
		PrintWriter out = response.getWriter();
		String ctxMesg = "context에 바인딩됩니다.";
		String sesMesg = "session에 바인딩됩니다.";
		String reqMesg = "request에 바인딩됩니다.";

		ServletContext ctx = getServletContext();
		HttpSession session = request.getSession();
		ctx.setAttribute("context", ctxMesg);
		session.setAttribute("session", sesMesg);
		request.setAttribute("request", reqMesg); // HttpServletContext 객체, HttpSession 객체, HttpServletRequest 객체를 얻은 후 속성을 바인딩한다.
		out.print("바인딩을 수행합니다.");
	}

}

3. 두 번째 서블릿인 GetAttribute 클래스를 다음과 같이 작성한다. 각 서블릿 API들의 getAttribute() 메서드를 이용해 속성 이름으로 바인딩한 값을 가져와 브라우저로 출력한다.

package sec01.ex01;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;


@WebServlet("/get")
public class GetAttribute extends HttpServlet {
	
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		
		response.setContentType("text/html;charset=utf-8");
		PrintWriter out = response.getWriter();
		ServletContext ctx = getServletContext();
		HttpSession sess = request.getSession();

		String ctxMesg = (String) ctx.getAttribute("context");
		String sesMesg = (String) sess.getAttribute("session");
		String reqMesg = (String) request.getAttribute("request"); // 각 서블릿 API에서 바인딩된 속성의 값을 가져온다.

		out.print("context값 : " + ctxMesg + "<br>");
		out.print("session값 : " + sesMesg + "<br>");
		out.print("request값 : " + reqMesg + "<br>");

	}

}

4. 브라우저에서 /set으로 요청해 속성을 바인딩한다.

- http://localhost:8090/pro10/set

5. Context와 Session 객체에 바인딩된 속성은 같은 브라우저에서 접근할 수 있으므로 값을 출력한다. 그러나 기존에 바인딩된 request 객체는 /get으로 요청하여 생성된 request 객체와 다르므로 null이 출력된다.

- http://localhost:8090/pro10/get

6. 인터넷 익스플로러에서 /get으로 요청하면? 익스플로러에서 요청했기 때문에 이번에는 크롬의 세션 객체에는 접근할 수 없어 null을 출력한다. 반면에 Context 객체에 바인딩된 데이터는 모든 브라우저에서 같은 결과를 출력한다.


2. 서블릿의 여러 가지 URL 패턴

* 이번에는 서블릿에 요청할 떄 사용하는 URL 패턴에 대해 자세히 알아보자.

* URL 패턴이란 실제 서블릿의 매핑 이름을 말한다. 즉, 서블릿 매핑 시 사용되는 가상의 이름으로, 클라이언트가 브라우저에서 요청할 때 사용되며 반드시 / (슬래시) 로 시작해야 한다.

* 서블릿 매핑 이름으로 사용되는 URL 패턴의 종류는 정확히 이름까지 일치하는지, 디렉터리까지만 일치하는지, 확장자만 일치하는지에 따라 세 가지로 나누어진다.


(1) 서블릿에 여러 가지 URL 패턴 적용 실습

* 여러 가지 URL 패턴을 사용해 서블릿에 요청하는 방법을 알아보자.

1. 다음과 같이 TestServlet1~3 클래스 파일을 준비한다.

2. 첫 번째 서블릿인 TestServlet1 클래스를 다음과 같이 작성한다. 이 서블릿은 /first/test로 요청할 때 실행된다. 브라우저의 요청 URL에 대해 서블릿의 여러 가지 메서드를 이용하여 요청 관련 정보를 가져온다.

package sec02.ex01;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;


@WebServlet("/first/test") // 정확히 이름까지 일치하는 URL 패턴
public class TestServlet1 extends HttpServlet {
	
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

		request.setCharacterEncoding("utf-8");
		response.setContentType("text/html;charset=utf-8");
		PrintWriter out = response.getWriter();
		String context = request.getContextPath(); // 컨텍스트 이름만 가져온다.
		String url = request.getRequestURL().toString(); // 전체 URL을 가져온다.
		String mapping = request.getServletPath(); // 서블릿 매핑 이름만 가져온다.
		String uri = request.getRequestURI(); // URI를 가져온다.
		out.println("<html>");
		out.println("<head>");
		out.println("<title>Test Servlet1</title>");
		out.println("</head>");
		out.println("<body bgcolor='green'>");
		out.println("<b>TestServlet1입니다.</b><br>");
		out.println("<b>컨텍스트명 : "+ context + "</b><br>");
		out.println("<b>전체경로 : "+ url +"<b><br>");
		out.println("<b>매핑명 : "+mapping+"<b><br>");
		out.println("<b>URI : " + uri + "<b>");	
		out.println("</body>");
		out.println("</html>");
		out.close();

	}

}

3. 두 번째 서블릿인 TestServlet2 클래스를 다음과 같이 작성한다. /first/ 디렉터리 이름으로 시작되는 요청에 대해 실행된다.

package sec02.ex01;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;


@WebServlet("/first/*") // 디렉터리 이름만 일치하는 URL 패턴
public class TestServlet2 extends HttpServlet {
	
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

		request.setCharacterEncoding("utf-8");
		response.setContentType("text/html;charset=utf-8");
		PrintWriter out = response.getWriter();
		String context = request.getContextPath();
		String url = request.getRequestURL().toString();
		String mapping = request.getServletPath();
		String uri = request.getRequestURI();
		out.println("<html>");
		out.println("<head>");
		out.println("<title>Test Servlet2</title>");
		out.println("</head>");
		out.println("<body bgcolor='yellow'>");
		out.println("<b>TestServlet2입니다.</b><br>");
		out.println("<b>컨텍스트명 : "+ context + "</b><br>");
		out.println("<b>전체경로 : "+ url +"<b><br>");
		out.println("<b>매핑명 : "+mapping+"<b><br>");
		out.println("<b>URI : " + uri + "<b>");	
		out.println("</body>");
		out.println("</html>");
		out.close();

	}

}

4. 세 번째 서블릿인 TestServlet3 클래스를 다음과 같이 작성한다. 이는 매핑 이름에 상관없이 확장자가 .do면 실행된다.

package sec02.ex01;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;


@WebServlet("*.do") // 확장자만 일치하는 패턴
/*@WebServlet("/*")*/ // 모든 요청 URL 패턴
public class TestServlet3 extends HttpServlet {
	
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

		request.setCharacterEncoding("utf-8");
		response.setContentType("text/html;charset=utf-8");
		PrintWriter out = response.getWriter();
		String context = request.getContextPath();
		String url = request.getRequestURL().toString();
		String mapping = request.getServletPath();
		String uri = request.getRequestURI();
		out.println("<html>");
		out.println("<head>");
		out.println("<title>Test Servlet3</title>");
		out.println("</head>");
		out.println("<body bgcolor='red'>");
		out.println("<b>TestServlet3입니다.</b><br>");
		out.println("<b>컨텍스트명 : "+ context + "</b><br>");
		out.println("<b>전체경로 : "+ url +"<b><br>");
		out.println("<b>매핑명 : "+mapping+"<b><br>");
		out.println("<b>URI : " + uri + "<b>");	
		out.println("</body>");
		out.println("</html>");
		out.close();

	}

}

5. 각각의 매핑 이름으로 요청해 보자. 우선 정확한 매핑 이름 (/first/test) 으로 요청한 경우에는 다음과 같이 출력된다.

- http://localhost:8090/pro10/first/test

6. 디렉터리 이름만 일치하는 경우에는 각각 다음과 같이 출력된다.

- http://localhost:8090/pro10/first/base

- http://localhost:8090/pro10/first/base.do

7. 다음은 확장자가 일치했을 경우의 출력 결과로, 각각 /base.do와 /second/base.do로 요청했을 때 출력 결과이다.

- http://localhost:8090/pro10/base.do

- http://localhost:8090/pro10/second/base.do

* /first/base.do로 요청하면 확장자명이 .do로 끝나지만 앞의 디렉터리 이름이 우선하므로 TestServlet2가 실행된다. 반면에 /second/base.do로 요청하면 /second 디렉터리는 존재하지 않으므로 확장자명 .do를 우선하여 TestServlet3이 실행된다.

8. 다음은 TestServlet3 클래스의 URL 패턴을 /* 로 설정한 후 요청한 결과이다.

9. 톰캣을 다시 실행한 후 /second/base.로 요청하여 결과를 출력한다.

- http://localhost:8090/pro10/second/base

* 확장자명은 지정하지 않을 수도 있고, do 대신 자신이 원하는 이름으로 지정해서 사용할 수도 있다.

  - 다만, do는 일반적으로 MVC나 프레임워크에서 자주 사용하는 확장자명이다.