サンプルコードで身につける: Ant で Spring の WAR を作る
解説で学んだ概念を、5つの具体例で確認します。素朴な例から実務寄りの例へと進みます。
1プロジェクトの tree
ビルド前のファイル一覧です。 の 11本は、人がダウンロードして置いたものです。src/ の直下がいきなり jp/co/... で、 という階層はありません。あの階層は というの規約で、 には無いためです(Maven は後のレッスンで扱います)。ビルドすると、classes/ と dist/shop.war が増えます。
.├── 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.java11 directories, 19 files
2build.xml の全文: war タスクが詰める場所を決める
この の中心は最後の war です。 で の中身を の直下に、classes でコンパイル結果を に、lib で lib/ の JAR を に入れます。lib-compile の JAR はコンパイル用のにだけ入れていて、WAR には入れていません。src や WebContent という名前は、上の に自分で書いたものです。
<?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 の に渡すという指定です。自分で書いた が無くても動くのは、ここが入口になっているからです。
<?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 で非推奨になったことを知らせるものです。
$ antBuildfile: /w/shop/build.xmlcompile:[mkdir] Created dir: /w/shop/build/classes[javac] Compiling 2 source files to /w/shop/build/classeswar:[mkdir] Created dir: /w/shop/dist[war] Building war: /w/shop/dist/shop.warBUILD SUCCESSFULTotal 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 started01-Oct-2026 22:28:03.867 INFO [main] org.springframework.web.context.ContextLoader.initWebApplicationContext Root WebApplicationContext initialized in 138 ms01-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 ms01-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] ms01-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/helloHTTP/1.1 200Content-Type: text/plain;charset=ISO-8859-1Content-Length: 36Date: Thu, 01 Oct 2026 22:28:04 GMTHello, world (from GreetingService)
5lib/ の JAR を1本抜くと、どこで気づけるか
11本を1本ずつ抜いて、ビルドから応答までを試した結果です(各1回)。ビルドが失敗して気づけるのは、自分のコードが直接使っている2本だけでした。 を抜いた場合は、ビルドも Tomcat への配置も成功と表示され、URL が 404 になるだけで、標準出力のログにエラーの行がありませんでした。原因は Tomcat の別のログファイル logs/localhost.<日付>.log にだけ記録されています。依存を辿ってくれる仕組みが Ant 本体には無いので、この表を人が管理することになります。
抜いた JAR 気づける時点 起きることspring-web ビルド コンパイルエラー package org.springframework.web.bind.annotation does not existspring-context ビルド コンパイルエラー package org.springframework.stereotype does not existspring-beans Tomcat に置いた時 配置に失敗 NoClassDefFoundError: org/springframework/beans/FatalBeanExceptionspring-core Tomcat に置いた時 配置に失敗 NoClassDefFoundError: org/springframework/core/NestedRuntimeExceptioncommons-logging Tomcat に置いた時 配置に失敗 NoClassDefFoundError: org/apache/commons/logging/Logspring-aop Tomcat に置いた時 初期化に失敗 NoClassDefFoundError: org/springframework/aop/TargetSourcespring-expression Tomcat に置いた時 初期化に失敗 NoClassDefFoundError: org/springframework/expression/ParserContextmicrometer-observation Tomcat に置いた時 DispatcherServlet の初期化に失敗。リクエストは 500micrometer-commons 最初のリクエスト 起動は成功。リクエストで 500 NoClassDefFoundError: io/micrometer/common/docs/KeyNamespring-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() exceptionjava.lang.ClassNotFoundException: org.springframework.web.servlet.DispatcherServletat org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1315)…