Maven Lifecycle (+ Gradle, Ant)
TIL

Maven Lifecycle (+ Gradle, Ant)

Intellij에서 우측의 maven을 클릭하면 clean, validate.. 등 다양한 버튼이 있다.
각각이 어떤 행위를 하는 버튼인지 알려면 maven의 lifecycle을 이해해야한다.

 

Maven

Maven이란 빌드도구다. 소스코드를 컴파일, 테스트, 정적 분석 등을 실시해서 실행 가능한 애플리케이션으로 생성을 도와주고 계속해서 늘어나는 라이브러리를 자동 추가 및 관리해주는 도구다. 만약 우리가 빌드도구를 사용하지 않고 빌드를 한다면 개발하면서 필요한 다양한 라이브러리들을 직접 다운로드하고 관리하기 어려울 것이고 빌드 프로세스를 수동으로 호출할 때 어떤 순서로 하고 무엇을 빌드할지 파악하기 어려울 것이다. 이를 도와주는 빌드도구 중 하나가 maven이다.

 

Maven으로 프로젝트를 생성하면 아래와 같이 폴더구조가 생성된다.

여기서 생성되는 pom.xml 파일을 통해 XML 문법으로 우리는 의존성을 관리해줄 수 있다.

Maven으로 생성한 패키지 구조

 

 

Maven에 도입된 lifecycle이라는 개념은 빌드 순서를 의미하는데 각 빌드 단계를 phase라고 하고, 각 phase들은 의존 관계를 가지고 있다.

Maven 빌드 과정

큰 틀은 default(기본, 프로젝트 빌드 및 배포), clean(이전 빌드에서 생성된 파일들을 삭제), site(프로젝트 사이트를 생성) 이고 각각은 세부적인 Phase들로 이루어져있다.

세부적인 phase는 위에서 언급했던 intellij에 나와있는 maven의 lifecycle을 기반으로 일부 나열하면 다음과 같다.

No Phase 내용
1 Clean 빌드 시 생성되었던 Output 및 파일들을 지워주는 단계
2 Validate 프로젝트가 올바른지 확인하고 필요한 모든 정보를 사용할 수 있는지 확인하는 단계
3 Compile 프로젝트의 소스 코드를 컴파일 하는 단계
4 Test 단위테스트를 수행하는 단계(테스트 실패 시 빌드 실패로 처리, 스킵 가능)
5 Package 실제 컴파일 된 소스 코드와 리소스들을 jar 파일 등의 배포를 위한 패키지로 만드는 단계
6 Verify 패키지의 유효성을 검사하고, 품질 기준을 만족하는지 체크하는 단계
7 Install 패키지를 로컬 저장소에 설치하는 단계 (다른 로컬 프로젝트에서 참조할 수 있도록)
8 Site 프로젝트 문서와 사이트 작성, 생성하는 단계
9 Deploy 만들어진 package를 원격 저장소에 release하는 단계 (다른 개발자나 프로젝트에서 참조할 수 있도록)

 

우리는 이러한 phase들을 다음과 같은 명령어로 실행해 볼 수 있다.

maven phase

단, 주의해야 할 점이 phase를 지정해서 위 구문을 실행하면 이전 phase까지 모두 실행된다.

 

 

이렇게 실행하는 phase는 plugin:goal에 지정된다. 다시말해 maven은 모든 기능들을 plugin 단위로 제공하는데, plugin은 각자 N개의 goal이라는 기능을 제공한다. 그리고 일부 goal들은 phase에 바인딩된다. maven의 goal을 좀 더 사용하기 편하게 추상화한 작업단계가 phase라고 생각하면 된다. 이 바인딩된 내용은 pom.xml을 통해 바꿀 수도 있고 phase에 goal 바인딩을 추가할 수도 있다.

 

만약 phase에 바인딩 되지 않은 goal을 실행하고 싶다면 다음과 같이 실행해야 한다.

mvn <plugin>:<goal>

반대로 goal이 없는 phase도 있다. (validate, initialize, verify..) 이들은 독립적으로 실행되지 않고 lifecycle을 통해서만 실행된다.

 

Maven의 다양한 plugin에 대해 알아보고 싶다면 다음 사이트에서 각 plugin을 눌러서 상세 내용과 goal을 확인할 수 있다.

 

Intellij에서 Maven의 lifecycle, plugin, dependency을 띄워볼 수 있다.

Intellij에서 확인한 maven 정보

 

Lifecycle 중 몇개만 실행해보자.

 

Lifecycle -> Clean을 실행해보면 다음과 같이 출력된다.

Deleting C:/Users.../target 부분에서 알 수 있듯이 target 폴더의 파일이 모두 지워진다.

target 폴더는 maven으로 빌드를 하면 프로젝트의 결과물인 jar 파일이 생성되는 폴더로, 나중에 실서버에 배포시 해당 jar파일로 배포하게 된다. 따라서 커밋대상이 아니고, maven build를 하면 생성되게 된다.

Clean 실행

Lifecycle -> Compile을 실행해보면 다음과 같이 출력된다.

Compile 실행

그리고 다음과 같이 target 폴더에 컴파일된 파일이 생성된다.

 

Lifecycle -> Package를 하면 다음과 같이 이전의 단계들과 함께 실행된다.

"C:\Program Files\Java\jdk-11\bin\java.exe" "-Dmaven.multiModuleProjectDirectory=C:\Users\admin\Downloads\demo (4)\demo" "-Dmaven.home=C:\Program Files\JetBrains\IntelliJ IDEA Community Edition 2020.3.3\plugins\maven\lib\maven3" "-Dclassworlds.conf=C:\Program Files\JetBrains\IntelliJ IDEA Community Edition 2020.3.3\plugins\maven\lib\maven3\bin\m2.conf" "-Dmaven.ext.class.path=C:\Program Files\JetBrains\IntelliJ IDEA Community Edition 2020.3.3\plugins\maven\lib\maven-event-listener.jar" "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA Community Edition 2020.3.3\lib\idea_rt.jar=50635:C:\Program Files\JetBrains\IntelliJ IDEA Community Edition 2020.3.3\bin" -Dfile.encoding=UTF-8 -classpath "C:\Program Files\JetBrains\IntelliJ IDEA Community Edition 2020.3.3\plugins\maven\lib\maven3\boot\plexus-classworlds-2.6.0.jar;C:\Program Files\JetBrains\IntelliJ IDEA Community Edition 2020.3.3\plugins\maven\lib\maven3\boot\plexus-classworlds.license" org.codehaus.classworlds.Launcher -Didea.version=2020.3.3 package
[INFO] Scanning for projects...
[INFO] 
[INFO] --------------------------< com.example:demo >--------------------------
[INFO] Building demo 0.0.1-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO] 
[INFO] --- maven-resources-plugin:3.2.0:resources (default-resources) @ demo ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Using 'UTF-8' encoding to copy filtered properties files.
[INFO] Copying 1 resource
[INFO] Copying 0 resource
[INFO] 
[INFO] --- maven-compiler-plugin:3.10.1:compile (default-compile) @ demo ---
[INFO] Nothing to compile - all classes are up to date
[INFO] 
[INFO] --- maven-resources-plugin:3.2.0:testResources (default-testResources) @ demo ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Using 'UTF-8' encoding to copy filtered properties files.
[INFO] skip non existing resourceDirectory C:\Users\admin\Downloads\demo (4)\demo\src\test\resources
[INFO] 
[INFO] --- maven-compiler-plugin:3.10.1:testCompile (default-testCompile) @ demo ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 1 source file to C:\Users\admin\Downloads\demo (4)\demo\target\test-classes
[INFO] 
[INFO] --- maven-surefire-plugin:2.22.2:test (default-test) @ demo ---
[INFO] 
[INFO] -------------------------------------------------------
[INFO]  T E S T S
[INFO] -------------------------------------------------------
[INFO] Running com.example.demo.DemoApplicationTests
12:38:37.048 [main] DEBUG org.springframework.test.context.BootstrapUtils - Instantiating CacheAwareContextLoaderDelegate from class [org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate]
12:38:37.057 [main] DEBUG org.springframework.test.context.BootstrapUtils - Instantiating BootstrapContext using constructor [public org.springframework.test.context.support.DefaultBootstrapContext(java.lang.Class,org.springframework.test.context.CacheAwareContextLoaderDelegate)]
12:38:37.085 [main] DEBUG org.springframework.test.context.BootstrapUtils - Instantiating TestContextBootstrapper for test class [com.example.demo.DemoApplicationTests] from class [org.springframework.boot.test.context.SpringBootTestContextBootstrapper]
12:38:37.094 [main] INFO org.springframework.boot.test.context.SpringBootTestContextBootstrapper - Neither @ContextConfiguration nor @ContextHierarchy found for test class [com.example.demo.DemoApplicationTests], using SpringBootContextLoader
12:38:37.098 [main] DEBUG org.springframework.test.context.support.AbstractContextLoader - Did not detect default resource location for test class [com.example.demo.DemoApplicationTests]: class path resource [com/example/demo/DemoApplicationTests-context.xml] does not exist
12:38:37.098 [main] DEBUG org.springframework.test.context.support.AbstractContextLoader - Did not detect default resource location for test class [com.example.demo.DemoApplicationTests]: class path resource [com/example/demo/DemoApplicationTestsContext.groovy] does not exist
12:38:37.098 [main] INFO org.springframework.test.context.support.AbstractContextLoader - Could not detect default resource locations for test class [com.example.demo.DemoApplicationTests]: no resource found for suffixes {-context.xml, Context.groovy}.
12:38:37.099 [main] INFO org.springframework.test.context.support.AnnotationConfigContextLoaderUtils - Could not detect default configuration classes for test class [com.example.demo.DemoApplicationTests]: DemoApplicationTests does not declare any static, non-private, non-final, nested classes annotated with @Configuration.
12:38:37.132 [main] DEBUG org.springframework.test.context.support.ActiveProfilesUtils - Could not find an 'annotation declaring class' for annotation type [org.springframework.test.context.ActiveProfiles] and class [com.example.demo.DemoApplicationTests]
12:38:37.184 [main] DEBUG org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider - Identified candidate component class: file [C:\Users\admin\Downloads\demo (4)\demo\target\classes\com\example\demo\DemoApplication.class]
12:38:37.186 [main] INFO org.springframework.boot.test.context.SpringBootTestContextBootstrapper - Found @SpringBootConfiguration com.example.demo.DemoApplication for test class com.example.demo.DemoApplicationTests
12:38:37.273 [main] DEBUG org.springframework.boot.test.context.SpringBootTestContextBootstrapper - @TestExecutionListeners is not present for class [com.example.demo.DemoApplicationTests]: using defaults.
12:38:37.274 [main] INFO org.springframework.boot.test.context.SpringBootTestContextBootstrapper - Loaded default TestExecutionListener class names from location [META-INF/spring.factories]: [org.springframework.boot.test.mock.mockito.MockitoTestExecutionListener, org.springframework.boot.test.mock.mockito.ResetMocksTestExecutionListener, org.springframework.boot.test.autoconfigure.restdocs.RestDocsTestExecutionListener, org.springframework.boot.test.autoconfigure.web.client.MockRestServiceServerResetTestExecutionListener, org.springframework.boot.test.autoconfigure.web.servlet.MockMvcPrintOnlyOnFailureTestExecutionListener, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverTestExecutionListener, org.springframework.boot.test.autoconfigure.webservices.client.MockWebServiceServerTestExecutionListener, org.springframework.test.context.web.ServletTestExecutionListener, org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener, org.springframework.test.context.event.ApplicationEventsTestExecutionListener, org.springframework.test.context.support.DependencyInjectionTestExecutionListener, org.springframework.test.context.support.DirtiesContextTestExecutionListener, org.springframework.test.context.transaction.TransactionalTestExecutionListener, org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener, org.springframework.test.context.event.EventPublishingTestExecutionListener]
12:38:37.286 [main] DEBUG org.springframework.boot.test.context.SpringBootTestContextBootstrapper - Skipping candidate TestExecutionListener [org.springframework.test.context.transaction.TransactionalTestExecutionListener] due to a missing dependency. Specify custom listener classes or make the default listener classes and their required dependencies available. Offending class: [org/springframework/transaction/interceptor/TransactionAttributeSource]
12:38:37.286 [main] DEBUG org.springframework.boot.test.context.SpringBootTestContextBootstrapper - Skipping candidate TestExecutionListener [org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener] due to a missing dependency. Specify custom listener classes or make the default listener classes and their required dependencies available. Offending class: [org/springframework/transaction/interceptor/TransactionAttribute]
12:38:37.286 [main] INFO org.springframework.boot.test.context.SpringBootTestContextBootstrapper - Using TestExecutionListeners: [org.springframework.test.context.web.ServletTestExecutionListener@13e547a9, org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener@3fb6cf60, org.springframework.test.context.event.ApplicationEventsTestExecutionListener@37ddb69a, org.springframework.boot.test.mock.mockito.MockitoTestExecutionListener@349c1daf, org.springframework.boot.test.autoconfigure.SpringBootDependencyInjectionTestExecutionListener@dfddc9a, org.springframework.test.context.support.DirtiesContextTestExecutionListener@4b9df8a, org.springframework.test.context.event.EventPublishingTestExecutionListener@5e8ac0e1, org.springframework.boot.test.mock.mockito.ResetMocksTestExecutionListener@aafcffa, org.springframework.boot.test.autoconfigure.restdocs.RestDocsTestExecutionListener@6955cb39, org.springframework.boot.test.autoconfigure.web.client.MockRestServiceServerResetTestExecutionListener@235a0c16, org.springframework.boot.test.autoconfigure.web.servlet.MockMvcPrintOnlyOnFailureTestExecutionListener@2b5f4d54, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverTestExecutionListener@5f7b97da, org.springframework.boot.test.autoconfigure.webservices.client.MockWebServiceServerTestExecutionListener@18b0930f]
12:38:37.289 [main] DEBUG org.springframework.test.context.support.AbstractDirtiesContextTestExecutionListener - Before test class: context [DefaultTestContext@1755e85b testClass = DemoApplicationTests, testInstance = [null], testMethod = [null], testException = [null], mergedContextConfiguration = [WebMergedContextConfiguration@736d6a5c testClass = DemoApplicationTests, locations = '{}', classes = '{class com.example.demo.DemoApplication}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true}', contextCustomizers = set[org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@6b0d80ed, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@15f47664, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.web.client.TestRestTemplateContextCustomizer@93081b6, org.springframework.boot.test.autoconfigure.actuate.metrics.MetricsExportContextCustomizerFactory$DisableMetricExportContextCustomizer@205d38da, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@0, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@5f0fd5a0, org.springframework.boot.test.context.SpringBootTestArgs@1, org.springframework.boot.test.context.SpringBootTestWebEnvironment@34b7ac2f], resourceBasePath = 'src/main/webapp', contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]], attributes = map['org.springframework.test.context.web.ServletTestExecutionListener.activateListener' -> true]], class annotated with @DirtiesContext [false] with mode [null].

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::               (v2.7.17)

2023-10-22 12:38:37.561  INFO 7384 --- [           main] com.example.demo.DemoApplicationTests    : Starting DemoApplicationTests using Java 11 on DESKTOP-THAI4MF with PID 7384 (started by admin in C:\Users\admin\Downloads\demo (4)\demo)
2023-10-22 12:38:37.562  INFO 7384 --- [           main] com.example.demo.DemoApplicationTests    : No active profile set, falling back to 1 default profile: "default"
2023-10-22 12:38:38.734  INFO 7384 --- [           main] com.example.demo.DemoApplicationTests    : Started DemoApplicationTests in 1.407 seconds (JVM running for 2.196)
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 2.058 s - in com.example.demo.DemoApplicationTests
[INFO] 
[INFO] Results:
[INFO] 
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
[INFO] 
[INFO] 
[INFO] --- maven-jar-plugin:3.2.2:jar (default-jar) @ demo ---
[INFO] Building jar: C:\Users\admin\Downloads\demo (4)\demo\target\demo-0.0.1-SNAPSHOT.jar
[INFO] 
[INFO] --- spring-boot-maven-plugin:2.7.17:repackage (repackage) @ demo ---
[INFO] Replacing main artifact with repackaged archive
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  6.350 s
[INFO] Finished at: 2023-10-22T12:38:40+09:00
[INFO] ------------------------------------------------------------------------

Process finished with exit code 0

그리고 target 폴더에 생성된 jar파일이 추가된다.

 

 

마지막으로 maven의 전체적인 특징을 정리하면 다음과 같다.

  1. 규칙성: 규칙에 의존하고 사전 정의된 목표를 제공한다.
  2. 정해진 라이프사이클에 의해 작업을 수행하며, 전반적인 프로젝트 관리 기능을 포함하고 있다.
  3. 종속성 관리를 해준다.
  4. 엄격한 프로젝트 구조를 규정하여 Maven에서 지원하지 않는 빌드 과정을 추가해야 하는 경우 상당히 복잡해진다.

 


추가) Gradle

최근에는 Ant와 Maven의 장점을 모아 2012년에 출시된 Gradle을 많이 사용한다. (안드로이드 프로젝트의 표준 빌드 시스템에도 Gradle이 채택되어있다.)

Gradle은 Maven과는 다르게 아래와 같이 프로젝트 구조가 생성된다.

Gradle로 생성된 프로젝트 구조

Maven에서는 pom.xml에 의존성을 작성해줬다면, Gradle에서는 Groovy로 작성된 build.gradle파일을 사용한다.

// build.gradle 파일 일부
dependencies {
	implementation 'org.springframework.boot:spring-boot-starter'
	implementation 'org.springframework.boot:spring-boot-starter-web'
	implementation 'javax.inject:javax.inject:1'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
	compileOnly 'org.projectlombok:lombok'
	annotationProcessor 'org.projectlombok:lombok'
	implementation 'org.springframework.boot:spring-boot-starter-jdbc'
	runtimeOnly 'com.h2database:h2'
}

 

Gradle의 빌드 과정은 다음과 같다. (Gradle LifeCycle)

No Phase 내용
1 Initialization 빌드 대상 프로젝트를 결정하고 각각에 대한 Project 객체를 생성하는 단계
2 Configuration 빌드 대상이 되는 모든 프로젝트의 빌드 스크립트를 실행하는 단계
3 Execution 구성 단계에서 생성하고 설정된 프로젝트의 테스크 중에 실행 대상을 결정하는 단계

 

특징

  1. 기존의 다른 빌드 도구들에서 사용하던 XML 문법이 아닌 Groovy 문법으로 스크립트를 작성해 XML보다 가독성이 좋고 간결한 스크립트를 작성할 수 있다.
  2. Gradle은 캐싱을 하기 때문에 Ant나 Maven보다 빌드 속도가 빠르다. Gradle vs Maven Comparison 에서는 이를 통해 Gradle이 Maven보다 빌드 속도가 최대 100배까지 벌어질 수 있다고 서술하고 있다.
  3. Gradle에는 멀티 프로젝트 빌드 기능이 있어 다중 모듈을 따로 빌드했을 때 일어날 수 있는 번거로움과 실수를 줄일 수 있다.
  4. Wrapper를 활용하여 Gradle이 설치되어있지 않은 환경에서도 사용 가능하다.

 


추가) Ant

Ant는 Maven과 Gradle 전에 가장 널리 사용되던 빌드 도구다. (Ant -> Maven -> Gradle 순서다.) 이클립스에 기본적으로 탑재되었고 Maven과 동일하게 XML 스크립트를 기반으로 작성되었다. 하지만 최근의 빌드 도구들과는 다르게 자동으로 라이브러리를 업데이트하는 기능이 없었기 때문에(Apache Ivy 출시 전 기준) 현재는 잘 사용되지 않는다.

 

특징만 간단하게 알아보자.

  1. 유연성: 어떠한 코딩 규칙이나 프로젝트 구조를 강조하지 않는다.
  2. Ant의 하위 프로젝트인 Apache Ivy를 통해 의존성 관리가 가능하다.
  3. 유연성이 장점인만큼 개발자가 모든 명령을 스스로 작성해야 하며 Ivy 등장 전에는 의존성 관리가 되지 않아 개발자가 파악해야 했고 유지보수가 어렵다.

Reference

 

https://min-0.tistory.com/entry/JAVA-JDK-%EB%9E%80JRE-%EB%9E%80-%EB%B9%8C%EB%93%9C%EB%8F%84%EA%B5%AC%EB%9E%80-Ant%EB%9E%80-Gradle%EC%9D%B4%EB%9E%80-Maven%EC%9D%B4%EB%9E%80build-tools 

https://min-0.tistory.com/entry/%EB%B9%8C%EB%93%9C%EB%8F%84%EA%B5%ACBuild-Tools%EB%9E%80-Maven-Gradle-%EC%B0%A8%EC%9D%B4%EC%A0%90-%EC%9E%A5%EB%8B%A8%EC%A0%90spring-build-tools 

https://yozm.wishket.com/magazine/detail/1700/

https://doosicee.tistory.com/entry/JAVA%EC%9D%98-%EB%B9%8C%EB%93%9C%ED%88%B4%EC%97%90-%EB%8C%80%ED%95%B4-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90

https://sw-architect.tistory.com/17

 

 

728x90
반응형