HAYASHIER.COM - Private Page
Spring MVCを導入 (+ MySQL, Redis)

Spring MVCが動作する仕組みについて、設定等が理解できるように、最初にイメージできるように整理しておきます。大きく以下の流れとなります。

  1. Front ControllerとなるDispatcherServletでリクエストを受付
  2. XMLファイルからハンドラーマッピングのエントリーを取得し、Controllerへリクエストを転送
  3. ControllerはModelAndViewのオブジェクトを返す
  4. DispatcherServletはXMLファイル中のViewResolverのエントリーを確認し、指定されたビューのコンポーネントを呼び出す

(引用) 22. Web MVC framework

(参考) Spring MVC Tutorial

Spring MVCでHello,Worldを表示 (Mavenでパッケージ管理)

実際にプロジェクトを作成していきます。以下の手順でMaven管理による Spring MVCのプロジェクトを作成します。

  1. File > New > Dynamic Web Project を選択

  2. 遷移先の画面で Project Name: HayashierWebsite のようにプロジェクト名を入力

  3. Target runtimeで[New Runtime…]を選択

  4. Apache Tomcat v9.0を選択し、[Create a new local server]を選択

  5. Tomcatのサイトから9.0.34のzipをダウンロードして展開

  6. 以下のように入力。Tomcatのディレクトリは上記ファイルをダウンロードしてきて展開したものの場所。JREは使用しているJavaのバージョンを確認して選択。 

    • Name: Apache Tomcat v9.0
    • Tomcat installation directory: /Users/hayashier/Downloads/apache-tomcat-9.0.34
    • JRE: Java SE 11.0.5 [11.0.5]
    $ java --version
    java 11.0.5 2019-10-15 LTS
    Java(TM) SE Runtime Environment 18.9 (build 11.0.5+10-LTS)
    Java HotSpot(TM) 64-Bit Server VM 18.9 (build 11.0.5+10-LTS, mixed mode)
  7. プロジェクト名を右クリックして、Configure > Convert to Maven Project と選択

  8. 遷移先の画面で以下を入力して、Finishを選択

    • Group Id: hayashier.website
    • Artifact Id: hayashier-website
  9. 生成されたpom.xmlを編集して以下のようなXMLファイルにします。

    <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>hayashier.website</groupId>
    <artifactId>hayashier-website</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>war</packaging>
    <properties>
        <org.springframework.version>5.2.5.RELEASE</org.springframework.version>
        <org.springframework.data.version>2.2.7.RELEASE</org.springframework.data.version>
        <javax.servlet.version>1.2</javax.servlet.version>
        <org.apache.tomcat.embed.version>9.0.34</org.apache.tomcat.embed.version>
    </properties>
    <build>
        <sourceDirectory>src</sourceDirectory>
        <plugins>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.0</version>
                <configuration>
                    <release>11</release>
                </configuration>
            </plugin>
            <plugin>
                <artifactId>maven-war-plugin</artifactId>
                <version>3.2.1</version>
                <configuration>
                    <warSourceDirectory>WebContent</warSourceDirectory>
                </configuration>
            </plugin>
        </plugins>
    </build>
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>${org.springframework.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${org.springframework.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>${org.springframework.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>${org.springframework.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
            <version>${org.springframework.version}</version>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>jstl</artifactId>
            <version>${javax.servlet.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-jasper</artifactId>
            <version>${org.apache.tomcat.embed.version}</version>
        </dependency>
    </dependencies>
    </project>
  10. プロジェクト名を右クリックして、Java EE Tools > Generate Deployment Descriptor Stub と選択してweb.xmlを生成

  11. 生成されたweb.xmlのweb-appタグ内に以下を追加

    <servlet>
        <servlet-name>mywebsite</servlet-name>
        <servlet-class>
        org.springframework.web.servlet.DispatcherServlet
        </servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>mywebsite</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
  12. web.xml中のservlet-nameタグに記載されている名前がサーブレット名でWEB-INF/以下に[servlet-name]-servlet.xmlという名前の設定ファイルをispatcherServlet初期化後に参照されるようになります。ここではmywebsite-servlet.xmlというファイル名で以下の内容を記載します。XMLやアノテーションの使用より Spring 3 で導入された JavaConfig を利用することも多いかと思いますが、ここではXMLによる例となります。

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans     
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/mvc 
        http://www.springframework.org/schema/mvc/spring-mvc.xsd
        http://www.springframework.org/schema/context 
        http://www.springframework.org/schema/context/spring-context.xsd">
 
    <mvc:annotation-driven />
    <context:component-scan
        base-package="hayashier.mywebsite" />
    <mvc:default-servlet-handler />

</beans>
  1. その他、CSS/JavaScriptを利用できるように[servlet-name]-servlet.xmlのbeansタグ内に以下を追加しておきます。ここでは、ローカルの開発環境での開発効率向上を優先してCSSはキャッシュを無効化していますが、不要な場合、mvc:resource-chainタグおよびその中身は不要です。
    <mvc:resources mapping="/js/**" location="/WEB-INF/resources/js/" />
    <mvc:resources mapping="/css/**" location="/WEB-INF/resources/css/">
        <mvc:resource-chain resource-cache="false">
            <mvc:resolvers>
                <mvc:version-resolver>
                    <mvc:content-version-strategy patterns="/**"/>
                </mvc:version-resolver>
            </mvc:resolvers>
        </mvc:resource-chain>
    </mvc:resources>
  1. 同ファイル内に共通ヘッダーを作成できるように以下も追加しておきます。
    <mvc:resources mapping="/common/**" location="/WEB-INF/views/common/" />
  1. 現状の設定のままだと、http://localhost:8080/hayashier-website/ でないとアクセスできない状態になっている。ルートアクセスするために、ServersタブでTomcat v 9.0 Server at localhostとなっている部分をクリックして展開
  2. Modulesタブを選択すると、Pathが/hayashier-websiteとなっていることが確認
  3. Pathを選択した状態で、[Edit…]を選択し、/に変更して再起動することでルートアクセスできるようになる。
  4. 再度15を実行
  5. Portsの項目でHTTP/1.1が8080になっているので、80に変更。その後、ポート番号を指定しなくてもアクセスできるようになる。
  6. Run As > Run on Server を選択
  7. http://localhost でアクセスできるようになる

Summary

ここまでで作成したファイルは以下のようになります。

<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>hayashier.website</groupId>
    <artifactId>hayashier-website</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>war</packaging>
    <properties>
        <org.springframework.version>5.2.5.RELEASE</org.springframework.version>
        <org.springframework.data.version>2.2.7.RELEASE</org.springframework.data.version>
        <javax.servlet.version>1.2</javax.servlet.version>
        <org.apache.tomcat.embed.version>9.0.34</org.apache.tomcat.embed.version>
    </properties>
    <build>
        <sourceDirectory>src</sourceDirectory>
        <plugins>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.0</version>
                <configuration>
                    <release>11</release>
                </configuration>
            </plugin>
            <plugin>
                <artifactId>maven-war-plugin</artifactId>
                <version>3.2.1</version>
                <configuration>
                    <warSourceDirectory>WebContent</warSourceDirectory>
                </configuration>
            </plugin>
        </plugins>
    </build>
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>${org.springframework.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${org.springframework.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>${org.springframework.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>${org.springframework.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
            <version>${org.springframework.version}</version>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>jstl</artifactId>
            <version>${javax.servlet.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-jasper</artifactId>
            <version>${org.apache.tomcat.embed.version}</version>
        </dependency>
    </dependencies>
</project>
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0">
  <display-name>Hayashier.com - Private page</display-name>
  <welcome-file-list>
    <welcome-file>index.jsp</welcome-file>
  </welcome-file-list>
  
  <servlet>
    <servlet-name>mywebsite</servlet-name>
    <servlet-class>
      org.springframework.web.servlet.DispatcherServlet
    </servlet-class>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>mywebsite</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>
</web-app>
<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans     
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/mvc 
        http://www.springframework.org/schema/mvc/spring-mvc.xsd
        http://www.springframework.org/schema/context 
        http://www.springframework.org/schema/context/spring-context.xsd">
 
    <mvc:annotation-driven />
    <context:component-scan
        base-package="hayashier.mywebsite" />
    <mvc:default-servlet-handler />

    <bean id="viewResolver"
        class="org.springframework.web.servlet.view.UrlBasedViewResolver">
        <property name="viewClass"
            value="org.springframework.web.servlet.view.JstlView" />
        <property name="prefix" value="/WEB-INF/jsp/" />
        <property name="suffix" value=".jsp" />
    </bean>

    <mvc:resources mapping="/js/**" location="/WEB-INF/resources/js/" />
    <mvc:resources mapping="/css/**" location="/WEB-INF/resources/css/">
        <mvc:resource-chain resource-cache="false">
            <mvc:resolvers>
                <mvc:version-resolver>
                    <mvc:content-version-strategy patterns="/**"/>
                </mvc:version-resolver>
            </mvc:resolvers>
        </mvc:resource-chain>
    </mvc:resources>
    <mvc:resources mapping="/common/**" location="/WEB-INF/views/common/" />

</beans>
<!DOCTYPE html>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<html>
    <head>
        <title>Test Title</title>
    </head>
    <body>
        Hello, World!
    </body>
</html>

Spring MVCによるルーティング

リクエストパスと実際に行う処理について、対象のControllerには@Controllerのアノテーションをつけ、処理を行いたいメソッドに対してパスを定義した@RequestMappingのアノテーションを付与します。 例えば、以下のようにTopControllerを作成して@Controllerのアノテーションを付与し、helloWorld()メソッドに@RequestMapping(“/top”)のアノテーションを付与することで、http://localhost/top のようにアクセスすることでhelloworld()メソッドによる処理が行われるようになります。

return new ModelAndView("top", "message", message)のように第1引数で指定した文字列の JSP ファイルのビューを返し、第3引数に渡した値に対し第2引数に渡した文字列をビューで指定することで呼び出すことができます。ここでは、“message”という文字列を第2引数として、第3引数に<h1>Hello, World!</h1>を渡しているため、ビューから${message}のように呼び出すことで、ビュー上で <h1>Hello, World!</h1>を表示させることができます。

package hayashier.mywebsite.controller;

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

@Controller
public class TopController {

    @RequestMapping("/top")
    public ModelAndView helloWorld() {
        String message = "<h1>Hello, World!</h1>";
        return new ModelAndView("top", "message", message);
    }
    
}
<!DOCTYPE html>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<html>
    <head>
        <meta charset="UTF-8">
        <title>Top | hayashier.com</title>
    </head>
    <body>
        ${message}
    </body>
</html>

Spring MVCにMySQL導入

pom.xml中のprojectタグに以下を追加

  <properties>
    <org.springframework-version>5.2.5.RELEASE</org.springframework-version>
    <mysql-connector-java.version>8.0.20</mysql-connector-java.version>
  </properties>

projectタグ内のdependenciesタグに以下を追加

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>${org.springframework-version}</version>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>${mysql-connector-java.version}</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-tx</artifactId>
    <version>${org.springframework-version}</version>
</dependency>

mywebsite-servlet.xmlのbeansタグにデータベースへアクセスするためのドライバー情報を追加

<bean id="dataSource"
    class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    <property name="driverClassName" value="com.mysql.jdbc.Driver" />
    <property name="url" value="jdbc:mysql://localhost:3306/mywebsite" />
    <property name="username" value="root" />
    <property name="password" value="Test1234" />
</bean>

<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    <property name="dataSource" ref="dataSource" />
</bean>

最小構成では、Controllerで以下のようにアクセス可能です。

@Controller
public class HomeController {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @RequestMapping("/article/")
    public String show(Model model) {

    final String selectQuery = "select * from Article";
    
    Map<?, ?> map = jdbcTemplate.queryForMap(selectQuery);
    model.addAttribute("data", map.toString());
    return "article";
    }
}

実際にはControllerに全てを記述せずにモデル等を定義することが多いかと思います。

ここでは、Articleのオブジェクトを表すクラスを以下のように定義します。

package hayashier.mywebsite.model;

public class Article {
    private Integer id;
    private String title;
    private String body;
    private String path;
    
    public Article() {
        
    }
    
    public Article(Integer id, String title, String body, String path) {
        this.id = id;
        this.title = title;
        this.body = body;
        this.path = path;
    }
    
    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }
    public String getTitle() {
        return title;
    }
    public void setTitle(String title) {
        this.title = title;
    }
    public String getBody() {
        return body;
    }
    public void setBody(String body) {
        this.body = body;
    }
    public String getPath() {
        return path;
    }
    public void setPath(String path) {
        this.path = path;
    }
}

Mapperを定義します。

package hayashier.mywebsite.mapper;

import java.sql.ResultSet;
import java.sql.SQLException;

import org.springframework.jdbc.core.RowMapper;

import hayashier.mywebsite.model.Article;

public class ArticleMapper implements RowMapper<Article> {
    public static final String BASE_SQL =
            "SELECT a.id, a.title, a.body, a.path"
            + " FROM Article a";

    @Override
    public Article mapRow(ResultSet rs, int nowNum) throws SQLException {
        Integer id = rs.getInt("id");
        String title = rs.getString("title");
        String body = rs.getString("body");
        String path = rs.getString("path");
        
        return new Article(id, title, body, path);
    }

}

DAOを定義します。

package hayashier.mywebsite.dao;

import java.util.List;

import javax.sql.DataSource;

import org.springframework.jdbc.core.support.JdbcDaoSupport;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

import hayashier.mywebsite.mapper.ArticleMapper;
import hayashier.mywebsite.model.Article;

@Repository
@Transactional
public class ArticleDAO extends JdbcDaoSupport {

    public ArticleDAO(DataSource dataSource) {
        this.setDataSource(dataSource);
    }
    
    public List<Article> getArticles() {
        String sql = ArticleMapper.BASE_SQL;
        
        Object[] params = new Object[] {};
        ArticleMapper mapper = new ArticleMapper();
        List<Article> articles = this.getJdbcTemplate().query(sql, params, mapper);
        
        return articles;
    }
    
    public Article getArticleFromPath(String path) {
        String sql = ArticleMapper.BASE_SQL
                + " WHERE a.path = ?";
        
        Object[] params = new Object[] { path };
        ArticleMapper mapper = new ArticleMapper();
        List<Article> articles = this.getJdbcTemplate().query(sql, params, mapper);
        
        if (articles.size() > 0) {
            return articles.get(0);
        }
        
        return new Article();
    }
}

Controllerから以下のように使用することができる

@Controller
public class ListController {

    @Autowired
    private ArticleDAO articleDAO;

    @RequestMapping("/list/{page_id}")
    public ModelAndView list(@PathVariable int page_id) {
        ArrayList<Article> articles = (ArrayList<Article>) articleDAO.getArticles();

MySQL上で以下のようにDDLを定義して、サンプルデータを挿入しておきます。

CREATE DATABASE IF NOT EXISTS `mywebsite` DEFAULT CHARACTER SET utf8mb4;
USE `mywebsite`;

DROP TABLE IF EXISTS Article;
CREATE TABLE Article (
  id integer NOT NULL AUTO_INCREMENT,
  title VARCHAR(255),
  body LONGTEXT,
  path VARCHAR(255),
  PRIMARY KEY (id),
  UNIQUE (path)
);

INSERT INTO Article (title, body, path) VALUES (
    'test title',
    'Test Body',
    'test-path'
);

設定ファイルを外部ファイル化

MySQLのホスト情報やユーザー名やパスワードを外部ファイル化しておきます。ファイルの場所は相対ディレクトリ指名やclasspath:,file:/// or file: が使用できます。

mywebsite-servlet.xmlにファイルの場所を示す以下のbeanタグを追加します。

    <bean id="appProperties"
        class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="locations">
            <list>
                <value>classpath:db.properties</value>
            </list>
        </property>
    </bean>

また、外部ファイルから読み込んだ値を設定するために同ファイル内に以下のbeanタグを追加しておきます。 

    <bean id="dataSource"
        class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver" />
        <property name="url" value="${db.url}" />
        <property name="username" value="${db.username}" />
        <property name="password" value="${db.password}" />
    </bean>

WebContent/WEB-INF以下にclassesディレクトリを作成し、WEB-INF/classes ディレクトリに.propertiesのファイルを配置します。

db.url=localhost
db.username=root
db.password=Test1234

Spring MVCにRedisの導入

pom.xmlに以下を追加

    <dependency>
        <groupId>org.springframework.data</groupId>
        <artifactId>spring-data-redis</artifactId>
        <version>${org.springframework.data.version}</version>
    </dependency>
    <dependency>
        <groupId>redis.clients</groupId>
        <artifactId>jedis</artifactId>
        <version>${jedis.version}</version>
    </dependency>

mywebsite-servlet.xmlの<value>classpath:db.properties</value>の下に<value>classpath:cache.properties</value>を追加してRedisの設定ファイルの場所を指定します。

    <bean id="appProperties"
        class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="locations">
            <list>
                <value>classpath:db.properties</value>
                <value>classpath:cache.properties</value>
            </list>
        </property>
    </bean>

外部ファイルで指定した値を読み込んで設定等行うために以下を追加します。

    <bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
        <property name="maxTotal" value="200" />
        <property name="maxIdle" value="50" />
        <property name="maxWaitMillis" value="3000" />
        <property name="testOnBorrow" value="true" />
    </bean>

    <bean id="jedisFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
        <property name="hostName" value="${cache.host}" />
        <property name="port" value="${cache.port}" />
        <property name="poolConfig" ref="jedisPoolConfig" />
        <property name="usePool" value="true" />
    </bean>
  
    <bean id="stringRedisSerializer" class="org.springframework.data.redis.serializer.StringRedisSerializer" />
    <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate" p:connection-factory-ref="jedisFactory" p:valueSerializer-ref="stringRedisSerializer" p:keySerializer-ref="stringRedisSerializer" />

WebContent/WEB-INF/classes以下にcache.propertiesという名前で以下の内容のファイルを作成します。

cache.host=localhost
cache.port=6379
cache.password=
cache.timeout=

DAOファイルを定義。ここれではRedisTemplateのHash型を使用しています。

@Repository
@Transactional
public class ArticleCacheDAO {

    // The purpose of this threshold is to prevent from not being able to get data
    //  caused by data loss event, for example, cache node failure or reboot
    public final static int CACHE_FOR_THREAHOLD = 0;

    @Autowired
    RedisTemplate<String,Object> cacheTemplate;

    @Resource(name="redisTemplate")
    HashOperations<String,String,Article> hashOps;

    private static final String KEY = "mywebsite:articles";
    TimeUnit ARTICLE_EXPIRED_TIME_UNIT = TimeUnit.DAYS;
    int ARTICLE_TIME_OUT = 24;

    public Article getArticle(String path) {
        return hashOps.get(KEY, path);
    }

    public List<Article> getAllArticles() { 
        return new ArrayList<Article>(hashOps.entries(KEY).values());
    }

    public void addArticle(Article article) {
        hashOps.putIfAbsent(KEY, article.getPath(), article);
        cacheTemplate.expire(KEY, ARTICLE_TIME_OUT, ARTICLE_EXPIRED_TIME_UNIT);
    }
    public void addArticles(List<Article> articles) {
        for (Article article : articles) {
            addArticle(article);
        }
    }

    public long getNumberOfArticles() {
        return hashOps.size(KEY);
    }

}

モデルをSerializableのインターフェイスとして実装するように変更しておきます。

public class Article implements Serializable {

あとは使う場所のクラスでメンバー変数として宣言して使用します。

@Autowired
private ArticleCacheDAO articleCacheDAO;

要件に応じて、SQLで取得する前にキャッシュから取得して、データがなければ、SQLで取得し、その後キャッシュも更新するといった実装をします。

public Article getArticleFromPath(String path) {
    Article article = articleCacheDAO.getArticle(path);
    if (article != null) return article;

    String sql = ArticleMapper.BASE_SQL
            + " WHERE a.path = ?";
    
    Object[] params = new Object[] { path };
    ArticleMapper mapper = new ArticleMapper();
    List<Article> articles = this.getJdbcTemplate().query(sql, params, mapper);
    
    if (articles.size() == 0) {
        return new Article();
    }
    
    article = articles.get(0);
    articleCacheDAO.addArticle(article);
    
    return article;
}

Tomcat上にSpring MVCのプロジェクトをデプロイ

先程はEclipseで組み込みの機能のTomcatを利用して動作確認をしていましたが、MacOS上にTomcatをインストールしてこの上で、Spring MVCを動作させます。上記でSpring MVCをwarファイルとして作成していたため、これをTomcatでWebサーバとして稼働させるものの上に配置して動作させます。

  1. 以下のようにHomebrewでTomcatをインストールして起動します。

    $ brew install tomcat
    $ brew services start tomcat
  2. http://localhost でTomcatのデフォルトページにアクセスできるようになります。 

  3. Tomcatで稼働するWebサーバのルートディレクトリの場所の確認をおこないます。brew installからインストールした場合、/usr/local/Cellar/tomcat/9.0.34/libexec/webapps/のディレクトリ(9.0.34の部分は、tomcatで使用しているバージョン)となり、こちらに移動しておきます。上記ディレクトリはbrew ls tomcatコマンドで確認でき、webappsディレクトリ以下が実際にWebサーバーとして稼働させるデフォルトのルートディレクトリとなります。

    $ brew ls tomcat
    /usr/local/Cellar/tomcat/9.0.34/bin/catalina
    /usr/local/Cellar/tomcat/9.0.34/homebrew.mxcl.tomcat.plist
    /usr/local/Cellar/tomcat/9.0.34/libexec/bin/ (17 files)
    /usr/local/Cellar/tomcat/9.0.34/libexec/conf/ (10 files)
    /usr/local/Cellar/tomcat/9.0.34/libexec/lib/ (32 files)
    /usr/local/Cellar/tomcat/9.0.34/libexec/logs/ (5 files)
    /usr/local/Cellar/tomcat/9.0.34/libexec/temp/safeToDelete.tmp
    /usr/local/Cellar/tomcat/9.0.34/libexec/webapps/ (623 files)
    /usr/local/Cellar/tomcat/9.0.34/libexec/work/ (2 files)
    /usr/local/Cellar/tomcat/9.0.34/libexec/ (2 files)
    /usr/local/Cellar/tomcat/9.0.34/RELEASE-NOTES
    /usr/local/Cellar/tomcat/9.0.34/RUNNING.txt
  4. warファイルをwebapps以下に配置

    • cp ~/eclipse-workspace/HayashierWebsite/target/hayashier-website-0.0.1-SNAPSHOT.war .
  5. 以下のURLでアクセスできるようになる

    • http://localhost:8080/hayashier-website-0.0.1-SNAPSHOT/
  6. server.xmlについて、コメントアウト等は除き、主要箇所は全体として以下のようになります。ここでは、Connectorタグでポート番号を8080番から80番に変更。hayashier-website-0.0.1-SNAPSHOT.warはwebapps以下でディレクトリとして展開されますが、このディレクトリをappBase=“webapps”の属性を持つHostタグ内のContextタグに対して、docBase=“hayashier-website-0.0.1-SNAPSHOT”のように指定します。 

    <?xml version="1.0" encoding="UTF-8"?>
    <Server port="8005" shutdown="SHUTDOWN">
                            :
                            :
    <Service name="Catalina">
        <Connector port="80" protocol="HTTP/1.1"
                connectionTimeout="20000"
                redirectPort="8443" />
        <Engine name="Catalina" defaultHost="localhost">
                            :
        <Host name="localhost"  appBase="webapps"
                unpackWARs="true" autoDeploy="true">
            <Context path="/" docBase="hayashier-website-0.0.1-SNAPSHOT" reloadable="true" />
            <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
                prefix="localhost_access_log" suffix=".txt"
                pattern="%h %l %u %t &quot;%r&quot; %s %b" />
        </Host>
        </Engine>
    </Service>
    </Server>
  7. Tomcatを再起動

    $ brew services restart tomcat
  8. http://localhost で作成していたアプリケーションが見られるようになります。

Error

cannot resolve to be a type のようにでる

importしてるやつも一度削除して、Eclipseによるimportの提案に従って追加

突然Springが機能しなくなる

Consoleには以下のエラー文

情報: No Spring WebApplicationInitializer types detected on classpath
4月 29, 2020 4:29:36 午後 org.apache.catalina.core.ApplicationContext log
情報: サーブレット [mywebsite] を利用不可能にマークします
4月 29, 2020 4:29:36 午後 org.apache.catalina.core.StandardContext loadOnStartup
重大: Web アプリケーション [] のサーブレット [mywebsite] の load() メソッドは例外を投げました。
java.lang.ClassNotFoundException: org.springframework.context.ApplicationContextAware
  • Tomcatを停止
  • Projectメニューから[Clean…]
  • ServersタブからTomcatを右クリックして[Clean…]
  • Tomcatをクリックし、Modulesタブでパスが初期状態になっているので変更(例. /hayashier-website から / に変更)
  • Tomcatを起動し、プロジェクトをサーバー上で稼働

(参照) No Spring WebApplicationInitializer types detected on classpath

Request processing failed; nested exception is org.springframework.data.redis.serializer.SerializationException: Cannot serialize; nested exception is org.springframework.core.serializer.support.SerializationFailedException: Failed to serialize object using DefaultSerializer; nested exception is java.lang.IllegalArgumentException: DefaultSerializer requires a Serializable payload but received an object of type [hayashier.mywebsite.model.Article]

対象のモデルにimplements Serializableを追加して、Serializableのインターフェイスとして実装

(参照) DefaultSerializer requires a Serializable payload but received an object of type [model.Admin]

ルートパスでアクセスできない

Tomcatのデフォルトページへのアクセスが http://localhost:8080/hayashier-website/ のURLとなるので、/hayashier-websiteを省略した形でアクセスできるようにするための設定手順となります。

  1. ルートアクセスするために、ServersタブでTomcat v 9.0 Server at localhostとなっている部分をクリックして展開
  2. Modulesタブを選択すると、Pathが/hayashier-websiteとなっていることが確認
  3. Pathを選択した状態で、[Edit…]を選択し、/に変更することでルートアクセスできるようになる。

(参考) SpringSource IDE does not use project name as root URL for Spring MVC application

Port番号を8080から80番へ変更

以下、Eclipse組み込みのTomcatの設定変更手順となります。

  • ServersタブでTomcat v 9.0 Server at localhostとなっている部分をクリックして展開
  • Portsの項目で、HTTP/1.1を8080から80に変更

(参考) How to change port numbers for Tomcat in Eclipse

  • Eclipse組み込みでばうTomcatのデフォルト設定を変更するためには、conf/server.xmlの以下のようなConnectorタグを8080番ポートを80番ポートへ変更
    <Connector port="8080" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8443" />

(参考) Change Tomcat port numbers in server.xml file

パスにプロジェクト名を含むスナップショットのディレクトリ名ではなくルートディレクトリでアクセスできるように変更

server.xmlのHostタグ内の以下のように変更

<Context path="/" docBase="hayashier-website-0.0.1-SNAPSHOT" reloadable="true" />

jspファイルのままでレンダリングされない

以下のタグは<!DOCTYPE html>の後に配置

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>

the type is not generic; it cannot be parameterized with arguments <Map<String,String>>

List<Map<String, String>> articles = new ArrayList<Map<String, String>>();

以下のようにインターフェイスではなく具象クラスで宣言

ArrayList<Map<String, String>> articles = new ArrayList<Map<String, String>>();

重大: パス[]を持つコンテキスト内のサーブレット[mywebsite]のServlet.service() が例外[Handler processing failed; nested exception is java.lang.NoClassDefFoundError: javax/servlet/jsp/jstl/core/Config]が根本的要因と共に投げられました。 java.lang.ClassNotFoundException: javax.servlet.jsp.jstl.core.Config

以下のエラーが発生

4月 27, 2020 7:03:55 午後 org.apache.catalina.core.StandardWrapperValve invoke
重大: パス[]を持つコンテキスト内のサーブレット[mywebsite]のServlet.service() が例外[Handler processing failed; nested exception is java.lang.NoClassDefFoundError: javax/servlet/jsp/jstl/core/Config]が根本的要因と共に投げられました。
java.lang.ClassNotFoundException: javax.servlet.jsp.jstl.core.Config
    at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1365)
    at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1188)
    at org.springframework.web.servlet.support.JstlUtils.exposeLocalizationContext(JstlUtils.java:103)
    at org.springframework.web.servlet.view.JstlView.exposeHelpers(JstlView.java:137)
    at org.springframework.web.servlet.view.InternalResourceView.renderMergedOutputModel(InternalResourceView.java:145)
    at org.springframework.web.servlet.view.AbstractView.render(AbstractView.java:316)
    at org.springframework.web.servlet.DispatcherServlet.render(DispatcherServlet.java:1373)
    at org.springframework.web.servlet.DispatcherServlet.processDispatchResult(DispatcherServlet.java:1118)
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1057)
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:943)
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)
    at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:634)
    at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:741)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202)
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:541)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139)
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
    at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:690)
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343)
    at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:373)
    at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)
    at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:868)
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1590)
    at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
    at java.base/java.lang.Thread.run(Thread.java:834)

pom.xmlに以下を追加してMavenプロジェクトをビルドし直す

    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>jstl</artifactId>
        <version>1.2</version>
    </dependency>

~.htmlの識別子をつけるとjspがrenderingされるが、識別子をつけない場合、renderingされない

pom.xmlに以下を追加

    <dependency>
        <groupId>org.apache.tomcat.embed</groupId>
        <artifactId>tomcat-embed-jasper</artifactId>
        <version>9.0.34</version>
    </dependency>

web.xmlのurl-patternを変更

  <servlet-mapping>
    <servlet-name>mywebsite</servlet-name>
    <url-pattern>/*</url-pattern>
  </servlet-mapping>

  <servlet-mapping>
    <servlet-name>mywebsite</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>