Servlet과 Container

Byung Seon Kang·2022년 9월 8일
0

서블릿에 대해

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

Servlet이 만들어진 이유

  • 초창기에는 static web page를 전달하는 Web server로만 존재했다.
    그래서 모든 유저는 이미 구성되어 있는 동일한 페이지를 보았다.
    하지만 점점 더 많은 요구사항이 생기면서 static web page만으로는 니즈를 만족시키기 힘들어졌다(마이페이지 등등 유저에 따라 바뀌어야 하는 동적페이지가 필요로 하게 됨)
  • 또한 웹 서버만으로는 데이터 저장이 불가능했다. 파일로, 혹은 데이터베이스로 저장하기 위해서 또는 response page를 만들기 위해서 form data를 처리하려면 다른 helper app을 사용했어야 함.

Helper app 중 하나는 CGI(Common Gateway Interface).

  • Perl script로 작성되었음(not java).

  • C, Python, PHP등등 많은 언어에서 사용할 수 있었음.

  • CGI를 사용하면 동적 웹페이지를 사용할 수 있었다.

  • 하지만 문제는 CGI는 모든 request마다 프로세스를 만드는 문제가 존재.

    너무 무거웠다.

  • 그래서 서블릿/JSP 와 이를 관리해주는 서블릿 컨테이너가 등장.

    • 요청당 쓰레드 단위로 관리하기때문에 훨씬 성능이 좋다.

Container

  • 서블릿은 main() 메소드를 가지고 있지 않다. 그렇다면 어떻게 실행되는 걸까?
    • 컨테이너를 통해 실행되고 관리된다.
  • 컨테이너의 예시로는 톰캣이 있다(서블릿/JSP 컨테이너).

Container

  • 컨테이너의 역할은 다음과같다.
    • Communication support
      • web server와 통신하기 쉽게 해준다.
      • ServerSocket, listening port, stream등을 자동으로 만들어준다.
    • multithreading support
      • 컨테이너는 요청마다 새로운 자바 쓰레드를 생성 및 소멸 관리
        • 다만 이는 thread safety를 신경쓰지 않아도 된다는 의미는 아님.
        • 여전히 동기화 이슈는 있을 수 있음.
    • Lifecycle Management
      • instantiating 및 initialize, inoke servlet method, 이후 사용뒤엔 servlet instance가 garbage collection에 잡히도록 설정해준다.
      • 쉽게 말하면 자원 관리에 대해 크게 신경 쓸 필요가 없어졌다.
    • Declarative Security
      • 크게 설정할 필요 없이 XML deployment descriptor에 쉽게 써놓기만하면 설정됨.
    • JSP 지원
    • 그밖에 logging등을 위한 필터링등이 있음.

Container 동작 방식(간단하게)

  1. Http Request가 client로부터 날아온다.

  2. Container가 이를 받아 HttpServletRequestHttpServletResponse 생성

  3. URL에 맞는 Servlet을 고른 뒤에 쓰레드를 생성한다. 그리고 그 서블릿 쓰레드에 Request object와 Response Object를 전달한다.
    여기서 servlet을 고르는 과정은 차후 설명하겠음.

  1. 개별 쓰레드에서 service() method를 호출한다.
    service method는 doGet() , doPost()와 같은 method를 다시 호출

  2. doGet() method는 동적 페이지를 생성하고 이를 response 객체에 붙여준다. 여기서 기억할 것은 container는 여전히 response 객체를 참조하고 있다는 것!

  3. thread가 수행을 완료하면 컨테이너는 response 객체를 HTTP response로 변경하고 이를 클라이언트에게 반환한다. 이후 request, response object를 제거한다.

코드로 동작방식을 대충 보면 다음과 같다.


public class MyServlet extends HttpServlet {
	public void doGet(HttpServletRequest req, HttpServletResponse res){
    	PrintWriter out = res.getWriter();
        java.util.Date today = new java.util.Date();
        out.println("<html> " +
        		"<body>" +
                "<br>" + today +
                "</body>" +
                "</html>");
    }
}
  • service() method에 대해서는 차후 살펴보도록 하겠음.
    • 우선 service() method가 doGet()을 호출한다는 것 정도만 알아두기.

Container가 Servlet을 고르는 과정

  • 서블릿은 3개의 이름을 가지고 있다.
  1. 실제 file path name
    말그대로 실제 파일 경로다.
    e.g.) main/src/MyClass.java
  2. Deployment name
    실제 파일 경로명이나 class와 동일하게 해도 되고, 다르게 해도 된다.
    실제 class, 파일 경로와 매핑시켜놓으면 이 deployment name을 사용해서 찾아간다.
    symbolic link라고 생각하면 편하다.
  3. public url name
    클라이언트가 사용
    https://www.naver.com 이런거
  • 근데 왜 3개 이름 다 가지고 있을까. 그냥 실제 경로 이름하나만 가져도 되지 않나?

  • 이유 : flexibility와 security

    • 실제 서버 구조를 유저가 알아버리게 되면 직접 접근이 되기때문에 보안상 좋지 않다.
    • 또한 servlet 파일의 위치가 변경된다고 하더라도 deployment name을 사용하면 바로 찾을 수 있기 때문에 유연성이 상당히 증가.
  • 이제 그럼 Deployment Descriptor로 url - servlet 매핑을 해보자.

  1. <servlet>
    Deployment name을 실제 class에 매핑시켜준다.
  2. <servlet-mapping>
    Deployment name을 public URL name에 매핑시켜준다.
<web-app ...>
  <servlet>
    <servlet-name>Internal name 1</servlet-name>
    <servlet-class>foo.Servlet1</servlet-class>
  </servlet>
  <servlet>
    <servlet-name>Internal name 2</servlet-name>
    <servlet-class>foo.Servlet2</servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>Internal name 1</servlet-name>
    <url-pattern>/public1</url-pattern>
  </servlet-mapping>
    <servlet-mapping>
    <servlet-name>Internal name 2</servlet-name>
    <url-pattern>/public2</url-pattern>
  </servlet-mapping>
  • 이렇게만 하면 각 서블릿을 url, Deployment name, actual path 3개 연결해서 사용하게 된다.
    • DD(Deployment Descriptor) 조금만 자세히
      • 이걸로 security role이나 configuration 등을 커스터마이징 가능.
      • 일단 이걸로 위처럼 설정을 실제 코드 만질 필요 없이 xml로 커스터마이징 할 수 있다는 정도만 알아두기.
  • 자 그런데 여기서 질문이 생긴다. 서블릿을 여러개 생성하는게 맞을까? 아래 내용을 보면서 천천히 생각해보자

구조를 발전시켜나가는 과정

시도 1.
처음부터 생각해보자. 우리는 single servlet을 통해 모든 기능을 실행하고 있다.
이때 servlet을 기능에 따라 구분하면 더 객체지향적인 디자인을 할 수 있지 않을까?
그래서 servlet을 각각 나눠보았다.

결과 1.
서블릿을 나누니 OO에 맞는 디자인을 할 수 있었다. 이렇게 서블릿을 나누고 나면 각 servlet에는 기능마다 필요한 데이터베이스 수정 및 읽기 기능, HTML을 반환하여 보여주는 stream등이 필요하게 될 것이다. 아래 코드를 보자.

public class MyAppServlet1 extends HttpServlet {
	public void doGet(HttpServletRequest req, HttpServletResponse res){
      
      //이 서블릿에서 수행할 비즈니스 로직
      //데이터베이스에 저장하고, 읽는 등의 코드가 있음.
      
      //여기는 프레젠테이션
      out.println("어쩌구 저쩌구...");
  }
}

문제점이 보이는가? 위 코드는 서블릿 내부에 비즈니스 로직과 presentation이 짬뽕되어있다.

시도2.
그래서 이제 JSP를 사용하기로 했다.
JSP를 통해 비즈니스 로직과 presentation을 구분해서 SoC(Separation of Concerns)를 고려한 코드를 짜보았다.

결과2.

public class MyAppServlet1 extends HttpServlet {
	public void doGet(HttpServletRequest req, HttpServletResponse res){
  	PrintWriter out = res.getWriter();
      java.util.Date today = new java.util.Date();
      
      //이 서블릿에서 수행할 비즈니스 로직
      //데이터베이스에 저장하고, 읽는 등의 코드가 있음.
    	
      //이젠 JSP page로 request를 forwarding 해주는 코드를 삽입
      //output stream에 HTML을 더이상 프린트하지 않는다.
  }
}

이제 코드가 상당히 나아졌다. 하지만 여전히 문제점이 존재한다.
서블릿에서 비즈니스 로직과 presentation을 분리했으나 여전히 서블릿에는 비즈니스 로직이 존재한다.
이렇게 되면 다른 서블릿에도 반복되는 비즈니스 로직을 적용시킬 수 없게 된다.

시도 3.
그래서 이젠 MVC 패턴을 사용하기로 했다.
MVC은 Model-Controller-View를 의미
Controller와 Model, View를 분리.
비즈니스 로직은 Model에 담겨있다.

결과3.

public class MyAppServlet1 extends HttpServlet {
	public void doGet(HttpServletRequest req, HttpServletResponse res){
    
      //비즈니스 로직을 수행할 모델로 요청 전달.
      
      //비즈니스 로직 수행한 것을 JSP 페이지로 포워딩할때 같이 전달.
      
  }
}

이렇게 하게 되면 서블릿 내부에 비즈니스 로직이 담겨있지 않기 때문에 책임의 구분이 더 깔끔해진다.

여기까지 왔으면 다시 위에 있던 질문을 해보자.

  • 서블릿이 기능에 따라 여러개가 필요할까?
  • 역할을 계속해서 구분하다보니 모든 서블릿이 동일한 코드를 가지게 되었다.
    비즈니스 로직은 모델에게 맡기고 뷰는 JSP에게 던져주는 통로 역할정도만 수행한다.
    여러개의 서블릿을 나눴음에도 동일한 코드만을 가지게 되었다면 의미가 없는 것이 아닐까?
  • 그렇다면 다시 서블릿을 하나로 두고 사용하는게 옳지 않을까?
    누군가는 객체지향적인 디자인이 아니라고 반박할 수도 있겠다.
  • 이 질문에 대한 대답은 차후 글에서 서술하겠다.

J2EE란

  • Java 2 Enterprise Edition은 Servlet과 JSP 스펙, EJB를 모두 포함한 녀석.
    • Web Container는 Web component(Servlet과 jsp)를 관리해주는 녀석
    • EJB Container Enterprise JavaBean, 즉 business component를 위해 존재한다.
    • J2EE 서버와 완벽히 부합하는 서버는 web Container와 EJB Container(이외에도 JNDI, JMS 구현체등등)를 가지고 있어야 한다.
    • 톰캣은 이러한 점에서 웹 컨테이너라고 볼 수 있겠다.
      • 톰캣에는 EJB 컨테이너가 존재하지 않기 때문
      • 그래서 보통 J2EE 스펙을 일부 만족하는 웹 컨테이너 또는 Servlet/JSP 컨테이너라고 부른다.
profile
왜 필요한지 질문하기

0개의 댓글