본문 바로가기
IT 공부/자바와 웹 애플리케이션

[그레이들] - 스프링 웹 프로젝트의 구조와 빌드

by exdus3156 2024. 1. 25.

1. 그레이들 프로젝트 구조

서버측 자바 웹 애플리케이션은 그레이들(Gradle)이나 메이븐(maven)과 같은 빌드툴(Build Tool) 프로그램을 사용해서 프로젝트를 관리한다.

물론 이렇게 사용하지 않고 이클립스나 인텔리제이가 제공하는 기본 프로젝트 템플릿 구조에 일일이 폴더를 만들고, 라이브러리를 다운받고, 패키징을 할 수도 있다. 그러나 이 과정이 매우 번거롭기 때문에 실무에서는  그냥 그레이들이나 메이븐을 사용한다.

내가 빌드툴을 잘 사용하는 것은 아니라 자세히는 모르지만, 프로젝트 빌드라는 것은 소스코드와 각종 파일 자원을 가지고 실제 활용 가능한 프로그램을 생성하는 일을 일컫는다. 자바 웹 프로젝트의 경우 war 파일을 만드는 것이다. (혹은 스프링부트처럼 웹 서버가 내장된 경우 jar 파일을 만드는 것이다) 그레이들 프로그램을 사용해 프로젝트 생성에서부터 시작해 최종 패키징까지 관리하는 것이다.

그레이들 자체는 독립적인 프로그램이다. 공식 홈페이지에서 다운받아 설치하면 위와 같이 콘솔 환경에서 명령어를 통해 프로젝트를 생성하거나, 특정 태스크를 실행할 수 있다. 그러나 나는 이렇게 독립적으로 그레이들을 사용할 수 있다는 감각만을 유지할 뿐, 대부분의 상황에서는 아래처럼 인텔리제이와 같은 IDE로 빌드 과정을 컨트롤한다.

 

여하튼, 그레이들을 사용해 프로젝트를 생성하는 경우, 아래와 같은 폴더 구조가 생성된다.

대부분의 파일들이 그레이들 프로그램을 사용하기 위한 파일들이며, 이 중에서도 주로 build.gradle 설정 파일을 주로 사용하므로 나머지에 대해서는 당장은 상세히 알지 않아도 될 것이다.

 

#1. build.gradle

필요한 부분만 설명해보면, 플러그인이란 (메이븐의 플러그인과 비슷한 개념인데) 외부에서 그레이들에 장착할 수 있는 작은 프로그램을 뜻한다. 프로젝트 빌드 시 플러그인을 다운받고, 그것을 사용해 프로젝트를 빌드하는 것이다. 

dependencies란 프로젝트 빌드 시 사용할 라이브러리를 명시하는 것이다. 그레이들은 이 부분을 읽고 Maven Repository에서 네트워크를 사용해 필요한 라이브러리를 다운받는다. 빌드 툴에서 가장 유용한 기능이 바로 이 라이브러리 다운 기능이다.

 

#. External Libraries

그레이들로 라이브러리를 다운받으면 그 라이브러리들(jar파일)은 내 컴퓨터의 어딘가에 저장된다. 그 상세한 위치는 아래와 같으나 이 경로를 굳이 외우는 것은 시간 낭비다. 그냥 로컬에 저장된다는 감각만 있으면 된다.

이렇게 로컬에 의존성 라이브러리를 저장하고 이것을 컴파일 시 클래스패스에 추가하면 import 문법이 잘 먹혀서 컴파일이 되는 것이다.

 

#. src 디렉토리

그레이들을 사용해 프로젝트를 생성하면 src가 기본으로 생성되고, 그 아래에는 main과 test 디렉토리로 구분된다. 이건 어떤 패키징을 하든 상관 없이 그레이들의 기본 소스코드 폴더 구조다.

main은 개발자가 작성하는 소스코드가 놓이는 곳이고, test는 작성한 객체를 가지고 테스트 코드를 만들 수 있는 곳이다. 

main과 test 코드 내부는 생긴 것이 똑같다. (webapp은 war 패키징을 위한 독특한 구조일 뿐이다. 따라서 스프링부트와 같이 jar 패키징을 하는 프로젝트는 wabapp 디렉토리가 없다.) 크게보면 java와 resources 디렉토리 두 개로 구성되며 main과 test 디렉토리 모두 같은 내부 구조다.

java 디렉토리가 바로 개발자가 작성하는 소스코드가 배치되는 곳이며, 프로젝트 개발 시 가장 자주 들어가는 곳이다. 사실상 이 java 폴더가 소스코드 패키지의 루트 폴더, 즉 클래스패스(classpath)다.

그런데 왜 resources 디렉토리가 있을까? resources 디렉토리의 정체는 사실 그레이들이 프로젝트를 컴파일하고 빌드할 때 클래스패스에 resources 디렉토리의 내부 폴더과 파일을 그대로 삽입하는 것에 불과하다.

즉, resources 디렉토리의 mappers 폴더나 log4j2.xml 같은 파일이 클래스패스에 그대로 놓인다는 것이다.

java 디렉토리와 resources 디렉토리가 구분되는 이유는 순수한 소스코드와 라이브러리가 클래스패스 경로에서 참조하는 외부 파일을 구분해 편의를 갖추기 위해서다.

자바의 많은 라이브러리들이 클래스패스를 기준으로 외부에서 파일을 참조해 실행되는 경우가 잦다. 그때 resources 디렉토리에 그 파일들을 배치하는 것이다.

※ 참고로 lombok의 설정 파일은 정작 java 디렉토리에 배치했는데, 그 이유는 롬복의 특징 때문이다. 롬복은 런타임이 아니라 컴파일 단계에서 외부 파일을 참조한다. 애초에 롬복이란 컴파일 전에 어노테이션 프로세서를 통해 소스코드를 편집하는 문서 편집 기능이다. 따라서 빌드 되기 전에 설정 파일을 요구하기 때문에 빌드되고 난 후에 클래스패스에 배치되는 resources 디렉토리에 설정 파일을 두지 않는 것이다.

 

#. webapp

webapp은 war 플러그인을 통해 최종적으로 war로 빌드할 때 필요한 폴더다. 그레이들로 war 패키지를 만들어 빌드하면 바로 이 webapp 폴더가 war 파일이 된다고 보면 된다. 

웹 프로젝트 개발 시 이 폴더의 구조를 토대로 톰캣이 상호작용한다고 가정하면 된다. 

물론 실제로 빌드를 하면 아래와 같은 구조의 war가 만들어진다. classes, 그리고 lib 폴더가 포함된다.

classes 디렉토리에는 바로 main의 java와 resources가 합쳐진 클래스들이 들어가게 된다. 아래처럼 java와 resources의 파일들이 한데 묶여 놓이게 되고, 바로 이 classes가 실제 실행 시 클래스패스가 된다.

lib 디렉토리에는 내가 build.gradle의 dependencies로 로컬에 다운받은 외부 라이브러리들이 들어가 함께 배포된다.

이렇게 lib에 라이브러리를 추가해야만 내가 작성한 코드가 실행되어 의존하는 객체를 호출할 때 그 라이브러리를 사용할 수 있을 것이다. 예를 들어, 기껏 ModelMapper 라이브러리를 다운 받아 소스코드를 작성하고 컴파일했는데, 정작 배포될 때 그 라이브러리가 없다면 사용 자체가 불가능할 것이다.

여기서 중요한 점은 모든 라이브러리를 war에 배포할 필요는 없다는 것이다.

 

#. 배포되어야 할 라이브러리와 아닌 것들

실제 build.gradle의 dependencies 항목들을 보면 compileOnly, testRuntimeOnly 의 키워드로 잡힌 항목들을 발견할 수 있다. 이것들은 함께 배포되지 않는다. 왜냐하면 실제 배포된 환경에서는 더 이상 필요없는 라이브러리이기 때문이다.

예를 들어, servlet-api 라이브러리가 compileOnly 키워드로 설정되었기 때문에 war에 배포되지 않는다. 오직 컴파일 단계에서만 로컬에 다운 받은 이 라이브러리가 사용될 뿐이다. 그 이유는 servlet-api 라이브러리는 실제 배포된 톰캣 프로그램이 내장하고 있기 때문이다.

그리고 junit 라이브러리들도 그렇다. 이것들은 빌드 시 test 단계에서 사용될 라이브러리이기 때문이다. 이미 테스트가 끝나 빌드하고 배포한 코드에 굳이 테스트 라이브러리가 있어야 할 이유가 어디 있겠는가?

lombok도 그렇다. 롬복은 컴파일 단계에서 어노테이션 프로세서를 통해 소스코드를 편집해주고, 자바 컴파일러는 이 소스코드 문서를 컴파일한다. 여기서 롬복의 역할은 끝이다. 왜 굳이 war에 함께 배포되어야 하겠는가.

그러나 spring framework 라이브러리들은 implementation 키워드로 설정되어 함께 war에 배포된다. 즉, 위에서 본 lib 폴더 안에 배치된다. 왜냐하면 톰캣이 해당 라이브러리를 런타임에서 실행해야 하기 때문이다.

 

 


2. 결론

그레이들이 프로젝트를 빌드하고 배포하는 구조를 파악하면 소스코드, 외부 파일들을 어떻게 어디에 배치할지, 그리고 왜 이 파일은 여기에 있어야 하는지에 대한 감각을 익힐 수 있다.

인텔리제이와 같은 IDE 환경에서는 순식간에 빌드를 하고 톰캣을 실행해 그 결과를 볼 수 있기 때문에 편의성은 굉장히 높지만, 그 빌드 과정과 결과가 당장 눈에 보이지 않아 실행 원리를 파악하는 것이 어려워진다.