Spring - (3) : 스프링 동작방식에 관하여

­이승환·2021년 12월 4일
0

spring

목록 보기
3/26

서론


참고링크 를 보고 개인 블로그에 정리한 내용이다.
서블릿 컨테이너는 개발자가 웹 서버와 통신하기 위해서 dispatcher sevlet을 만들고, 소켓을 만들어 특정 포트에 리스닝하고, 스트림을 생성하는 등의 복잡한 일들을 대신 처리해준다. 컨테이너는 Servlet의 생성부터 소멸까지 일련의 사이클을 관리한다. 서블릿 컨테이너는 요청이 들어올때바다 새로운 자바 스레드를 생성한다. 우리가 알고 있는 서블릿 컨테이너는 대표적으로 tomcat이다.

서블릿이란?

  • 자바를 이용해서 웹을 만들기 위해 필요한 기술
  • 클라이언트가 요청을 하면 그에 대한 결과를 다시 전송해줘야하고, 그 역할을 담당하는 클래스
  • html 을 사용해서 응답할 수 있음
  • java thread를 생성함
  • UDP 처리보다 속도가 느림
  • HTML 변경시 servlet을 재컴파일해야하는 경우도 있음
  • 서블릿이란 프로그램을 개발할때 반드시 구현해야 하는 메서드를 선언하고 있는 인터페이스임
  • GenericServlet 이란 servlet 인터페이스를 상속해서 앱에서 필요한 기능을 구현한 추상 클래스
  • HttpServlet 이란 위 추상클래스를 상속받은 서블릿이고, HTTP 프로토콜 요청 메서드에 적합하게 구현

서블릿 동작과정

  1. 사용자가 url을 클릭하면 http request를 톰캣에 전달
  2. Servlet Container(tomcat) 은 HttpServletRequest + HttpServletResponse 객체 인스턴스화
  3. url을 분석해서 어느 서블릿에 대한 요청인지 찾음
  4. service() 메소드를 호출하여, POST/GET 여부에 따라 doGet() || doPost() 실행
  5. 동적 페이지 생성후 HttpServletResponse 전달
  6. HttpServletRequest && HttpServletResponse 소멸

서블릿 생성주기

  • init() : 서버가 켜질 때 한번만 생성
  • service() : 모든 유저들의 요청을 받는 역할 (멀티 스레드로 동시성 프로그램)
  • destroy() : 서버가 꺼질 때 한번만 실행

서블릿 컨테이너란

  • 웹 애플리케이션 서버중에서 HTTP 요청을 받아 처리하는 기초를 맡음
  • 웹 프레임워크가 제공하는 기능은 서블릿 컨테이너 위에서 동작하는 서블릿, 필터, 이벤트 리스너 등을 구현한 것이므로, 결국 우리는 비즈니스 로직만 수행하면 서블릿 컨테이너가 알아서 애플리케이션을 구동함
  • 사용자 정의 서블릿은 서블릿 컨테이너 내에 등록된 후 서블릿 컨테이너에 의해 생성, 호출, 소멸이 이루어짐

서블릿 실행순서

  • 서블릿의 실행 순서는 서블릿 컨테이너가 관리함
  • 서블릿에 의해 사용자가 정의한 서블릿 객체가 생성되고 호출되고 사라지게 됨
  • 위에서 언급한 것을 IoC라고 하기도 함(관리 측면)
  • 서블릿 컨테이너는 클라이언트로부터 처음 요청이 들어오면 현재 실행할 서블릿이 최초의 요청인지 판단하고, 없으면 해당 서블릿을 새로 생성함

아래는 서블릿 구현체임

package javax.servlet.http; import java.io.IOException; import java.io.OutputStreamWriter; import java.io.PrintWriter; 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.servlet.ServletOutputStream; import javax.servlet.ServletRequest; 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"; private static final String LSTRING_FILE = "javax.servlet.http.LocalStrings"; private static ResourceBundle lStrings = ResourceBundle.getBundle(LSTRING_FILE); public HttpServlet() { } protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String protocol = req.getProtocol(); String msg = lStrings.getString("http.method_get_not_supported"); if (protocol.endsWith("1.1")) { resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, msg); } else { resp.sendError(HttpServletResponse.SC_BAD_REQUEST, msg); } } protected long getLastModified(HttpServletRequest req) { return -1; } protected void doHead(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { NoBodyResponse response = new NoBodyResponse(resp); doGet(req, response); response.setContentLength(); } protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String protocol = req.getProtocol(); String msg = lStrings.getString("http.method_post_not_supported"); if (protocol.endsWith("1.1")) { resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, msg); } else { resp.sendError(HttpServletResponse.SC_BAD_REQUEST, msg); } } protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String protocol = req.getProtocol(); String msg = lStrings.getString("http.method_put_not_supported"); if (protocol.endsWith("1.1")) { resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, msg); } else { resp.sendError(HttpServletResponse.SC_BAD_REQUEST, msg); } } protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String protocol = req.getProtocol(); String msg = lStrings.getString("http.method_delete_not_supported"); if (protocol.endsWith("1.1")) { resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, msg); } else { resp.sendError(HttpServletResponse.SC_BAD_REQUEST, msg); } } private Method[] getAllDeclaredMethods(Class c) { if (c.getName().equals("javax.servlet.http.HttpServlet")) return null; int j = 0; Method[] parentMethods = getAllDeclaredMethods(c.getSuperclass()); Method[] thisMethods = c.getDeclaredMethods(); if (parentMethods != null) { Method[] allMethods = new Method[parentMethods.length + thisMethods.length]; for (int i = 0; i < parentMethods.length; i++) { allMethods[i] = parentMethods[i]; j = i; } j++; for (int i = j; i < thisMethods.length + j; i++) { allMethods[i] = thisMethods[i - j]; } return allMethods; } return thisMethods; } protected void doOptions(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { Method[] methods = getAllDeclaredMethods(this.getClass()); boolean ALLOW_GET = false; boolean ALLOW_HEAD = false; boolean ALLOW_POST = false; boolean ALLOW_PUT = false; boolean ALLOW_DELETE = false; boolean ALLOW_TRACE = true; boolean ALLOW_OPTIONS = true; for (int i = 0; i < methods.length; i++) { Method m = methods[i]; if (m.getName().equals("doGet")) { ALLOW_GET = true; ALLOW_HEAD = true; } if (m.getName().equals("doPost")) ALLOW_POST = true; if (m.getName().equals("doPut")) ALLOW_PUT = true; if (m.getName().equals("doDelete")) ALLOW_DELETE = true; } String allow = null; if (ALLOW_GET) if (allow == null) allow = METHOD_GET; if (ALLOW_HEAD) if (allow == null) allow = METHOD_HEAD; else allow += ", " + METHOD_HEAD; if (ALLOW_POST) if (allow == null) allow = METHOD_POST; else allow += ", " + METHOD_POST; if (ALLOW_PUT) if (allow == null) allow = METHOD_PUT; else allow += ", " + METHOD_PUT; if (ALLOW_DELETE) if (allow == null) allow = METHOD_DELETE; else allow += ", " + METHOD_DELETE; if (ALLOW_TRACE) if (allow == null) allow = METHOD_TRACE; else allow += ", " + METHOD_TRACE; if (ALLOW_OPTIONS) if (allow == null) allow = METHOD_OPTIONS; else allow += ", " + METHOD_OPTIONS; resp.setHeader("Allow", allow); } protected void doTrace(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { int responseLength; String CRLF = "\r\n"; String responseString = "TRACE " + req.getRequestURI() + " " + req.getProtocol(); Enumeration reqHeaderEnum = req.getHeaderNames(); while (reqHeaderEnum.hasMoreElements()) { String headerName = (String) reqHeaderEnum.nextElement(); responseString += CRLF + headerName + ": " + req.getHeader(headerName); } responseString += CRLF; responseLength = responseString.length(); resp.setContentType("message/http"); resp.setContentLength(responseLength); ServletOutputStream out = resp.getOutputStream(); out.print(responseString); out.close(); return; } 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 / 1000 * 1000)) { // 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); } } private void maybeSetLastModified(HttpServletResponse resp, long lastModified) { if (resp.containsHeader(HEADER_LASTMOD)) return; if (lastModified >= 0) resp.setDateHeader(HEADER_LASTMOD, lastModified); } public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException { HttpServletRequest request; HttpServletResponse response; try { request = (HttpServletRequest) req; response = (HttpServletResponse) res; } catch (ClassCastException e) { throw new ServletException("non-HTTP request or response"); } service(request, response); } }

스프링 컨테이너란

  • IoC, DI에 대해서 이해할 필요가 있음
  • Spring Container는 Bean들의 생명 주기를 관리하는 역할이며 IoC, DI를 활용함
  • BeanFactory를 상속한 ApplicationContext 클래스가 존재함 이 두개의 컨테이너로 빈들 제어 관리

동작 원리

  1. 웹 애플리케이션이 실행되면 tomcat은 web.xml을 로딩
  2. 해당 파일에 등록되어 있는 ContextLoaderListner가 생성됨, 해당 클래스는 ServletContextListner 인터페이스로 구현되어 있고 이는 applicationContext.xml 을 로딩
  3. applicationContext.xml 에 등록되어 있는 설정에 따라 Spring Container 구동되고, 여기서 개발자가 작성한 비즈니스 로직들의 service, dao, vo 등의 객체들이 생성됨
  4. 클라이언트로부터 웹 애플리케이션 요청이 들어옴
  5. DispatcherServlet이 생성됨, DispatcherServlet은 FrontController의 역할을 수행함, 클라이언트로부터 들어온 메시지를 분석해서 알맞은 PageController에게 전달하고 응답받아 결과를 전달해줌, PageController는 HandlerMapping, ViewResolver 클래스로 구성
  6. DispatcherServlet은 servlet-context.xml을 로딩
  7. 해당 DispatcherServlet에 맞는 SpringContiner가 구동되면, 응답에 맞는 PageController들이 동작하게 됨

Spring boot & Sevlet

  • 스프링 부트는 내장 톰캣을 가지고 있음
  • 스프링 부타그 사용자 정의 프로그램을 구현한 클래스는 DispatchServlet임
  • FrontController 의 역할을 맡고 있음

스프링 부트 실행과정

  1. ServletContainerInitializaer를 구현한 TomcatStarter의 onStartup 메소드 실행
  2. DispatcherServletAutoConfiguration.class에 구성되어 있는 DispatcherServlet 빈을 자동으로 등록해줌
@Configuration @Conditional({DispatcherServletAutoConfiguration.DefaultDispatcherServletCondition.class}) @ConditionalOnClass({ServletRegistration.class}) @EnableConfigurationProperties({HttpProperties.class, WebMvcProperties.class}) protected static class DispatcherServletConfiguration { private final HttpProperties httpProperties; private final WebMvcProperties webMvcProperties; public DispatcherServletConfiguration(HttpProperties httpProperties, WebMvcProperties webMvcProperties) { this.httpProperties = httpProperties; this.webMvcProperties = webMvcProperties; } @Bean( name = {"dispatcherServlet"} ) public DispatcherServlet dispatcherServlet() { DispatcherServlet dispatcherServlet = new DispatcherServlet(); dispatcherServlet.setDispatchOptionsRequest(this.webMvcProperties.isDispatchOptionsRequest()); dispatcherServlet.setDispatchTraceRequest(this.webMvcProperties.isDispatchTraceRequest()); dispatcherServlet.setThrowExceptionIfNoHandlerFound(this.webMvcProperties.isThrowExceptionIfNoHandlerFound()); dispatcherServlet.setEnableLoggingRequestDetails(this.httpProperties.isLogRequestDetails()); return dispatcherServlet; } @Bean @ConditionalOnBean({MultipartResolver.class}) @ConditionalOnMissingBean( name = {"multipartResolver"} ) public MultipartResolver multipartResolver(MultipartResolver resolver) { return resolver; } }
  • Dispatcher 서블릿이 스프링에 빈으로 등록
  • 서블릿 컨테이너(DispatcherServlet) 컨텍스트에 서블릿을 등록
  • 서블릿 컨테이너 필터에 등록설정 해놓은 필터들을 등록
  • DispatcherServlet에 각종 핸들러 매핑(자원 url)들이 등록 (컨트롤러 빈들이 다 생성되어 싱글톤으로 관리)

클라이언트 요청부터 DispatcherServlet 까지

Application context

Dispatcher 서블릿이 생성되면서 주의할 점이 하나 있다. 디스패처 서블릿이 생성되면서 Webapplicationcontext가 생성된다. 하나는 dispatch에 의해 생성되는 WebApplicationContext 그리고 스프링에 contextLoader에 의해 생성되는 Root WebapplicationContext가 있다. 이 둘은 부모 자식 관계이다. 구조는 아래와 같을 수 있다. 그러면 최종적으로 아래와 같은 구조로 스프링이 돌아가게 된다. 위와 같이 구성이유는 2개 이상의 DispatcherServlet을 등록하게 되면 RootWebApplicationContext를 공유하기위해서 사용할 수 있다.

profile
Mechanical & Computer Science

0개의 댓글