Unit Test에서 AssertThat을 사용하자

Junit 4.4부터 assertThat 메서드가 추가됐다. 이 메서드는 hamcrest 라이브러리의 사용을 통합하며 assertions을 작성하는데 있어 더 나은 방법을 제공한다. hamcrest가 static 메서드로 제공하는 여러 matcher를 사용할 수 있고 이러한 static 메서드는 체이닝할 수 있어서 기존 assertXXX 메서드보다 더 많은 유연성을 제공한다. 그 외에도 assertThat을 사용했을 때 어떤 이점이 있는지 알아보자.

자세히 보기

Micrometer

Micrometer란?

micrometer.io에서는 Micrometer에 대해서 다음과 같이 소개하고 있다.

Micrometer provides a simple facade over the instrumentation clients for the most popular monitoring systems, allowing you to instrument your JVM-based application code without vendor lock-in. Think SLF4J, but for metrics

Micrometer는 JVM 기반의 애플리케이션에서 다양한 모니터링 도구가 제공하는 클라이언트 라이브러리에 대한 facade를 제공한다. 로깅 관련된 시스템에서는 SLF4J가 있다면 모니터링(metric) 시스템에서는 Micrometer가 있는 것이다.

즉, 모니터링 시스템을 만드는 vendor들은 Micrometer 인터페이스를 따르기 때문에 Micrometer를 사용하면 애플리케이션 내의 코드 상에서는 모니터링 시스템 클라이언트로 어떤 것을 사용할지에 대한 고민에서 벗어나 Micrometer를 이용해 애플리케이션 metric을 수집하기만 하면 된다.
(모니터링 시스템을 선택하는 것은 런타임 시점에 정해진다고 생각하면 된다.)

자세히 보기
스">Iterator 인터페이스

Iterator 인터페이스는 요소를 하나씩 나열하면서 루프 변수와 같은 역할을 수행합니다.

1
2
3
4
public interface Iterator() {
boolean hasNext();
Object next();
}

hasNext 메소드는 다음 요소가 존재하는지를 조사하기 위한 메소드입니다. 다음 요소가 존재하면 true를 반환하고, 다음 요소가 존재하지 않는 마지막 요소라면 false를 반환합니다. 즉, hasNext는 루프의 종료 조건으로 사용됩니다.

next 메소드는 집합체의 요소를 1개 반환합니다. 또한 next 메소드를 호출했을 때 다음 요소를 반환하도록 내부 상태를 다음으로 진행시켜 두는 역할도 함께합니다.

Book 클래스

Book 클래스는 책을 나타내는 클래스입니다.

1
2
3
4
5
6
7
8
9
10
11
public class Book {
private String name;

public Book(String name) {
this.name = name;
}

public String getName() {
return name;
}
}

BookShelf 클래스

BookShelf 클래스는 책장을 나타내는 클래스입니다. 이 클래스를 집합체로 다루기 위해 Aggregate 인터페이스를 구현합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class BookShelf implements Aggregate {
private Book[] books;
private int last = 0;

public BookShelf(int maxsize) {
this.books = new Book[maxsize];
}

public Book getBookAt(int index) {
return books[index];
}

public void appendBook(Book book) {
this.books[last] = book;
last++;
}

public int getLength() {
return last;
}

@Override
public Iterator iterator() {
return new BookShelfIterator(this);
}
}

iterator 메소드는 BookShelf 클래스에 대응하는 Iterator로서, BookShelfIterator라는 클래스의 인스턴스를 생성해서 그것을 반환합니다.

BookShelfIterator 클래스

BookshelfIterator 를 Iterator로서 다루기 위해 Iterator 인터페이스를 구현합니다. bookShelf 필드는 BookShelfIterator가 검색할 책장이고, index 필드는 현재 가리키는 책을 가리키는 첨자입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
 public class BookShelfIterator implements Iterator {
private BookShelf bookShelf;
private int index;

public BookShelfIterator(BookShelf bookShelf) {
this.bookShelf = bookShelf;
this.index = 0;
}

@Override
public boolean hasNext() {
return index < bookShelf.getLength();
}

@Override
public Object next() {
Book book = bookShelf.getBookAt(index);
index++;
return book;
}
}

Main 클래스

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Main {
public static void main(String[] args) {
BookShelf bookShelf = new BookShelf(4);
bookShelf.appendBook(new Book("Effective Java"));
bookShelf.appendBook(new Book("Head First Java"));
bookShelf.appendBook(new Book("Thinking In Java"));
bookShelf.appendBook(new Book("Agile Java"));

Iterator it = bookShelf.iterator();
while (it.hasNext()) {
Book book = (Book) it.next();
System.out.println(book.getName());
}
}
}
1
2
3
4
5
// 실행결과
Effective Java
Head First Java
Thinking In Java
Agile Java

Iterator 패턴의 구성요소

사고 넓히기

구현에 상관 없이 Iterator를 사용할 수 있다.

1
2
3
4
while (it.hasNext()) {
Book book = (Book) it.next();
System.out.println(book.getName());
}

여기서 사용되고 있는 것은 hasNext와 next라는 Iterator의 메소드 뿐입니다. BooShelf의 구현에서 사용되고 있는 메소드는 호출되고 있지 않습니다. 결국 위 코드의 while 루프는 BookShelf의 구현에 의존하지 않습니다.

그렇기 때문에 현재 배열을 사용해 구현하고 있는 BookShelf를 List를 사용하도록 수정해도, 위의 while 루프는 전혀 변경하지 않아도 동작합니다.

Aggregate와 Iterator의 대응

BookShelfIterator는 BookShelf가 어떻게 구현되고 있는지 알기 때문에, ‘다음 책’을 얻기 위해 getBookAt 메소드를 호출할 수 있었습니다.

만약 BookShelf의 구현을 전부 변경하고, getBookAt 메소드라는 인터페이스(API)도 변경된다면 BookShelfIterator의 수정이 필요하게 됩니다.

그레이들 기초

그레이들

그루비와 그레이들

그루비 는 자바 가상머신에서 동작하는 오픈 소스 스크립트 언어이다. 그루비는 자바 문법을 더욱 쉽게 쓰기 위해 스크립트 언어와 비슷한 문법으로 되어 있어서, 대부분의 자바 프로그래머는 자바 코드를 작성하는 느낌으로 그루비를 작성할 수 있다. 동적 언어이며 자바와 달리 작성한 스크립트를 컴파일할 필요 없이 직접 실행할 수도 있다.

또한, 일부 자바 프레임워크에서는 그루비를 지원한다. 대표적인 스프링 프레임워크에서도 그루비를 이용할 수 있다.

이처럼 자바와 거의 같고, 스크립트 언어처럼 부담 없이 작성해서 바로 실행할 수 있는 특징을 고려하면, 그루비를 사용해서 자바 빌드 도구를 만들려는 생각은 자연스럽다.

그루비의 이러한 이점을 최대한 활용해서 개발한 빌드 도구가 그레이들 이다.

그레이들이란

그레이들은 그루비를 사용한 빌드 도구이다. 메이븐은 XML을 이용하여 빌드 정보를 기술했는데, 그레이들은 그루비를 이용해 빌드 정보를 기술하기 한다. 때문에 자바 프로그래머가 좀 더 쉽게 다룰 수 있다.

그레이들의 특징은 다음과 같다.

  • 유연한 언어로 기술
    그루비라는 프로그래밍 언어를 사용해서 기술하기 때문에 유연하게 각종 처리를 수행할 수 있다. 또한 기술하는 내용을 분할하거나 구조화하는 것도 간단하다.
  • 태스크로 처리
    그레이들은 ‘태스크’라는 개념을 이용해 프로그램을 작성한다. 다양한 용도별로 태스크를 만들어서 그 안에 처리를 기술한다.
  • 자바/그루비/스칼라 기본 지원 + 알파
    그레이들은 자바 가상 머신에서 동작하는 언어를 중심으로 지원한다. (별도의 네이티브 코드 플러그인을 사용하면 C/C++ 등, 다른 언어에도 대응할 수 있다.)
  • 각종 도구와 통합
    여러 도구들(앤트, 아파치 아이비 등)과 통합되어 처리를 실행할 수 있다. 또한, 메이븐의 pom.xml을 그레이들용으로 변환하는 도구도 있다.
  • 메이븐 중앙 저장소 대응
    그레이들에서는 메이븐 중앙 저장소를 지원하기 때문에, 중앙 저장소에 있는 라이브러리 모두 그대로 이용 가능하다.

그레이들 사용하기

그레이들 소프트웨어에는 그루비가 포함되어 있기 때문에, 단지 그레이들을 사용하는 용도라면 그루비를 설치할 필요는 없다. (그레이들을 설치하는 것만으로는 그루비 언어를 이용해서 프로그래밍할 수는 없다.)

그레이들 프로젝트 생성

그레이들 명령어를 사용해 프로젝트를 생성할 수 있다.

1
2
3
mkdir gradle-app
cd gradle-app
gradle init --type java-library

gradle init

그레이들 명령어는 gradle OO 형태로 실행한다. 위에서 프로젝트를 생성하며 사용한 init은 그레이들에서 '태스크' 라고 부른다. 이 init은 프로젝트의 기본적은 파일과 폴더를 생성한다.

–type은 생성할 프로젝트의 타입을 지정하는 옵션인데, java-library는 자바 프로젝트임을 나타낸다. 지정한 언어의 샘플 코드를 생성한다. 이 옵션을 생략하면 그레이들 프로젝트의 기본적인 파일들만 생성된다.

그레이들 실행과 태스크

그레이들에서는 다양한 처리를 위해 ‘태스크’를 이용한다. 태스크란, 실행할 처리를 모아놓은 단위로 그레이들에서 처음부터 포함된 것도 있고, 프로그래머가 작성할 수도 있다. 이 태스크를 실행하는 것이 그레이들에서 빌드를 관리하는 기본적인 방법이다. 프로젝트를 컴파일하거나, 실행하는 모든 처리에 태스크를 이용한다.

생성된 gradle-app 폴더 내부의 구조를 살펴보자.

  • .gradle 폴더 : 태스크로 생성된 파일 등을 보존한다.
  • gradle 폴더 : 기본값으로는 그레이들 환경을 모아놓은 wrapper 파일이라고 하는 파일들이 들어 있다.
  • src 폴더 : 소스 코드 관련 파일을 이곳에 작성한다.
  • build.gradle : 그레이들 빌드 파일, 이곳에 프로젝트의 빌드 내용을 기술한다.
  • settings.gradle : 빌드 설정 정보를 기술한 파일. 빌드를 실행하기 전에 읽히기 때문에, 필요한 라이브러리를 읽는 등의 기술을 할 수 있다.

이번에는 생성된 파일의 코드를 확인해보자.

build.gradle

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 자바 프로그램을 빌드할 경우에는 java 플러그인을 로드한다.
apply plugin: 'java'

// 저장소 정보를 관리하는 프로퍼티이다. 이곳에서 저장소를 설정할 수 있다. (로컬 환경이나 원격 저장소 기술)
// jcenter는 그레이들에서 중앙 저장소로 이용되는 저장소이다.
// 메이븐 중앙 저장소는 mavenCentral 메서드를 이용해서 사용할 수 있다.
repositories {
jcenter()
}

// 의존성에 관한 설정을 관리하는 프로퍼티이다. 필요한 라이브러리등의 정보를 기술한다.
dependencies {
compile 'org.slf4j:slf4j-api:1.7.21'
testCompile 'junit:junit:4.12'
}

settings.gradle

1
2
// 루트 프로젝트의 이름을 설정한다. 루트 프로젝트는 다수의 프로젝트를 관리할 때 기본이 되는 프로젝트를 가리킨다. 여기에서는 '이 빌드 파일로 빌드할 프로젝트의 이름'이라고 생각하면 된다.
rootProject.name = 'gradle-app'

인텔리제이에서 사용하기

인텔리제이는 표준으로 그레이들을 지원한다. 인텔리제이에서 그레이들용 프로젝트를 생성할 경우, gradle init –type java-library 명령어를 사용하는 경우와 결과가 조금 다르다.

build.gradle

1
2
3
4
5
6
7
8
9
10
11
12
13
14
group 'com.jongmin'
version '1.0-SNAPSHOT'

apply plugin: 'java'

sourceCompatibility = 1.8

repositories {
mavenCentral()
}

dependencies {
testCompile group: 'junit', name: 'junit', version: '4.12'
}

시작 부분에 그룹 ID와 버전을 지정하는 문장이 포함되어 있다. gradle init으로 작성한 build.gradle에서는 포함되지 않았는데, 그 이유는 메이븐 저장소를 이용하기 때문이다. 메이븐에는 모든 프로그램에 그룹 ID와 아티팩트 ID가 할당되어 있다.

sourceCompatibility는 자바 소스 코드의 버전을 가리킨다.

태스크 실행

메이븐과 마찬가지로 실행할 내용을 ‘Run…’ -> ‘Edit Configurations’ 메뉴에서 컨피그레이션에 설정하면 인텔리제이의 ‘Run’으로 프로그램의 빌드와 실행, 디버그 등의 기능을 수행할 수 있다.

build.gradle

그래이들은 build.gradle에 기술한 코드를 필요에 따라 실행하여 빌드를 실행한다.

그레이들은 ‘그루비를 사용하는 빌드 도구’이다. 하지만 이는 정확한 설명은 아니다. 그레이들은 ‘그루비 그 자체’는 아니고 **’그루비 기반의 DSL(Domain Specific Language)’**이다.

DSL은 ‘도메인 고유 언어’라고 불리는데, 특정한 용도에 한정된 언어를 말한다. 그 언어 그 자체는 아니고, 특정한 용도에 맞게 해당 언어를 기반으로 각색한 것이다. 그레이들에서 사용되는 언어는 ‘그루비를 기반으로 작성된 Gradle DLS’이다.

그레이들은 기본적으로 태스크를 작성하여 실행한다. 태스크는 실행할 처리를 모아서 명령어로서 실행할 수 있게 한 것이다. 다음과 같은 형식으로 정의한다.

1
2
3
task 이름{
...실행할 처리...
}

이렇게 정의한 태스크는 명령행에서 gradle 이름 형태로 실행할 수 있다.

간단한 예제를 작성해 실행해보자. 다음과 같이 build.gradle을 수정한다.

1
2
3
4
5
6
7
8
9
task hello {
doLast {
println();
println("=================");
println("Welcome to Gradle!");
println("=================");
println();
}
}

수정한 후, 명령행에서 다음과 같이 실행한다.

1
gradle hello

결과는 다음과 같다.

1
2
3
4
5
6
7
8
> Task :hello

=================
Welcome to Gradle!
=================

BUILD SUCCESSFUL in 2s
1 actionable task: 1 executed

그레이들은 기본적으로 빌드 도구이지만, 여기에서는 빌드도 컴파일도 하지 않고, 단지 메시지를 표시했다. 태스크는 작성된 처리를 실행할 뿐이지, 반드시 빌드와 관련된 기능이 포함되어야 하는 것은 아니다.

quiet 모드로 실행

이번에는 quiet 모드로 실행해보자.

1
gradle -q hello

결과는 다음과 같다.

1
2
3
=================
Welcome to Gradle!
=================

이렇게 하면 hello에서 println한 내용만 출력되며, 그 이외의 내용은 표시되지 않는다. -q 옵션은 태스크를 quiet 모드로 실행해 예외 발생 등 중요한 문제 이외의 표시가 제한된다. 태스크의 실행 결과만 알고 싶을 때 편리한 옵션이다.

액션 리스트

태스크는 다양한 ‘액션’을 내부에 가지고 있다. 태스크를 실행하면 준비된 액션이 순서대로 실행된다. 이 액션을 관리하는 것이 ‘액션 리스트’이다. 액션은 어떤 역할을 하는지가 정해져 있다. 필요에 따라 액션에 처리를 추가해 태스크를 조합할 수 있다.

액션 중에서도 doFirstdoLast가 가장 많이 사용된다. 각각 ‘처음에 실행되는 액션’과 ‘마지막에 실행되는 액션’이다. 이들을 이용해 태스크의 처음과 마지막에 처리를 실행할 수 있다.

매개변수 이용

태스크를 실행할 때 어떤 정보를 태스크에 전달하고 싶은 경우도 있다. 이럴 때 매개변수를 이용할 수 있다.

1
2
3
4
5
6
7
8
9
task hello {
doLast {
def total = 0;
for(def i in 1..num.toInteger()) {
total += i;
}
println("total: " + total);
}
}

1에서 num까지 합하는 태스크이다. 여기서 사용되는 num이 매개변수로 전달되는 프로퍼티이다. 이처럼 매개변수에서 전달되는 값을 프로퍼티로 사용할 수 있다. 단, 주의할 점은 값이 String이라는 것이다.

hello 태스크는 다음과 같이 실행한다.

1
gradle hello -q -Pnum=100

이처럼 태스크를 실행하는 동안에는 -P프로퍼티=값 형태로 특정 변수에 값을 전달할 수 있다.

동적 태스크 실행

스크립트를 사용해서 동적으로 태스크를 생성할 수도 있다.

1
2
3
4
5
6
7
8
def arr = ["one", "two", "three"];
arr.each {s ->
task "$s" {
doLast {
println("this is $s task.");
}
}
}

다음과 같이 실행한다.

1
gradle -q one

java 플러그인 사용하기

자바로 개발할 때 필요한 기본적인 기능은 ‘java’ 플러그인에 포함되어 있다. build.gradle에서 로드하여 이용한다.

1
apply plugin: 'java'

다음의 명령어로 빌드한다.

1
gradle java

위 명령어를 실행하면 프로젝트가 컴파일되고 JAR 파일이 생성된다. 컴파일로 생성된 파일들은 build 폴더에 보관된다. 이 안의 libs 폴더 안에 gradle-app.jar 파일이 생성된다.

build 폴더 안에는 classes 폴더도 있는데, 여기에는 컴파일된 클래스 파일이 보관된다. gradle java에서는 우선 소스 코드를 컴파일하여 클래스 파일을 작성한 후, 이를 모아서 JAR 파일로 만드는 일련의 처리가 자동으로 수행된다.

gradle java와 gradle build

grade java는 자바 프로그램의 빌드를 수행하지만, gradle build는 어떤 언어로 작성된 프로젝트라도 빌드한다. (그레이들은 자바 이외에도 그루비나 스칼라 등 많은 언어를 지원하고 각각의 언어에서 빌드를 수행하는 플러그인을 제공한다.)

java 플러그인의 태스크

java 플러그인에는 이 외에도 몇 가지 태스크가 더 있다.

  • java
    자바 소스 코드를 컴파일하고 그 외에 필요한 리소스 파일들을 모아서 JAR 파일을 생성한다. 프로그램을 배포할 때 이 태스크로 JAR 파일을 만들면 유용하다. 단, 이 java 태스크로 생성된 JAR 파일은 Executable이 아니라는 점에 주의해야 한다.
  • compileJava
    자바 소스 코드를 모두 컴파일한다. 보존할 장소(build 안의 classes 폴더)가 없다면 폴더를 자동으로 생성하고 그 안에 클래스 파일을 작성한다.
  • processResources
    리소스 파일을 클래스 디렉터리(classes 폴더) 안에 복사한다.
  • classes
    소스 코드 컴파일과 리소스 파일 복사를 실행한다. compileJava와 processResources가 합쳐진 것이라 생각해도 된다.
  • test
    프로그램 테스트를 실행한다. 소스 코드와 관련된 컴파일을 수행하고 테스트에 필요한 리소스 복사 등을 수행한 뒤 JUnit으로 테스트를 실행한다. JUnit 라이브러리를 이용할 수 없는 상태에서는 테스트를 실행할 때 오류가 발생한다.
  • jar
    프로그램을 컴파일하고 리소스 파일 등을 준비한 뒤, JAR 파일로 패키징한다. 단, 파일을 단순히 JAR 파일에 모을 뿐이며 Executable jar을 작성하는 것은 아니다.
  • javadoc
    소스 코드를 해석하여 Javadoc 파일을 생성한다. build 안의 docs 폴더 안에 javadoc 폴더를 작성하여 파일을 보관한다.
  • clean
    빌드로 생성된 파일을 모두 삭제한다.

java 플러그인의 태스크 이용하기

1
2
3
4
5
task doit(dependsOn: [compileJava, jar]) {
doLast {
println "*** compiled and created jar! ***"
}
}

doit 태스크에는 매개변수가 포함되어 있다. dependsOn은 이 매개변수가 ‘의존성’을 지정하는 것을 가리킨다. ‘compileJava, jar’은 2개의 태스크를 모아놓은 배열이다. 즉, doit이 compileJava와 jar이라는 2개의 태스크에 의존한다.

이 상태에서 dependsOn에서 태스크를 지정하면 그 태스크가 실행되기 전에 의존하는 모든 태스크가 실행된다. 그리고 의존하는 태스크의 실행이 완료된 후에 doit 태스크의 doLast가 호출된다.

의존성을 지정해서 실행하는 방법 이외에 태스크를 직접 실행할 수도 있는데, 태스크의 execute 메서드를 호출하면 된다.

1
2
3
4
5
6
7
8
task doit {
doLast {
println "*** compiled now! ***"
tasks.compileJava.execute()
println "*** create jar! ***"
tasks.jar.execute()
}
}

실행하면 의존성을 지정해서 실행할 때와 같은 결과를 얻지만, “Deprecated Gradle features were used in this build, making it incompatible with Gradle 5.0.” 라는 메시지가 출력된다. 그레이들 버전 5.0 부터는 execute 메서드를 직접 호출하는 것이 호환되지 않기 때문이다.

application 플러그인

java 플러그인에는 프로그램을 실행하는 태스크가 없어 application 플러그인을 사용해 애플리케이션을 실행해야 한다.

1
apply plugin: 'application"

build.gradle에 이처럼 작성하고 그 다음에 mainClassName 프로퍼티에 메인 클래스(실행할 클래스)를 설정한다.

1
mainClassName = "com.jongmin.gradle.App"

application 플러그인에는 run 태스크가 포함되어 있다. 이것을 실행하면 mainClassName에 지정된 클래스가 실행된다.

1
gradle run

다음과 같이 작성하면 jar을 실행한 후 run으로 클래스를 실행할 수 있다.

1
2
3
4
5
6
7
8
9
10
apply plugin: 'java'
apply plugin: 'application'

mainClassName = "com.jongmin.gradle.App"

task doit(dependsOn:[jar, run]) {
doLast {
println("*** do it! ***")
}
}

빌드 도구와 메이븐

빌드 도구

빌드 도구란?

개발 환경의 변화와 빌드

자바를 처음 공부할 때는 이클립스 혹은 인텔리제이 같은 IDE만으로 프로그램을 뚝딱 만들 수 있다. 그러나 현업에 투입되면 무언가 복잡한 환경을 만나게 된다.

하지만 실제 자바 입문시절에 배운 과정과 현업에서 사용하는 과정에 차이는 없다. 다만 다양한 도구를 사용하여 더 전문화하여 현업 도구에 활용할 뿐이다.

  • 명령행에서 컴파일하기
    아주 간단한 프로그램이라면 명령행에서 javac를 이용하는 것만으로도 충분하다. 하지만 라이브러리 등을 이용하면, classpath에 다수의 라이브러리 경로를 기술한 뒤에 컴파일 해야 한다. 또한 생성된 클래스 파일을 모아 JAR 파일을 생성하려면 이 역시 모두 명령어로 실행해야 한다.
    소스 코드의 컴파일에서 JAR 파일의 생성까지 긴 과정을 수행하려면 방대한 명령어가 필요한데, 이를 매번 수작업으로 작성하면 큰 수고가 든다.
  • 프로젝트 및 라이브러리 설치
    최근에는 개발할 때 모든 프로그램을 처음부터 만드는 경우가 거의 없다. 프로그램에 필요한 기능은 라이브러리를 이용하거나, 프레임워크를 이용하여 애플리케이션을 개발한다.
    이런 경우, 필요한 소프트웨어를 갖추고 정해진 대로 파일을 구성해야 한다.
  • 테스트 자동화
    단순한 프로그램이라면 컴파일 후 실행 및 동작만 확인하는 것으로 충분하지만, 어느 정도 규모 있는 프로그램은 프로그램 생성과 함께 테스트를 실행하는 것이 일반적이다.
  • 프로그램 배포
    웹 애플리케이션은 구현한 프로그램을 서버에 배포하게 된다. 이런 작업을 수작업으로 시행하기가 번거롭다.

빌드 도구의 역할

‘빌드 도구’는 단순히 프로그램을 컴파일하여 애플리케이션을 생성하는 작업 그 이상으로 다양한 기능을 제공한다.

  • 프로그램 빌드
    프로그램을 컴파일하고, 지정된 디렉터리에 필요한 리소스를 모아서 프로그램을 완성한다. 그때 라이브러리등 필요한 파일을 설치하도록 지정할 수 있다.
  • 프로그램 테스트와 실행
    빌드된 프로그램의 실행뿐 아니라 테스트 기능도 제공한다. 빌드를 실행 할 때, 빌드가 완료되면 곧바로 테스트를 실행하는 도구도 있다.
  • 라이브러리 관리
    프로그램에서 필요한 라이브러리들을 관리한다. 빌드 실행 시 자동으로 라이브러리를 다운로드하고 설치하는 등의 작업을 한다.
  • 배포 작업
    빌드한 프로그램을 배포하는 기능을 제공한다.

개발 도구와 빌드 도구

개발 도구에 있어 빌드 도구를 다루는 방식은 크게 두 가지이다.

  • 빌드 도구를 이용하는 기능이 포함된 경우
    이클립스는 메이븐, 인텔리제이는 메이븐과 그레이들을 지원한다.
  • 개발 도구에서 명령어로 실행하는 경우

메이븐 기초

메이븐은 아파치 소프트웨어 재단이 개발하는 오픈 소스 빌드 도구이다. ‘아파치 앤트(Ant)’의 후속으로 개발되었고, 자바 프로그램 개발을 대상으로 한 오픈 소스 빌드 도구이다.

메이븐 특징

  • 빌드 파일은 XML로 작성
  • 단위 작업 ‘골’
    골은 메이븐에서 실행하는 작업의 목적을 지정한다. 메이븐 명령어를 실행할 때 골을 지정하면, 어떤 작업을 수행하여 무엇을 작성할지 지정할 수 있다.
  • 라이브러리 관리와 중앙 저장소
    빌드를 실행하는 사이에, 빌드 파일에 기술된 정보를 바탕으로 필요한 라이브러리를 자동으로 다운로드하여 포함시킨다. 이를 가능하게 하는 것이 중앙 저장소이다. 중앙 저장소는 메이븐에서 이용 가능한 라이브러리를 모아서 관리하는 웹 서비스이다.
  • 테스트와 문서 생성
    엔트의 표준에는 포함되지 않았던 JUnit 테스트 및 Javadoc 문서 생성 등의 기능을 갖추고 있다.
  • 플러그인을 이용한 확장
    플러그인을 사용하면 메이븐에 기능을 추가할 수 있다.

메이븐 프로젝트 생성

메이븐에 포함된 **archetype:generate**라는 골을 이용하면, 간단하게 프로젝트의 기본 부분을 만들 수 있다. 아키타입(archetype)은 프로그램의 템플릿 모음이다.

Intellij를 이용해서도 메이븐을 기반으로 프로그램을 빌드하는 프로젝트를 생성할 수 있다. New Project -> 목록에서 Maven 선택 -> Create from archetype 체크 -> maven-archetype-quickstart 선택

메이븐에서는 mvn 명령어로 각종 조작을 할 수 있는데, 이 명령어들을 인텔리제이의 ‘Run’을 이용하여 실행할 수 있다. 실행할 내용을 컨피그레이션에 설정하면 인텔리제이의 ‘Run’으로 프로그램의 빌드와 실행, 디버그 등의 기능을 수행할 수 있다. (‘Run…’ -> Edit Configurations’ 메뉴에서 설정 가능)

pom.xml

pom.xml 파일에서 POM은 ‘Project Object Model’을 말한다. 이 파일에 프로젝트에 관한 각종 정보를 기술한다.

<project>와 기본속성

  • 모델 버전

    1
    <modeVersion>4.0.0</modeVersion>

    기본적으로 메이븐은 하위 호환성을 지원하기 때문에 이후 새로운 버전이 되더라도 이곳의 버전 번호를 바꾸면 이외 부분은 크게 수정하지 않고도 사용할 수 있다.

  • 그룹 ID

    1
    <groupId>com.jongmin</groupId>

    그룹 ID는 작성할 프로그램이 어디에 소속되어 있는지를 나타낸다.

  • 아티팩트 ID

    1
    <artifactId>mvn-app</artifactId>

    그룹 ID와 함께 프로그램을 식별하는 데 사용된다. ID이기 때문에 같은 그룹 내에서 같은 프로젝트 이름이 중복되지 않도록 주의해야 한다.

  • 버전

    1
    <version>1.0-SNAPSHOT</version>

    메이븐을 사용하는 프로젝트를 빌드하거나 패키징한 경우 여기서 지정된 번호가 생성된 프로그램의 버전으로 설정된다. 보통은 생성된 JAR 파일의 파일명에도 사용된다.

  • 패키지 종류

    1
    <packaging>jar</packaging>

    보통은 jar을 지정하지만, zip이라고 지정하면 ZIP 파일로 패키징한다.

  • 애플리케이션 이름

    1
    <name>mvn-app</name>

    작성하는 애플리케이션의 이름을 지정한다. 그룹 ID나 아티팩트 ID와 달리 유일한 값일 필요가 없다.

  • URL

    1
    <url>http://maven.apache.org</url>

    기본값으로는 메이븐 사이트의 URL이 지정되어 있다.

**<properties>**는 pom.xml에서 이용되는 속성값을 설정한다.

1
2
3
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

기본값으로는 <project.build.sourceEncoding>라는 항목이 설정되어 있는데 이는 소스 파일의 문자 인코딩 방식을 지정한다.

<dependencies>와 의존성 설정

dependencies 태크를 통해 필요한 라이브러리를 관리할 수 있다. 여기에 의존성을 적어두면 필요한 라이브러리 등을 자동으로 다운로드하여 설치 등을 할 수 있다.

<dependency> 태그를 설정하는 것만으로 의존 라이브러리가 자동으로 로드되는 것은 메이븐의 중앙 저장소 때문이다. 중앙 저장소는 메이븐을 개발한 아파치 소프트웨어 재단이 운영하는 사이트이다.

기본적인 ‘골’

메이븐은 골을 지정하여 실행할 처리의 역할을 정한다.

1
mvn 골
  • compile

    1
    mvn compile

    자바 소스 코드 파일을 컴파일 한다. 프로젝트 폴더 내에 target 폴더가 생성된다.

  • test-compile

    1
    mvn test-compile

    유닛 테스트용 클래스를 컴파일한다. src 폴더 안의 test 안에 작성된 유닛 테스트용 소스 코드 파일을 컴파일하여, target 폴더 안에 test-class 폴더를 작성하고 그 안에 클래스 파일을 생성한다.

  • test

    1
    mvn test

    메이븐은 테스트(유닛 테스트)가 거의 표준 기능으로 포함되어 있다. 테스트를 개별적으로 실행하는 골이 test이다.작성한 유닛 테스트용 클래스를 이용하여 테스트가 실행되고 그 결과가 출력된다.

  • package

    1
    mvn package

    mvn compile을 실행하면 클래스 파일이 생성되지만, 일반적으로 자바 프로그램은 클래스파일을 그대로 배포하지는 않는다. 일반적으로 JAR 파일 등으로 패키징하여 배포한다.

    명령 한 번으로 프로그램을 컴파일하여 유닛 테스트를 실행한 후 JAR 파일로 패키징하는 처리가 모두 자동적으로 수행된다.

    실행 후 target 폴더 안에 jar 파일이 생성된다.

  • clean

    1
    mvn clean

    메이븐은 프로그램을 빌드하면서 컴파일된 클래스 파일 뿐만 아니라, 테스트, 압축을 실행하는 파일 등을 만든다. clean은 부가적으로 생성된 파일을 모두 지운다.

프로그램 실행하기

클래스가 하나인 코드는 java 명령어로도 쉽게 실행할 수 있다. 그러나 다양한 라이브러리를 이용하는 프로젝트에서는 모든 클래스 경로를 직접 지정해야 하기 때문에 java 명령어를 이용해 실행하는 일은 번거롭다.

메이븐에는 표준으로 자바 프로그램을 실행하는 골은 없다. 하지만 exec-java-plugin 플러그인을 이용하면 프로그램을 실행할 수 있다.

pom.xml의 <project> 태그 안에 있는 <dependencies> 종료 태그의 다음 행에 다음과 같이 플러그인을 추가한다.

1
2
3
4
5
6
7
8
9
10
11
12
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.5.0</version>
<configuration>
<mainClass>com.jongmin.App</mainClass>
</configuration>
</plugin>
</plugins>
</build>

명령행에 다음과 같이 실행하면 App 클래스가 실행된다. 만약 mvn clean으로 프로젝트의 빌드 결과물을 제거한 경우 다시 mvn package로 빌드한 후에 실행한다.

1
mvn exec:java

exec-maven-plugin은 메인 클래스를 지정해야 한다. 플러그인에 정보를 지정할 때는 <configuration> 태그를 이용한다.

빌드 플러그인

<build> 태그는 빌드에 관한 정보를 기술하는 태그이다. 형태는 다음과 같다.

1
2
3
4
5
6
7
<build>
<plugins>
<plugin>...</plugin>
<plugin>...</plugin>
...
</plugins>
</build>

<build>와 <plugins> 태그는 여러 개 사용할 수 없다. 반드시 1개씩 있고, 그 안에 모든 <plugin>을 모아서 사용한다.

메이븐의 골과 플러그인

exec:java 골은 플러그인을 사용해 추가된 것이다. 사실, 지금까지 사용했던 모든 골들도 플러그인으로 추가된 것이다.

  • compile : maven-compiler-plugin
  • package : maven-jar-plugin
  • test : maven-surefire-plugin

하지만 위의 플러그인은 표준으로 포함되어 있기 때문에 플러그인이라고 의식하지 못했던 것이다.

표준이 아닌 <plugin> 태그에 의해 추가된 플러그인의 골을 지정하는 경우에는 xx:xx와 같이 요소가 둘인 경우가 일반적이다. 플러그인 하나가 여러 골을 가질 수도 있기 때문에 ‘플로그인:골’ 형태로 기술한다.

<plugin>이 필수는 아니다. 플러그인으로 추가하여 이용하는 골이라고 해서 <plugin>에 기술하지 않으면 사용하지 못하는 것은 아니다. <plugin>은 플러그인에 포함된 설정 등의 정보를 기술하는 태그이다. 그렇기 때문에 설정이 필요하지 않으면 기술할 필요가 없다.

인텔리제이에서 사용하기

플러그인을 통해 개발 도구의 프로젝트로 변환이 가능하다.

1
mvn idea:idea

위 골을 실행하면 인텔리제이에서 프로젝트를 다루는데 필요한 파일들이 생성된다.

1
mvn idea:clean

인텔리제이 프로젝트에서 인텔리제이 관련 파일을 삭제하여 원래의 메이븐 프로젝트로 돌리려면 위의 골을 실행한다.

실행 가능한 JAR 파일 만들기

앞서 mvn package로 패키징했지만 이렇게 생성된 JAR 파일은 단순히 패키징 된 것이기 때문에 실행되지는 않는다.

1
java -jar 00.jar

따라서 위의 명렁을 실행해도 00.jar에 기본 Manifest 속성이 없어 실행에 실패하게 된다.

실행 가능한 JAR 파일을 만들기 위해서는 maven-jar-plugin을 이용해 다음과 같은 <plugin> 태그를 작성하면 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>버전</version>
<configuration>
<archive>
<manifest>
<addClasspath>true</addClasspath>
<mainClass>메인 클래스</mainClass>
</manifest>
</archive>
</configuration>
</plugin>

<archive> 태그는 압축에 관한 설정이다. addClasspath는 클래스 경로에 JAR 파일이 있는 경로를 추가하기 위한 태그인데 보통은 true로 지정한다.

이렇게 설정 후 다시 mvn package로 JAR 파일을 생성한 후 java -jar로 실행해보면 문제없이 실행할 수 있다.

저장소 이용

<dependency> 를 추가하는 것만으로 필요한 라이브러리를 추가해 사용할 수 있었던 것은 중앙 저장소 때문이다. 그런데 저장소가 중앙 저장소만 있는 것은 아니다. 다른 원격 저장소나 로컬 저장소도 있다.

로컬 저장소

자신이 만든 라이브러린, 그다지 유명하지 않은 라이브러리라면 아직 중앙 저장소에 공개되지 않을 수도 있다. 이러한 라이브러리는 로컬 저장소를 이용해 사용할 수 있다.

  • 원격 저장소 : 네트워크를 거쳐 서버에 접속하여 이용하는 공개된 저장소. 중앙 저장소도 원격 저장소의 한 종류이다.
  • 로컬 저장소 : 로컬 환경에 있는 저장소이다.

원격 저장소 이용

원격 저장소는 pom.xml에 <repositories> 태그 안에 저장소 정보를 기술한다.

1
2
3
4
5
6
7
<repositories>
<repository>
<id>저장소 ID</id>
<name>이름</name>
<url>저장소 주소(URL)</url>
</repository>
</repositories>

로컬 저장소에 라이브러리 추가하기

추가하고자 하는 라이브러리 프로젝트에서 다음과 같이 실행하면 target에 빌드된 JAR 파일을 로컬 저장소에 설치한다.

1
mvn install

또는 설치할 JAR 파일이 별도로 준비되어 있다면 install:install-file 골을 실행해서 지정한 라이브러리 파일을 로컬 저장소에 설치할 수 있다.

1
2
3
4
5
6
mvn install:install-file
-Dfile="라이브러리 jar의 경로"
-DgroupId="그룹 ID"
-DartifactId="아티팩트 ID"
-Dpackaging="패키징(jar)"
-Dversio="버전(1.0)"

로컬 저장소의 위치 알아보기

로컬 저장소의 위치는 다음과 같다.

1
홈 디렉터리/.m2/repository

이 폴더에는 라이브러리가 그룹 ID마다 폴더로 정리되어 있다.

(이펙티브 자바 2판) 리뷰

Java 개발자라면 꼭 읽어보아야 한다는 Effective Java 책이다.
Java 언어를 처음 접하는 개발자보다는 어느정도 Java를 이용해 개발을 하고 있는 개발자에게 많이 추천되는 책이다. 실제 책 도입 부분에서도 Java 언어를 처음 공부하는 개발자 보다는 중급 이상의 프로그래머 반열에 오르려면 반드시 읽어야 할 내용들을 포함하고 있다고 말하고 있다.

나는 아무래도 이직 후 자바 개발과 스프링 프레임워크를 처음 접하게 되었는데, 자바 웹 애플리케이션 개발 전반에 대해 이해하고자 스프링 관련된 서적을 먼저 읽고 있었지만, 여러 팀 동료분들이 코드리뷰를 해주시면서 “이펙티브 자바” 책을 읽어보면 도움이 많이 될 것 같다고 조언해주셔서 읽어보게 되었다.

이 책은 총 78개의 규칙으로 구성되어 있으며, 각 규칙은 최고의 프로그래머와 노련한 프로그래머 대부분이 유용하다고 믿는 지침들을 요약한 것이다. 책에서 등장하는 첫 번째 규칙이 **”규칙 1. 생성자 대신 정적 팩터리 메서드를 사용할 수 없는지 생각해 보라”**인데, 동료분께 코드리뷰 받으면서 조언 받았던 부분이기도 했다. 그래서 첫 부분부터 아주 재밌게 읽어나갔다. 그 외에도 평소 코드리뷰에서 말씀해 주셨던 여러 내용들을 이 책을 통해 자세하게 알 수 있었다.

책 자체 내용은 쉽게 읽을 수 있는 정도는 아니었다. 오히려 이해하기 위해 고민하는 부분에서 깨닫게 되는 부분도 많은것 같다. (특히 제네릭 부분과 병행성 부분은 아직도 좀 어려운데 이 부분은 앞으로 어느 정도 시간이 지난 후 다시 읽어보면 또 다르게 받아들여지지 않을까 싶다.)
결과적으로 책을 읽고 난 후, 이부분을 개발할 때는 어떤 부분을 고려해 보아야 겠다라는 생각이 조금은 들게 된것 같다.

책 자체는 얇지는 않지만 각 파트 별로 여러개의 규칙으로 구성되어 있고, 각 규칙들은 정말 길어봐야 5~6장 정도의 분량이기 때문에 짬내서 읽기에도 좋았던것 같다.
Java를 사용해 개발하고 있는 개발자 분들 중 읽어 보시지 않은 분들이 계시다면 꼭 추천드리고 싶은 책이다.