Dev Study
← 解説「Ant で Spring の WAR を作る」に戻る

サンプルコードで身につける: Ant で Spring の WAR を作る

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

1プロジェクトの tree

ビルド前のファイル一覧です。 の 11本は、人がダウンロードして置いたものです。src/ の直下がいきなり jp/co/... で、 という階層はありません。あの階層は というの規約で、 には無いためです(Maven は後のレッスンで扱います)。ビルドすると、classes/ と dist/shop.war が増えます。

ビルド前の treeコマンド出力
.
├── WebContent
│ ├── WEB-INF
│ │ ├── applicationContext.xml
│ │ ├── dispatcher-servlet.xml
│ │ └── web.xml
│ └── index.html
├── build.xml
├── lib
│ ├── commons-logging-1.3.5.jar
│ ├── jspecify-1.0.0.jar
│ ├── micrometer-commons-1.16.7.jar
│ ├── micrometer-observation-1.16.7.jar
│ ├── spring-aop-7.0.9.jar
│ ├── spring-beans-7.0.9.jar
│ ├── spring-context-7.0.9.jar
│ ├── spring-core-7.0.9.jar
│ ├── spring-expression-7.0.9.jar
│ ├── spring-web-7.0.9.jar
│ └── spring-webmvc-7.0.9.jar
├── lib-compile
│ └── jakarta.servlet-api-6.1.0.jar
└── src
└── jp
└── co
└── example
└── shop
├── service
│ └── GreetingService.java
└── web
└── HelloController.java
 
11 directories, 19 files

2build.xml の全文: war タスクが詰める場所を決める

この の中心は最後の war です。 で の中身を の直下に、classes でコンパイル結果を に、lib で lib/ の JAR を に入れます。lib-compile の JAR はコンパイル用のにだけ入れていて、WAR には入れていません。src や WebContent という名前は、上の に自分で書いたものです。

build.xml(37行)設定ファイル
<?xml version="1.0" encoding="UTF-8"?>
<project name="shop" default="war" basedir=".">
 
<property name="src.dir" value="src"/>
<property name="web.dir" value="WebContent"/>
<property name="lib.dir" value="lib"/>
<property name="build.dir" value="build"/>
<property name="classes.dir" value="${build.dir}/classes"/>
<property name="dist.dir" value="dist"/>
 
<!-- compile-time classpath: JARs that go into the WAR + servlet API (provided by Tomcat at run time) -->
<path id="compile.classpath">
<fileset dir="${lib.dir}" includes="*.jar"/>
<fileset dir="lib-compile" includes="*.jar"/>
</path>
 
<target name="clean" description="remove build/ and dist/">
<delete dir="${build.dir}"/>
<delete dir="${dist.dir}"/>
</target>
 
<target name="compile" description="javac: src/ -> build/classes/">
<mkdir dir="${classes.dir}"/>
<javac srcdir="${src.dir}" destdir="${classes.dir}" classpathref="compile.classpath"
release="25" includeantruntime="false" encoding="UTF-8"/>
</target>
 
<target name="war" depends="compile" description="build dist/shop.war">
<mkdir dir="${dist.dir}"/>
<war destfile="${dist.dir}/shop.war" webxml="${web.dir}/WEB-INF/web.xml">
<fileset dir="${web.dir}" excludes="WEB-INF/web.xml"/>
<classes dir="${classes.dir}"/>
<lib dir="${lib.dir}" includes="*.jar"/>
</war>
</target>
 
</project>

3web.xml の全文: Tomcat に何を起こしてもらうかを書く

WAR を置かれた が読む設定ファイルです。listener と servlet の2か所に の名が書いてあり、Tomcat はこの名前のクラスを起こします。 の /app/* は、/app/ で始まる URL を Spring の に渡すという指定です。自分で書いた が無くても動くのは、ここが入口になっているからです。

WebContent/WEB-INF/web.xml設定ファイル
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_6_1.xsd"
version="6.1">
 
<!-- root context: service layer -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
 
<!-- servlet context: web layer -->
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/dispatcher-servlet.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/app/*</url-pattern>
</servlet-mapping>
</web-app>

4ant でビルドし、Tomcat に置いて、curl で呼ぶ

の出力、WAR を置いた Tomcat のログ、応答の順に並べたものです。Tomcat のログでは、Deploying の行の後に Spring 側の初期化の行が続き、Deployment ... has finished で終わります。Tomcat が先に動いていて、その中で Spring が起こされていることが、行の順番から読み取れます。途中の WARNING は、XML の設定の一部が 7.0 で非推奨になったことを知らせるものです。

$ ant → Tomcat 11.0.26 のログ(抜粋)→ $ curl(# の行は補足。ホスト側のポート 18391 をコンテナの 8080 につないでいる)コマンド出力
$ ant
Buildfile: /w/shop/build.xml
 
compile:
[mkdir] Created dir: /w/shop/build/classes
[javac] Compiling 2 source files to /w/shop/build/classes
 
war:
[mkdir] Created dir: /w/shop/dist
[war] Building war: /w/shop/dist/shop.war
 
BUILD SUCCESSFUL
Total time: 1 second
 
# dist/shop.war を Tomcat の webapps/ に置いて起動したときのログ
01-Oct-2026 22:28:03.388 INFO [main] org.apache.catalina.startup.HostConfig.deployWAR Deploying web application archive [/usr/local/tomcat/webapps/shop.war]
01-Oct-2026 22:28:03.729 INFO [main] org.springframework.web.context.ContextLoader.initWebApplicationContext Root WebApplicationContext: initialization started
01-Oct-2026 22:28:03.867 INFO [main] org.springframework.web.context.ContextLoader.initWebApplicationContext Root WebApplicationContext initialized in 138 ms
01-Oct-2026 22:28:03.881 INFO [main] org.springframework.web.servlet.DispatcherServlet.initServletBean Initializing Servlet 'dispatcher'
01-Oct-2026 22:28:03.900 WARNING [main] org.springframework.web.servlet.config.MvcNamespaceHandler.init The XML configuration namespace for Spring MVC is deprecated, please use Java configuration instead.
01-Oct-2026 22:28:04.004 INFO [main] org.springframework.web.servlet.DispatcherServlet.initServletBean Completed initialization in 123 ms
01-Oct-2026 22:28:04.007 INFO [main] org.apache.catalina.startup.HostConfig.deployWAR Deployment of web application archive [/usr/local/tomcat/webapps/shop.war] has finished in [619] ms
01-Oct-2026 22:28:04.012 INFO [main] org.apache.catalina.startup.Catalina.start Server startup in [644] milliseconds
 
$ curl -i http://localhost:18391/shop/app/hello
HTTP/1.1 200
Content-Type: text/plain;charset=ISO-8859-1
Content-Length: 36
Date: Thu, 01 Oct 2026 22:28:04 GMT
 
Hello, world (from GreetingService)

5lib/ の JAR を1本抜くと、どこで気づけるか

11本を1本ずつ抜いて、ビルドから応答までを試した結果です(各1回)。ビルドが失敗して気づけるのは、自分のコードが直接使っている2本だけでした。 を抜いた場合は、ビルドも Tomcat への配置も成功と表示され、URL が 404 になるだけで、標準出力のログにエラーの行がありませんでした。原因は Tomcat の別のログファイル logs/localhost.<日付>.log にだけ記録されています。依存を辿ってくれる仕組みが Ant 本体には無いので、この表を人が管理することになります。

抜いた JAR ごとの結果と、commons-logging・spring-webmvc を抜いたときの Tomcat のログ表
抜いた JAR 気づける時点 起きること
spring-web ビルド コンパイルエラー package org.springframework.web.bind.annotation does not exist
spring-context ビルド コンパイルエラー package org.springframework.stereotype does not exist
spring-beans Tomcat に置いた時 配置に失敗 NoClassDefFoundError: org/springframework/beans/FatalBeanException
spring-core Tomcat に置いた時 配置に失敗 NoClassDefFoundError: org/springframework/core/NestedRuntimeException
commons-logging Tomcat に置いた時 配置に失敗 NoClassDefFoundError: org/apache/commons/logging/Log
spring-aop Tomcat に置いた時 初期化に失敗 NoClassDefFoundError: org/springframework/aop/TargetSource
spring-expression Tomcat に置いた時 初期化に失敗 NoClassDefFoundError: org/springframework/expression/ParserContext
micrometer-observation Tomcat に置いた時 DispatcherServlet の初期化に失敗。リクエストは 500
micrometer-commons 最初のリクエスト 起動は成功。リクエストで 500 NoClassDefFoundError: io/micrometer/common/docs/KeyName
spring-webmvc (標準出力には出ない) ビルドも配置も成功と表示。/app/* がすべて 404。別のログファイルに記録
jspecify (気づかない) 何も起きなかった(200 も 404 も正常)
 
# commons-logging を抜いた WAR を置いたときの Tomcat のログ(… は省略)
SEVERE [main] org.apache.catalina.startup.HostConfig.deployWAR Error deploying web application archive [/usr/local/tomcat/webapps/shop.war]
…
Caused by: java.lang.NoClassDefFoundError: org/apache/commons/logging/Log
…
Caused by: java.lang.ClassNotFoundException: org.apache.commons.logging.Log
 
# spring-webmvc を抜いた WAR を置いたときの logs/localhost.<日付>.log(標準出力には出ない。… は省略)
SEVERE [main] org.apache.catalina.core.StandardContext.loadOnStartup Servlet [dispatcher] in web application [/shop] threw load() exception
java.lang.ClassNotFoundException: org.springframework.web.servlet.DispatcherServlet
at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1315)
…

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