Java로 Web Application을 만들려면 꼭 필요한게 Maven, Gradle 같은 빌드 툴이다. IntelliJ나 Spring Initializr 같은 곳에서 Maven 혹은 Gradle 같은 빌드툴을 선택하기만 하면 알아서 세팅해준다 😃
그래서 그것에 대한 감사함은 진정하게는 없었다 히히 😏
빌드툴을 쓰는 이유는 귀찮은 빌드들을 다 해주기 때문인데, 빌드툴을 무의식적으로 쓰다가 갑자기 든 생각이 있다.
빌드는 얼마나 귀찮길래 이런게 생긴걸까?
간단하게나마 직접 빌드를 체험해보고 싶은 마음에 인터넷을 뒤져 해보게됐다.
소스 코드
USER@DESKTOP-R5TAR1M MINGW64 ~/Desktop/공부/Java/HelloWorld
$ rmdir -p src/main/java/com/buildexc/app target/classes
USER@DESKTOP-R5TAR1M MINGW64 ~/Desktop/공부/Java/HelloWorld
$ tree
.
|-- src
| `-- main
| `-- java
| `-- com
| `-- buildexc
| `-- app
`-- target
`-- classes
8 directories
프로젝트 디렉토리를 비슷하게 src/main/java/com/buildexc/app
, target/classes
로 만들었다.
USER@DESKTOP-R5TAR1M MINGW64 ~/Desktop/공부/Java/HelloWorld
$ cd src/main/java/com/buildexc/app
USER@DESKTOP-R5TAR1M MINGW64 ~/Desktop/공부/Java/HelloWorld/src/main/java/com/buildexc/app
$ vi App.java
USER@DESKTOP-R5TAR1M MINGW64 ~/Desktop/공부/Java/HelloWorld/src/main/java/com/buildexc/app
$ cat App.java
public class App {
public static void main(String[] args) {
System.out.println("Hello World!");
}
}
USER@DESKTOP-R5TAR1M MINGW64 ~/Desktop/공부/Java/HelloWorld/src/main/java/com/buildexc/app
$ cd ../../../../../../
USER@DESKTOP-R5TAR1M MINGW64 ~/Desktop/공부/Java/HelloWorld
$ tree
.
|-- src
| `-- main
| `-- java
| `-- com
| `-- buildexc
| `-- app
| |-- App.java
`-- target
`-- classes
8 directories, 1 file
src/main/java
안의 패키지로 들어가서 App.java
를 생성했다.
소스 코드 컴파일
소스 코드를 javac로 컴파일 하면
USER@DESKTOP-R5TAR1M MINGW64 ~/Desktop/공부/Java/HelloWorld
$ javac src/main/java/com/buildexc/app/App.java -d target/classes
USER@DESKTOP-R5TAR1M MINGW64 ~/Desktop/공부/Java/HelloWorld
$ tree
.
|-- src
| `-- main
| `-- java
| `-- com
| `-- buildexc
| `-- app
| |-- App.java
`-- target
`-- classes
|-- App.class
8 directories, 2 files
target/classes
안에 컴파일 된 .class
가 생겼다. -d
는 directory로, 컴파일이 된 파일이 위치할 디렉토리를 지정하는 것이다. 근데 package 선언을 안해서 App.java
만 달랑 됐다.
USER@DESKTOP-R5TAR1M MINGW64 ~/Desktop/공부/Java/HelloWorld/src/main/java/com/buildexc/app
$ vi App.java
USER@DESKTOP-R5TAR1M MINGW64 ~/Desktop/공부/Java/HelloWorld/src/main/java/com/buildexc/app
$ cat App.java
package com.buildexc.app;
public class App {
public static void main(String[] args) {
System.out.println("Hello World!");
}
}
USER@DESKTOP-R5TAR1M MINGW64 ~/Desktop/공부/Java/HelloWorld
$ tree
.
|-- src
| `-- main
| `-- java
| `-- com
| `-- buildexc
| `-- app
| |-- App.java
`-- target
`-- classes
`-- com
`-- buildexc
`-- app
|-- App.class
11 directories, 2 files
다시 App.java
로 들어가서 package를 넣어줬다. 그러고 다시 컴파일을 하니 깔끔해졌다.
USER@DESKTOP-R5TAR1M MINGW64 ~/Desktop/공부/Java/HelloWorld
$ java -cp target/classes com.buildexc.app.App
Hello World!
이제 target/classes
안의 App
을 java.exe
로 오픈해봤다. 잘 된다. 이 때 package를 전부 classpath로 지정해버리면 NoClassDefFoundError
가 뜬다. 어디서 에러가 뜨는걸까?
USER@DESKTOP-R5TAR1M MINGW64 ~/Desktop/공부/Java/HelloWorld
$ java -cp target/classes/com/buildexc/app -verbose App
[0.011s][info][class,load] opened: C:\jdk-11\lib\modules
[0.028s][info][class,load] java.lang.Object source: jrt:/java.base
[0.029s][info][class,load] java.io.Serializable source: jrt:/java.base
[0.029s][info][class,load] java.lang.Comparable source: jrt:/java.base
...
[0.175s][info][class,load] java.security.BasicPermissionCollection source: jrt:/java.base
[0.176s][info][class,load] java.security.SecureClassLoader$DebugHolder source: jrt:/java.base
[0.176s][info][class,load] sun.security.util.Debug source: jrt:/java.base
[0.176s][info][class,load] java.io.IOException source: jrt:/java.base
[0.177s][info][class,load] sun.launcher.LauncherHelper$ResourceBundleHolder source: jrt:/java.base
...
Error: Could not find or load main class App
Caused by: java.lang.NoClassDefFoundError: com/buildexc/app/App (wrong name: App)
언제나처럼 클래스들을 로딩하고, 디버그를 하는 과정에서 IOException
이 떴다. package를 App.java
에서는 선언했는데 실행할 때 포함 안하면 Input과정에서 오류가 생기는 것 같다. Classpath를 target/classes
로 하고 실행할 App
을 패키지까지 포함해서 스크립트를 쓰면 된다.
커스텀 클래스
USER@DESKTOP-R5TAR1M MINGW64 ~/Desktop/공부/Java/HelloWorld
$ cd src/main/java/com/buildexc/app && vi Imported.java
USER@DESKTOP-R5TAR1M MINGW64 ~/Desktop/공부/Java/HelloWorld/src/main/java/com/buildexc/app
$ cat Imported.java
package com.buildexc.app;
public class Imported {
public void helloFromImported() {
System.out.println("Hello World from Imported Class!");
}
}
이제 Imported.java
라는 파일을 만들었다. App
이라는 메인 소스 파일 안에 임포트해서 띄울 클래스다.
커스텀 클래스 컴파일
이걸 타겟에다가 컴파일했다.
USER@DESKTOP-R5TAR1M MINGW64 ~/Desktop/공부/Java/HelloWorld/src/main/java/com/buildexc/app
$ javac -d ../../../../../../target/classes Imported.java
USER@DESKTOP-R5TAR1M MINGW64 ~/Desktop/공부/Java/HelloWorld/src/main/java/com/buildexc/app
$ cd ../../../../../.. && tree
.
|-- src
| `-- main
| `-- java
| `-- com
| `-- buildexc
| `-- app
| |-- App.java
| `-- Imported.java
`-- target
`-- classes
`-- com
`-- buildexc
`-- app
|-- App.class
`-- Imported.class
11 directories, 4 files
App
을 컴파일 했을 때 처럼 잘 됐다.
이제 App.java
의 코드를 Imported
의 클래스를 임포트 받아 띄우게끔 수정했다.
USER@DESKTOP-R5TAR1M MINGW64 ~/Desktop/공부/Java/HelloWorld/src/main/java/com/buildexc/app
$ vi App.java
USER@DESKTOP-R5TAR1M MINGW64 ~/Desktop/공부/Java/HelloWorld/src/main/java/com/buildexc/app
$ cat App.java
package com.buildexc.app;
import com.buildexc.app.Imported;
public class App {
public static void main(String[] args) {
System.out.println("Hello World!");
Imported importedClass = new Imported();
importedClass.helloFromImported();
}
}
그리고 위에 처럼 javac로 컴파일 해봤다.
USER@DESKTOP-R5TAR1M MINGW64 ~/Desktop/공부/Java/HelloWorld/src/main/java/com/buildexc/app
$ javac -d ../../../../../../target/classes App.java
App.java:3: error: cannot find symbol
import com.buildexc.app.Imported;
^
symbol: class Imported
location: package com.buildexc.app
App.java:9: error: cannot find symbol
Imported importedClass = new Imported();
^
symbol: class Imported
location: class App
App.java:9: error: cannot find symbol
Imported importedClass = new Imported();
^
symbol: class Imported
location: class App
3 errors
cannot find symbol. 뭔 말인지 모르겠댄다 😐 무엇이 잘못된건가? Verbose로 진찰해봤다.
javac -verbose -d ../../../../../../target/classes App.java
[parsing started SimpleFileObject[C:\Users\USER\Desktop\공부\Java\HelloWorld\src\main\java\com\buildexc\app\App.java]]
[parsing completed 28ms]
[loading /modules/jdk.security.jgss/module-info.class]
[loading /modules/java.scripting/module-info.class]
…
[loading /modules/jdk.jartool/module-info.class]
[search path for source files: .]
[search path for class files: C:\jdk-11\lib\modules,.]
App.java:3: error: cannot find symbol
import com.buildexc.app.Imported;
^
symbol: class Imported
location: package com.buildexc.app
[loading /modules/java.base/java/lang/Object.class]
…
[loading /modules/java.base/java/lang/annotation/ElementType.class]
[checking com.buildexc.app.App]
[loading /modules/java.base/java/io/Serializable.class]
…
[loading /modules/java.base/java/lang/CharSequence.class]
App.java:9: error: cannot find symbol
Imported importedClass = new Imported();
^
symbol: class Imported
location: class App
App.java:9: error: cannot find symbol
Imported importedClass = new Imported();
^
symbol: class Imported
location: class App
[total 366ms]
현재 디렉토리는 src/main/java/com/buildexc/app
인데, search path for source file이 현재 디렉토리다. 당연히 현재 디렉토리에는 com.buildexc.app이라는 패키지가 하위에 존재하지 않으니 모르겠단거다. 이런 에러가 뜬 이유는 컴파일 과정에서 소스 코드를 읽기보단 컴파일된 바이트 코드를 임포트하기 때문이라고 생각한다.
즉, Imported.java
를 컴파일한 Imported.class
가 없으니 이런 것일거다. 왜냐면 import Imported;
라고 임포트를 쓰는걸 본 적이 없지 않은가? 이런 식으로 임포트하면 에러가 뜰거다. 혹시 모르니 확인해봤다.
$ cat App.java
package com.buildexc.app;
import Imported;
public class App {
public static void main(String[] args) {
System.out.println("Hello World!");
Imported importedClass = new Imported();
importedClass.helloFromImported();
}
}
위와 같이 임포트 방식을 바꾸고 javac로 컴파일 해봤다. 컴파일이 되는지 확인하기 위함이니 클래스 생성 목적지는 정하지 않고 컴파일 해봤다.
$ javac App.java
App.java:3: error: '.' expected
import Imported;
^
1 error
역시나 되지 않는다.
클래스들은 target/classes
들에 모아놓기로 했으니, classpath를 지정하지 않아서 생긴 버그라는 생각이 들었다. 다시 임포트를 바꾼 후, cp를 지정해 컴파일해봤다. 보기 쉽게 제일 위의 디렉토리로 이동해서 했다.
USER@DESKTOP-R5TAR1M MINGW64 ~/Desktop/공부/Java/HelloWorld
$ javac -cp target/classes -d target/classes src/main/java/com/buildexc/app/App.java
USER@DESKTOP-R5TAR1M MINGW64 ~/Desktop/공부/Java/HelloWorld
$ tree
.
|-- src
| `-- main
| `-- java
| `-- com
| `-- buildexc
| `-- app
| |-- App.java
| `-- Imported.java
`-- target
`-- classes
`-- com
`-- buildexc
`-- app
|-- App.class
`-- Imported.class
11 directories, 4 files
Classpath를 지정하고 directory를 지정한 후 컴파일을 하니 역시나 잘 됐다.
USER@DESKTOP-R5TAR1M MINGW64 ~/Desktop/공부/Java/HelloWorld
$ java -cp target/classes com.buildexc.app.App
Hello World!
Hello World from Imported Class!
성공적으로 임포트된 것 또한 확인됐다.
이와 같이 객체지향의 특징을 살려, 여러개의 클래스를 넣어놓고, 하나의 main
을 돌리려면 클래스들을 만들고 classpath를 지정해 모두 미리 컴파일을 해야한다. 일일히 classpath를 지정하는게 귀찮으니 예전의 개발자들은 환경변수로 빼서 했다.
이렇게 커스텀 클래스들을 직접 정의하고 임포트해서 빌드까지 해봤다. 하지만 커스텀 클래스나 Java API말고도 웹 상에 존재하는 모듈들이 있지 않나? 대표적으로 JUnit같은 모듈말이다.
외부 라이브러리 임포트
우선 이런 외부 라이브러리들을 담아두기 위한 디렉토리를 생성했다.
USER@DESKTOP-R5TAR1M MINGW64 ~/Desktop/공부/Java/HelloWorld
$ mkdir lib
USER@DESKTOP-R5TAR1M MINGW64 ~/Desktop/공부/Java/HelloWorld
$ tree
.
|-- lib
|-- src
| `-- main
| `-- java
| `-- com
| `-- buildexc
| `-- app
| |-- App.java
| `-- Imported.java
`-- target
`-- classes
`-- com
`-- buildexc
`-- app
|-- App.class
`-- Imported.class
12 directories, 4 files
JUnit jar파일을 다운 받아 lib
폴더에 넣었다.
https://mvnrepository.com/artifact/junit/junit/4.13.2
테스트 코드 파일
그리고 src
폴더 아래 test/java
를 생성하고 main
과 같은 package를 만들어 ImportedTest.java
를 만들었다.
USER@DESKTOP-R5TAR1M MINGW64 ~/Desktop/공부/Java/HelloWorld
$ mkdir -p src/test/java/com/buildexc/app && cd src/test/java/com/buildexc/app
USER@DESKTOP-R5TAR1M MINGW64 ~/Desktop/공부/Java/HelloWorld/src/test/java/com/buildexc/app
$ vi ImportedTest.java
USER@DESKTOP-R5TAR1M MINGW64 ~/Desktop/공부/Java/HelloWorld/src/test/java/com/buildexc/app
$ cat ImportedTest.java
package com.buildexc.app;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
public class ImportedTest {
@Test
public void testImportedClass() {
Imported importedClass = new Imported();
importedClass.helloFromImported();
System.out.println("But this is just test!");
assertEquals(1, 2);
}
}
USER@DESKTOP-R5TAR1M MINGW64 ~/Desktop/공부/Java/HelloWorld/src/test/java/com/buildexc/app
$ cd ../../../../../.. && tree
.
|-- lib
| `-- junit-4.13.2.jar
|-- src
| |-- main
| | `-- java
| | `-- com
| | `-- buildexc
| | `-- app
| | |-- App.java
| | `-- Imported.java
| `-- test
| `-- java
| `-- com
| `-- buildexc
| `-- app
| `-- ImportedTest.java
`-- target
`-- classes
`-- com
`-- buildexc
`-- app
|-- App.class
`-- Imported.class
17 directories, 6 files
테스트 컴파일
이제 ImportedTest.java
를 컴파일 하려했는데…
USER@DESKTOP-R5TAR1M MINGW64 ~/Desktop/공부/Java/HelloWorld
$ javac -cp lib/junit-4.13.2.jar;target/classes -d target/classes src/test/java/com/buildexc/app/ImportedTest.java
error: no source files
bash: target/classes: Is a directory
안된다. Bash라서 여러 classpath를 설정할 때 세미콜론이 아니라 콜론을 써야하는걸까?
USER@DESKTOP-R5TAR1M MINGW64 ~/Desktop/공부/Java/HelloWorld
$ javac -cp lib/junit-4.13.2.jar:target/classes -d target/classes src/test/java/com/buildexc/app/ImportedTest.java
src\test\java\com\buildexc\app\ImportedTest.java:3: error: package org.junit does not exist
import org.junit.Test;
^
src\test\java\com\buildexc\app\ImportedTest.java:4: error: package org.junit does not exist
import static org.junit.Assert.assertEquals;
^
src\test\java\com\buildexc\app\ImportedTest.java:4: error: static import only from classes and interfaces
import static org.junit.Assert.assertEquals;
^
src\test\java\com\buildexc\app\ImportedTest.java:8: error: cannot find symbol
@Test
^
symbol: class Test
location: class ImportedTest
src\test\java\com\buildexc\app\ImportedTest.java:10: error: cannot find symbol
Imported importedClass = new Imported();
^
symbol: class Imported
location: class ImportedTest
src\test\java\com\buildexc\app\ImportedTest.java:10: error: cannot find symbol
Imported importedClass = new Imported();
^
symbol: class Imported
location: class ImportedTest
src\test\java\com\buildexc\app\ImportedTest.java:13: error: cannot find symbol
assertEquals(1, 2);
^
symbol: method assertEquals(int,int)
location: class ImportedTest
7 errors
콜론을 썼더니 아예 classpath를 하나도 못 읽는 모습이다. 구글링을 해보니
https://groups.google.com/g/msysgit/c/E16M9hCW2_4?pli=1
bash가 세미콜론을 자동으로 커맨드 구분하는 용으로 써서 그런 것이었다. Windows cmd 쓰는게 좀 헷갈려 git bash를 썼더니 이런 문제가…맥북으로 바꾸던가 해야지 😑 아무튼 이 문제는 세미콜론을 인식하게끔 \를 붙여주면 된다더라. 이걸 찾으려고 Java도 재다운 받아봤었는데…검색 잘 하는 것도 능력인 것 같다.
USER@DESKTOP-R5TAR1M MINGW64 ~/Desktop/공부/Java/HelloWorld
$ javac -cp lib/junit-4.13.2.jar\;target/classes -d target/classes src/test/java/com/buildexc/app/ImportedTest.java
USER@DESKTOP-R5TAR1M MINGW64 ~/Desktop/공부/Java/HelloWorld
$ tree
.
|-- lib
| `-- junit-4.13.2.jar
|-- src
| |-- main
| | `-- java
| | `-- com
| | `-- buildexc
| | `-- app
| | |-- App.java
| | `-- Imported.java
| `-- test
| `-- java
| `-- com
| `-- buildexc
| `-- app
| `-- ImportedTest.java
`-- target
`-- classes
`-- com
`-- buildexc
`-- app
|-- App.class
|-- Imported.class
`-- ImportedTest.class
17 directories, 7 files
이제 됐다! jar로 돼있는 외부 라이브러리와 기존의 클래스들 경로를 classpath로 넣고 해야 ImportedTest.java
안의 import가 정상적으로 된다. ImportedTest
가 정상적으로 컴파일 된게 확인 됐으니 이제 실행해보려 했다.
테스트 커맨드 실행
JUnit을 커맨드로 실행하려면 junit jar 안의 JUnitCore
를 java.exe
로 오픈하면 된다. 물론 classpath도 정상적으로 설정해줘야한다.
https://junit.org/junit4/javadoc/4.13/org/junit/runner/JUnitCore.html
USER@DESKTOP-R5TAR1M MINGW64 ~/Desktop/공부/Java/HelloWorld
$ java -cp target/classes\;lib/junit-4.13.2.jar org.junit.runner.JUnitCore com.buildexc.app.ImportedTest
JUnit version 4.13.2
Exception in thread "main" java.lang.NoClassDefFoundError: org/hamcrest/SelfDescribing
at java.base/java.lang.ClassLoader.defineClass1(Native Method)
at java.base/java.lang.ClassLoader.defineClass(ClassLoader.java:1016)
at java.base/java.security.SecureClassLoader.defineClass(SecureClassLoader.java:174)
…
JUnit에 기본적으로 쓰이는 Hamcrest를 안 넣었다.
https://mvnrepository.com/artifact/org.hamcrest/hamcrest-core/1.3
USER@DESKTOP-R5TAR1M MINGW64 ~/Desktop/공부/Java/HelloWorld
$ tree
.
|-- lib
| |-- hamcrest-core-1.3.jar
| `-- junit-4.13.2.jar
|-- src
| |-- main
| | `-- java
| | `-- com
| | `-- buildexc
| | `-- app
| | |-- App.java
| | `-- Imported.java
| `-- test
| `-- java
| `-- com
| `-- buildexc
| `-- app
| `-- ImportedTest.java
`-- target
`-- classes
`-- com
`-- buildexc
`-- app
|-- App.class
|-- Imported.class
`-- ImportedTest.class
17 directories, 8 files
넣은걸 확인했으니 이번엔 제발 잘 되길 바라며 오픈해봤다 🙏
USER@DESKTOP-R5TAR1M MINGW64 ~/Desktop/공부/Java/HelloWorld
$ java -cp target/classes\;lib/junit-4.13.2.jar\;lib/hamcrest-core-1.3.jar org.junit.runner.JUnitCore com.buildexc.app.ImportedTest
JUnit version 4.13.2
.Hello World from Imported Class!
But this is just test!
E
Time: 0
There was 1 failure:
1) testImportedClass(com.buildexc.app.ImportedTest)
java.lang.AssertionError: expected:<1> but was:<2>
at org.junit.Assert.fail(Assert.java:89)
at org.junit.Assert.failNotEquals(Assert.java:835)
at org.junit.Assert.assertEquals(Assert.java:647)
at org.junit.Assert.assertEquals(Assert.java:633)
at com.buildexc.app.ImportedTest.testImportedClass(ImportedTest.java:13)
FAILURES!!!
Tests run: 1, Failures: 1
정상적으로 출력됐다 🙌 1 != 2
이니 테스트가 실패한 것도 확인됐다.
USER@DESKTOP-R5TAR1M MINGW64 ~/Desktop/공부/Java/HelloWorld
$ java -cp target/classes\;lib/junit-4.13.2.jar\;lib/hamcrest-core-1.3.jar org.junit.runner.JUnitCore com.buildexc.app.ImportedTest
JUnit version 4.13.2
.Hello World from Imported Class!
But this is just test!
Time: 0.016
OK (1 test)
실패는 그냥 기분 나쁘니 assertEquals(1, 1)로 바꿔서 성공시켰다 🤨
빌드 툴 없이 빌드를 시도해봤는데, 여기까지 내가 해본 것은
- 메인 소스 코드를 Classpath 지정 후 컴파일 및 실행
- 커스텀 클래스 생성 후 임포트, 컴파일 및 실행
- 외부 라이브러리 다운로드 후 임포트, 컴파일 및 실행
이다.
당연히 classpath를 환경변수로 설정해 진행했으면 그나마 좀 덜 귀찮았겠지만, 그게 문제가 아녔다. 그 외에 반복적인 것도 많았고 classpath와 package를 구분 못해서 버벅인 것도 있었고 귀찮았던 점도 많아서, 잠깐 해보는 것인데도 자잘하게 놓치는 것들이 계속 나온다. 특히 git bash 문제는 상상도 못했던거다. 참나…
하지만 빌드는 여기서 끝이 아니지 않나? 이렇게 만든 어플리케이션을 jar로 압축했다. 어플리케이션이라기에 너무 작긴 하지만, 아무튼 실천이 의미있는 것 아닌가?😁
jar 파일로 압축
target/classes
에 모아놓은 바이트 코드들을 JDK에 들어있는 jar을 실행해 하나로 묶었다.
USER@DESKTOP-R5TAR1M MINGW64 ~/Desktop/공부/Java/HelloWorld
$ cd target/classes
USER@DESKTOP-R5TAR1M MINGW64 ~/Desktop/공부/Java/HelloWorld/target/classes
$ jar c .
K-*▒▒ϳR0▒3▒▒r.JM,IM▒u▒META-INF/MANIFEST.MF▒M▒▒LK-.▒
*h▒%&▒*8▒▒%▒j▒r▒rP<▒>>PK
▒*2Wcom/PK
com/buildexc/PK
▒3Wcom/buildexc/appo}2Wcom/buildexc/app/App.classmP▒J▒@▒▒-m▒5ժ▒V_▒▒y"▒▒BE▒▒d▒)I6▒T▒▒▒▒?▒▒϶J▒s▒̞▒▒▒▒▒▒▒}l▒Ƞ▒A ▒4t,)XV▒DQBU▒▒▒U▒ԡ▒;▒C▒▒}͐86g▒u▒▒▒▒̞KL▒3▒▒Ҽ▒
▒G▒pM▒u4▒▒▒▒▒▒qm▒dfg^ ▒6▒e(▒▒▒▒Eo▒▒▒▒}~▒▒G▒Đ▒▒mG▒▒9"▒▒▒Z
▒▒▒剁▒▒▒u5▒▒jrg ▒94▒▒Υ▒zs~▒½▒▒▒▒▒
▒B▒▒Ԕ▒▒E▒~Fh5FUE▒!▒▒▒▒▒▒l▒▒▒2▒˓Ry▒▒▒(▒P▒▒▒▒z2Wcom/buildexc/app/Imported.classmP▒J▒P=▒▒▒1▒/▒▒▒▒B▒▒,]T▒▒BQ▒R▒y\k▒MnHoD?K
.▒?J▒j▒▒▒;g▒9▒▒▒▒▒▒▒(a▒▒▒e4t4▒Ұ▒▒▒P:▒C_▒1▒▒▒Ca$=▒P▒▒!▒L▒▒7▒i▒s!▒8▒▒E▒Xq▒A▒▒$v▒▒O{▒g▒2▒▒▒t-;▒▒▒▒zjm/▒] ;▒[W▒▒4}Z*▒&2▒F{▒u|i]▒~▒▒*▒v0dh▒3hQZ▒▒t▒▒/KE▒|8▒▒▒"▒0}9▒t9▒▒)3▒ţ7▒g▒▒Y(▒20Oq▒▒ˤ@▒▒f▒▒5]▒y5ٔ▒7P▒▒▒▒82W#com/buildexc/app/ImportedTest.classuQ▒n1=N▒lj
M▒4▒Bh▒5)R▒▒▒"▒▒▒▒
▒"▒▒$V▒hw▒^D?▒<▒H|▒▒▒
X▒x▒̙3▒▒▒ϯ▒<▒G3▒&G MoZ▒▒▒;5▒9▒p▒▒:▒▒f▒▒Щv/V▒▒C▒▒L▒Z▒S▒&O▒*;▒㘐▒SֽN&sjzKk▒▒▒▒▒D▒▒▒D▒OS▒▒&▒\=2▒,▒▒$▒P▒▒<▒▒#]▒-▒|rw.?I▒1I8▒u<U▒'▒\,▒%S▒
▒'▒Z▒▒Sm▒▒繥▒▒z <ƓO▒0t▒▒▒{▒U}▒0▒▒,|;▒▒ A▒S▒▒(3ɒ▒mxf▒Jhf&'▒fTd▒ ▒e:uC▒)▒▒{7▒3
▒)▒▒▒?$▒▒=▒▒▒▒▒[▒2j!d▒▒▒˘▒[▒F▒a▒~گ▒▒ٛu▒dtVv.▒▒▒a▒E▒Z▒ޮ▒TPP&▒▒▒҇K▒D▒.P▒▒▒z▒▒W▒]P!▒Q+Z 4I▒M▒▒o▒F▒\/▒▒▒
PG7▒▒i3WMETA-INF/▒i3W<▒>>=META-INF/MANIFEST.MFPK
▒*2W▒com/PK
▒com/buildexc/PK
▒3W
com/buildexc/appo}2W▒▒▒[9com/buildexc/app/App.clas▒z2W▒▒▒▒8▒▒com/buildexc/app/Imported.clas2WG7▒▒▒#acom/buildexc/app/ImportedTest.classiK
또 못 알아먹을 소리나 한다. 눈치껏 살펴보니 manifest가 없다는 것 아닐까?
https://docs.oracle.com/javase/tutorial/deployment/jar/build.html
The basic format of the command for creating a JAR file is:
jar cf jar-file input-file(s)
The options and arguments used in this command are:
The c option indicates that you want to create a JAR file.
The f option indicates that you want the output to go to a file rather than to stdout.
f
가 없으면 기본적으로 stdout
으로 나오는 것 같다. 그러니까, 저 위에 못 알아먹을 소리가 압축된 jar이 바이트 코드로 나온 것이라고 생각한다. 눈치껏 살펴봤는데, 난 눈치가 없는 편이긴 한가보다😁
USER@DESKTOP-R5TAR1M MINGW64 ~/Desktop/공부/Java/HelloWorld/target/classes
$ jar cfv ../app.jar .
added manifest
adding: com/(in = 0) (out= 0)(stored 0%)
adding: com/buildexc/(in = 0) (out= 0)(stored 0%)
adding: com/buildexc/app/(in = 0) (out= 0)(stored 0%)
adding: com/buildexc/app/App.class(in = 515) (out= 347)(deflated 32%)
adding: com/buildexc/app/Imported.class(in = 447) (out= 312)(deflated 30%)
adding: com/buildexc/app/ImportedTest.class(in = 654) (out= 439)(deflated 32%)
USER@DESKTOP-R5TAR1M MINGW64 ~/Desktop/공부/Java/HelloWorld/target/classes
$ cd .. && ls
app.jar classes/
target
디렉토리에 어플리케이션 jar 파일이 생성됐다. 이렇게 만든 jar파일을 서드파티로 누구나 쓸 수 있게됐다!
커스텀 jar 파일 실행
jar 파일도 압축된 바이트 코드다. 즉, java.exe
로 실행이 가능하다는 것이다. JUnitCore
를 통해 바로 테스트 클래스를 실행시켰던 것처럼 말이다. 그럼 java.exe
로 jar 파일을 열어봤다.
USER@DESKTOP-R5TAR1M MINGW64 ~/Desktop/공부/Java/HelloWorld/target
$ cd ..
USER@DESKTOP-R5TAR1M MINGW64 ~/Desktop/공부/Java/HelloWorld
$ java -cp target/app.jar com.buildexc.app.App
Hello World!
Hello World from Imported Class!
이해가 쉽도록 최상위 디렉토리로 이동해 실행했다. 테스트 파일 소스 코드를 컴파일 했을 때처럼 classpath로 jar파일을 포함시키고 package를 특정해 App
을 실행하니 정상적으로 어플리케이션이 작동했다.
여기서 내가 실행할 main
이 들어있는 클래스는 com.buildexc.app.App
이다. jar 파일이 시작할 때 처음으로 들어갈 엔트리 포인트를 jar을 생성하는 과정에서 추가할 수 있다.
USER@DESKTOP-R5TAR1M MINGW64 ~/Desktop/공부/Java/HelloWorld
$ cd target/classes
USER@DESKTOP-R5TAR1M MINGW64 ~/Desktop/공부/Java/HelloWorld/target/classes
$ jar cfve ../app.jar com.buildexc.app.App .
added manifest
adding: com/(in = 0) (out= 0)(stored 0%)
adding: com/buildexc/(in = 0) (out= 0)(stored 0%)
adding: com/buildexc/app/(in = 0) (out= 0)(stored 0%)
adding: com/buildexc/app/App.class(in = 515) (out= 347)(deflated 32%)
adding: com/buildexc/app/Imported.class(in = 447) (out= 312)(deflated 30%)
adding: com/buildexc/app/ImportedTest.class(in = 654) (out= 439)(deflated 32%)
다시 최상위 디렉토리로 이동해 java -jar
로 바로 실행시켜봤다.
USER@DESKTOP-R5TAR1M MINGW64 ~/Desktop/공부/Java/HelloWorld/target/classes
$ cd ../..
USER@DESKTOP-R5TAR1M MINGW64 ~/Desktop/공부/Java/HelloWorld
$ java -jar target/app.jar
Hello World!
Hello World from Imported Class!
이런 식으로 jar 파일이 생성될 때 그 안에 조건을 넣을 수 있는데, 이걸 미리 정의해서 보통 resources
아래 manifest에 넣어놓고, 빌드를 할 때 jar -m
을 통해 manifest를 읽고 jar 파일을 만들 수 있다.
이렇게 한 프로젝트의 빌드 과정을 대충이나마 해봤다. 간단하게 했는데도 여간 귀찮은게 아니다. 역시 툴이 있으면 직접 해보는게 감사함을 심어주는 것 같다. 나는 주로 gradle을 쓰는 편인데, 어떻게 쓰는지만 서칭해서 에러 안 나도록 써보려했지, 이렇게 직접 빌드를 해볼 생각을 왜 못 했을까. 아무튼 맛이라도 봤으니 다음에는 gradle에 대해 해봐야겠다
'Java' 카테고리의 다른 글
자바 Collection 이야기 (0) | 2024.07.16 |
---|---|
자바 인터페이스 이야기 - 사용 이유 (0) | 2024.05.28 |
Java, JVM, JRE, JDK (2) | 2023.11.29 |