자바를 MVC패턴을 공부하면서 분명 서블릿서블릿 했는데 무슨 말인지 와닿지 않았다. 그래서 도대체 이게 무엇인가, 언젠가는 공부를 해야지 하고 다짐하고 넘어갔다. 오늘 드디어 서블릿을 뿌시는 기회가 왔다. 그래서 공부를 해보자.
우선 다양한 기관과 사람들의 정의를 보자
자바 서블릿은 자바를 사용하여 웹페이지를 동적으로 생성하는 서버측 프로그램 혹은 그 사양을 말한다. (위키백과)
A servlet is a Java programming language class that is used to extend the capabilities of servers that host applications accessed by means of a request-response programming model. Although servlets can respond to any type of request, they are commonly used to extend the applications hosted by web servers. For such applications, Java Servlet technology defines HTTP-specific servlet classes. (오라클)
서블릿이란 Dynamic Web Page를 만들 때 사용되는 자바 기반의 웹 애플리케이션 프로그래밍 기술입니다. 웹을 만들때는 다양한 요청과 응답이 있고, 이 요청과 응답에는 규칙이 있습니다. 서블릿은 웹 요청과 응답의 흐름을 간단한 메서드 호출만으로 체계적으로 다룰 수 있게 해주는 기술입니다. (코딩팩토리)
일단 여러 정의를 보면 대강 동적인 웹페이지 서비스를 제공하는데 도움을 주는 기술정도로 이해하면 될거 같다.
그럼 Servlet동작 방식을 살펴보며 좀 더 구체적으로 살펴보자
- 사용자가 URL을 입력하면 HTTP Request가 Servlet Container로 전송한다.
- 요청을 받은 Servlet Container는 HttpServletRequest, HttpServletResponse 객체를 생성한다.
- web.xml을 기반으로 사용자가 요청한 URL이 어느 서블릿에 대한 요청인지 찾는다.
- 해당 서블릿에서 service메소드를 호출하여 클라이언트의 GET,POST여부에 따라 doGet() 또는 doPost()를 호출한다.
- doGet() 혹은 doPost() 메소드는 동적 페이지를 생성한 후 HttpServletResponse객체에 응답을 보낸다.
- 응답이 끝나면 HttpServletRequest, HttpServletResponse 두 객체를 소멸시킨다.
⇒ 이러한 기술 자체 혹은 클라이언트에서 요청한 요구사항을 서블릿이라고 부른다.
서블릿은 스스로 동작하지 않고 관리가 필요하다. 서블릿을 관리하는 역할을 하는 것이 서블릿 컨테이너이다. 대표적인 서블릿 컨테이너는 톰캣이다. 서블릿 컨테이너는 클라이언트와 request, response를 주고 받기 위해 웹서버와 소켓을 만들어 통신한다. 결국 소켓을 만들고 관리하는 다양하고 복잡한 과정을 쉽게 생략해주는게 서블릿 컨테이너의 역할이다.
좀더 알아보면 아래와 같다.
서블릿 컨테이너의 역할(ex. 톰캣)
- 웹서버와의 통신
- 알아서 소켓을 생성하여 다양한 과정을 생략하게 한다.
- 생명주기 관리
- 서블릿 인스턴스화
- 초기화 메소드 호출
- 요청이 들어오면 적절한 서블릿 메소드 호출
- 서블릿 소멸시 가비지 컬렉터 실행
- 멀티 스레드 지원
- 요청마다 자바 스레드를 생성, 서비스가 실행되고 나면 스레드는 자동 소멸, 서버가 알아서 스레드 관리함
- 보안 관리
⇒ 결론적으로 서블릿 컨테이너를 사용하면 통신과 관련한 다양한 이슈들을 간단하게 해결해준다.
그럼 이제는 대강 알수 있다. 서블릿이란 클래스 혹은 패키지 형태로 동적 웹페이지를 제공하는 프로그램을 의미한다.
그럼 조금 더 알아보기 위해 실제 코드를 까보자 (feat.MVC패턴)
서블릿 동작방식중 이런게 있었다.
4. web.xml을 기반으로 사용자가 요청한 URL이 어느 서블릿에 대한 요청인지 찾습니다.
그럼 당연히 web.xml을 까보면서 시작한다. 예전에 강의로 들었던 web.xml을 열어본다.
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee
https://jakarta.ee/xml/ns/jakartaee/web-app_5_0.xsd"
version="5.0" metadata-complete="true">
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
/WEB-INF/spring/service-context.xml
/WEB-INF/spring/security-context.xml
</param-value>
</context-param>
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring/servlet-context.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup> <!-- tomcat이 실행할때 servlet이 적용. 이를 통해 첫 접근에도 빠르게 로드 가능하다. -->
<async-supported>true</async-supported> <!-- 비동기적으로 로드된다. -->
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<filter>
<filter-name>charaterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>charaterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
대강보면 인코딩 방식이나, context의 위치 정의, 서블릿에 대한 정의가 들어가 있다. 딱보면 먼가 명세서 같은 느낌이 팍팍온다.
그럼 여기서 servlet-context.xml을 가져오는데 여기를 까보자
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc
https://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<!-- <context:annotation-config/> -->
<context:component-scan
base-package="com.newlecture.web.controller"></context:component-scan> <!-- 메모리에 올려준다. -->
<bean
class="org.springframework.web.servlet.view.UrlBasedViewResolver">
<property name="viewClass"
value="org.springframework.web.servlet.view.tiles3.TilesView" />
<property name="order" value="1" />
</bean>
<bean
class="org.springframework.web.servlet.view.tiles3.TilesConfigurer">
<property name="definitions" value="/WEB-INF/tiles.xml" />
</bean>
<bean
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/view/"></property>
<property name="suffix" value=".jsp"></property>
<property name="order" value="2" />
</bean>
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!-- setting maximum upload size -->
<property name="maxUploadSize" value="314572800"/>
</bean>
<mvc:resources location="/static/" mapping="/**"></mvc:resources>
<mvc:annotation-driven>
<mvc:message-converters> <!-- @ResponseBody로 String 처리할때 한글처리 -->
<bean
class="org.springframework.http.converter.StringHttpMessageConverter">
<property name="supportedMediaTypes">
<list>
<value>text/html;charset=UTF-8</value>
</list>
</property>
</bean>
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"/>
</mvc:message-converters>
</mvc:annotation-driven>
<!-- Controller를 메모리에 올리고 나서 mapping을 해준다. -->
</beans>
천천히 흝어보면 어노테이션을 어디서부터 읽을것인지, IoC컨테이너에 어떤 클래스를 Bean으로 구현해놓을 것인지 명시해 놓았다.
자 그럼, 대강 어떤 명세서가 서블릿으로 제공되는지는 알게 되었다. 그럼 실제로 요청이 들어오면 어떻게 되는데 컨트롤러쪽을 살펴보자.
@Controller // 객체화 되기 위해서 반드시 필요하다.
@RequestMapping("/customer/notice/") // 하위 경로와 결합이 되어 요청된다.
public class NoticeController {
@Autowired // DI를 이곳에서 한다. 만약 무언가 수행하고 싶으면 함수에 autowired하면 된다.
private NoticeService noticeService;
@RequestMapping("list")
//public String list(@RequestParam(name="p", defaultValue="1") String page) throws ClassNotFoundException, SQLException{
public ModelAndView list(HttpServletRequest request) throws ClassNotFoundException, SQLException{
ModelAndView mv = new ModelAndView("notice.list");
JDBCNoticeService jdbc = new JDBCNoticeService();
List<Notice> getList = jdbc.getList(1, "TITLE","");
mv.addObject("list", getList);
System.out.println("getList");
return mv;
}
@RequestMapping("detail")
public String detail() {
return "notice.detail";
}
}
실제 URI가 어떻게 제공되는지 보면 리턴값을 문자열로 바로 내보거나 ModelAndView에 데이터를 심어서 보낸다. 그럼 tiles.xml로 넘어가서 해당 문자열이 속하는 “notice.*” 필터에 걸려서 해당되는 jsp가 클라이언트에 전달된다. (MVC 패턴을 사용할때 tiles를 사용해서 살짝 복잡하다.)
또한 데이터같은 경우 addObject함수를 사용하여 데이터를 포함해주고 ModelAndView객체를 반환하여 자동으로 데이터를 JSP파일과 맵핑해준다.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE tiles-definitions PUBLIC
"-//Apache Software Foundation//DTD Tiles Configuration 3.0//EN"
"http://tiles.apache.org/dtds/tiles-config_3_0.dtd">
<tiles-definitions>
<definition name="root.*" template="/WEB-INF/view/inc/layout.jsp">
<put-attribute name="title" value="공지사항" />
<put-attribute name="header" value="/WEB-INF/view/inc/header.jsp" />
<put-attribute name="body" value="/WEB-INF/view/{1}.jsp" />
<put-attribute name="footer" value="/WEB-INF/view/inc/footer.jsp" />
</definition>
<definition name="admin.board.*.*" template="/WEB-INF/view/admin/inc/layout.jsp">
<put-attribute name="title" value="공지사항" />
<put-attribute name="header" value="/WEB-INF/view/inc/header.jsp" />
<put-attribute name="visual" value="/WEB-INF/view/admin/inc/visual.jsp" />
<put-attribute name="aside" value="/WEB-INF/view/admin/inc/aside.jsp" />
<put-attribute name="body" value="/WEB-INF/view/admin/board/{1}/{2}.jsp" />
<put-attribute name="footer" value="/WEB-INF/view/inc/footer.jsp" />
</definition>
<definition name="notice.*" template="/WEB-INF/view/customer/inc/layout.jsp">
<put-attribute name="title" value="공지사항" />
<put-attribute name="header" value="/WEB-INF/view/inc/header.jsp" />
<put-attribute name="visual" value="/WEB-INF/view/customer/inc/visual.jsp" />
<put-attribute name="aside" value="/WEB-INF/view/customer/inc/aside.jsp" />
<put-attribute name="body" value="/WEB-INF/view/customer/notice/{1}.jsp" />
<put-attribute name="footer" value="/WEB-INF/view/inc/footer.jsp" />
</definition>
</tiles-definitions>
너무 급 마무리한거 같긴한데, 정리를 하자면 web.xml에서 servlet에 관한 다양한 초기설정이 명시된다는 것을 알 수 있다. 그리고 servlet-context.xml에서 추가적인 설정을 통해 필요한 클래스를 주입하게된다.
실제 요청이 들어오면 ModelAndView를 통해 어떤 뷰를 사용할건지, 모델은 어떤데이터를 전달할건지 정하여 반환해준다. 그럼 자동으로 JSP파일에 데이터가 맵핑되어 사용자에게 전달된다.
래퍼런스
https://mangkyu.tistory.com/14
https://velog.io/@han_been/서블릿-컨테이너Servlet-Container-란
https://www.youtube.com/channel/UC5-ixpj8DioZqmrasj6Ihpw
'개발' 카테고리의 다른 글
JAVA의 스레드를 공부하자 (0) | 2022.02.26 |
---|---|
[TDD] 자바와 JUnit을 활용한 실용주의 단위 테스트 (작성중) (0) | 2022.02.24 |
[JUnit5] JUnit5를 공부해보자(Spring Boot, REST API) (0) | 2022.02.24 |
JAVA 인터페이스는 왜 쓰는 걸까? (0) | 2022.02.18 |
OOP(Object-Oriented Programming, 객체 지향 프로그래밍)이 머냐? (0) | 2022.02.16 |