Table of contents
톰캣 직접 사용
톰캣을 직접 생성하여 서블릿을 등록해보자.
package jch.inflearn;
import org.apache.catalina.Context;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.startup.Tomcat;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
public class Application {
public static void main(String[] args) throws LifecycleException {
Tomcat tomcat = new Tomcat();
tomcat.setPort(8080);
Context context = tomcat.addContext("/", "/");
HttpServlet servlet = new HttpServlet() {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
PrintWriter writer = resp.getWriter();
writer.println("<html></head><title>");
writer.println("Hey Tomcat");
writer.println("</title></head>");
writer.println("<body><h1>Hello Tomcat</h1></body>");
writer.println("</html>");
}
};
String servletName = "helloServlet";
tomcat.addServlet("/", servletName, servlet);
context.addServletMappingDecoded("/hello", servletName);
tomcat.start();
tomcat.getServer().await();
}
}
- 17 ~ 20: 톰캣을 생성하여 포트를 8080로 설정하면서 contextPath와 docBase를 지정한다.
- 22 ~ 32: 요청을 받을 서블릿을 생성한 뒤, HTTP 요청을 수행할 메소드를 구현한다. 예제에서는 get요청을 구현하여 HTML을 응답하는 코드를 작성했다.
- 33 ~ 35: 해당 서블릿을 톰캣에 등록하면서 URL을 매핑한다.
- 37 ~ 38: 애플리케이션을 실행한 뒤, localhost:8080/hello을 요청하면 HTML이 반환될 것이다.
서블릿 웹 서버 생성
스프링부트의 내장 톰캣은 어떻게 동작하는 것일까? 지난 강의에서 등장한 자동설정과 관련이 있다. 자동설정을 통해 톰캣이랑 서블릿이 자동설정으로 설정된다. 먼저 서블릿 웹 서버(서블릿 컨테이너)를 생성하는 org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration 클래스를 살펴보자.
@Configuration
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@ConditionalOnClass(ServletRequest.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(ServerProperties.class)
@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })
public class ServletWebServerFactoryAutoConfiguration {
// ...
}
- 3: ServletRequest라는 클래스가 클래스패스에 있으면 자동설정으로 사용한다.
- 6~9: 다른 설정파일을 참조하여 사용한다
@Import 어노테이션을 이용해 다른 설정파일들을 참조하는데, tomcat, jetty와 undetrow에 대한 설정을 참조하는 것을 알 수 있다. 다음으로 EmbeddedTomcat 클래스를 살펴보자.
@Configuration
@ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class })
@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
public static class EmbeddedTomcat {
@Bean
public TomcatServletWebServerFactory tomcatServletWebServerFactory() {
return new TomcatServletWebServerFactory();
}
}
ConditionalOnClass에있는 클래스를 빈으로 등록하지 않았기 때문에 조건을 만족하면서 EmbeddedTomcat을 통해 TomcatServletWebServerFactory를 빈으로 등록하게 된다. 마지막으로 서버를 커스터마이징하는 TomcatServletWebServerFactory를 들여다보자.
@Override
public WebServer getWebServer(ServletContextInitializer... initializers) {
Tomcat tomcat = new Tomcat();
File baseDir = (this.baseDirectory != null) ? this.baseDirectory
: createTempDir("tomcat");
tomcat.setBaseDir(baseDir.getAbsolutePath());
Connector connector = new Connector(this.protocol);
tomcat.getService().addConnector(connector);
customizeConnector(connector);
tomcat.setConnector(connector);
tomcat.getHost().setAutoDeploy(false);
configureEngine(tomcat.getEngine());
for (Connector additionalConnector : this.additionalTomcatConnectors) {
tomcat.getService().addConnector(additionalConnector);
}
prepareContext(tomcat.getHost(), initializers);
return getTomcatWebServer(tomcat);
}
해당 팩토리 클래스에는 getWebServer라는 메소드가 있다. getWebServer메소드에서 톰캣을 생성하고 초기작업을 수행한 뒤 반환한다. 우리가 스프링부트 애플리케이션을 실행할 때, 웹 애플리케이션을 실행하는 경우 이러한 자동설정 과정을 거쳐서 등록된 내장 웹 서버를 사용하게 된다.
내장 서버에서 알 수 있듯이, 스프링부트의 자동 설정은 복잡한 설정 과정을 보다 상세하고 유연하게 설정할 수 있도록 도와준다.
서블릿 기반의 MVC
우리의 애플리케이션이 서블릿 기반의 MVC라면 DispatcherServlet이 필요하다. DispatcherServlet 또한 웹서버와 마찬가지로 자동설정으로 등록된다. org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration를 살펴보자.
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass(DispatcherServlet.class)
@AutoConfigureAfter(ServletWebServerFactoryAutoConfiguration.class)
@EnableConfigurationProperties(ServerProperties.class)
public class DispatcherServletAutoConfiguration {
// ...
@Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
public DispatcherServlet dispatcherServlet() {
DispatcherServlet dispatcherServlet = new DispatcherServlet();
dispatcherServlet.setDispatchOptionsRequest(
this.webMvcProperties.isDispatchOptionsRequest());
dispatcherServlet.setDispatchTraceRequest(
this.webMvcProperties.isDispatchTraceRequest());
dispatcherServlet.setThrowExceptionIfNoHandlerFound(
this.webMvcProperties.isThrowExceptionIfNoHandlerFound());
return dispatcherServlet;
}
}
dispatcherServlet() 메소드에서 DispatcherServlet을 빈으로 등록한다. DispatcherServlet은 HttpServlet을 상속해서 만든 스프링 MVC의 핵심 클래스이다.
참고
서블릿 컨테이너를 만드는일과 DispatcherServlet을 만들어서 등록하는 일이 분리되어있다. 서블릿 컨테이너는 우리의 설정에 따라 얼마든지 바뀔 수 있지만, 서블릿은 변하지 않는다. 서블릿 컨테이너의 종류에 상관없이 서블릿을 등록하기 때문에 분리되어있다는 정도로만 이해해도 좋다.
해당 포스팅은 스프링 부트 개념과 활용 강의 내용을 토대로 작성하였습니다.