이전글
SpringMVC (1) - WAS와 WebServer
목표 클라이언트 요청 시 응답을 받기까지 흐름 이해하기. WAS와 WebServer의 개념과 차이점 이해하기. WAS가 있어도 WebServer를 같이 사용하면 좋은점 이해하기. 클라이언트-서버 통신 기본적인 통신
han98-dev.tistory.com
목표
- Servlet이 뭔지 알고 Servlet Container 동작방식 이해하기
- Front Controller의 개념 이해하기
- Spring Container와 Bean 이해하기
- Bean을 등록하는 방법 알기
Servlet Container
이전 글에서 WAS에 대해서 알아보았다. 자바는 기본적으로 Tomcat을 사용하고, WAS는 Servlet Container라고도 불린다고 했었다. Servlet은 무엇일까?
Servlet
클라이언트의 요청을 처리하고, 결과를 반환하는 Servlet 클래스 구현 규칙을 지킨 자바 웹 프로그래밍 기술
솔직히 이 문장이 선명하게 와닿지 않는다. 쉽게 설명하면 클라이언트가 요청을 보내면 일련의 과정을 거쳐서 응답을 생성한 후 반환하는데 받은 요청을 기반으로 응답을 만드는 것이 서블릿의 역할이다.
예를 들어 로그인 요청으로 아이디와 비밀번호를 보낸다면 로그인을 담당하는 서블릿이 요청정보(아이디, 비밀번호)를 가지고 검사를 한 뒤 적절한 응답을 보낸다.(오류가 있으면 틀렸다는 응답, 일치한다면 로그인 성공 응답)
Servlet Container

Servlet Container는 이름 그대로 이러한 서블릿들을 담고있는 컨테이너다. 요청이 들어오면 요청을 처리할 수 있는 서블릿을 골라서 필요한 로직을 처리하여 응답을 반환한다.
예시 코드
public class MemoApplication {
public static void main(String[] args) {
// 톰캣 서버
ServletWebServerFactory serverFactory = new TomcatServletWebServerFactory();
// 웹서버. addServlet으로 웹서버에 Servlet 추가
// HttpService를 익명 클래스로 service 구현
WebServer webServer = serverFactory.getWebServer(servletContext -> {
servletContext.addServlet("hello",new HttpServlet() {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 상태 200
resp.setStatus(HttpStatus.OK.value());
// CONTENT_TYPE
resp.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN_VALUE);
// 바디
resp.getWriter().print("Hello "+name);
}
}).addMapping("/hello");}); // Mapping
webServer.start();
}
}
위의 예시는 Tomcat 서버에 hello라는 서블릿을 추가한 코드이다. 매핑은 "/hello"로 되어있고 구현 로직은 response의 status, content_type, body의 값을 입력하는 로직이다.
위의 코드를 실행하고 http://localhost:8080/hello으로 요청을 보내면 Servlet Container가 /hello를 보고 hello Servlet에게 요청을 위임하게 되고 hello Servlet의 구현부(service())가 실행된다.
Front Controller

서블릿 컨테이너는 여러 서블릿을 가지고 있고 요청이 들어오면 알맞은 서블릿을 선택해서 일을 위임한다고 했다. 이 방식의 문제점은 무엇일까?
각기 다른 요청일지라도 공통적으로 수행해야 하는 로직이 있을 수 있다. 보안이나 인증, 다국어 등 모두가 수행해야 하는 공통기능을 현재의 방식대로라면 모든 서블릿에 중복적으로 입력되어야한다. 공통기능을 처리해 줄 하나의 처리기가 필요한데 이를 위해서 Front Controller가 등장한다.
Front Controller는 모든 요청을 받고 공통로직을 수행 후 요청 URI에 따라 Handler에게 요청을 위임한다.
예시
public class MemoApplication {
public static void main(String[] args) {
// 톰캣 서버
ServletWebServerFactory serverFactory = new TomcatServletWebServerFactory();
// 웹서버
WebServer webServer = serverFactory.getWebServer(servletContext -> {
servletContext.addServlet("frontController",new HttpServlet() {
HelloController helloController = new HelloController();
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 인증, 보안, 다국어, 공통기능 수행 했다고 침
if(req.getRequestURI().equals("/hello") // /hello && GET
&& req.getMethod().equals(HttpMethod.GET.name())){
String name = req.getParameter("name");
String ret = helloController.hello(name);
// 상태
resp.setStatus(HttpStatus.OK.value());
resp.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN_VALUE);
resp.getWriter().print(ret);
} else if(req.getRequestURI().equals("/users")){ // users
//
} else {
resp.setStatus((HttpStatus.NOT_FOUND.value())); // 404
}
}
}).addMapping("/*"); // Mapping (모든 요청 수신)
});
webServer.start();
}
}
public class HelloController {
public String hello(String name) {
return "hello " + name;
}
}
서블릿 컨테이너와의 차이점을 확인해보자
- 아래
addMapping("/*")에서 모든 요청을 수신하고 있다. service()메서드에 들어오면 공통로직을 수행한다. (주석 자리에 들어감)- if문으로 request의 URI에 따라 로직을 분기한다.
- "/hello"의 구현부를 보면 "name"파라미터를
helloController(name)으로 요청을 위임하는 것을 볼 수있다. (핸들러)
Spring Container
Front Controller를 구현해서 URI에 따른 요청을 핸들러에게 처리를 위임했다. 그러기 위해서 객체를 생성했다(helloController). Spring Container을 사용하면 Spring에서 객체를 관리해준다.
Bean

Spring Container에 저장된 객체를 Bean이라고 하는데 필요한 객체를 Bean으로 등록해놓으면 Spring에서 객체를 관리해주고 필요할 때마다 객체를 생성할 필요없이 Spring Container에서 꺼내 쓰면된다. 그럼 어떻게 객체를 Bean으로 등록할 수 있을까?
Bean 등록하기
Component Scan
@Component어노테이션을 활용해서 Bean에 등록하는 방법이다. 객체로 등록되기를 원하는 클래스 위에 @Component어노테이션을 붙이면 Spring이 Bean으로 등록시켜준다. 그리고 실행 메서드에는 @ComponentScan어노테이션을 붙여서 @Component가 붙은 클래스를 Bean으로 등록한다.
예시
@ComponentScan
public class MemoApplication {
public static void main(String[] args) {
// 톰캣 서버
ServletWebServerFactory 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") // /hello && GET
&& req.getMethod().equals(HttpMethod.GET.name())){
String name = req.getParameter("name");
HelloController helloController = applicationContext.getBean(HelloController.class);
String ret = helloController.hello(name);
// 상태
resp.setStatus(HttpStatus.OK.value());
resp.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN_VALUE);
resp.getWriter().print(ret);
} else if(req.getRequestURI().equals("/users")){ // users
//
} else {
resp.setStatus((HttpStatus.NOT_FOUND.value())); // 404
}
}
}).addMapping("/*"); // Mapping (모든 요청 수신)
});
webServer.start();
}
}
@Component
public class HelloController {
public String hello(String name) {
return "hello " + name;
}
}
차이점은 HelloController 객체를 생성하는 방식이다.
이전에는 new HelloController를 사용해서 객체를 생성했는데 현재는 applicationContext.getBean(HelloController.class) 메서드로 Spring Container에 있는 HelloController 객체를 받아와서 작업을 수행했다.
Configuration
@Configuration과 @Bean어노테이션을 사용해서 Bean으로 등록할 수 있다. 바로 예시를 보면서 설명하겠다.
예시
@Configuration
public class MemoApplication {
@Bean
public HelloController helloController() {
return new HelloController();
}
public static void main(String[] args) {
// 톰캣 서버
ServletWebServerFactory 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") // /hello && GET
&& req.getMethod().equals(HttpMethod.GET.name())){
String name = req.getParameter("name");
HelloController helloController = applicationContext.getBean(HelloController.class);
String ret = helloController.hello(name);
// 상태
resp.setStatus(HttpStatus.OK.value());
resp.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN_VALUE);
resp.getWriter().print(ret);
} else if(req.getRequestURI().equals("/users")){ // users
//
} else {
resp.setStatus((HttpStatus.NOT_FOUND.value())); // 404
}
}
}).addMapping("/*"); // Mapping (모든 요청 수신)
});
webServer.start();
}
}
우선 @Bean어노테이션이 붙은 helloController()메서드를 살펴보겠다.helloController()는 HelloController객체를 반환한다. 이 메서드 위헤 @Bean어노테이션을 붙임으로써 HelloController객체가 Bean으로 등록된다. @Bean을 사용하기 위해서는 @Bean이 등록된 클래스에 @Configuration을 붙여줘야한다. 그래야 Spring Container가 뜰 때 @Bean이 붙은 메서드를 실행해서 Bean에 객체를 등록해준다.
왜 Bean으로 등록해서 사용할까?
그냥 객체 생성해서 사용하면 되는데 왜 Bean으로 등록해서 사용할까?
- Direct Injection이 가능하다.
- 객체를 관리해주기 때문에 편하다.
- Spring Container는 기본적으로 싱글톤으로 객체를 관리한다.
다음글
SpringMVC (3) - SpringMVC
목표 dispatcherServlet 이해하기 SpringMVC 패턴의 흐름 이해하기 각각의 요소가 무슨 역할을 하고 왜 있는지 이해하기 DispatcherServlet 앞선 포스팅에서 FrontController를 구현하여 모든 요청을 받을 다음에
han98-dev.tistory.com
참고
'Java > Spring' 카테고리의 다른 글
| [Spring] QueryString 객체로 받기 (0) | 2023.04.23 |
|---|---|
| [Spring] Github Action 적용하기 (+ properties 추가) (0) | 2023.04.23 |
| SpringMVC (1) - WAS와 WebServer (0) | 2023.04.19 |
| 스프링 배치 이해하기 (0) | 2023.04.18 |
| 애너테이션 (0) | 2023.02.05 |