
웹 애플리케이션(web application) 또는 웹 앱은 소프트웨어 공학적 관점에서 인터넷이나 인트라넷을 통해 웹 브라우저에서 이용할 수 있는 응용 소프트웨어를 말한다.
소프트웨어를 브라우저에서 이용할 수 있다는 것은 기존의 응용 소프트웨어 혹은 프로그램이라고 불리는 것들을 생각해보면 이해하기 쉽다. 이러한 것들은 하드 디스크에 설치해서 실행했다. 즉, 운영체제 위에서 작동했다. 그러나 웹 애플리케이션은 이보다 한 층 더 올라가 브라우저 위에서 이용할 수 있는 소프트웨어이다. 그리고 프로그램은 사용자의 입력을 받아 설계된 명령어에 따른 결과를 반환한다.
그래서 웹 애플리케이션은 사용자와 '상호작용'하며 브라우저에서 이용가능한 프로그램이다.

브라우저에 접속해서 웹 사이트에 들어가는게 원래 그런거 아닌가? 라고 생각할 수 있지만 그렇지 않다. 과거의 웹 사이트는 브라우저의 요청에 따라 그저 서버에 저장된 문서를 사용자에게 보여줄 뿐이었다. 이러한 웹 사이트를 정적 웹 페이지라 부른다.
그러나 웹 애플리케이션이라고 부르는 것들은 앞서 언급했다시피 사용자와 상호작용하는 기능을 수행한다. 즉, 사용자의 입력에 따라 특정한 연산을 수행하고 그에 따른 결과를 반환한다. 이러한 이유로 웹 애플리케이션을 동적 웹 페이지라고 부른다. 오늘날 대부분의 웹 사이트는 동적 웹 페이지로 구성되어 있다. 대표적인 예가 로그인이다. 로그인은 특정 사용자임을 인증하고 해당 사용자에 따라 다른 화면을 보여준다. 로그인 뿐만 아니라 결제, 댓글, 좋아요와 같은 기능들이 모두 동적 웹 페이지의 예이다.
앞서 설명했듯이 웹 브라우저는 '요청'하고 서버는 컨텐츠를 '응답'한다. 브라우저의 주소창에 https://naver.com 를 입력하는 것 그 자체가 NAVER 웹 페이지에 접근하겠다는 요청이다.
웹 서버는 브라우저의 요청에 대해 정적 컨텐츠를 응답한다. 단지 서버에 저장된 컨텐츠를 어떠한 가공없이 응답으로 보낸다. 그러나 웹 애플리케이션 서버(WAS : Web Application Server)는 브라우저의 요청에 따라 특정한 연산을 수행한 결과 즉, 동적 컨텐츠를 응답한다. 그리고 이 WAS에 웹 애플리케이션이 배포되어 있다. 웹 애플리케이션의 로직은 WAS에서 실행되고 사용자는 브라우저에서 웹 애플리케이션에 요청을 보낸다.
WAS는 동적 컨텐츠 뿐만 아니라 정적 컨텐츠도 응답할 수 있기 때문에 WAS만으로도 웹 시스템을 구성할 수 있다. 그러나 WAS의 부담을 줄이기 위해 웹 서버는 정적 컨텐츠만 응답하도록 하고, WAS는 웹 애플리케이션의 로직을 수행하도록 하여 동적 컨텐츠를 응답하도록 구성한다.

HTTP(HyperText Transfer Protocol)는 컴퓨터간의 데이터 교환방식을 정의한 약속이다. 웹 브라우저와 웹 서버는 서로 요청과 응답을 주고 받으며 '통신'한다. 여기서 요청하는 측을 클라이언트, 요청에 대해 응답을 제공하는 측을 서버라고 한다. Http는 이처럼 클라이언트와 서버가 주고받는 메세지의 양식을 규정한 통신규약이다. 그리고 웹 브라우저와 웹 서버는 이 Http를 기반으로 작동한다.
클라이언트 요청을 처리하고 그 결과를 다시 클라이언트에게 전송하는 Servlet 클래스의 구현 규칙을 지킨 동적 컨텐츠를 생성하기 위한 Java 프로그램이다. 클라이언트의 요청에 대해 동적 컨텐츠를 응답하기 때문에 당연히 웹 애플리케이션 '서버'에서 작동하는 프로그램이다.
이 말이 어떤 의미인지 이해하기 위해 Servlet이 없을 때, 웹 애플리케이션이 브라우저의 요청을 수행하도록 개발자가 어떻게 해야하는지 생각해보자. 아래 사진은 HTTP의 클라이언트의 요청메세지와 서버의 응답메세지이다. 만약 Servlet을 사용하지 않는다면 개발자는 이 메세지를 컴퓨터가 해석할 수 있도록 '파싱'해야한다. HTTP메세지는 그저 문자열이기 때문이다. 요청 메세지를 분석해서 웹 애플리케이션 로직을 수행하는 데 필요한 데이터를 읽어오고, 로직을 수행한 뒤 그 결과를 반환하기 위해 응답 메세지를 구성해야 한다. Servlet이 있다면 이러한 웹 애플리케이션 로직 전, 후의 작업을 대신 수행하여 개발자는 로직에 보다 집중할 수 있다.

즉, 개발자는 클라이언트의 각 요청에 필요한 Servlet 클래스들을 구현해두고, 요청이 올 때마다 그에 대한 처리를 Servlet 객체가 대신하도록 한다. Servlet 객체는 생성, 로직 실행, 소멸을 반복하며 클라이언트의 요청을 처리한다. 그런데 이러한 Servlet 객체(프로그램)의 동작을 개발자가 직접 제어하지 않는다. '서블릿 컨테이너'라고 하는 것이 Servlet 객체의 동작을 관리한다. 다음은 WAS와 서블릿 컨테이너의 구조이다.

HTTP를 기반으로 클라이언트와 서버가 통신할 수 있도록 Servlet을 구현하려면 HttpServlet 클래스를 상속받은 클래스를 구현해야 한다. 다시 말해, 웹 브라우저의 요청을 처리하기 위한 Servlet 클래스를 정의하려면 반드시 HttpServlet를 상속받아야 한다. HttpServelt 클래스 또한 상위 추상 클래스(GenericServlet)와 인터페이스(Servlet)를 구현한 클래스이다. 이제 그 상속 구조를 알아보자.

Servlet < Interface >
void init()void service(ServletRequest request, ServletResponse response)void destroy()GenericServlet < Abstract class >
String getInitParameter(String name) {...}ServletConfig getServletConfig() {...}ServletContext getServletContext() {...}HttpServlet < Abstract class >
void doGet(HttpServletRequest request, HttpServletResponse response) {...}void doPut(HttpServletRequest request, HttpServletResponse response) {...}void doPost(HttpServletRequest request, HttpServletResponse response) {...}void doDelete(HttpServletRequest request, HttpServletResponse response) {...}void service(HttpServletRequest request, HttpServletResponse response) {...} 사용자 정의 Servlet 클래스
public class HelloServlet extends HttpServlet {...}
웹 브라우저(클라이언트)가 URL요청을 보내면 웹 서버는 HTTP Request 메세지를 해석한 뒤, 서블릿에 대한 요청이면 메세지를 서블릿 컨테이너로 보낸다.
서블릿 컨테이너가 HttpServletRequest, HttpServletResponse 객체를 생성한다. (서블릿이 직접 생성하지 않는다.)
서블릿 컨테이너가 web.xml을 분석해서 어느 서블릿에 대한 요청인지 찾는다.
해당 서블릿이 최초 요청이라면, 서블릿 클래스를 메모리에 로드하고 객체를 생성한다. 이후의 요청은 이 때 생성한 객체를 사용하여 요청을 처리한다. 이처럼 객체를 한번만 생성하여 사용하는 것을 '싱글톤 패턴'이라고 한다.
init() 메서드를 실행해서 서블릿 객체의 초기화작업을 수행한다. 이 메서드는 서블릿 객체가 생성된 다음에 호출되는 메서드다. 그래서 최초 요청이 아니라면 init()는 실행되지 않는다.
service() 메서드를 실행해서 클라이언트의 요청을 처리한다. 이 때 서블릿 컨테이너가 생성한 HttpServletRequest, HttpServletResponse 객체를 인자로 전달받는다.
service() 메서드가 종료되면 서블릿 프로그램이 종료된다. 그리고 서블릿 컨테이너는 실행결과를 웹 서버에 전달하고, HttpServletRequest, HttpServletResponse 객체를 소멸시킨다. 웹 서버는 클라이언트의 요청에 대해 응답을 보낸다.
destroy() 메서드는 서블릿이 수정되었을 때 실행된다. 이미 메모리에 로드되어 있는 서블릿을 소멸시켜야 수정된 서블릿을 로드할 수 있기 때문이다.
코드와 콘솔창을 통해 보다 자세히 알아보자.
import java.io.IOException;
import java.io.PrintWriter;
import java.rmi.ServerException;
import jakarta.servlet.ServletConfig;
import jakarta.servlet.ServletContext;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
public class HelloServlet extends HttpServlet {
// 서블릿 최초 요청 시 init()가 실행된다.
@Override
public void init() throws ServletException {
System.out.println("HelloServlet의 init() 메서드 실행 ...");
}
// 소스코드가 변경되면 destroy()가 실행되어 객체가 소멸된다.
@Override
public void destroy() {
System.out.println("HelloServlet의 destroy() 메서드 실행 ...");
}
// HTML 컨텐츠를 응답으로 보낸다.
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServerException, IOException {
System.out.println("HelloServlet의 service() 메서드 실행 ...");
// HttpServletRequest 객체를 이용해 응답메세지의 타입을 html로 지정한다.
response.setContentType("text/html; charset=utf-8");
// 웹 브라우저에 출력하기 위한 객체를 HttpServletRequest객체를 이용해 생성한다.
PrintWriter out = response.getWriter();
// 위에서 생성한 PrintWriter 객체를 이용해 웹 브라우저에 출력할 컨텐츠를 작성한다.
out.print("<!DOCTYPE html>");
out.print("<html lang='ko'>");
out.print("<head>");
out.print("<meta charst='utf-8'>");
out.print("<title>헬로 서블릿</title>");
out.print("</head>");
out.print("<h1>헬로 서블릿</h1>");
out.print("<p>나는 서블릿입니다.</p>");
out.print("<p>서블릿의 라이프 사이클 메서드를 확인하세요.</p>");
out.print("<p></p>");
out.print("</body>");
out.print("</html>");
}
}
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="https://jakarta.ee/xml/ns/jakartaee" xmlns:web="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_5_0.xsd http://xmlns.jcp.org/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="5.0">
<!--
서블릿의 경로와 별칭을 지정한다.
-->
<servlet>
<servlet-name>hello-servlet</servlet-name>
<servlet-class>com.sample.servlet.HelloServlet</servlet-class>
</servlet>
<!--
위에서 지정한 서블릿의 별칭과 URL주소를 매핑한다.
-->
<servlet-mapping>
<servlet-name>hello-servlet</servlet-name>
<url-pattern>/hello</url-pattern>
</servlet-mapping>
</web-app>

만약 여기서 소스코드가 수정되면 자동으로 destroy() 메서드가 실행되어 HelloServlet 객체가 소멸된다.

코드를 보면 알 수 있듯 Java 소스코드 내에 HTML 태그가 삽입되어 있다. 위 예시는 간단한 HTML 컨텐츠를 응답으로 보내는 형태이지만, 동적 컨텐츠 응답이라는 서블릿의 목적에 맞게 코드를 작성하면 훨씬 더 복잡해진다. 또한 서블릿 클래스를 작성한 후, URL주소와 해당 서블릿을 web.xml을 통해 매핑해야 한다. 이러한 단점을 극복하기 위해 JSP가 등장했다.
JSP는 HTML내에 Java코드를 삽입하여 '웹 서버'에서 동적 웹 페이지를 생성하여 웹 브라우저에 제공하는 서버 사이드 스크립트 언어다. 위에서 언급한 Servlet의 단점을 극복하기 위해 등장했다. 웹 브라우저로부터 요청이 들어오면 WAS는 JSP파일을 Java코드로 이루어진 Servlet으 변환한다. 그리고 이후의 과정은 Servlet이 실행되는 과정과 똑같다.
아래는 웹 브라우저로부터 요청이 들어온 후, JSP가 실행되는 과정이다.

https://ko.wikipedia.org/wiki/%EC%9B%B9_%EC%95%A0%ED%94%8C%EB%A6%AC%EC%BC%80%EC%9D%B4%EC%85%98
https://medium.com/@chrisjune_13837/web-%EC%9B%B9%EC%84%9C%EB%B2%84-%EC%95%B1%EC%84%9C%EB%B2%84-was-app%EC%9D%B4%EB%9E%80-692909a0d363
https://blog.naver.com/allstar927/90161809512
https://ssup2.github.io/theory_analysis/Servlet_Servlet_Container/
https://woojong92.tistory.com/entry/Servlet%EA%B5%AC%EC%A1%B0%EC%99%80-HttpServlet-%ED%81%B4%EB%9E%98%EC%8A%A4
https://dinfree.com/lecture/backend/javaweb_2.2.html