서론
Java언어로 웹 애플리케이션을 개발할 때, 주로 스프링 프레임워크를 사용하며, 이와 함께 Maven이나 Gradle과 같은 빌드 툴을 활용한다. 이 빌드 툴은 코드를 컴파일해 주고 테스트를 실행하며 배포하는 작업등을 자동화해준다고 한다. 이런 빌드 툴의 도움 없이 수동으로 웹 애플리케이션을 빌드와 배포해보면서 빌드 툴이 어떤 역할을 하는지 그리고 웹 애플리케이션의 구조와 어떤 방식으로 동작하는지 도움이 될 거 같아 이렇게 글을 작성해 보았다.
본론
0. 개발 환경
OS : MacOS
JAVA : 17.0.12
Tomcat : 9.0.13
1. 디렉터리 구성
먼저 웹 애플리케이션을 만들기 위해 디렉터리를 구성해주었다. 디렉터리 구조는 표준 웹 애플리케이션의 디렉토리 구조를 따르도록 설계하였고 최종적으로 만든 디렉터리는 다음과 같다.
.
├── src
│ └── main
│ ├── java
│ │ └── com
│ │ └── web
│ └── webapp
│ └── WEB-INF
│ ├── classes
│ ├── lib
│ └── views
└── target
└── classes
1.1 디렉토리 설명
src/main/java/
이 디렉토리는 Java 애플리케이션의 모든 소스 코드 파일이 위치하는 곳이다.
Java 소스 코드는 패키지 구조를 따라 배치되며, Java의 package 선언과 일치해야 한다.
예를 들어, `com.web.myapp` 패키지에 속한 클래스 파일은 `/main/java/com/web/myapp/` 아래에 위치해야 한다.
패키지 구조는 Java의 클래스 로더가 정확하게 클래스를 찾을 수 있도록 한다.
src/main/webapp/
이 디렉토리는 Java 웹 애플리케이션의 웹 관련 리소스가 위치하는 곳이다.
여기에는 HTML 파일, JSP 파일, JavaScript 파일, CSS 파일 등 웹 관련 리소스가 위치한다.
src/main/webapp/WEB-INF/
이 디렉터리는 Java 웹 애플리케이션의 핵심 설정 파일과 보안 관련 파일들이 위치하는 특별한 디렉토리이다.
이 디렉토리 아래에 있는 파일들은 외부에서 직접 접근할 수 없으며, 애플리케이션 내부에서만 사용될 수 있다.
src/main/webapp/WEB-INF/web.xml
web.xml 파일은 웹 애플리케이션의 전반적인 동작 방식을 설정하는 데 사용된다.
src/main/webapp/WEB-INF/lib/
웹 애플리케이션에서 사용하는 외부 라이브러리(JAR)가 위치하는 디렉터리이다.
src/main/webapp/WEB-INF/classes/
컴파일된 Java 클래스 파일들이 위치하는 디렉터리이다.
src/main/java/ 디렉터리에 작성된 Java 소스 코드가 컴파일되어 이곳에 저장된다.
src/main/webapp/WEB-INF/views/
이 디렉터리에는 JSP 파일, Thymeleaf 템플릿, FreeMarker 템플릿 등,
사용자가 보는 화면을 렌더링 하는 템플릿 파일들이 위치하는 디렉터리이다.
2. 소스 코드 작성
디렉터리를 구성하였으니 이제 소스 코드를 작성해 보자. 일단 간단하게 동작하는지만 확인하기 위해 "Hello World"를 출력하는 코드를 작성하였다. 소스 코드는 `/src/main/com/web/` 아래에 작성해 주었다. 따라서 자바 코드 내에서도 `package` 키워드를 사용하여 해당 클래스가 `com.web` 패키지에 속해 있다는 것을 명시한다.
만약 `package`를 명시하지 않으면 해당 클래스는 기본 패키지에 속하게 된다. 기본 패키지에 속한 클래스는 명시적 패키지 구조가 없기 때문에 클래스가 루트 디렉터리에 위치하게 되며 논리적인 그룹화나 관리 기능을 사용할 수 없다. 즉 컴파일된 `.class`파일도 별도의 패키지 구조 없이 프로젝트 루트 디렉토리에 저장된다.
정리하면 패키지는 Java에서 클래스들을 논리적으로 그룹화하는 데 사용되며, 이를 통해 같은 이름의 클래스라도 서로 다른 패키지에서 독립적으로 사용할 수 있다. IDE를 사용하면 자동으로 package 선언이 추가되지만, IDE 없이 작업할 때는 반드시 직접 package를 선언해야 한다.
cd src/main/java/com/web
vi Main.java
package com.web;
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello World");
}
}
.
├── src
│ └── main
│ ├── java
│ │ └── com
│ │ └── web
| | └── Main.java
│ └── webapp
│ └── WEB-INF
│ ├── classes
│ ├── lib
│ └── views
└── target
└── classes
3. 소스 코드 컴파일
컴파일하기 앞서 먼저, Java는 소스 코드를 직접 실행하지 않고, 자바 컴파일러인 'javac'를 이용해 JVM이 이해할 수 있는 바이트코드로 컴파일하고, 이 바이트 코드를 JVM이 각 운영체제와 하드웨어에 맞게 해석하고 실행한다. 이러한 방식 덕분에 Java는 플랫폼 독립성을 가질 수 있다.
이제 만들었던 `Main.java` 파일을 컴파일해보자 아래 명령어를 통해 만들었던 소스 파일을 컴파일할 수 있다.
javac src/main/java/com/web/Main.java -d src/main/webapp/WEB-INF/classes
- javac : Java 컴파일러로,. java확장자를 가진 소스 파일을 바이트코드로 변환하는 명령어이다.
- src/main/java/com/web/Main.java :컴파일할 Java 소스 파일의 경로이다.
- -d :javac 명령어에 사용되는 옵션 중 하나로, 컴파일된 바이트 코드가 저장될 디렉터리를 지정한다.
- src/main/webapp/WEB-INF/classes :이 경로는 컴파일된 바이트코드를 저장할 경로를 의미한다.
정리하면, 이 명령어는 src/main/java/com/web/Main.java 파일을 컴파일하여 Main.class 파일을 생성하고, 그 파일을 src/main/webapp/WEB-INF/classes 디렉터리에 저장한다. 또한, 컴파일된 파일은 원본 소스 파일의 패키지 구조를 그대로 유지한다.
또한 Java 웹 애플리케이션에서. class 파일을 WEB-INF/classes 디렉터리에 배치하는 것은 표준적인 관행이며, 이는 Java 서블릿 컨테이너(예: Tomcat)가 해당 디렉터리에서 컴파일된 클래스 파일을 로드할 수 있도록 하기 위함이다. WEB-INF/classes 디렉토리는 웹 애플리케이션의 내부 리소스 중 하나로, 외부에서 직접 접근할 수 없으며, 애플리케이션이 실행될 때 서블릿 컨테이너가 이 디렉토리에서 클래스를 찾아 로드한다.
.
├── src
│ └── main
│ ├── java
│ │ └── com
│ │ └── web
│ │ └── Main.java
│ └── webapp
│ └── WEB-INF
│ ├── classes
│ │ └── com
│ │ └── web
│ │ └── Main.class
│ ├── lib
│ └── views
└── target
└── classes
4. 소스 코드 실행
컴파일하였다면 이제 직접 실행해 볼 차례이다. 위와 같이 `tree`명령어를 사용하여 -d 옵션으로 지정한 디렉터리에. class 파일이 저장된 것을 확인해 볼 수 있다. 이처럼 자바 소스 코드가 자바 컴파일러에 의해 컴파일되면 바이트코드(. class 파일)가 생성된다. 이제 이 생성된 바이트코드는 JVM이 실행하게 된다.
실행과정은 다음과 같다, 먼저, JVM의 클래스 로더가 해당 클래스를 찾아 메모리에 적재한다. 클래스가 메모리에 적재된 후, 클래스 파일의 정보는 런타임 데이터 영역에 저장되고 이후, JVM의 실행 엔진이 바이트코드를 해석하고 실제로 명령을 실행하게 된다.
바이트 코드를 실행하려면 아래 명령어를 사용하면 된다.
java -cp src/main/webapp/WEB-INF/classes com.web.Main
- cp : cp는 Java에서 클래스패스를 지정하는 데 사용된다. 클래스패스는 JVM이 Java 클래스 파일을 찾을 때 참조하는 경로 목록을 의미한다 Java 프로그램을 실행할 때, -cp(또는 -classpath) 옵션을 통해 JVM이 지정된 디렉터리나 JAR 파일에서 필요한 클래스들을 찾는다. 클래스패스는 여러 디렉토리나 JAR 파일을 포함할 수 있으며, 클래스 파일들이 올바르게 로드되도록 JVM이 참조할 경로를 설정하는 데 필수적인 역할을 한다.
- com.web.Main : com.web.Main은 com.web 패키지 안에 있는 Main이라는 클래스라는 의미이다. JVM이 경로에 있는 클래스를 찾아 실행하게 된다.
단일 클래스패스 지정
java -cp src/main/webapp/WEB-INF/classes com.web.Main
- 이 경우, JVM은 src/main/webapp/WEB-INF/classes 디렉터리에서 com.web.Main 클래스 파일을 찾아 실행한다.
여러 클래스패스 지정
java -cp src/main/webapp/WEB-INF/classes:/some/other/path com.web.Main
- 이 경우 JVM은 두 경로(src/main/webapp/WEB-INF/classes와 /some/other/path)를 모두 검색하여 필요한 클래스 파일을 찾는다. 윈도에서는 : 가 아닌 ; 를 사용하여 경로를 구분한다.
명령어 실행 시 작성한 코드가 정상적으로 출력되는 것을 확인할 수 있다. 이제 본격적으로 Servlet을 통해 웹 애플리케이션을 만들어 보자.
5. Servlet을 이용한 웹 애플리케이션 구현
Java 서블릿을 이용해 HTTP GET 요청을 처리하고, 클라이언트에게 "Hello World"를 응답하는 웹 애플리케이션을 만들 예정이다. 먼저, src/main/java/com/web/ 디렉터리에 MyApp이라는 클래스를 생성해 주었다. 이 클래스는 HttpServlet을 상속받아 서블릿 기능을 구현하게 된다.
이제 MyApp 클래스에 HttpServlet을 상속받고, HTTP GET 요청을 처리할 doGet 메서드를 오버라이드한다. 서블릿은 Java EE(Servlet API)의 핵심 구성 요소로, 웹 서버와 통신하여 HTTP 요청을 처리하고, HTTP 응답을 생성하는 역할을 한다.
5.1 JAVA EE(Java Platform, Enterprise Edition)
Java EE(Java Platform, Enterprise Edition)는 기업에서 사용하는 대규모 소프트웨어를 만들기 위한 Java 플랫폼이다. 쉽게 말해, Java EE는 복잡한 웹사이트나 비즈니스 애플리케이션을 개발할 때 필요한 도구와 규칙을 모아 놓은 "상자" 같은 것이다.
package com.web;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class MyApp extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
PrintWriter out = response.getWriter();
out.println("Hello World!");
out.close();
}
}
이제 작성한 MyApp.java 파일을 컴파일해야 한다. 위에서 컴파일을 하기 위해서는 `javac` 명령어를 사용한다고 하였고 전체 명령어는 다음과 같다. 하지만 아래 명령어를 사용하면 다음과 같이 에러 메시지가 출력된다. 이유가 뭘까?
javac src/main/java/com/web/MyApp.java -d src/main/webapp/WEB-INF/classes/
이유는 다음과 같다. MyApp.java 파일에서는 HttpServlet, HttpServletRequest, HttpServletResponse와 같은 서블릿 API 클래스들을 사용한다. 하지만 Java의 기본 라이브러리에는 서블릿 API가 포함되어 있지 않기 때문에, javac 명령만으로 컴파일을 시도하면 서블릿 API가 없어서 javax.servlet 관련 클래스를 찾을 수 없다는 에러가 발생하게 된다.
5.2 의존성 관리
이 경우, 빌드 툴이 없는 상황에서는 다음과 같은 작업을 수동으로 진행해야 한다.
- 우선, 서블릿 API를 제공하는 JAR 파일을 인터넷에서 찾아 다운로드해야 하고,
- 해당 JAR 파일의 경로를 컴파일 명령어에 직접 포함시켜야 한다.
이전에는 Maven이나 Gradle 같은 빌드 툴이 이러한 라이브러리 관리 작업을 자동으로 처리해 주었기 때문에, 개발자가 직접 라이브러리를 다운로드하거나 설정할 필요가 없다. 이처럼 애플리케이션이 제대로 동작하기 위해 필요한 외부 라이브러리나 패키지들을 자동으로 다운로드하고 관리하는 것을 의존성 관리라고 한다.
5.3 Maven을 이용한 의존성 관리
Maven에서는 프로젝트 설정 파일인 pom.xml에서 필요한 라이브러리를 의존성(dependency)으로 선언한다. 그러면 Maven이 해당 라이브러리를 중앙 저장소에서 자동으로 다운로드하고, 프로젝트에 추가해 준다.
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
</dependencies>
위 코드에서 javax.servlet-api 라이브러리는 Maven Central Repository에서 자동으로 다운로드되고, 프로젝트의 클래스패스에 추가된다. 개발자는 수동으로 JAR 파일을 관리할 필요 없이, 필요한 라이브러리만 선언하면 하기 때문에 매우 편리하게 사용할 수 있다.
Gradle을 이용한 의존성 관리
Gradle에서도 비슷하게, build.gradle 파일에서 필요한 라이브러리를 의존성으로 선언한다.
dependencies {
providedCompile 'javax.servlet:javax.servlet-api:4.0.1'
}
Gradle도 Maven처럼 라이브러리를 다운로드하고 클래스패스에 자동으로 추가한다.
의존성 관리의 장점
- 자동화: 필요한 라이브러리를 수동으로 찾고 설치할 필요 없이, 빌드 툴이 자동으로 관리해준다.
- 버전 관리: 사용 중인 라이브러리의 버전을 명확히 지정할 수 있어, 프로젝트 내의 버전 충돌을 방지할 수 있다.
- 업데이트 용이성: 라이브러리의 버전을 변경할 때, 설정 파일에서 버전 번호만 수정하면 자동으로 최신 버전으로 업데이트할 수 있다.
결론적으로 의존성 관리는 프로젝트가 필요로 하는 외부 라이브러리들을 효율적으로 관리하는 것을 의미한다. Maven이나 Gradle 같은 빌드 툴은 이 의존성 관리 과정을 자동화해 주기 때문에, 개발자는 라이브러리를 쉽게 추가하고, 버전 충돌 없이 프로젝트를 안정적으로 유지할 수 있다.
다시 돌아와서, 우리는 현재 빌드 툴 없이 수동으로 작업하고 있기 때문에, 서블릿 API와 같은 외부 라이브러리를 직접 설정해야 한다. 이를 위해, 컴파일 시 서블릿 API JAR 파일을 클래스패스에 수동으로 포함시켜야 한다, 서블릿 API는 직접 다운로드할 수 있지만, Tomcat을 설치했다면 해당 JAR 파일이 이미 시스템에 존재한다.
Tomcat을 설치한 경우, 서블릿 API JAR 파일은 보통 "/opt/homebrew/opt/tomcat@9/libexec/lib" 경로에 위치하고 있고 servlet-api.jar 파일을 클래스패스에 포함시켜 컴파일해야 한다.

javac -cp /opt/homebrew/opt/tomcat@9/libexec/lib/servlet-api.jar -d ./src/main/webapp/WEB-INF/classes/ ./src/main/java/com/web/MyApp.java
- -cp 옵션은 Java에서 클래스패스를 지정하는 데 사용된다고 설명하였다. 클래스패스는 JVM이 Java 클래스 파일을 찾을 때 참조하는 경로 목록을 의미한다. Java 프로그램을 컴파일하거나 실행할 때, -cp(또는 -classpath) 옵션을 통해 JVM이 지정된 디렉터리나 JAR 파일에서 필요한 클래스들을 찾는다.
- 즉, "/opt/homebrew/opt/tomcat@9/libexec/lib/servlet-api.jar" 경로를 클래스패스로 지정하면, 컴파일 시 서블릿 API 클래스들(javax.servlet.HttpServlet, HttpServletRequest, HttpServletResponse 등)을 이 JAR 파일에서 찾을 수 있게 된다.
- 이제 -d 옵션을 통해 컴파일된 파일 된. class 파일을 /src/main/webapp/WEB-INF/classes/ 경로에 저장하도록 설정하였고 그런 다음 컴파일할 파일의 경로를 입력하여 MyApp.java 파일을 컴파일해주었다.
정상적으로 컴파일이 완료되면 다음과 같은 디렉터리 구조를 갖게 된다.
.
├── src
│ └── main
│ ├── java
│ │ └── com
│ │ └── web
│ │ ├── Main.java
│ │ └── MyApp.java
│ └── webapp
│ └── WEB-INF
│ ├── classes
│ │ └── com
│ │ └── web
│ │ └── Main.class
│ ├── lib
│ └── views
└── target
└── classes
6. WEB.xml 파일 설정
web.xml 파일은 Java 웹 애플리케이션의 배포 서술자로 웹 애플리케이션이 어떻게 동작해야 하는지, 어떤 서블릿이 어떤 URL과 연결되는지, 그리고 기타 설정 정보를 정의하는 역할을 한다.
먼저 "src/main/webapp/WEB-INF/" 경로에 web.xml 파일을 만들어 주었고 다음과 같은 내용을 작성하였다.
vi src/main/webapp/WEB-INF/web.xml
<web-app>
<servlet>
<servlet-name>myApp</servlet-name>
<servlet-class>com.web.MyApp</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>myApp</servlet-name>
<url-pattern>/hello</url-pattern>
</servlet-mapping>
</web-app>
- <web-app> : 웹 애플리케이션의 루트 요소이다. web.xml 파일 내 모든 설정을 이 태그 안에서 작성한다. 이 태그는 전체 애플리케이션의 설정을 포함하는 컨테이너 역할을 한다.
- <servlet> : 태그는 특정 서블릿 클래스를 정의한다. 즉, 서블릿이 어떻게 동작하는지 정의하고 해당 서블릿 클래스가 어떤 것인지 지정한다.
- <servlet-name>: 서블릿의 이름을 지정한다. 이 이름은 서블릿을 참조할 때 사용된다.
- <servlet-class>: 서블릿의 실제 클래스 경로를 지정한다.
- <servlet-mapping> : 태그는 서블릿과 URL 경로를 연결(매핑)한다. 즉, 클라이언트가 특정 URL로 요청을 보낼 때, 어떤 서블릿이 그 요청을 처리할지를 지정하는 역할을 한다.
- <servlet-name>: 매핑할 서블릿의 이름을 지정합니다. 이 이름은 위에서 정의한 서블릿 이름과 동일해야 한다.
- <url-pattern>: 클라이언트가 요청할 URL 경로를 지정한다. 여기서는 /hello로 설정되어 있어, 사용자가 /hello 경로로 접근하면 myApp 서블릿이 요청을 처리하게 된다.
.
├── src
│ └── main
│ ├── java
│ │ └── com
│ │ └── web
│ │ ├── Main.java
│ │ └── MyApp.java
│ └── webapp
│ └── WEB-INF
│ ├── classes
│ │ └── com
│ │ └── web
│ │ ├── Main.class
│ │ └── MyApp.class
│ ├── lib
│ ├── views
│ └── web.xml
└── target
└── classes
7. Tomcat에 애플리케이션 배포
7.1 파일 복사
이제 작성된 Java 애플리케이션을 Tomcat 서버에 배포해 보자. 먼저, WEB-INF 디렉터리와 그 안에 있는 설정 파일 및 클래스 파일들을 "target/classes" 디렉토리로 복사해 주었다. 이렇게 하면 Tomcat이 애플리케이션을 실행할 때 필요한 파일들이 한 곳에 모이게 된다.
하지만 굳이 "target/classes" 경로로 복사하지 않고도, 이미 존재하는 "webapp/WEB-INF" 아래에 있는 파일들을 사용할 수 있다. 이번에는 Maven의 디렉토리 구조를 따르기 위해 target/classes 디렉터리를 만들어 복사 작업을 수행한 것이다.
cp -R src/main/webapp/WEB-INF target/classes
- 여기서 사용된 -r 명령어는 그 안의 모든 파일과 하위 디렉터리까지 재귀적으로 복사하도록 하는 옵션이다.
즉, 이 명령어는 "src/main/webapp/WEB-INF" 디렉토리 안에 있는 모든 파일과 하위 디렉터리를 "target/classes" 디렉터리로 복사하는 명령어이다.
.
├── src
│ └── main
│ ├── java
│ │ └── com
│ │ └── web
│ │ ├── Main.java
│ │ └── MyApp.java
│ └── webapp
│ └── WEB-INF
│ ├── classes
│ │ └── com
│ │ └── web
│ │ ├── Main.class
│ │ └── MyApp.class
│ ├── lib
│ ├── views
│ └── web.xml
└── target
└── classes
└── WEB-INF
├── classes
│ └── com
│ └── web
│ ├── Main.class
│ └── MyApp.class
├── lib
├── views
└── web.xml
톰캣에 웹 애플리케이션을 배포하려면 Tocat의 `webapps` 디렉터리에 애플리케이션을 배치해야 한다. Tomcat은 기본적으로 이 `webapps` 디렉터리에서 애플리케이션을 찾아 자동적으로 배포한다. 배포하는 방식에는 크게 2가지가 존재한다.
- WAR 배포 : WAR 파일이란 Java 웹 애플리케이션을 배포하기 위한 표준 형식의 압축 파일이다. tomcat은 WAR 파일을 자동으로 해제하여 내부 파일들을 적절한 위치에 배치하여 애플리케이션을 실행시킨다.
- 디렉터리 배포 : WAR 파일로 패키징 하지 않고 WEF-INF를 직접 `webapps` 폴더로 복사하여 배포하는 방식이다.
7.2 디렉터리 배포
이전에 만들었던 WEB-INF 디렉터리와 그 안의 파일들을 직접 Tomcat의 webapps 디렉터리로 복사하여 배포할 수 있다.
cp -R src/main/webapp/WEB-INF /opt/homebrew/opt/tomcat@9/libexec/webapps/myapp
7.3 WAR 파일 배포
먼저 "/target/classes/" 경로에 위치하여 아래 명령어를 사용하여 압축한다.
jar -cvf myapp.war *
- jar : Java의 기본 제공 압축 유틸리티로, JAR 또는 WAR 파일을 생성할 때 사용된다.
- -c : 파일을 새로 생성하겠다는 의미
- -v : 생성과정에서 어떤 파일이 추가되는지 자세히 보여준다.
- -f : 출력 파일 이름을 지정한다. myapp.war으로 지정
- * : 현재 디렉터리에 있는 모든 파일과 디렉터리를 압축 대상으로 지정한다.
이제 만든 war 파일을 tomcat/webapps/경로에 복사해야 한다. 명령어는 다음과 같다.
cp myapp.war /opt/homebrew/opt/tomcat@9/libexec/webapps/
8. 실행
이제 톰캣을 실행해 보자. 톰캣을 실행할 때에는 "/opt/homebrew/opt/tomcat@9/bin" 경로에 들어가서 `./startup.sh`를 입력하면 된다. 반대로 종료할 때는 `./shutdown.sh` 를 입력하여 톰캣 서버를 종료시킬 수 있다.
./shutdown.sh
./startup.sh
결론
Java 웹 애플리케이션을 수동으로 빌드하고 배포하는 전체 과정을 직접 경험해 보았다. 일반적으로 Maven이나 Gradle 같은 빌드 툴을 사용하면 코드를 자동으로 컴파일하고, 필요한 라이브러리를 쉽게 관리하며, 배포 작업도 손쉽게 처리할 수 있다. 하지만 이번에는 빌드 툴 없이 수동으로 작업하면서, 웹 애플리케이션의 구조와 동작 방식을 조금이나마 이해할 수 있었다.
과정을 요약하면 Java 소스 코드 작성부터 시작해서, 컴파일, 서블릿 구현, 그리고 Tomcat을 통한 배포까지의 전 과정을 수동으로 진행했었다. 특히, 서블릿 API 의존성 관리와 Tomcat 서버 배포에서 Maven, Gradle 같은 빌드 툴이 얼마나 많은 작업을 자동화해 주는지 체감할 수 있었다.
마지막으로, 수동으로도 웹 애플리케이션을 성공적으로 배포할 수 있었지만, 빌드 툴을 사용하면 훨씬 더 효율적으로 개발 작업을 진행할 수 있다는 점을 다시 한번 확인할 수 있었고 재밌었다. 다음에는 IDE 없이 Maven을 설정하여 프로젝트 관리를 해봐야겠다. 이 과정이 삽질처럼 보일 수 있겠지만 실습하고 정리하는 과정에서 여러 가지를 배울 수 있었기 때문에 유익하였다.