출처: https://12bme.tistory.com/555
위 출처의 내용을 그대로 받아 적은 게시물이며, 내용을 더 잘 이해하기 위한 활동입니다.
서블릿 컨테이너는 개발자가 웹서버와 통신하기 위하여 소켓을 생성하고, 특정 포트에 리스팅하고, 스트림을 생성하는 등의 복잡한 일들을 할 필요가 없게 해준다.
컨테이너는 seblet의 생성부터 소멸까지의 일령늬 과정(Life Cycle)을 관리한다. 서블릿 컨테이너는 요청이 들어올 때 마다 새로운 자바 스레드를 만든다. 우리가 알고 있는 대표적인 Servlet Container가 있다. 바로 Tomcat이다. 톰캣같은 wasr가 java파일을 컴파일해서 Class로 만들고 메모리에 올려 servlet객체를 만든다.
init(): 서버가 켜질 때 한번만 실행
service: 모든 유저들의 요청들을 받는다.
destroy(): 서버가 꺼질 때 한번만 실행
먼저 Spring Container를 이해하기 위해서는 IOC와 DI를 이해해야 한다. Spring Container는 Bean들의 생명주기를 관리한다. Spring Container는 어플리케이션을 구성하는 Bean들을 관리하기 위해 IoC를 사용한다. Spring Container종류에는 BeanFactory와 이를 상속한 ApplicationContext가 존재한다. 이 두개의 컨테이너로 의존성 주입된 빈들을 제어하고 관리할 수 있다. 아래는 스프링 웹 애플리케이션 동작 원리이다.
웹 애플리케이션이 실행되면 Tomcat(WAS)에 의해 web.xml이 로딩된다.(load-on-startup으로 톰캣 시작시 servlet생성 가능하도록 설정 가능)
web.xml에 등록되어 있는 ContextLoaderListener(Java class)가 생성된다. ContextLoaderListener 클래스는 ServletContextListener 인터페이스를 구현하고 있으며, ApplicationContext를 생성하는 역할을 수행한다.
생성된 ContextLoaderListener는 applicationContext.xml을 로딩한다.
applicationContext.xml에 등록되어 있는 설정에 따라 Spring Container가 구동된다. 이때 개발자가 작성한 비즈니스 로징에 대한 부분과 DAO, VO 객체들이 생성된다.
클라이언트로부터 웹애플리케이션 요청이 온다.
DispatcherServlet(Servlet)이 생성된다. DispatcherServlet은 FrontController의 역할을 수행한다. 클라이언트로부터 요청 온 메시지를 분석하여 알맞은 PageController에게 전달하고 응답을 받아 요청에 따른 응답을 어떻게 할지 결정만 한다. 실질적인 작업은 PageControlle에서 이뤄지기 때문이다. 이러한 클래스들을 HandlerMapping, ViewResolver클래스라고 한다.
DispatcherServlet은 servlet-context.xml(spring-mvc.xml)을 로딩한다.
두 번째 Spring Container가 구동되면 응답에 맞는 PageController들이 동작한다. 이때 첫 번째 Spring Container가 구동되면서 생성된 DAO, VO, ServiceImpl 클래스들과 협업하여 알맞은 작업을 처리하게 된다.
서블릿 컨테이너는 웹애플리케이션 서버중에서 HTTP요청을 받아 처리하는 기초 역할을 맡고 있다. 대부분의 웹프레임워크가 제공하는 기능은 서블릿 컨테이너 위에서 동작하는 서블릿, 필터, 이벤트리스너 등을 적절하게 구현한 것이다. 따라서 사용자가 웹프레임워크로 작성한 웹 애플리케이션은 결국 서블릿 컨테이너 위에서 동작한다. 서블릿 컨테이너의 종류로는 톰캣, 제티 등이 서블릿 컨테이너로 널리 사용되고 있다.
서블릿 컨테이너에 의해 프로그램이 실행되기 위해서는 표준 즉 Servlet Interface를 구현해줘야 한다. 사용자 정의 서블릿은 서블릿 컨테이너 내에 등록된 후 서블릿 컨테이너에 의해 생성, 호출, 소멸이 이루어진다.
때로는 서블릿은 자신의 상태 변경 시점을 알아내 적절한 리소스 왹득/반환 등의 처리를 해야하므로 Servlet 인터페이스에 init/destroy메서드가 정의된다. 다시말해 서블릿 컨테이너는 서블릿의 생명주기에 따라 서블릿의 상태를 변경하면서 서블릿 인터페이스에 정의되 각 메서드를 불러준다.
HTTP프로토콜로 전달된 메시지는 서블릿 컨테이너에서 해석되고 재조합되어 웹 프로그래머가 작성한 서블릿으로 전달되는 과정을 거친다.
Servlet은 서블릿 프로그램을 개발할 때 반드시 구현해야 하는 메서드를 선언하고 있는 인터페이스이다. 이 표준을 구현해야 서블릿 컨테이너가 해당 서블릿을 실행할 수 있다.
GenericServlet은 Servlet인터페이스를 상속하여 클라이언트-서버 환경에서 서버단의 애플리케이션으로서 필요한 기능을 구현한 추상 클래스이다. service()메소드를 제외한 모든 메서드를 재정의하여 적절한 기능으로 구현했다. GenericServlet클래스를 상속하면 애플리케이션의 프로토콜에 따라 메서드 재정의 구문을 적용해야 한다.
일반적으로 서블릿이라하면 거의 대부분 HttpServlet을 상속받는 서블릿을 의미한다. HttpServlet은 GenericServlet을 상속받았으며, GenericServlet의 유일한 추상 메서드인 service를 HTTP 프로토콜 요청 메서드에 적합하게 재구현해놨다.
이미 DELETE, GET, HEAD, OPTIONS, POST, TRACE를 처리하는 메소드가 모두 정의되어 있다.
서블릿의 실행 순서는 개발자가 관리하는게 아닌 서블릿 컨테이너가 관리를 한다. 즉 서블릿에 의해 사용자가 정의한 서블릿 객체가생성되고 호출되고 사라진다. 즉 이렇게 개발자가 아닌 프로그램에 의해 객체들이 관리되는 것을 IoC(Inversion of Control)이라고 한다.
다음은 서블릿 컨테이너의 생명주기를 도식화한 것이다.
여기에서 서블릿컨테이너가 종료된다면 사용자 정의 HttpServlet의 destroy()가 호출될 것이다.
다음은 HttpServlet의 구현체 내부이다.
pacjage javax.servlet.http;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrinWriter;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Method;
import java.text.MessageFormat;
import java.util.Enumeration;
import java.util.Locale;
import java.util.ResourceBundle;
import javax.servlet.GenericServlet;
import javax.servlet.ServletException;
import javax.servelt.ServletOuputStream;
import javax.servlet.ServletResponse;
public abstract class HttpServlet extends GenericServlet implements java.io.Sericalizable {
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";
private static final String HEADER_IFMODSINCE = "if-Modified-Since";
private static final String HEADER_LASTMOD = "Last-Modified"l
private static final String LSTRING_FILE = "javax.servlet.http.LocalStrings";
private static ResourceBundle IStrings = ResourceBundle.getBundle(LSTRING_FILE);
public HttpServlet() {}
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String protocl = req.getProtocol();
String msg = IString.getString("https.method_get_not_supported");
if(protocol.endWith("1.1")){
resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOW, msg);
} else {
resp.sendError(HttpServletResponse.SC_BAD_REQUEST, msg);
}
}
}
... 다 적다간 정신 나갈것 같아서 나중에 다시 적겠습니다.
위에서 설명한 SErvlet들의 상속 구조는 다음과 같을 것이다. 개발자가 정의한 MemberServlet이 구현한 service를 서블릿 컨테이너가 실행할 것이다. 만약 service가 아닌 method 방식으로 처리하고 싶다면 해당 Http Method방식을 구현하면 된다.
개발자는 HTTPServlet을 상속받고 Https Method에 맞게 서블릿을 구현할 수 있다. 그런데 여기에서 HTTPServlet을 상속하고 구현한 클래슫ㄹ이 많을 텐데 어떻게 요청된 URL에 따라 각각에 서블릿으로 보내줄 수 있을까?
답은 web.xml 또는 @WebServlet어노테이션이다. 서블릿 2.5까지만 해도 web.xml로만 제어 가능했지만, 3.0부터 어노테이션으로 클라이언트 접근을 제어할 수 있게 되었다. 다음과 같이 URL에 맞게 서블릿을 매핑할 수 있다.
<servlet>
<servlet-name>member</servler-name>
<servlet-class>com.test.MemberServlet</servlet-class>
</servlet>
<Servlet-mapping>
<servlet-name>member</servlet-name>
<url-pattern>/member</url-pattern>
</Servlet-mapping>
@WebServlet("/member")
public class MemberServlet extends HttpServlet{
...
}
하지만 위의 방식대로 url마다 모두 매핑해서 사용해야 한다면 유지보수, 확장성 등을 생각하면 무모하다. 그래서 여기서 한 단계 더 진보한 것이 MVC패턴이다. MVC패턴은 모델(비즈니스로직), 뷰(화면), Controller(최초 Request를 받는 곳)으로 나누고 개발을 하는 것이다.
그럼 MVC패턴에서는 어덯게 사용자 URL을 받는 것일까. 그것은 FrontController 패턴이다. FrontController패턴은 모든 클라이언트에 요청을 최 앞단에 FrontController를 두고 각각에 컨트롤러에 매핑해주는 방식이다.
모든 요청을 다음과 같이 FrontController로 넘긴다.
<servlet>
<servlet-name>front</servlet-name>
<servlet-class>con.test.FrontController</servlet-class>
</servlet>
<servlet-name>front</servlet-name>
<uri-pattern>*</uri-pattern>
public class FrontController extends HttpServlet {
HashMap<String, Controller> controllerUrls = null;
@Override
public void init(ServletConfig sc) throws ServletException {
controllerUrls = new HashMap<String, Controller>();
controllerUrls.put("/memberInsert.do", new MemberINterController())
controllerUrls.put("/memberDelete.do", new MemberDeleteController())
}
public vodi service(HttpServletRequest req, HttpServletResponse res) {
String uri = req.getRequestURI();
Controller subController = controllerUrls.get(uri);
subController.execute(req, res);
}
}
FrontController는 이제 요청되어진 URI에 따라 등록되아진 Controller를 실행할 것이다. 컨트롤러는 그 뒤에 서비스, 리파지초리를 실행하고 최종적으로 뷰를 화면에 그리게 될 것이다.
spring boot는 내부적으로 내장 톰캣을 가지고 있다. 즉 스프링 부트가 실행되면서 내부적으로 내장 톰캣 즉 서블릿 컨테이너가 실행된다.
스프링 부트에서 사용자 정의 프로그램을 구현한 프로그램인 서블릿은 DispatchServlet이다. 스프링 부트에서 DispatchServlet이 FrontController의 력할을 한다.
Spring boot는 ServletContainerInitializer를 구현한 TomcatStater의 onStartup 메소드를 먼저 실행한다. 톰캣이 실해되고 다음 조건이 만족하면 Dispatcher Servlet이 등록되어 진다.
DispatcherServletAutoConfiguration.class에 구성되어져 있는 DispatchServlet 빈 등록으로 자동 등록되어진다. 다음은 해당 소스내용이다.
@Configuration
@conditional({DispatcherServletAutoConfiguration.DefaultDispatcherServletCondition.class})
@ConditionalOnClass({ServletRegistration.class})
@EnableConfiguraionProperties({HttpProperties.class, WebMbvProperties.class})
protected static class DispatcherServletConfiguration {
private final HttpProperties httpProperties;
private final WebMvcProperties webMbvProperties;
public DispatcherServletConfiguration(HttpProperties httpProperties, WebMbvProperties weMvcProperties){
this.httpProperties = httpsProperties;
this.webMbcProperties = webMvcProperties;
}
@Bean(
name = {""dispatcherServlet}
)
public DespatcherServlet dispatcherServlet() {
DispatcherServlet dispatcherServlet = new DispatcherServlet();
dispatcherServlet.setDispatchOptionsRequest(this.webMbc.Properties.isDispatchOptionsRequest());
dispatcherServlet.setDispatchTraceRequest(this.webMvcProperties.isDispatchTraceRequest());
dispacherServlet.setThrowExceptionIfNoHandlerFound(this.webMvcProperties.isThrowExceptionIfNoHandlerFound())
dispatcherServlet.setEnableLoggingRequestDetails(this.httpProperties.isLogRequestDetails());
return dispatcherServlet;
}
@Bean
@ConditionalOnBean({MeltipartResolver.class})
@ConditionalOnMissingBean(
name = {"multipartResolver"}
)
public MultipartResolver multipartResolver(MultipartResolver resolver) {
return resolver;
}
}
FrameworkServlet은 HttpServlet을 상속받고 있다. DispatcherServlert은 FrameworkServlet을 상속받고 있다. 이말은 즉 DispatcherServlet이 FrontController라는 것이다. 다음은 요청 후 실행 순서를 설명한 것이다. 여기에서 주의할 점은 서블릿 컨테이너처럼 요청이 왔을 때 객체를 생성하는게 아닌 이미 컨트롤러들이 빈으로 등록되어져 있다는 것을 생갹해야한다.
여기에서 Dispacher 서블릿이 생성되면 주의할 점이 하나 있다. 디스패처 서블릿이 생성되면서 Webapplicationcontext가 생성된다.
하나는 dispatch에 의해 생성되는 WEbApplicationContext그리고 스프링에 contextLoader에 의해 생성되는 RootWebApplicationContext가 있다. 이 둘은 부모 자식 관계이다. 구조는 아래와 같을 수 있다. 그러면 최종적으로 아래와 같은 구조로 스프링 이 돌아가게 된다.
위와 같이 구성한 이유는 2개 이상의 DispatcherServlet을 등록하게 되면 RootWebApplicationContext를 공유하기 위해서 사용할 수 있다.
좋은 글 잘 읽었습니다.