평범한 웹 서버
웹서버 프로그램
을 붙여 동적인 페이지를 생성하였다서블릿도 동적인 페이지를 만들기 위해 웹서버에 붙이는 프로그램 중 하나이다
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
를 활용하여 개발할 수 있다는 의미다.
- 이로써 개발자는 요청처리 로직들에만 신경 쓸 수 있게 된다.