
평범한 웹 서버

웹서버 프로그램을 붙여 동적인 페이지를 생성하였다서블릿도 동적인 페이지를 만들기 위해 웹서버에 붙이는 프로그램 중 하나이다


HttpServletRequest.getMethod() 메소드를 호출하여 대신할 수 있다.서블릿을 이용하여 웹 요청을 다루면, 개발자들은 비지니스 로직에 더 집중할 수 있게 된다.
public class MyServlet extends HttpServlet {
@Override
public void init(ServletConfig config) throws ServletException {
super.init(config);
}
@Override
public void destroy() {
super.destroy();
}
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.service(req, resp);
}
}
init 메소드가 호출되고destroy 메소드가 호출된다.service 메소드는 클라이언트의 요청을 처리할 때 호출되는 메소드이다.public abstract class HttpServlet extends GenericServlet
{
private static final String METHOD_DELETE = "DELETE";
private static final String METHOD_HEAD = "HEAD";
private static final String METHOD_GET = "GET";
private static final String METHOD_OPTIONS = "OPTIONS";
private static final String METHOD_POST = "POST";
private static final String METHOD_PUT = "PUT";
private static final String METHOD_TRACE = "TRACE";
// 중략
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException
{
String method = req.getMethod();
if (method.equals(METHOD_GET)) {
long lastModified = getLastModified(req);
if (lastModified == -1) {
// servlet doesn't support if-modified-since, no reason
// to go through further expensive logic
doGet(req, resp);
} else {
long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
if (ifModifiedSince < lastModified) {
// If the servlet mod time is later, call doGet()
// Round down to the nearest second for a proper compare
// A ifModifiedSince of -1 will always be less
maybeSetLastModified(resp, lastModified);
doGet(req, resp);
} else {
resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
}
}
} else if (method.equals(METHOD_HEAD)) {
long lastModified = getLastModified(req);
maybeSetLastModified(resp, lastModified);
doHead(req, resp);
} else if (method.equals(METHOD_POST)) {
doPost(req, resp);
} else if (method.equals(METHOD_PUT)) {
doPut(req, resp);
} else if (method.equals(METHOD_DELETE)) {
doDelete(req, resp);
} else if (method.equals(METHOD_OPTIONS)) {
doOptions(req,resp);
} else if (method.equals(METHOD_TRACE)) {
doTrace(req,resp);
} else {
//
// Note that this means NO servlet supports whatever
// method was requested, anywhere on this server.
//
String errMsg = lStrings.getString("http.method_not_implemented");
Object[] errArgs = new Object[1];
errArgs[0] = method;
errMsg = MessageFormat.format(errMsg, errArgs);
resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
}
}
}
HttpServlet 추상 클래스를 보면, service 메소드의 역할을 자세히 알 수 있다.XXX에 대한 요청이 들어오면 -> doXXX() 메소드를 호출한다.개발자들은 처리하고 싶은 요청 HTTP 메소드에 해당하는
doXXX()메소드를 재정의해주면 된다.
public class MyServlet extends HttpServlet {
// 중략
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
PrintWriter writer = resp.getWriter();
writer.println("hello hjoon!");
}
}
MyServlet이 처리할 URL을 매핑해주면,doGet() 메소드가 실행된다. 서블릿으로 요청을 처리하기 위해서
service의 메소드만 재정의해서 처리 방법을 지정하면 된다.
서블릿 컨테이너는 서블릿을 담아 관리하는 녀석이다.

사용자의 요청이 들어오면 서블릿 컨테이너는 해당 요청과 매핑된 서블릿을 설정 파일 web.xml을 통해 찾는다.
<servlet>
<servlet-name>MyServlet</servlet-name>
<servlet-class>com.jsp.web.MyServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>MyServlet</servlet-name>
<url-pattern>/hello</url-pattern>
</servlet-mapping>
서블릿 컨테이너가 어떤 서블릿이 어떤 요청과 매핑되어있는지 알기 위해 위와 같이 설정파일 web.xml에 미리 정의되어있어야 한다.서블릿 컨테이너는 설정 파일을 읽고 필요한 서블릿을 확인한 후, 해당 서블릿 인스턴스가 컨테이너에 있는지 확인한다.
-> 있다면, 그 인스턴스를 그대로 사용하고
-> 없다면, 생성한다 (이 때, 서블릿의init 메소드가 호출된다)
서블릿 컨테이너의 스레드를 생성하고, 미리만든 HttpServletResponse, HttpServletRequest 객체를 인자로 service 메소드를 호출한다.
(서비스가 호출될때, 재정의된 doXXX() 메소드가 수행된다)
HttpServletResponse, HttpServletRequest 객체를 소멸시킨다.서블릿은 소멸시키지 않는다.생성한
서블릿을 소멸시키지 않는 이유는 무엇일까
->서블릿은 Singleton 으로 관리되기 때문이다.
서블릿 인스턴스는 소멸되지 않고 그대로 있다가서블릿 컨테이너에 의해 다시 호출되어 재사용 된다.
서블릿 컨테이너는 결국서블릿의 생명주기를 관리하는 객체이다.
서블릿을 생성하고, 필요한 시점에 호출하고, 적절한 시점에 소멸시키는 역할을 한다.서블릿 컨테이너에게 위임된다.만약 하나의 요청을 처리하는 도중에 다른 요청이 들어오면 어떻게 될까?

서블릿 컨테이너는 멀티스레드로 이를 처리한다.
스레드당 다른 서블릿이 처리할 수 있고,
여러 스레드에서 한 서블릿의 여러 요청을 동시에 처리할 수 있다.
멀티스레드를 사용한다는 것 자체만으로 조심해야 한다.
-> 멀티스레드 자체로 큰 비용이 발생한다. (Context Switching 시 오버헤드 발생)
-> 요청에 따라 많은 스레드를 생성하다 서버의 하드웨어적 한계를 넘어버리면 터질 수 있다.
요청마다
서블릿을 정해주는 매커니즘은 비효율적이다.

멀티스레딩을 다루어야 한다
-> 비용 측면에서 비효율적
Handler의 공통 로직이 매번 중복된다
-> 개발 측면에서 비효율적
클라이언트의 요청을 앞 단에서 처리하는 매니저를 두는 패턴이
프론트 컨트롤러 패턴이다.

Spring MVC는 프론트 컨트롤러 패턴을 따른다.

Dispatcher Servlet이라고 한다.Dispatcher Servlet만 두고 모든 요청을 다 받을 수 있다.
이전에는 요청에 따라 모든 서블릿을 정의하고
요청을 수행할 때마다 매번 스레드를 생성했다.
프론트 컨트롤러를 사용하고나서
하나의 서블릿만 정의하고 모든 요청을 수행할 수 있다.
클라이언트의 요청이 많이 몰려서
Dispatcher Servlet의 작업이 밀리면 어떡하지..?
-> 역할 분담을 해야 한다.

Dispatcher Servlet
모든 클라이언트의 요청을 받고
Handler Mapping
요청을 처리할 때, Controller를 찾아서 반환해주고
Handler Adapter
해당 Controller의 메소드를 호출하여 처리 로직을 수행하고
그 결과를 Model And View 객체로 변환해서 Dispatcher Servlet에게 넘기고
View Resolver
Dispatcher Servlet은 View Resolver를 통해 View를 찾거나 생성한다.
View에 Model 데이터를 넣어 JSP, Thymeleaf 처럼 데이터를 담은 출력 파일로 응답한다.
하지만 이와 같이 역할 분담 자체만으로 일이 많아진다.
Spring 덕분에 Handler Mapping, Handler Adapter, View Resolver 들을 개발자가 직접 개발할 필요가 없어진다.
-> 이 3개는 Spring Container로부터 주입받아 Dispatcher Servlet이 이들을 사용하고 동작하게 된다.
결국 개발자들이 신경써야할 부분은 처리 Handler (Controller) 뿐이다.
프로그램이 동작하는 동안 사용되는 Java 객체들을 프레임워크가 대신 관리하게 하고 보관하기 위해서 사용되는 바구니이다.

Servlet WebApplicationContext
웹 요청 처리와 관련된 객체들이 담겨 있다.
(Controllers, ViewResolver, HandlerMapping)
Root WebApplicationContext
웹 요청 처리와 관련된 Bean 이외의 컴포넌트들이 관리된다.
(Service, Repositories)
Spring Container는
개발에 필요한 부분이나Dispatcher Servlet이 요청을 처리할 때 필요한 부분을 알아서 주입해준다.

Spring Container에 의해 관리되고Dispatcher Servlet이 알아서 사용할 수 있게 된다.
Dispatcher Servlet의 요청처리 뿐만아니라Root WebApplicationContext 를 활용하여 개발할 수 있다.결국 Spring으로 웹 요청을 처리한다는 것은
Spring MVC에서 제공하는Dispatcher Servlet과 웹 요청 처리 관련 구현체들을 사용할 수 있다는 뜻이다.
동시에Spring Container, 즉Spring IoC를 활용하여 개발할 수 있다는 의미다.
- 이로써 개발자는 요청처리 로직들에만 신경 쓸 수 있게 된다.
