Containerless 개발 준비
- 컨테이너 리스 방식은 서블릿 컨테이너와 관련된 번거롭고 복잡한 작업들에 대해 개발자가 더이상 신경쓰지 않도록 스프링 컨테이너에 올라가는 빈에만 집중할 수 있도록 해준다.
- main() 메서드 하나만 실행시켰는데 Tomcat(Java의 서블릿 컨테이너)이 뜨고 스프링 컨테이너도 뜬다. 애플리케이션을 동작시킨다.
- 그런데 만약, Spring Boot가 없다면?
서블릿 컨테이너 띄우기
- 서블릿 컨테이너(Servlet container)란?
- 자바의 표준 기술인 servlet에 대해 서블릿을 구현한 컨테이너를 말하며 종류가 다양하다. ex) Tomcat
- springio를 통해 스프링 프로젝트를 셋업하면 자동으로 내장형 톰캣(embedded tomcat)이라는 라이브러리가 제공된다.
- 기본으로 세팅된 HellobootApplication의 애너테이션과 run() 부분을 지우고 직접 구현해보자.
- Spring boot는 Tomcat 외에도 Jetty(제티)같은 다른 서블릿 컨테이너도 지원할 수 있고 일관된 방식으로 동작하도록 추상화되어 있다.
Ex)
<hide/>
public class HellobootApplication {
public static void main(String[] args) {
TomcatServletWebServerFactory serverFactory = new TomcatServletWebServerFactory();
WebServer webServer = serverFactory.getWebServer();
webServer.start();
}
}
- TomcatServletWebServerFactory: 스프링 부트가 Tomcat servlet 컨테이너를 내장해서 프로그램에서 코드로 쉽게 시작해서 도우미 역할을 하는 클래스이다.
- getWebServer(): 서블릿 컨테이너를 생성하다.
- WebServer: 인터페이스
Note) 실행 결과 - 404에러 (정상으로 부트가 뜸)
서블릿 등록
- Servlet container안에 들어가는 Servlet은 웹 컴포넌트와 같다.
- getWebServer() 안에 매개변수로 ServletContainerInitializer를 넣을 수 있다.
- ServletContextInitializer: 메서드가 하나인 functional 인터페이스이며 람다식으로 대체 가능하다.
- addServlet(): servlet를 생성한다.
- 다음, 어느 서블릿이 처리할 건지 매핑해줘야한다.
- addServlet() 을 통해 생성된 서블릿에 이어서 addMapping()을 통해서 매핑해준다.
- addMapping("/hello"): "/hello"를 통해 들어온 웹 요청이 service 메서드로 들어온다.
- 웹 응답의 세 가지 요소 status code/body/content-type를 세팅한다.
- HttpServlet를 상속하는 익명클래스를 만든다. HttpServlet 안에 선언된 메서드의 일부를 override
- service(): 요청을 만드는데 필요한 object, 응답을 만드는데 필요한 object가 매개변수로 전달된다.
Ex)
<hide/>
public class HellobootApplication {
public static void main(String[] args) {
TomcatServletWebServerFactory serverFactory = new TomcatServletWebServerFactory();
WebServer webServer = serverFactory.getWebServer(servletContext -> {
servletContext.addServlet("hello", new HttpServlet() {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
resp.setStatus(200);
resp.setHeader("Content-Type", "text/plain");
resp.getWriter().print("Hello Servlet ! ");
}
}).addMapping("/hello");
});
webServer.start();
}
}
Note) 실행 결과
서블릿 요청 처리
- 앞에서 살펴본 방식을 보면 Header를 String 형태로 하드 코딩해서 넣는 형태인데 이는 오타를 낼 수 있어서 위험하다.
- 따라서, Enum 형태로 정의된 클래스를 활용한다. HttpHeaders.CONTENT_TYPE
Ex)
<hide/>
public static void main(String[] args) {
TomcatServletWebServerFactory serverFactory = new TomcatServletWebServerFactory();
WebServer webServer = serverFactory.getWebServer(servletContext -> {
servletContext.addServlet("hello", new HttpServlet() {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
String name = req.getParameter("name");
resp.setStatus(HttpStatus.OK.value());
resp.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN_VALUE);
resp.getWriter().print("Hello ! " + name);
}
}).addMapping("/hello");
});
webServer.start();
}
}
Note) 실행 결과
프론트 컨트롤러
- 앞서 살펴본 서블릿은 각 요청에 대해 담당한 서블릿을 하나씩 매핑해주는 작업이 필요했다.
- 그런데, 대부분의 서블릿에서 필요로 하는 공통적인 작업이 각 서블릿 코드 안에 중복되어 존재한다. 중복을 없애서 효율성을 높이자.
- 각 URL에 맞게 각기 다른 서블릿이 매핑된다.
- 모든 서블릿에 공통적으로 필요한 작업에 대해 컨트롤러라는 오브젝트에서 공통적으로 처리
- 요청의 종류에 따라서, 로직을 처리하는 다른 오브젝트에게 다시 위임해서 전달하는 방식으로 처리하려한다.
- 프론트 컨트롤러를 내장한 프레임워크가 등장하기 시작했다.
- ex) 인증, 보안, 다국어 처리, 공통 처리 등
프론트 컨트롤러로 전환
- 기존에 만들었던 "hello"라는 서블릿 이름을 frontcontroller로 변경한다.
- 중앙화된 처리를 위해서 모든 요청을 다 받을 수 있도록 "/*" 라고 넣어준다.
- 기존에는 서블릿 컨테이너가 url을 통해서 서블릿을 매핑했다면 이제는 프론트 컨트롤러가 그 역할을 하려한다.
- 따라서, 다음과 같이 url 값에 따라서 로직을 구분하려한다.
- 그럼 컨트롤러 클래스의 @GetMapping 은 어떻게 기능을 구현할 수 있을까?
- request.getMethod()를 통해서 Http 메서드의 종류를 파악할 수 있다.
Ex)
<hide/>
public static void main(String[] args) {
TomcatServletWebServerFactory serverFactory = new TomcatServletWebServerFactory();
WebServer webServer = serverFactory.getWebServer(servletContext -> {
servletContext.addServlet("frontcontroller", new HttpServlet() {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
if(req.getRequestURI().equals("/hello") && req.getMethod().equals(HttpMethod.GET.name())){
String name = req.getParameter("name");
resp.setStatus(HttpStatus.OK.value());
resp.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN_VALUE);
resp.getWriter().print("Hello ! " + name);
}
else if(req.getRequestURI().equals("/user")){
}else {
resp.setStatus(HttpStatus.NOT_FOUND.value());
}
}
}).addMapping("/*");
});
webServer.start();
}
Note) 실행 결과
- 위 로직의 else문으로 들어가서 404 에러가 반환된다.
Hello 컨트롤러 매핑과 바인딩
- 앞에서 만든 컨트롤러 클래스를 하나 만들어서 컨트롤러 안에 있는 메서드 hello()에 처리를 위임한다.
- 웹 요청에 대해서 인자 값을 넘겨주는 걸 "바인딩" 이라고 한다.
- 지금까지 스프링을 사용하지 않고 서블릿 기술만 가지고 프론트 컨트롤러를 만들었다.
<hide/>
public static void main(String[] args) {
TomcatServletWebServerFactory serverFactory = new TomcatServletWebServerFactory();
WebServer webServer = serverFactory.getWebServer(servletContext -> {
HelloController helloController = new HelloController();
servletContext.addServlet("frontcontroller", new HttpServlet() {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
if(req.getRequestURI().equals("/hello") && req.getMethod().equals(HttpMethod.GET.name())){
String name = req.getParameter("name");
String result = helloController.hello(name);
resp.setStatus(HttpStatus.OK.value());
resp.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN_VALUE);
resp.getWriter().print(result);
}
else if(req.getRequestURI().equals("/user")){
}else {
resp.setStatus(HttpStatus.NOT_FOUND.value());
}
}
}).addMapping("/*");
});
webServer.start();
}
title
- content
- content
title
- content
- content
출처 - 인프런 토비의 스프링
'Spring Framework > 토비의 스프링' 카테고리의 다른 글
Chapter 05. DI와 테스트, 디자인 패턴 (0) | 2023.07.04 |
---|---|
Chapter 04. 독립 실행형 스프링 애플리케이션 (0) | 2023.06.24 |
Chapter 02. 스프링 부트 시작하기 (0) | 2023.06.18 |
Chapter 01. 스프링 부트 살펴보기 (0) | 2023.06.17 |
Chapter 01. Object와 의존 관계 (2) | 2023.03.19 |