▸JSP & Servlet/기본 상식

Tomcat(톰캣), JSP, Servlet(서블릿)의 기본 개념 및 구조

코데방 2020. 2. 12.
728x90

JSP와 Servlet(서블릿)은 모두 자바로 웹 어플리케이션을 만들기 위한 도구입니다. 기존 자바를 통해 채팅 프로그램 등을 만들었듯이, 기본 원리는 거의 유사합니다. 다만 웹(Web)을 조금 더 쉽게 다룰 수 있도록 해주는 확장 기능이라고 생각하면 될 것 같습니다.

 

[ JSP ]

  • 확장자가 .jsp인 파일
  • Java Server Page
  • html 문서 안에 자바 언어를 삽입해 사용할 수 있도록 해줌

[ Servlet(서블릿) ]

  • 확장자가 .java인 파일
  • 자바의 일반적인 클래스와 동일한 개념
  • 웹을 다룰 수 있도록 해주는 "HttpServlet" 클래스를 상속받은 클래스를 의미함

JSP와 Servlet은 완전 다른 개념이 아니며, Servlet을 사용해 웹을 만들 경우 화면 인터페이스 구현이 워낙 까다로운 단점을 보완하기 위해 만든 스크립트 언어가 JSP라고 볼 수 있습니다.

 


 

 

웹 어플리케이션은 일반적으로 아래와 같은 구조로 만들어집니다. WEB, WAS, DB 서버는 물리적인 서버가 아닌 논리적인 구조입니다. 물리적인 구성은 서버의 스펙과 사용자 수, 보안 및 네트워크 구조 등에 맞춰 설계됩니다. 

 

  1. 사용자가 URL(또는 IP)을 통해 WEB 서버를 호출하고 요청사항을 객체(request)에 담아 전송
  2. WEB 서버는 요청 객체(request)를 받아서 바로 처리하거나 어플리케이션 서버(WAS)로 객체 전달
  3. WAS 서버는 요청에 대한 내용과 요청 객체(request)를 받아 적절히 처리 (필요시 DB 작업 진행)
  4. WAS 서버는 처리 후 결과를 응답 객체(response)에 담아 WEB서버로 회신
  5. WEB 서버는 응답 객체(response)를 다시 사용자에게 회신
  6. 사용자의 브라우저는 WEB 서버가 보내준 코드를 해석해 화면을 구성하여 출력

 


 

[ WEB 서버 단일 구성 ]

 

먼저 가장 기본적인 홈페이지 구성입니다. 개발자는 미리 모든 페이지를 다 만들어 둔 뒤 웹서버에 올려두고 사용자가 URL을 입력하거나 하이퍼링크가 걸린 글/그림 등을 클릭하면 웹서버가 해당하는 페이지를 사용자에게 보내줍니다. 웹 브라우저는 페이지를 받아서 화면으로 만든 뒤 사용자에게 시각화하여 보여줍니다.

 

페이지는 주로 html, CSS, JavaScript로 이루어져 있습니다. html은 뼈대를 만들고 CSS는 html 컨텐츠를 꾸며주는 정적 언어입니다. JavaScript는 동적 스크립트로 상황에 따라 다른 결과를 출력할 수 있는 동적인 스크립트 언어입니다. 쉽게 말해 html에서는 if문을 사용할 수 없는데 JavaScript는 이게 가능하다라고 생각하면 됩니다. html, CSS만으로 이루어진 웹페이지는 그냥 PDF 파일을 보여주는것과 크게 다르지 않습니다. 세 언어 모두 웹서버에서 코드를 보내주면 브라우저가 해석해서 실행합니다.

 

 

 


 

[ WEB - WAS 구성 ]

 

위의 WEB 서버 단일 구성 방식은 사용자가 요청하면 해당 페이지를 넘겨주는 정적인 방식입니다. 필요한 페이지마다 자바스크립트 등으로 복잡한 로직을 구성해서 모든 페이지를 미리 준비해놔야한다는 단점이 있습니다. 그리고 복잡한 로직이 들어있는 페이지를 모두 사용자에게 전송해야 한다는 문제도 있습니다. 컴퓨터의 성능과 네트워크 기술이 발전하며 웹 서비스의 복잡도가 점차 증가했고, 그만큼 효율적으로 웹 서비스를 구성해야할 필요성이 생겼습니다. 이를 위해 등장한 개념이 WAS(Web Application Server) 입니다.

 

기존 웹서버가 사용자가 요청하는 페이지를 단순하게 반환했다면, WAS는 사용자의 요청 내용(request)을 받아 짜여진 로직대로 잘 처리한 뒤 웹페이지를 만들어 사용자에게 응답(response) 해주는 방식입니다. 물론 만들어진 웹페이지는 웹서버를 통해 사용자에게 전달됩니다. 강력한 프로그래밍 언어(Java 등)를 사용해 로직을 처리하기 때문에 보다 복잡한 로직을 처리하기 수월하고, 사용자는 웹서버가 주는 페이지만 받아서 실행하므로 내부의 로직을 알 수 없게 되어 보안이 강화되는 효과도 있습니다. 또한 복잡한 연산은 성능이 뛰어난 서버단에서 해결하고 사용자에게 결과값만을 보내주기 때문에 사용자는 그만큼 빠르게 결과를 받아볼 수 있고 주고 받는 정보량(코드)이 줄어들어 네트워크 부하도 줄일 수가 있게 됩니다.

 

WEB서버와 WAS는 물리적으로 나눌 수도 있고 한 서버안에 기능적으로 나눠둘 수도 있습니다. 중요한건 서로 수행하는 기능이 다르다는 점입니다. 웹서버는 연산이 필요없는 정적 페이지(또는 이미지, 파일 등)은 자신이 처리하고, 연산이 필요할 경우 WAS에게 요청 객체를 넘겨 연산을 수행한 뒤 다시 결과를 받아 반환해줍니다. 서로 역할을 적절히 분담하기 때문에 리소스를 효율적으로 사용할 수 있습니다.

 

 

 


 

 

[ WEB - WAS - DB 구성]

 

사용자와 서버 간 데이터를 주고 받는 일이 많아지며, 이를 효율적으로 저장하고 편집하기 위해 DBMS(DataBase Management System)가 개발되었습니다. DB에는 서버가 연산을 위해 필요한 각종 정보를 저장해두고 WAS에서 연산을 수행하며 필요한 정보를 DB에서 가져오고 편집합니다. 주요 정보를 DB에 모아둠으로써 정보의 구조를 보다 효율적으로 구성할 수 있게 되었고, 사용자가 직접 주요 정보에 다가갈 수 없도록 함으로써 보안도 강화할 수 있습니다. 사용자는 서버에 정보를 저장해둠으로써 기기에 제한받지 않고 서비스를 사용할 수 있습니다.

 

 

 


 

 

[ 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)의 분산 컴포넌트, 웹 서비스 규칙 등을 추가로 가지고 있으며, 서블릿과 JSP를 구현하는 기능을 서블릿 컨테이너라고 합니다. 

 

 

 

[ Apache Tomcat (톰캣) ]

  • Apache : WEB 서버
  • Apache Tomcat : WAS (Web Application Server)

아파치(Apache)는 비영리 재단 이름입니다. 프로그램 이름을 좀 헷갈리게 지어놨는데 그냥 Apache는 웹서버 전용 기능이고 Apache Tomcat은 WAS 기능을 합니다. 물론 Tomcat에도 기본적인 웹서버 기능이 있어서 공부할 때는 톰캣만 사용해서 웹서버를 구성할 수 있습니다. "Apache Tomcat = 경량 Apache + Tomcat"이라고 생각하면 됩니다. 다만 Apache 웹서버에 비해 기능이 한정적이라 규모가 있는 웹서비스는 둘을 분리해서 사용하는 경우가 많습니다.

 

Tomcat(톰캣)은 WEB/WAS의 기능을 가진 자바 어플리케이션입니다. JAVA EE 기반으로 만들어졌다고 합니다. WAS는 자바로 만들어진 JSP와 Servlet을 구동하기 위한 서블릿 컨테이너 역할을 수행합니다. 컨테이너란 JSP를 서블릿으로 바꿔서 실행해주는 역할과, 서블릿의 생명주기를 관리하며 웹 환경에서 서블릿이 구동될 수 있도록 해주는 프로그램입니다. WAS에서는 여러 개의 컨테이너를 구성해서 각각 독립적인 서비스로 구동시키는 것도 가능합니다.

 

따라서 WAS의 컨테이너는 웹서버에서 보내준 요청을 가지고 스레드를 생성한 후, 필요한 jsp나 servlet 파일을 구동해서 로직을 수행하게 한 뒤 결과로 생성된 응답 객체를 웹서버에 보내주는 역할을 합니다. 하나의 WAS에 하나의 컨테이너만 사용한다면 굳이 WAS와 컨테이너를 별도로 나눠서 생각할 필요는 없을 것 같습니다. 

 

 

 


 

[ Servlet(서블릿)과 JSP ] 

 

Servlet(서블릿)은 JAVA EE 에서 제공하는 서블릿 클래스를 상속받은 클래스를 말합니다. 항상 보던 .java의 클래스 파일과 동일합니다. 다만 그러다보니 최종적으로 클라이언트가 받아봐야하는 html 코드를 출력해주는 작업이 너무 번거롭습니다. IO 입출력과 같이 스트림 객체를 생성해서 클라이언트에게 보내줄 html 코드를 전부 print 해줘야 합니다. 아래와 같은 방식이 됩니다.

 

		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();

 

 

코드를 보면 알겠지만 자바 안에 html 코드가 들어간다고는 하지만 사실 저건 코드를 삽입하는게 아니라 그냥 출력해주는 방식입니다. 복잡한 레이아웃을 가진 페이지를 구성하려면 코드가 끝도 없이 길어질 수밖에 없습니다.

 

그래서 나온 것이 JSP입니다. JSP는 Servlet과 반대로 기본적으로는 Html 코드 형식을 하되, 중간에 자바를 사용해 로직을 구성할 수 있도록 했습니다. 아래와 같은 형식입니다. Html 기반으로 작성한 뒤 필요한 부분만 자바 코드를 삽입해주면 돼서 페이지 레이아웃을 구성하는데 Servlet에 비해 훨씬 편리해졌습니다.

 

<%@ 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는 먼저 Servlet 파일(.java) 파일로 변환됩니다. 우리가 Servlet 파일로 직접 작성하는 것과 같은 코드로 변환되는 것이죠. 그리고 이렇게 변환된 서블릿 파일을 다시 컴파일해서 .class 파일로 만든 뒤 실행합니다. 실행 결과는 자바 언어가 모두 사라진 Html 코드가 됩니다. 위의 for문으로 0~9까지 출력한 JSP 파일은 아래와 같이 서블릿 파일로 변환되어 컴파일 된 뒤 최종적으로 Html로 변환되어 사용자에게 날아갑니다. 매우 짧은 구문인데도 서블릿 파일로 변환되면 엄청난 길이의 코드가 되네요.

 

처음 구동할 때는 변환 과정이 한 번 더 있으므로 서블릿보다 느리지만, 첫 구동에서 class 파일을 생성해 두면 두 번째부터는 변환과정 및 컴파일 과정이 없기 때문에 서블릿과 거의 동일하게 작동합니다. 

 

 

* 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 파일 변환 후 최종적으로 사용자에게 전달된 코드

<!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>

 

 


 

 

위와 같은 특징으로 인해 일반적으로 JSP는 화면구성에 사용되고 서블릿은 로직을 수행하는데 사용되는 경우가 많습니다. 이 부분은 다음 글에서 다룰 MVC 구조에서 추가로 다루도록 하겠습니다. 

 

참고로 JSP는 서버에서 모두 처리되어 최종적으로 클라이언트에게는 Html 형식으로 전달되기 때문에 클라이언트단에서 처리가 필요한 동적 처리는 실행할 수 없습니다. 따라서 클라이언트(브라우저)에서 실행되는 동적 처리는 자바스크립트를 통해 처리해줘야 합니다. JSP에서 자바스크립트도 사용할 수 있습니다.

728x90

댓글

💲 추천 글