Dev Study
← 解説「ツール無しで手でビルドする」に戻る

サンプルコードで身につける: ツール無しで手でビルドする

解説で学んだ概念を、5つの具体例で確認します。素朴な例から実務寄りの例へと進みます。

1依存の無い2クラスを、3つのコマンドで

ソース2ファイルから始めて、、、 の順に打った記録です。 out は、に合わせて out/com/example/app/ のようなフォルダを自分で作って を置きます。jar はそれを app.jar にまとめ、 で指定した入口の名をに書きます。

手でビルドして実行する(JDK 25)コマンド出力
$ find . -type f | sort
./src/com/example/app/Main.java
./src/com/example/greet/Greeter.java
 
$ javac -d out $(find src -name "*.java")
 
$ find out -type f | sort
out/com/example/app/Main.class
out/com/example/greet/Greeter.class
 
$ java -cp out com.example.app.Main
Hello, world!
 
$ jar --create --file app.jar --main-class com.example.app.Main -C out .
 
$ ls -l app.jar && jar tf app.jar
-rw-r--r-- 1 root root 1800 Oct 1 22:26 app.jar
META-INF/
META-INF/MANIFEST.MF
com/
com/example/
com/example/app/
com/example/app/Main.class
com/example/greet/
com/example/greet/Greeter.class
 
$ java -jar app.jar
Hello, world!

2依存の JAR があると、コンパイルにも実行にも -cp が要る

Greeter が外部のライブラリ()を使うように変えた版です。 は自分でダウンロードして に置きました。置いただけではコンパイラは見てくれず、 で場所を教えると通ります。実行のときも同じで、教えないと (クラスが見つからない)で落ちます。[exit 1] は失敗の終了コードを記録した行です。

依存 JAR が1本ある場合(JDK 25)コマンド出力
$ find . -type f | sort
./lib/commons-lang3-3.21.0.jar
./src/com/example/app/Main.java
./src/com/example/greet/Greeter.java
 
$ javac -d out $(find src -name "*.java")
src/com/example/greet/Greeter.java:3: error: package org.apache.commons.lang3 does not exist
import org.apache.commons.lang3.StringUtils;
^
src/com/example/greet/Greeter.java:7: error: cannot find symbol
return "Hello, " + StringUtils.capitalize(name) + "!";
^
symbol: variable StringUtils
location: class Greeter
2 errors
[exit 1]
 
$ javac -d out -cp lib/commons-lang3-3.21.0.jar $(find src -name "*.java")
 
$ java -cp out com.example.app.Main
Exception in thread "main" java.lang.NoClassDefFoundError: org/apache/commons/lang3/StringUtils
at com.example.greet.Greeter.greet(Greeter.java:7)
at com.example.app.Main.main(Main.java:7)
Caused by: java.lang.ClassNotFoundException: org.apache.commons.lang3.StringUtils
at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:580)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:490)
... 2 more
[exit 1]
 
$ java -cp out:lib/commons-lang3-3.21.0.jar com.example.app.Main
Hello, World!

3自分の JAR に依存は入らない

上の続きで、app.jar を作って起動したものです。 で見ると、中にあるのは自分の .class 2個だけで、commons-lang3 は入っていません。そのため app.jar は失敗します。動かすには、app.jar と を -cp に並べ、入口のクラスを名指しします。-jar を付けると -cp の指定は無視されるので、この2つは一緒に使えません。

依存のある app.jar を起動する(JDK 25)コマンド出力
$ jar --create --file app.jar --main-class com.example.app.Main -C out .
 
$ jar tf app.jar
META-INF/
META-INF/MANIFEST.MF
com/
com/example/
com/example/app/
com/example/app/Main.class
com/example/greet/
com/example/greet/Greeter.class
 
$ java -jar app.jar
Exception in thread "main" java.lang.NoClassDefFoundError: org/apache/commons/lang3/StringUtils
at com.example.greet.Greeter.greet(Greeter.java:7)
at com.example.app.Main.main(Main.java:7)
Caused by: java.lang.ClassNotFoundException: org.apache.commons.lang3.StringUtils
at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:580)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:490)
... 2 more
[exit 1]
 
$ java -cp app.jar:lib/commons-lang3-3.21.0.jar com.example.app.Main
Hello, World!
 
$ java -cp "app.jar:lib/*" com.example.app.Main
Hello, World!

4Spring を Maven 無しで動かす: JAR を9本集める

7.0.9 の入口になる を使うために、手で集めた JAR の一覧と大きさです。自分が直接使うのは spring-context ですが、それが必要とする JAR、さらにその先が必要とする JAR を辿ると9本になりました。lib/* と書けばまとめて指定できますが、書き下すと最後のコマンドの長さになります。出力の beans defined: 5 は、 が管理している部品()の定義の数です。

9本の JAR をクラスパスに並べて実行する(Spring Framework 7.0.9 / JDK 25)コマンド出力
$ ls -l lib/*.jar | awk "{print \$5, \$9}"
73737 lib/commons-logging-1.3.5.jar
3819 lib/jspecify-1.0.0.jar
51745 lib/micrometer-commons-1.16.7.jar
83914 lib/micrometer-observation-1.16.7.jar
426204 lib/spring-aop-7.0.9.jar
928155 lib/spring-beans-7.0.9.jar
1399435 lib/spring-context-7.0.9.jar
2035755 lib/spring-core-7.0.9.jar
327140 lib/spring-expression-7.0.9.jar
 
$ javac -d out -cp "lib/*" $(find src -name "*.java")
 
$ java -cp "out:lib/*" com.example.app.Main
Hello, Spring without Maven!
beans defined: 5
 
##### the same, classpath spelled out
$ java -cp out:lib/commons-logging-1.3.5.jar:lib/jspecify-1.0.0.jar:lib/micrometer-commons-1.16.7.jar:lib/micrometer-observation-1.16.7.jar:lib/spring-aop-7.0.9.jar:lib/spring-beans-7.0.9.jar:lib/spring-context-7.0.9.jar:lib/spring-core-7.0.9.jar:lib/spring-expression-7.0.9.jar com.example.app.Main
Hello, Spring without Maven!
beans defined: 5

51本抜くとどこで気づけるか

9本から1本ずつ抜いて、コンパイルと実行を試した結果です(各1回)。コンパイラが型を調べるときに必要になる JAR はコンパイルで気づけますが、Spring の内部だけが使う JAR は、コンパイルが通って実行時に落ちます。下の3本は抜いてもこの実行では動きましたが、エラーが起きた経路や Web アプリでは必要になったので、不要とは言えません。

抜いた JAR ごとの結果と、commons-logging を抜いたときのエラー表
抜いた JAR javac java 最初のエラー
spring-context エラー エラー package org.springframework.stereotype does not exist
spring-beans エラー エラー class file for org.springframework.beans.factory.support.DefaultListableBeanFactory not found
spring-core エラー エラー class file for org.springframework.core.io.DefaultResourceLoader not found
spring-aop 通る エラー NoClassDefFoundError: org/springframework/aop/TargetSource
spring-expression 通る エラー NoClassDefFoundError: org/springframework/expression/ParserContext
commons-logging 通る エラー NoClassDefFoundError: org/apache/commons/logging/LogFactory
jspecify 通る 動く -
micrometer-observation 通る 動く -
micrometer-commons 通る 動く -
 
=== without commons-logging-1.3.5.jar
javac exit 0
java exit 1 (classes compiled with full lib)
Exception in thread "main" java.lang.NoClassDefFoundError: org/apache/commons/logging/LogFactory
at org.springframework.context.support.AbstractApplicationContext.<init>(AbstractApplicationContext.java:187)
at org.springframework.context.support.GenericApplicationContext.<init>(GenericApplicationContext.java:122)
at org.springframework.context.annotation.AnnotationConfigApplicationContext.<init>(AnnotationConfigApplicationContext.java:68)
at org.springframework.context.annotation.AnnotationConfigApplicationContext.<init>(AnnotationConfigApplicationContext.java:102)
at com.example.app.Main.main(Main.java:7)
Caused by: java.lang.ClassNotFoundException: org.apache.commons.logging.LogFactory

図やコードに出てくる用語(タップで説明)