스프링에 대한 것을 보다보면 servlet이 어쩌고, jsp가 어쩌고 하는 내용들이 종종 보였습니다. 그래서 볼 때 마다 무슨말이지.. 했었던 것을 정리해보려고 합니다.
(그 전에 웹 서비스의 구조와 정적컨텐츠, 동적컨텐츠 이해하고 가기~)
https://orange-makiyato.tistory.com/48
웹 개발의 발전과정을 보면 아래와 같습니다.
1. Html
- 웹의 초창기에 웹사이트가 크지 않았을 때에는 html로 작성된 정적 컨텐츠들로 구성하는데 큰 무리가 없었습니다. 개발자가 미리 html로 웹 페이지를 만들어 놓고 클라이언트로부터 요청이 오면 웹서버가 해당 html을 보여주는 것이 대부분이었던거죠. 하지만 시대가 발전하고 웹사이트의 규모가 커지면서 동적컨텐츠를 처리해야할 일이 생겼습니다. 그래서 생긴 것이 servlet 입니다.
2. Servlet
어떻게 생겼는지부터 바로 보겠습니다.
writer.println("<html>");
writer.println("<head>");
writer.println("</head>");
writer.println("<body>");
writer.println("<h1>helloWorld~</h1>");
writer.println("name : " + request.getParameter("name") + "<br/>");
writer.println("id : " + request.getParameter("id") + "<br/>");
writer.println("pw : " + request.getParameter("pw" + "<br/>"));
writer.println("major : " + request.getParameter("major") + "<br/>");
writer.println("protocol : " + request.getParameter("protocol") + "<br/>");
writer.println("</body>");
writer.println("</html>");
writer.close();
Servlet 은 서버에서 동적컨텐츠를 보내거나 데이터 처리를 수행하기 위해 자바로 작성된 프로그램입니다. 확장자가 .java인 파일이죠. 자바의 일반적인 클래스와 동일한 개념입니다. 웹을 다룰 수 있도록 해주는 "HttpServlet" 이라는 클래스를 상속받은 클래스입니다. 그런데 보면 그 안에 html 구문이 들어있습니다. 사실 이건 코드를 삽입하는게 아니라 그냥 출력해주는 방식입니다. 최종적으로 클라이언트가 받아봐야하는 html 코드를 전부 print 해줘야 해서 매우 번거롭습니다. 복잡한 레이아웃을 가진 UI 화면을 구성하려면 코드가 끝도 없이 길어질 수밖에 없겠죠. 그래서 나온 것이 JSP입니다!
3. JSP (Java Server Page)
<%@ page language="java" contentType="text/html; charset=EUC-KR"
pageEncoding="EUC-KR"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="EUC-KR">
<title>Insert title here</title>
</head>
<body>
<%-- 자바 코드 삽입 --%>
<%
for(int i = 0; i < 10; i++) {
out.println(i);
}
%>
</body>
</html>
JSP는 확장자가 .jsp인 파일로 Servlet과 반대로 기본적으로는 Html 코드 형식을 하되, 중간에 자바를 사용해 로직을 구성할 수 있도록 했습니다. Html 기반으로 작성한 뒤 필요한 부분만 자바 코드를 삽입해주면 돼서 페이지 레이아웃을 구성하는데 Servlet에 비해 훨씬 편리해졌습니다.
Servlet과 JSP는 완전 다른 개념이 아니며, Servlet을 사용해 웹을 만들 경우 화면 인터페이스 구현이 워낙 까다로운 단점을 보완하기 위해 만든 스크립트 언어가 JSP라고 볼 수 있습니다. 동적컨텐츠를 위한 역할이라는 것은 동일한거죠.
JSP는 먼저 Servlet 파일(.java) 파일로 변환됩니다. 우리가 Servlet 파일로 직접 작성하는 것과 같은 코드로 변환되는 것이죠. 그리고 이렇게 변환된 Servlet 파일을 다시 컴파일해서 .class 파일로 만든 뒤 실행합니다. 실행 결과는 자바 언어가 모두 사라진 Html 코드가 됩니다. 위의 for문으로 0~9까지 출력한 JSP 파일은 아래와 같이 Servlet 파일로 변환되어 컴파일 된 뒤 최종적으로 Html로 변환되어 사용자에게 날아갑니다. 매우 짧은 구문인데도 Servlet 파일로 변환되면 엄청난 길이의 코드가 되네요.
* JSP(.jsp) → Servlet(.java)으로 변환된 코드
/*
* Generated by the Jasper component of Apache Tomcat
* Version: Apache Tomcat/9.0.30
* Generated at: 2020-02-12 15:41:55 UTC
* Note: The last modified time of this file was set to
* the last modified time of the source file after
* generation to assist with modification tracking.
*/
package org.apache.jsp;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.jsp.*;
public final class Sample2_jsp extends org.apache.jasper.runtime.HttpJspBase
implements org.apache.jasper.runtime.JspSourceDependent, org.apache.jasper.runtime.JspSourceImports {
private static final javax.servlet.jsp.JspFactory _jspxFactory = javax.servlet.jsp.JspFactory.getDefaultFactory();
private static java.util.Map<java.lang.String, java.lang.Long> _jspx_dependants;
private static final java.util.Set<java.lang.String> _jspx_imports_packages;
private static final java.util.Set<java.lang.String> _jspx_imports_classes;
static {
_jspx_imports_packages = new java.util.HashSet<>();
_jspx_imports_packages.add("javax.servlet");
_jspx_imports_packages.add("javax.servlet.http");
_jspx_imports_packages.add("javax.servlet.jsp");
_jspx_imports_classes = null;
}
private volatile javax.el.ExpressionFactory _el_expressionfactory;
private volatile org.apache.tomcat.InstanceManager _jsp_instancemanager;
public java.util.Map<java.lang.String, java.lang.Long> getDependants() {
return _jspx_dependants;
}
public java.util.Set<java.lang.String> getPackageImports() {
return _jspx_imports_packages;
}
public java.util.Set<java.lang.String> getClassImports() {
return _jspx_imports_classes;
}
public javax.el.ExpressionFactory _jsp_getExpressionFactory() {
if (_el_expressionfactory == null) {
synchronized (this) {
if (_el_expressionfactory == null) {
_el_expressionfactory = _jspxFactory
.getJspApplicationContext(getServletConfig().getServletContext()).getExpressionFactory();
}
}
}
return _el_expressionfactory;
}
public org.apache.tomcat.InstanceManager _jsp_getInstanceManager() {
if (_jsp_instancemanager == null) {
synchronized (this) {
if (_jsp_instancemanager == null) {
_jsp_instancemanager = org.apache.jasper.runtime.InstanceManagerFactory
.getInstanceManager(getServletConfig());
}
}
}
return _jsp_instancemanager;
}
public void _jspInit() {
}
public void _jspDestroy() {
}
public void _jspService(final javax.servlet.http.HttpServletRequest request,
final javax.servlet.http.HttpServletResponse response)
throws java.io.IOException, javax.servlet.ServletException {
if (!javax.servlet.DispatcherType.ERROR.equals(request.getDispatcherType())) {
final java.lang.String _jspx_method = request.getMethod();
if ("OPTIONS".equals(_jspx_method)) {
response.setHeader("Allow", "GET, HEAD, POST, OPTIONS");
return;
}
if (!"GET".equals(_jspx_method) && !"POST".equals(_jspx_method) && !"HEAD".equals(_jspx_method)) {
response.setHeader("Allow", "GET, HEAD, POST, OPTIONS");
response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED,
"JSP들은 오직 GET, POST 또는 HEAD 메소드만을 허용합니다. Jasper는 OPTIONS 메소드 또한 허용합니다.");
return;
}
}
final javax.servlet.jsp.PageContext pageContext;
javax.servlet.http.HttpSession session = null;
final javax.servlet.ServletContext application;
final javax.servlet.ServletConfig config;
javax.servlet.jsp.JspWriter out = null;
final java.lang.Object page = this;
javax.servlet.jsp.JspWriter _jspx_out = null;
javax.servlet.jsp.PageContext _jspx_page_context = null;
try {
response.setContentType("text/html; charset=EUC-KR");
pageContext = _jspxFactory.getPageContext(this, request, response, null, true, 8192, true);
_jspx_page_context = pageContext;
application = pageContext.getServletContext();
config = pageContext.getServletConfig();
session = pageContext.getSession();
out = pageContext.getOut();
_jspx_out = out;
out.write("\r\n");
out.write("<!DOCTYPE html>\r\n");
out.write("<html>\r\n");
out.write("<head>\r\n");
out.write("<meta charset=\"EUC-KR\">\r\n");
out.write("<title>Insert title here</title>\r\n");
out.write("</head>\r\n");
out.write("<body>\r\n");
out.write("\r\n");
out.write('\r');
out.write('\n');
for (int i = 0; i < 10; i++) {
out.println(i);
}
out.write("\r\n");
out.write("\r\n");
out.write("</body>\r\n");
out.write("</html>");
} catch (java.lang.Throwable t) {
if (!(t instanceof javax.servlet.jsp.SkipPageException)) {
out = _jspx_out;
if (out != null && out.getBufferSize() != 0)
try {
if (response.isCommitted()) {
out.flush();
} else {
out.clearBuffer();
}
} catch (java.io.IOException e) {
}
if (_jspx_page_context != null)
_jspx_page_context.handlePageException(t);
else
throw new ServletException(t);
}
} finally {
_jspxFactory.releasePageContext(_jspx_page_context);
}
}
}
* JSP(.jsp) → Servlet(.java) → .class 파일 변환 후 최종적으로 사용자에게 전달되는 HTML 코드
<!DOCTYPE html>
<html>
<head>
<meta charset="EUC-KR">
<title>Insert title here</title>
</head>
<body>
0
1
2
3
4
5
6
7
8
9
</body>
</html>
처음 구동할 때는 변환 과정이 한 번 더 있으므로 Servlet 보다 느리지만, 첫 구동에서 class 파일을 생성해 두면 두 번째부터는 변환과정 및 컴파일 과정이 없기 때문에 Servlet 과 거의 동일하게 작동합니다.
이렇게 JSP만 이용한 개발방식이 유행하다가 유지보수단계에서 많은 단점을 느껴 Servlet과 JSP를 동시에 사용하는 개발방식이 시작되었습니다.
4. MVC 패턴
MVC 패턴이란, WAS 안에서의 역할을 3가지로 나눠서 구성하는 디자인 패턴을 말합니다.
- M (Model, 모델) : 로직(연산) 수행(데이터 작업 포함)을 담당함
- V (View, 뷰) : 사용자 화면 출력을 담당함
- C (Controller, 컨트롤러) : 중앙에서 Model과 View의 제어를 담당함
각각의 장점을 살려 Servlet 은 자바코드 작성이 편하기 때문에 주로 데이터를 받아 가공하고 다시 View에 전달하는 Controller 역할로 사용하고 JSP는 HTML태크 사용이 용이하기 때문에 사용자에게 결과를 보여주는 View 역할을 하게 되었습니다. 출력을 위한 View 코드와 로직처리를 위한 java 코드를 분리하기 때문에 유지보수가 용이해졌습니다.
Controller (Servlet)
- 모든 요청은 컨트롤러로 모입니다. 사용자가 직접 URL/IP를 입력해서 접근하건, 뷰(View)에서 다른 뷰(View)를 호출하건, 모델(Model)에서 다른 모델을 호출하건 모두 컨트롤러를 거치게 됩니다. 어떤 요청이 들어오는지 분석하고 이 요청을 처리하기 위한 Model을 사용하여 처리합니다. 일종의 작업분배기 역할로서 모든 흐름제어를 맡습니다.
톰캣 서버에서 구동되는 가장 핵심적인 역할이기 때문에 HttpServlet 클래스를 상속받아 Servlet 이 됩니다. Servlet 은 톰캣 서버와 정보를 주고 받을 수 있는 기능이 있고, 특히 사용자의 요청 내용과 커넥션 정보 등을 내장 객체 형태로 가지고 있어 이를 적절히 처리할 수 있는 기능을 가지고 있습니다. 이 정보를 Model에게 넘겨서 로직을 수행하게 하고 다시 결과를 받아 View에게 전달해 최종 화면을 사용자에게 넘겨줍니다. 또는 바로 View를 호출해 사용자에게 화면을 보여줄 수도 있습니다.
Model (서비스 클래스)
- 실제 로직 수행 역할을 하는 자바 클래스입니다. Command는 로직을 수행하는 기능을 가진 클래스를 의미하고 DAO/DTO는 DB와 연동되어 데이터 작업을 담당하는 기능의 클래스입니다. Controller 로부터 로직 처리 요청(게시판 글쓰기, 회원가입 등)이 들어오면 이를 수행하고 결과를 Controller 에 반환합니다.
View (JSP)
- 최종 작업 결과물을 가지고 적절한 화면을 구성해서 사용자에게 전달하는 기능을 말합니다. 화면 구성은 JSP가 편리하기 때문에 대부분 JSP를 사용해 뷰를 구성합니다. 톰캣서버는 컨트롤러가 최종적으로 실행시킨 JSP 파일을 Servlet 으로 변환해서 컴파일한 뒤 실행해줍니다. 결과적으로 사용자에게 도달하는 데이터는 html 형태의 코드가 됩니다. 브라우저는 이 코드를 받아 화면을 구성하고 사용자에게 보여줍니다. 출력뿐만 아니라 Controller 에 요청을 보내는 용도로도 사용됩니다.
WAS는 Servlet과 JSP를 구동하기 위한 Servlet 컨테이너 역할을 수행합니다. 컨테이너란 JSP를 Servlet 으로 바꿔서 실행해주는 역할과, Servlet 의 생명주기를 관리하며 웹 환경에서 Servlet 이 구동될 수 있도록 해주는 프로그램입니다. 따라서 WAS의 컨테이너는 웹서버에서 보내준 요청을 가지고 스레드를 생성한 후, 필요한 jsp나 servlet 파일을 구동해서 로직을 수행하게 한 뒤 결과로 생성된 응답 객체를 웹서버에 보내주는 역할을 합니다. 컨테이너는 하나일 수도 있고 여러개일 수도 있습니다.
5. FrameWork
MVC패턴을 사용하는데에도 불편함이 있었습니다. 개발할 때마다 너무 많은 클래스파일을 만들어야 했고 A프로젝트에서 사용했던 Controller 를 B 프로젝트에서 다시 사용할 수 없었습니다. 해당 비지니스에 핏하게 개발되었기 때문에 다른 프로젝트에서는 필요없는 코드이고 결국 다시 코드를 쳐야했던거죠. 설령 다시 사용한다고 해도 if else 문이 계속해서 붙게 됩니다. 이러한 한계점을 느끼고 Controller 에 대한 재사용성이 필요해지면서 MVC패턴을 따르는 FrameWork들이 생기기 시작합니다.
초반에 사용했던 것은 Struts FrameWork 입니다. 재사용성이 부족했던 많은 부분들을 재사용이 가능하도록 미리 구현해 놓은 덕분에 개발 기간이 많이 단축되었다고 합니다. struts는 아파치 자카르타 프로젝트에서 앤트(Ant), Log4J, 톰캣(Tomcat)과 더불어 가장 유명하고 성공한 프로젝트중의 하나입니다. 아파치 자카르타 프로젝트들 모두가 그렇지만, struts 또한 오픈소스로 개발되고 있기 때문에 struts를 이용하여 웹 애플리케이션을 개발한다고 할지라도 추가 개발 비용은 발생하지 않습니다.
이후에는 JAVA EE 의 EJB(Enterprise JavaBeans - 후술) 를 이용한 개발이 유행하기도 했으며 Spring 이 등장한 이유이기도 합니다.
Spring FrameWork
[Spring의 등장 배경]
JAVA SE, JAVA EE 부터 보겠습니다.
JAVA SE : JAVA Standard Edition
JAVA EE : JAVA Enterprise Edition
간단히 JAVA EE는 JAVA SE의 확장 버전으로 서버 개발을 위한 추가 기능을 제공하는 플랫폼입니다. 간단한 응용프로그램과 서버 구축은 JAVA SE만으로도 구성이 가능하지만, Tomcat 등의 WAS를 이용하는 서버 개발은 JAVA EE에서 추가로 제공하는 기능을 사용합니다. 특히 JSP와 Servlet을 만들고 실행하는 규칙과, EJB(Enterprise JavaBeans)의 분산 컴포넌트, 웹 서비스 규칙 등을 추가로 가지고 있습니다.
Java EE의 다른 기능들로는 비동기 메세지 처리를 위한 JMS(Java Message Service), 데이터베이스 처리용 API인 JDBC(Java Database Connectivity), 트랜잭션 처리를 위한 JTA(Java Transaction API) - 분산 트랜잭션 지원, 디렉토리 서비스를 위한 JNDI(Java Naming and Directory Interface), 메일 지원(Java M<ail API) 등 많은 기능들이 있습니다.
이중에서 EJB는 대량의 트랜잭션을 안정적으로 처리할 수 있게 해줍니다. 현재 사이트에서 다른 여러 원격 사이트에 대해 작업을 하던 도중 한군데서 에러가 나서 다른 사이트를 포함한 이전의 모든 처리를 롤백해야 할 경우 이러한 분산 트랜잭션을 지원해줍니다. 이처럼 트랜잭션, 분산기술, Entity Bean(ORM)등의 기술을 지원하여 많은 기업들이 자바 표준 기술로서 사용하였습니다.
하지만 EJB 사용에도 문제점이 있었습니다. 하나의 기능을 구현하기 위해 클래스 간 상속, 인터페이스의 구현 등 각 클래스 간의 의존도가 커져서 배보다 배꼽이 더 큰 상황이 생긴거죠. 그래서 2002년 로드 존슨은 EJB의 문제점을 지적한 책(J2EE Design and Development)을 출간했습니다.
이 책에는 EJB없이도 고품질의 애플리케이션 개발할 수 있다는 내용과 예제 코드를 선보였고, 그 코드에 스프링의 핵심이 되는 개념이 들어있었습니다. (BeanFactory, ApplicationContext, POJO, DI, IoC) 이 책이 유명해지자 개발자들이 책의 예제코드를 프로젝트에 사용하기 시작했고, Juergen Hoeller(유겐 휠러)와 Yann Caroff(얀 카로프)가 로드 존슨에게 오픈 소스 프로젝트를 제안했습니다. 여기서 J2EE(EJB)를 겨울로 빗대어 Spring이란 이름의 프레임워크가 탄생하게 됩니다!
여기까지 Servlet과 JSP를 알아보기 위해 웹 개발의 히스토리를 간략하게 돌아보았습니다~
참고 및 출처
https://m.blog.naver.com/acornedu/221128616501
https://www.youtube.com/watch?v=PH8-V6ah0XQ&ab_channel=IT%EB%8A%A6%EA%B3%B5%EA%B9%80%EB%B6%80%EC%9E%A5
https://codevang.tistory.com/191
https://codevang.tistory.com/192
https://m.blog.naver.com/qhdqhdekd261/221690113143
https://jong99.tistory.com/124
http://gnujava.com/board/article_view.jsp?article_no=1797&page_no=159&menu_cd=18&idx_notice=NOTICE_FLAG+DESC%2C&board_no=5
http://gnujava.com/board/article_view.jsp?article_no=1798&page_no=159&menu_cd=18&idx_notice=NOTICE_FLAG+DESC%2C&board_no=5
'SpringBoot' 카테고리의 다른 글
@RequiredArgsConstructor 와 @AllArgsConstructor (0) | 2023.11.15 |
---|---|
@Transactional(readOnly = true) 중간에 save(insert) 하기 (0) | 2023.08.03 |
@NotNull, @NotEmpty, @NotBlank 문자열 검증 (0) | 2022.08.30 |
Java Mockito when(), BDDMokito given() (0) | 2022.08.08 |
logging @Slf4j (0) | 2022.08.05 |
댓글