-
서블릿 작동 원리와 xml파일 servlet 생성 후 매핑 및 doGet(), init()web( jsp, servlet )/servlet 2024. 7. 12. 11:35
▤ 목차
✔ 서블릿 작동 원리
⌨ 서블릿이란?
클라이언트가 요청을 하면 해당 요청에 맞는 처리를 한 후 결과를 반환하는 자바 웹 프로그래밍 기술이다.
자바 서블릿(Java Servlet)은 자바를 사용하여 웹페이지를 동적으로
생성하는 서버 측 프로그램 혹은 그 사양을 말하며, 흔히 "서블릿"이라 불린다.
...
서블릿은 JSP와 비슷한 점이 있지만,
JSP가 HTML 문서 안에 Java 코드를 포함하고 있는 반면,
서블릿은 자바 코드 안에 HTML을 포함하고 있다는 차이점이 있다.
-wiki
- 클라이언트의 요청에 대해 동적으로 작동하는 웹 어플리케이션 컴포넌트이다.
- html을 통해 요청 받고 응답한다.
- java 기반의 프로그래밍 기술이기에 java Thread를 이용하여 동작한다.
- MVC 패턴에서 controller로 이용된다.
- HTTP 프로토콜 서비스를 지원하는 import javax.servlet.http.HttpServlet 클래스를 상속받는다.
- servlet 파일을 변경한 경우 재컴파일을 해야 한다.
동적인 페이지를 제공하기 위해서 웹서버는 다른 곳에 도움을 받아 동적인 페이지를 작성해야 한다.
이때 웹서버가 동적인 페이지를 제공할 수 있도록 도와주는 어플리케이션이 서블릿이며 동적인 페이지를 생성하는 어플리케이션이 CGI이다.
CGI(Common Gateway Interface)란?
CGI는 웹서버와 프로그램 간 교환방식(interface)을 의미한다.
서블릿과 비슷한 기술이라 생각하면 된다.
CGI는 요청이 있을 때마다 새로운 프로세스가 생성되어 응답한다.
(Servlet은 외부 요청마다 프로세스보다 가벼운 스레드로써 응답하므로 보다 가볍다.)
- 특정 플랫폼에 의존하지 않는다.
웹서버 등으로부터 외부 프로그램을 호출하는 조합을 가리킨다.
✨ 라이프 사이클
서블릿 컨테이너가 하는 일은 통신지원, 라이프 사이클 관리, 멀티 스레딩.. 이 있다.
Life Cycle
클래스 로딩 > 서블릿 인스턴스화(생성자 실행) > init() > service() 또는 doGet()/doPost() > destroy()클라이언트로 서블릿 파일을 요청받으면 서블릿을 바로 호출하지 않고 컨테이너(톰켓)에게 요청을 넘긴다.
컨테이너는 request, response 객체를 만들어 이를 인자로 서블릿 doGet(), doPost() 메서드 중 하나를 호출한다.
+ jsp 페이지도 서블릿 클래스로 변환된 후 같은 라이프 사이클을 수행한다.
위의 그림과 같이 일반 사용자 요청 처리를 서블릿 컨테이너 내부에서 스레드 단위로 요청 처리를 한다.
이렇게 개발자가 아닌 컨테이너가 제어하는 것을 제어의 역전(IoC)라고 한다.
간단하게 보면 위와 같은 흐름을 탄다.
서블릿의 흐름을 조금 더 자세하게 알아보자.
위에서 언급했듯이 과정은 init() > service() > destroy() 방식으로 흘러간다.
실제로 웹서버 실행과 서블릿 컨테이너로 가는 사이에 해당 요청의 file 유무를 검사한다.
이때, file이 없다면 404 에러를 발생시킨다. (참고로 4 ** 에러는 클라이언트단 에러이다.)
해당 file이 있다면 다음 단계인 서블릿 컨테이너로 들어간다.
만약 처음 들어온 client라면 메모리 로딩 > 서블릿 객체 생성 > ServletConfig 객체 생성 init()을 실행한다.
즉, init() 메서드는 최초 한 번만 실행된다.
이후에는 들어오는 사람마다 HttpServletRequest, HttpServletResponse 객체를 생성한다.
서블릿 컨테이너
+ 컨테이너는 웹 컴포넌트를 저장하는 저장소 역할, 메모리 로딩, 객체 생성 및 초기화 등 서블릿의 생명주기를 관리하고 JSP를 서블릿으로 변환하는 기능을 수행하는 프로그램이다.
+ 클라이언트의 http 요청을 서블릿에 전달하고 서블릿의 http 응답 겨로 가를 클라이언트에 돌려준다
+ 서블릿 엔진이라고도 하며 서블릿 컨테이너는 서블릿 컴포넌트를 실행하는 환경을 제공한다.👏 중요
서블릿을 수정했으면 서버를 재실행해야 한다.
서블릿은 하나의 상태만 가지고 있다.
초기화가 되지 않았다?
1. 초기화되는 중이다. (생성자를 실행하거나 init 메서드가 실행 중)
2. 소멸되는 중이다.(destroy 메서드가 실행 중)
3. 존재하지 않는다.
✔ xml파일 servlet 생성 후 매핑
⌨ servlet 매핑 이유
클라이언트에서 URL을 이용해 서버에게 파일을 요청한다.
클라이언트의 요청이 들어오면 servlet은 xml을 먼저 읽어본다.
위와 같은 방식으로 진행된다.
물리적 클래스명을 사용하면 편하지만 아래와 같은 단점이 생긴다.
클래스 명이 길어지면 입력이 불편해지고 클래스 이름이 노출되기에 보안에 취약하다는 단점이 생긴다.
때문에 논리적 클래스명(별명)을 매핑해 주는 방식을 사용한다.
매핑하는 방법
- web.xml 직접 작성하기
- 서블릿 어노테이션 이용하기
어노테이션은 나중에 나온 기술이고 편리하지만 동작방식을 알기 위해서는 xml을 만져보는 것이 좋다.
🔷 web.xml 직접 작성
<servlet> <servlet-name>abc</servlet-name> <!-- @WebServlet("/HelloServlet") 해당 어노테이션이 나오기 전 xml 처리 --> <servlet-class>pack.HelloServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>abc</servlet-name> <url-pattern>/HelloServlet</url-pattern> <url-pattern>/Hello.kor</url-pattern> <!-- 논리적 파일명(별명)을 적어줌. 같은 이름인 servlet-name을 찾아가서 해당 class를 사용한다. --> <url-pattern>/dajeong</url-pattern> <!-- 논리적 파일명(별명)--> </servlet-mapping>
url-pattern 태그를 만들어 논리적 명을 설정해 주면
클라이언트가 url을 요청할 때 논리적 파일명을 통해 들어오면 같은 servlet-name으로 매핑된 클래스로 들어오게 된다.
<body> <h2>servlet 연습</h2> <a href="HelloServlet">show HelloServlet 1 </a> <br> <a href="Hello.kor">show HelloServlet 2</a> <br> <a href="dajeong">show HelloServlet 3</a> </body>
해당 html 파일과 연결되어 파일을 불러온다.
직접 작성하면 가시적으로 보이지만 변경될 때마다 xml파일을 수정해야 하는 단점이 존재한다.
🔷 서블릿 어노테이션
@WebServlet("/HelloServlet") public class HelloServlet extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {} }
이렇게 직접 하나만 작성해도 되고 만약 논리적인 이름이 여러 개라면 아래와 같이 작성할 수 있다.
@WebServlet( name = "ServletGo", urlPatterns = { "/HelloServlet", "/Hello.kor", "/dajeong" }, loadOnStartup = 1 )
아래의 html파일을 웹브라우저에 실행시키면 해당 파일과 연결되었음을 알 수 있다.
<body> <h2>servlet 연습</h2> <a href="HelloServlet">show HelloServlet 1 </a> <br> <a href="Hello.kor">show HelloServlet 2</a> <br> <a href="dajeong">show HelloServlet 3</a> </body>
👏 정리
servlet은 xml을 먼저 읽어 본 후 어노테이션을 읽는다.보통 어노테이션은 관련 코드 상단에 작성하기 때문에 xml 파일을 열어 수정하는 것보다 수정이 편리하다.
xml은 전반전으로 설정할 때 코드를 넣어주는 것이 좋다. 전역으로 설정되는 코드이다.
어노테이션은 지역적이라고 생각할 수 있다.
서블릿 라이프 사이클에서 가장 중요한 메서드는 크게 2개가 있다.
더 주된 부분은 init()/ destroy()/ service() 메서드이다.
✔init() / destroy()
⌨ init() _초기화하기
public void init(ServletConfig config) throws ServletException { num=1; System.out.println("init 수행 num = "+num); }
어떻게 활용될지 가장 궁금했던 메서드이다.
init() 메서드는 서버가 서블릿의 인스턴스를 구성한 직후 서버에 의해 호출된다.
서버가 시작되어 처음 요청될 때, service() 메서드가 호출되기 직전 수행되는 메서드이다.
(초기화할 기회를 주는 메서드이다.)
서버 관리자의 요청으로 어떤 경우든 서블릿이 첫 번째 요청을 처리하기 전에 init() 호출되는 것을 보장한다.
- 서블릿이 사용하는 객체를 생성하거나 로드한다.
- 서블릿 초기화 작업 수행 시 자동으로 호출되는 메서드이다.
- 초기화할 코드가 있다면 init() 메서드를 재정의하자. 재정의가 가능하다.
- 서블릿 일생 중 단 한번 호출된다. 즉, 서블릿이 다시 로드되지 않는 이상 변경되지 않는다.
> 동적인 값을 주면 안 된다.
🎈사용 예시
public class exServlet extends HttpServlet { private static final long serialVersionUID = 1L; String defaultName; int defaultAge; public void init(ServletConfig config) throws ServletException { // 최초 생성되는 곳. 최초의 클라이언트만 거쳐가는 메서드이다. super.init(config); // init 메서드에서 서블릿 필드 초기화 defaultName = config.getInitParameter("defaultName"); if(defaultName == null) { defaultName = "안녕"; } } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 지정되지 않은 경우 먼저 실행되는 메서드 response.setContentType("text/html;charset=utf-8"); //mime type과 문자 코드 PrintWriter out = response.getWriter(); out.println("<html><body>"); out.println("<h1>hi hi</h1>"); out.print("이름은 "+defaultName+"이다."); out.println("</body></html>"); out.close(); } }
이런 식으로 초기화하는 경우에 사용된다.
💡
config.getInitParameter
메서드는 xml에서 값을 가져오는 메서드이다.
미리 xml에서 값을 지정해 주거 위의 코드처럼 인위적으로 null인 경우 값을 초기화할 수 있다.
더보기<web-app> <servlet> <servlet-name>MyServlet</servlet-name> <servlet-class>com.example.MyServlet</servlet-class> <init-param> <param-name>defaultName</param-name> <param-value>John Doe</param-value> </init-param> </servlet> <servlet-mapping> <servlet-name>MyServlet</servlet-name> <url-pattern>/servlet-url</url-pattern> </servlet-mapping> </web-app>
xml 파일은 서블릿의 배치 정보를 정의하는 정적인 파일이다.
💡
super.init(config);
위의 코드는 HttpServlet 클래스의 init메서드를 호출하는 코드이다.
HttpServlet클래스를 읽어보면 init() 메서드는 정의되어 있지 않았다.
HttpServlet클래스가 GeneticSetvlet을 상속받고 있었다.
GenericServlet에 init() 메서드를 정의하고 있다.
대신에 HttpServlet 클래스에서 필요한 초기화 작업을 수행하고 필요한 경우에 doGet, doPost를 오버라이드 해서 서블릿을 구현한다.
따라서, super.init(config); 을 호출할 필요가 없다.
⌨ destroy _ 소멸
public void destroy() { .close() }
- 서블릿이 소멸되기 전에 가진 자원들을 깨끗하게 정리할 기회를 준다.
- 일생에 오직 한 번만 호출된다.
- 파라미터가 없는 메서드이며 void로 리턴값이 없다.
- public으로 선언해야 한다.
- throws절 사용이 불가능하다.
📝 xml파일에서도 초기화 처리
웹 서버 서비스가 시작되면 자동으로 호출된다.
현재 서블릿 클래스의 초기화를 담당하는 메서드이다.
xml파일에서도 초기화 처리가 가능하다.
<servlet> <init-param> <param-name>file_name</param-name> <param-value>패키지파일.확장자</param-value> </init-param> </servlet>
✔service()
✏️코드
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { num +=1; System.out.println("service 실행 num = "+num); //doGet(request, response); //doPost(request, response); }
- 컨테이너가 서블릿 service() 메서드를 호출합니다.
- 브라우저에서 지정한 방식에 따라 doGet()/ doPost()를 호출 시 결정한다.
- 만약, 클라이언트 쪽에서 get방식으로 http 메서드를 날렸다면 service() 메서드는 서블릿의 doGet() 메서드를 호출한다.
- service() 메서드가 끝나면, 스레드를 소멸하거나 컨테이너가 관리하는 스레드 풀(pool)로 돌려보낸다.
- request와 response 객체는 가비지 컬렉션이 될 준비를 할 것이다.
👏 중요
service() 메서드가 없으면 get, post형식으로 나눠 처리된다.
나누고 싶지 않으면 service 메서드를 사용하면 된다.
즉, get, post 보다 우선순위가 높다.
서블릿의 대부분의 일생을 이 부분에서 보낸다.
✔doGet()
웹이 실행될 때 서블릿이 해당 파일을 받아본 후, 파일이 자바로 되어 있으면 톰캣으로 넘긴다.
톰캣은 해당 파일을 들고 JDK을 시켜 컴파일을 시킨다.
해당 doGet() 메서드에 출력값이 있으면 웹브라우저에 출력하게 된다.
⌨ 형식
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html;charset=utf-8");//mime type과 문자코드 PrintWriter out = response.getWriter();//웹브라우저 출력 }
http get방식이기에 URL에 해당 변수의 데이터가 노출(header)된다.
💻 코드로 보기
자바와 같다. 하지만 자바는 System.out.println을 통해 해당 출력장치인 console창에 출력하지만
response.getWriter() 메서드를 통해 웹브라우저에 출력시킨다.
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // get 요청 시 수행 num +=1; System.out.println("get 요청시 실행 num = "+num); }
기본 메서드 요청 방식은 get이기에 만약 service()가 없으면 자동으로 수행되는 메서드이다.
✔getPost()
⌨ 형식
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // post 요청 시 수행 num +=1; System.out.println("poet 요청시 실행 num = "+num); }
http post 방식이기에 데이터를 body에 변수를 포함시켜 요청한다.
보안에 강하고 전달 데이터의 크기 용량이 크다.
🎇동일한 name이 2개 이상인 경우 배열 ( getParameterValues )
<form action="exServlet" method="get" id="frm"> <label for="name">이름 : </label><input type="text" name="name" id="name" value="haha"> <br> <label for="age">나이 : </label><input type="text" name="age" id="age" value="123"> <br> <label for="address">주소 : </label><input type="text" name="address" id="addr1" value="서울시"> <br> <label for="address">주소 : </label><input type="text" name="address" id="addr2" value="송파구"> <br> <input type="button" onclick="submit()" value="전 송"> </form>
위와 같이 주소를 같은 name으로 지정하는 경우, servlet 파일로 전송될때 배열로 받을 수 있다.
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 지정되지 않은 경우 먼저 실행되는 메서드 response.setContentType("text/html;charset=utf-8"); //mime type과 문자 코드 System.out.println("doGet() 실행"); String addr[] = request.getParameterValues("address"); PrintWriter out = response.getWriter(); for(String a : addr) { out.println("안녕" + a); } }
[ 넘어오는 출력값이 깨지는 경우 ]
더보기결과를 출력할때 넘어오는 값이 깨져서 출력됐다.
1. html mata 태크 확인해보기.
<meta charset="utf-8">
2. 넘어오는 characterEncoding 추가해주기
request.setCharacterEncoding("utf-8");
😊 코드 전체로 확인해 보기
package pack; import java.io.IOException; import java.io.PrintWriter; import javax.servlet.ServletConfig; 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("/exServlet") public class exServlet extends HttpServlet { private static final long serialVersionUID = 1L; public void init(ServletConfig config) throws ServletException { // 최초 생성되는 곳. 최초의 클라이언트만 거쳐가는 메서드이다. System.out.println("init() 실행"); } protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // doget,dopost와 같은 일처리를 하지만 먼저 실행되는 메서드 해당 메서드가 있으면 doGet,doPost가 실행하지 않는다. System.out.println("service() 실행"); } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 지정되지 않은 경우 먼저 실행되는 메서드 //response.setContentType("text/html;charset=utf-8"); //mime type과 문자 코드 System.out.println("doGet() 실행"); } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // post 방식 지정인 경우 실행되는 메서드 System.out.println("doPost() 실행"); } public void destroy() { // TODO Auto-generated method stub System.out.println("destroy() 실행 "); } }
자, 이런 경우, 결과는 console창에 어떻게 찍힐까?
최초 실행하는 경우, init이 찍힌 후 다시 들어오면 실행이 되지 않는다.
이후 service 메서드가 실행이 되는 doGet과 doPost는 실행이 되지 않았다.
이후 해당 스레드가 소멸될 때 destroy 메서드가 실행이 된다.
즉, service 메서드 안에서 doGet/ doPost를 호출하지 않는 이상 doGet과 doPost는 의미가 없다.
(특정 http 메서드에 대한 처리가 필요하다면 오버라이드해서 구현하면 된다.)
service()가 doget/ dopost 보다 우선 실행되는 것을 알았다. 그럼, 만약 service 메서드가 없는 경우에는 어떨까?
service 메서드를 주석처리하고 실행을 해보면 default 전달 방식인 get 방식으로 전달된다.
post 방식을 원한다면, html 파일의 form태그에 method를 post로 지정해주면 된다.
<form action="exServlet" method="post" id="frm">
'web( jsp, servlet ) > servlet' 카테고리의 다른 글
세션과 쿠키 (0) 2024.07.15