이 사이트의 실습은 자바스크립트를 켜야 움직입니다. 아래는 같은 내용을 글로 적은 것입니다.
기초개념 › Java
라이브러리는 무엇이고, 없으면 어떻게 되는가
라이브러리는 남이 만들어 둔 클래스들입니다. 이 실습에서는 lib.Greeter 클래스 하나를 jar 파일(클래스 파일을 묶어 둔 파일) greet.jar에 담아 두었습니다. 내 코드 App.java는 import lib.Greeter;로 그 클래스를 가져다 쓰는데, Greeter는 App.java 안에 없습니다. 그래서 컴파일할 때도, 실행할 때도 컴파일러(javac)와 JVM(java)은 클래스패스(클래스 파일을 찾아볼 곳의 목록)에서 Greeter를 찾아야 하고, 명령에서는 -cp 뒤에 그 목록을 적습니다. 내 프로젝트가 쓰는 라이브러리를 의존성이라고 부르며, 빌드 도구 Gradle에서는 implementation으로 적은 의존성이 컴파일할 때와 실행할 때의 클래스패스에 모두 들어갑니다. 이 실습은 javac 명령과 java 명령에서 greet.jar를 하나씩 빼 가며 실제로 컴파일하고 실행해, 어느 단계에서 어떤 오류가 나는지를 봅니다. (Gradle은 이 실습에서 실행하지 않습니다.)
실행으로 대조 · JDK 21에서 네 가지 조합을 greet.jar를 만들어 javac와 java 명령 그대로 실행해 대조
# App.java
// greet.jar 안에 있는, 남이 만든 클래스를 가져온다
import lib.Greeter;
public class App {
public static void main(String[] args) {
System.out.println(Greeter.hello("kim"));
}
}
# greet.jar (Greeter.java를 컴파일해 jar로 묶은 것)
package lib;
public class Greeter {
public static String hello(String name) {
return "hello, " + name;
}
}
# 터미널
javac -cp greet.jar App.java
java -cp greet.jar;. App
컴파일러와 실행 환경은 JDK 21.0.12.1(javac 21, java 21)입니다. 네 가지 조합을 모두 대조했습니다. 먼저 lib.Greeter 클래스(hello 메서드 하나)를 javac와 jar 도구로 greet.jar에 묶었고(외부 jar는 받지 않았습니다), 조합마다 새 폴더에 greet.jar와 App.java를 놓고 화면에 적힌 터미널 명령 javac와 java를 그대로 실행했습니다. 화면의 코드는 Probe가 실제 파일 내용과 실제 실행한 명령으로 만들어 남긴 글과 같습니다. 대조한 것은 컴파일 성공 여부, 오류의 개수와 진단 코드(compiler.err.doesnt.exist와 compiler.err.cant.resolve.location를 화면의 두 가지 오류 설명으로 옮겼습니다)와 오류가 난 줄, App.class가 만들어졌는지, java 명령의 종료 코드와 출력, 실행 오류의 종류(NoClassDefFoundError)와 그 위치(App.java:6)입니다. 컴파일에 실패한 조합에서도 java 명령을 실제로 실행해, App 클래스를 찾을 수 없다는 오류가 나는 것을 확인했습니다. 단계 문장이 기대는 사실도 따로 실행해 확인했습니다. greet.jar에는 lib/Greeter.class가 들어 있다는 것(jar tf), 컴파일에 성공한 폴더에 생긴 클래스 파일은 App.class 하나뿐이라는 것, java 명령에서 -cp greet.jar만 적고 점(.)을 빼면 현재 폴더의 App.class를 찾지 못한다는 것, 컴파일 전 폴더에는 App.java와 greet.jar만 있었다는 것입니다. 명령은 Windows 11에서 실행했고, 클래스패스의 구분자 세미콜론(;)은 Windows 기준입니다(macOS와 Linux는 콜론). 화면에는 한 가지로만 적었습니다. CLASSPATH 환경 변수는 실행하는 프로세스에서 지웠고, 이 변수가 설정된 환경에서는 -cp를 적지 않은 명령의 결과가 달라질 수 있습니다. java 실행기의 오류 문장은 이 PC의 언어(한국어)로 나왔고, 다른 언어 설정에서는 문장이 다릅니다. 문서는 직접 열어 확인했습니다. Oracle의 javac 도구 문서(https://docs.oracle.com/en/java/javase/21/docs/specs/man/javac.html)에서 --class-path(-cp)가 사용자 클래스 파일을 찾을 곳을 정하고, -cp도 CLASSPATH 환경 변수도 없으면 현재 폴더가 쓰이며, 구분자가 Windows에서 세미콜론이라는 것을, java 도구 문서(https://docs.oracle.com/en/java/javase/21/docs/specs/man/java.html, 앞부분 10만 자만 읽었고 -cp 설명은 그 안에 있었습니다)에서 -cp가 클래스 파일을 찾을 디렉터리, JAR 파일, ZIP 파일의 목록이고 지정하지 않으면 현재 폴더(.)라는 것을, NoClassDefFoundError API 문서(https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/NoClassDefFoundError.html)에서 클래스를 불러오려는데 정의를 찾을 수 없을 때 던져지며 컴파일할 때는 있었던 정의를 지금은 찾을 수 없는 경우라는 것을, Gradle 문서의 Declaring dependencies(https://docs.gradle.org/current/userguide/declaring_dependencies_basics.html)에서 모듈 의존성을 group:name:version 좌표로 적는다는 것과 dependencies 안에 implementation으로 적는 예를, Java Plugin 문서(https://docs.gradle.org/current/userguide/java_plugin.html)의 설정 표에서 implementation이 컴파일할 때와 실행할 때 모두 필요한 의존성이고 compileClasspath와 runtimeClasspath에 포함된다는 것을 따랐습니다. Gradle은 실행하지 않았고, 저장소에서 jar를 내려받는 과정은 문서로도 확인하지 못해 적지 않았습니다. 오류의 개수와 종류, 오류 문장은 명세가 정한 것이 아니라 이 JDK에서 관찰한 것입니다. 이 밖에 라이브러리가 다시 다른 라이브러리를 끌어오는 것(전이 의존성), 버전, 클래스패스 여러 곳에 같은 클래스가 있을 때의 순서, 모듈 시스템은 다루지 않았고, 의존성과 공급망은 "세부 동작 실습: 의존성과 공급망"에서, 의존성을 보고 설정을 자동으로 만들어 주는 스프링 부트는 "세부 동작 실습: 스프링 부트 자동 설정"에서 다룹니다.
줄을 그은 곳은 처음 코드에서 지운 것이고, 색을 칠한 곳은 새로 쓴 것입니다.