山崎屋の技術メモ

IT業界で働く中で、気になること、メモしておきたいことを書いていきます。

Spring MVC を使用して Web アプリケーションの作成。Boot は使わない。

最近では Spring Boot で簡単にプロジェクトを作成できて便利ですね。

だけど Boot に頼ってばかりだと Spring の仕組みを忘れてしまうので、たまには一から Spring MVC を使用した Web アプリ( Hello World! )を作ってみたいと思います。

Eclipse 上で Tomcat を起動し、アプリが実際にブラウザでアクセスできるところまでを確認します。

Spring のバージョンは、 5.2.4、サーブレットコンテナには Tomcat 9 を使用します。

開発には Spring Tool Suite(STS)を使用しました。
Spring Tool Suite(STS)のインストールと起動 - 山崎屋の技術メモ

Tomcat のインストールはこちらを参考に。
Tomcat9 インストール - 山崎屋の技術メモ

さっそく作っていきましょう。

maven プロジェクトの作成

メニューから File → New → Other... を選択します。
f:id:yyama1556:20200321100737p:plain

そして Maven → Maven プロジェクト。
f:id:yyama1556:20200321100804p:plain

Maven で用意されているひな形を使用せず、シンプルなプロジェクトを作成するので、「Create a simple project (skip archetype selection)」にチェックをして「Next」をクリック。
f:id:yyama1556:20200321101309p:plain

「Group Id」、「Artifact Id」を入力して「Finish」をクリック。
f:id:yyama1556:20200321101548p:plain

これで中身のない Maven プロジェクトが作成されました。
f:id:yyama1556:20200321102216p:plain

ここから各種ファイルを作成・編集し、Web アプリを作成していきます。

pom.xml の編集

Spring MVC で必要な jar ファイルの依存関係を定義します。

最終的に以下のようになりました。

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>org.yyama</groupId>
  <artifactId>sample</artifactId>
  <version>0.0.1-SNAPSHOT</version>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>11</maven.compiler.source>
    <maven.compiler.target>11</maven.compiler.target>
  </properties>
  
  <dependencies>
    <dependency>
	    <groupId>org.springframework</groupId>
	    <artifactId>spring-webmvc</artifactId>
	    <version>5.2.4.RELEASE</version>
    </dependency>
	<dependency>
	    <groupId>javax.servlet</groupId>
	    <artifactId>javax.servlet-api</artifactId>
	    <version>4.0.1</version>
	    <!-- 動作時はTomcat のライブラリを使用するので開発時だけ依存する。 -->
    	<scope>provided</scope>
	</dependency>
  </dependencies>
  
</project>

pom.xml の修正をしたら、依存関係の更新を忘れないようにしましょう。プロジェクトを右クリックして「Maven → Update Project...」です。

Bean 定義ファイルの作成

通常 Spring MVC プロジェクトでは 2 つの Bean 定義ファイルを作成します。

一つはドメインやサービスなど、ビジネスロジックのクラスを管理する Bean 定義。

もう一つはコントローラやビューを管理する Bean 定義です。

管理する Bean が多く、2 ファイルで足りない場合は更に分割しても良いでしょう。

ビジネスロジックの Bean 定義

resource 配下に config フォルダを作成し、spring.xml ファイルを作成します。

格納フォルダやファイル名は好みで変えても問題ありません。

今回は Hello World! を表示するだけなのでビジネスロジックはありません。以下の通り空っぽです。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
</beans>

プレゼンテーション層の Bean 定義

同様に config 配下に spring-mcv.xml を作成し、以下のように記載します。各設定の意味はコメントで記載しています。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
    xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:util="http://www.springframework.org/schema/util"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

	<!-- Spring MVC の機能を使うことを宣。 -->
	<!-- この宣言をすることで、 @Component などのアノテーションが使えるようになる。 -->
	<mvc:annotation-driven />
	
	<!-- Bean となるクラスファイルが格納されているパッケージを宣言。 -->
	<!-- Spring はこのパッケージ配下を自動でスキャンし、Bean として登録する。 -->
	<context:component-scan base-package="org.yyama" />
	
	<!-- JSP を使用するための宣言 -->
	<mvc:view-resolvers>
		<!-- コントローラが JSP 名(拡張子なし)を返した際、Spring が 「.jsp」を付与し「/WEB-INF/views/」配下から探すように設定。 -->
		<mvc:jsp prefix="/WEB-INF/views/" suffix=".jsp"/>
	</mvc:view-resolvers>

</beans>

component-scan については別記事もあるので詳しく知りたい方は参照してください。
【Spring Framework】component-scanのいろいろ① - 山崎屋の技術メモ
【Spring Framework】component-scanのいろいろ② - 山崎屋の技術メモ
【Spring Framework】component-scanのいろいろ③ - 山崎屋の技術メモ

コントローラの作成

次にリクエストを受け付けるコントローラを作成します。
「src/main/java」フォルダの下に「org.yyama.controller」パッケージを作成し「SampleController.java」を作成します。

package org.yyama.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class SampleController {
	@RequestMapping("/")
	public String sample() {
		return "sample";
	}
}

@Controller を指定すると Spring が Bean として管理してくれます。

@RequestMapping はリクエスト URL と処理を担当するメソッドを紐づけます。過去の記事を参照ください。
【Spring MVC】@RequestMapping の基本 - 山崎屋の技術メモ

JSP の作成

「src/main」の下に「webapps/WEB-INF/views」というディレクトリを作成し、「sample.jsp」を作成します。中は次の通りです。

<%@ page language="java" contentType="text/html; charset=utf-8"
	pageEncoding="utf-8"%>
<!DOCTYPE html>
<html>
<head>
	<meta charset="utf-8">
	<title>山崎サンプル</title>
</head>
<body>
	Hello yama World!
</body>
</html>

web.xml の作成

「WEB-INF」 配下に「web.xml」を次のように作成します。各設定の意味はコメントで記載しています。

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"                  
         version="4.0">
  
  <!-- ビジネスロジックのBean 定義ファイル -->       
  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>
      classpath:/config/spring.xml
    </param-value>
  </context-param>
  
  <!-- リスナーを登録 -->
  <listener>
    <listener-class>
      org.springframework.web.context.ContextLoaderListener
    </listener-class>
  </listener>
  
  <!-- 文字のエンコーディングを指定し  -->
  <filter>
    <filter-name>characterEncodingFilter</filter-name>
    <filter-class>
      org.springframework.web.filter.CharacterEncodingFilter
    </filter-class>
    <init-param>
      <param-name>encoding</param-name>
      <param-value>UTF-8</param-value>
    </init-param>
  </filter>

  <!-- 上記フィルターをすべての URL で適用する。 -->
  <filter-mapping>
    <filter-name>characterEncodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>

  <!-- Spring MVC アプリの場合、だいたいは唯一のサーブレットを登録する。 -->
  <servlet>
    <servlet-name>dispatcherServlet</servlet-name>
    <servlet-class>
      org.springframework.web.servlet.DispatcherServlet
    </servlet-class>
    <!-- Spring MVC の Bean 定義を登録 -->
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>
        classpath:/config/spring-mvc.xml
      </param-value>
    </init-param>
  </servlet>

  <!-- すべての URL リクエストについて、上記で登録したサーブレットで処理する。 -->
  <servlet-mapping>
    <servlet-name>dispatcherServlet</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>

</web-app>

これで一通り Web アプリケーションに必要なファイルが準備できました。

ファイルの配置について載せておきます。
f:id:yyama1556:20200322110822p:plain

Eclipse 上で単体実行

せっかくなので Eclipse 上で Tomcat を起動させ、作ったアプリの単体動作確認の方法までメモしておきます。

Eclipse で Tomcat を実行する。

まず、Server のウィンドウを表示させます。「Window → Show View → Other...」で表示する View の選択画面を表示します
f:id:yyama1556:20200321151447p:plain

「Server → Servers」を選択し、Server ウィンドウを表示させます。
f:id:yyama1556:20200321151739p:plain

Server ウィンドウで右クリックして「New → Server」を選択します。
f:id:yyama1556:20200321152003p:plain

サーバの種類を選択する画面が表示されるので「Apache → Tomcat v9.0 Server」を選択します。
f:id:yyama1556:20200321152239p:plain

Tomcat のインストールディレクトリを聞かれるので、入力して「Next」。
f:id:yyama1556:20200321152621p:plain

次に Tomcat に乗せるアプリを選択する画面に行きますが、我々が作成したサンプルアプリは動的 Web アプリとして認識されていないようで表示されません。
f:id:yyama1556:20200321152826p:plain

あとから Web アプリを選択しなおせばいいので、一旦「finish」ボタンを押して Server の作成を完了させます。

Eclipse に Dynamic Web Module(動的Webプロジェクト) として認識させる

パッケージエクスプローラで sample プロジェクトを選択し、右クリック → Properties → Project Facets を選択します。次のような画面が表示されました。
f:id:yyama1556:20200321153515p:plain

This project is not configured to use project facets. Converting this project to faceted form will allow you to use easily control the available technologies.

プロジェクトファセットが設定されていないため、動的 Web アプリとして認識されていないようです。

先ほどの画面の「Convert to faceted from...」を選択し、次の画面で Dynamic Web Module にチェックを入れて Version に 4.0 を選択しました。
f:id:yyama1556:20200321154144p:plain

Server ウィンドウの Tomcat9 を右クリックして「Add or Remove」を選択したら、無事 sample アプリケーションが表示されたので右側に移動して「Finish」を押します。
f:id:yyama1556:20200322103253p:plain

この操作で困ったことが二つ起きましたので解決方法メモしておきます。

ワーニングが発生

Eclipse の Problems ウィンドウに警告が表示されるようになりました。
f:id:yyama1556:20200322111654p:plain

Description の内容は次の通りです。

Classpath entry org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER will not be exported or published. Runtime ClassNotFoundExceptions may result.

なおワーニングを放置して実行すると Tomcat 起動時に次の例外が発生します。

重大: クラス [org.springframework.web.context.ContextLoaderListener] のアプリケーションリスナの設定中にエラーが発生しました
java.lang.ClassNotFoundException: org.springframework.web.context.ContextLoaderListener
	at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1365)
	at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1188)
	at org.apache.catalina.core.DefaultInstanceManager.loadClass(DefaultInstanceManager.java:540)
	at org.apache.catalina.core.DefaultInstanceManager.loadClassMaybePrivileged(DefaultInstanceManager.java:521)
	at org.apache.catalina.core.DefaultInstanceManager.newInstance(DefaultInstanceManager.java:150)
 ・
 ・
 ・
(以下略)

ググって解決方法を調べました。
spring - java.lang.ClassNotFoundException: org.springframework.web.context.ContextLoaderListener - Stack Overflow

パッケージエクスプローラのプロジェクトを「右クリック → properties → Deploy Assenbly」と進み Add ボタンを押します。
f:id:yyama1556:20200322120225p:plain

「Java Build Path Entry」を選択して「Next」。
f:id:yyama1556:20200322120543p:plain

続いて「Maven Dependencies」を選択して「Finish」。
f:id:yyama1556:20200322120628p:plain

これでワーニングが消えました。めでたし。

webapps 以下のディレクトリとファイルがデプロイされない問題

このまま Tomcat を起動して URL を入力しても 404 エラーとなってしまいました。

調べると webapps 配下のディレクトリとファイルがデプロイされていません。

なのでデプロイの設定に追記します。

プロジェクト直下の「.setting」ディレクトリの中に「org.eclipse.wst.common.component」というファイルがあります。(パッケージエクスプローラだと表示されないファイルなので Navigator 等を利用してください。)

f:id:yyama1556:20200322121303p:plain

ファイルに一行追加します。追加した箇所はコメントを参照してください。

<?xml version="1.0" encoding="UTF-8"?><project-modules id="moduleCoreId" project-version="1.5.0">
    <wb-module deploy-name="sample">
        <wb-resource deploy-path="/" source-path="/src/main/webapps" /><!-- ここを追加 -->
        <wb-resource deploy-path="/WEB-INF/classes" source-path="/src/main/java"/>
        <wb-resource deploy-path="/WEB-INF/classes" source-path="/src/main/resources"/>
        <wb-resource deploy-path="/WEB-INF/classes" source-path="/src/test/java"/>
        <wb-resource deploy-path="/WEB-INF/classes" source-path="/src/test/resources"/>
        <wb-resource deploy-path="/WEB-INF/classes" source-path="/target/generated-sources/annotations"/>
        <wb-resource deploy-path="/WEB-INF/classes" source-path="/target/generated-test-sources/test-annotations"/>
        <wb-resource deploy-path="/" source-path="/WebContent" tag="defaultRootSource"/>
        <property name="context-root" value="sample"/>
        <property name="java-output-path" value="/sample/target/classes"/>
    </wb-module>
</project-modules>

実行する

ようやくこれで動きます。

Tomcat を起動して、ブラウザで「http://localhost:8080/sample」にアクセスすると。。。

f:id:yyama1556:20200322122134p:plain

めでたしめでたし。

まとめ

以上、Spring Boot を頼らずに Spring Web アプリケーションを作るサンプルでした。

思いもよらず長編となった。。。

Spring Boot のありがたみ。。。

Spring Framework 5 プログラミング入門

Spring Framework 5 プログラミング入門

Spring Boot 2 プログラミング入門

Spring Boot 2 プログラミング入門