※当サイトの記事には、広告・プロモーションが含まれます。

Java MVCモデル

『山河果てるとも  天正伊賀悲雲録(著:伊藤潤)』読了しました。タイトルに「悲」って言葉が入っていますので、ご想像の通りなんともやりきれない話ではありますが、ラストは多少救いがあるのかな?という感じです。

物語的には面白かったです。

そんなこんなで、Javaは、MVCモデルの学習に突入しました。

 

MVCモデル

今回のサンプルではデータベースを使ってないけど、大体のイメージ図。

f:id:ts0818:20170731201950p:plain

 

要素 役割
モデル(Model) データの格納やデータ処理(DBなど)
ビュー(View) ユーザーに対して画面を表示
コントローラー(Controller) ユーザーからの要求を受け取り、Modelに処理を、Viewに画面表示を依頼する

httpリクエストで、 

  1. Servletにアクセス
  2. ServletからModelへ
  3. ModelでDBとやりとり
  4. ModelからServleteへ
  5. ServletからViewへ
  6. Viewがクライアントで表示される

というような流れです。

MVCを試してみる

実際に「動的 Webプロジェクト」を作って、MVCモデルを試していきます。

f:id:ts0818:20170731203333j:plain

「パッケージ・エクスプローラー」で以下のような構成になるようファイルを作成。

f:id:ts0818:20170731203526j:plain

Servletのファイルを作成する際は、「新規(N)」>「その他(O)...」を選択後、「Web」>「サーブレット」で作成します。

データ格納用クラス(JavaBeans)

package model;

import java.io.Serializable;

public class Health implements Serializable {
	/**
	 * フィールド変数
	 */
	private double height;
	private double weight;
	private double bmi;
	private String bodyType;

	public double getHeight() {
		return height;
	}

	public void setHeight(double height) {
		this.height = height;
	}

	public double getWeight() {
		return weight;
	}

	public void setWeight(double weight) {
		this.weight = weight;
	}

	public double getBmi() {
		return bmi;
	}

	public void setBmi(double bmi) {
		this.bmi = bmi;
	}

	public String getBodyType() {
		return bodyType;
	}

	public void setBodyType(String bodyType) {
		this.bodyType = bodyType;
	}
}

データ処理用クラス(データベースを使う場合は、ここでそういった処理も行う感じになるかと。)

package model;

public class HealthCheckLogic {

	// BMIを計算するメソッド
	// Healthクラスのインスタンスに値をセット
	public void execute(Health health) {
		// BMIを算出
		// 身長、体重をセット
		double weight = health.getWeight();
		double height = health.getHeight();
		double bmi = weight / (height / 100.0 * height / 100.0);

		// BMIをセット
		health.setBmi(bmi);

		// BMI指数から体型を判定して設定
		String bodyType;
		if(bmi < 18.5) {
			bodyType = "痩せ形";
		} else if(bmi < 25) {
			bodyType = "普通";
		} else {
			bodyType = "肥満";
		}

		// 体型をセット
		health.setBodyType(bodyType);
	}
}

Controllerクラス(はじめに、クライアントからのアクセスを受け付ける。)

package servlet;

import java.io.IOException;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import model.Health;
import model.HealthCheckLogic;

/**
 * Servlet implementation class HealthCheck
 */
@WebServlet("/HealthCheck")
public class HealthCheck extends HttpServlet {
	private static final long serialVersionUID = 1L;

    /**
     * @see HttpServlet#HttpServlet()
     */
    public HealthCheck() {
        super();
    }

	/**
	 * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
	 */
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		// 『http://localhost:8080/Web/HealthCheck』でアクセスで初めに呼び出さる
		// 『/WEB-INF/jsp/healthCheck.jsp』が表示される
    RequestDispatcher dispatcher = request.getRequestDispatcher("/WEB-INF/jsp/healthCheck.jsp");
    dispatcher.forward(request, response);
	}

	/**
	 * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
	 */
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		// formからの値を取得
		String weight = request.getParameter("weight");
		String height = request.getParameter("height");

		// Healthクラスのインスタンスに値をセット
		Health health = new Health();
		health.setHeight(Double.parseDouble(height));
		health.setWeight(Double.parseDouble(weight));

		// BMIを計算
		HealthCheckLogic healthCheckLogic = new HealthCheckLogic();
		healthCheckLogic.execute(health);

		// 値をセット
		request.setAttribute("health", health);

		//『/WEB-INF/jsp/healthCheckResult.jsp』に飛ぶ
		RequestDispatcher dispatcher = request.getRequestDispatcher("/WEB-INF/jsp/healthCheckResult.jsp");
		dispatcher.forward(request, response);
	}
}

Viewクラス。(初期画面)

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>スッキリ健康診断</title>
<style>
html, body {
  height: 100%;
}

#form-page {
  display: table;
  width: 100%;
}

#form-wrapper {
  display: table-cell;
  vertical-align: middle;
}

#form {
  border: 1px solid #ccc;
  border-radius: 2px;
  margin: 0 auto;
  max-width: 300px;
  padding: 4px;
}

.form-inner {
  border: 1px solid #eee;
  border-radius: 2px;
  padding: 8px;
}

.form-inner h1 {
  text-align: center;
}

.button {
  text-align: right;
}
</style>
</head>
<body id="form-page">
  <div id="form-wrapper">
    <div id="form">
      <div class="form-inner">
        <h1>スッキリ健康診断</h1>
          <form action="/Web/HealthCheck" method="POST">
	          <div class="form-group">
	            <label>身長:</label><input type="text" name="height" value="">(cm)
	          </div>

	          <div class="form-group">
	            <label>体重:</label><input type="text" name="weight" value="">(kg)
	          </div>
	          <div class="button">
              <input type="submit" value="診断">

            </div>
          </form>
      </div><!-- .form-inner -->
    </div><!-- #form -->
  </div>
</body>
</html>

Viewクラス(結果画面)

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@page import="model.Health"%>
<%
// requestインスタンスにセットされた値を取得
Health health = (Health)request.getAttribute("health");
%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>スッキリ健康診断</title>
</head>
<body>
  <h1>スッキリ健康診断の結果</h1>
  <p>
  身長:<%= health.getHeight() %><br>
  体重:<%= health.getWeight() %><br>
  BMI :<%= health.getBmi() %><br>
  体型:<%= health.getBodyType() %>
  </p>
  <a href="/Web/HealthCheck">戻る</a>
</body>
</html>

ファイルを用意できたら、サーバーを起動し...たところで404エラー!

⇩  下記サイトによると、server.xmlで問題が起こってるとのこと

『複数のコンテキストに "/???" のパスがあります』のエラーを解消する - R42日記

f:id:ts0818:20170731220756j:plain

server.xml 

<?xml version="1.0" encoding="UTF-8"?>
<!--
  Licensed to the Apache Software Foundation (ASF) under one or more
  contributor license agreements.  See the NOTICE file distributed with
  this work for additional information regarding copyright ownership.
  The ASF licenses this file to You under the Apache License, Version 2.0
  (the "License"); you may not use this file except in compliance with
  the License.  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
--><!-- Note:  A "Server" is not itself a "Container", so you may not
     define subcomponents such as "Valves" at this level.
     Documentation at /docs/config/server.html
 --><Server port="8005" shutdown="SHUTDOWN">
  <Listener className="org.apache.catalina.startup.VersionLoggerListener"/>
  <!-- Security listener. Documentation at /docs/config/listeners.html
  <Listener className="org.apache.catalina.security.SecurityListener" />
  -->
  <!--APR library loader. Documentation at /docs/apr.html -->
  <Listener SSLEngine="on" className="org.apache.catalina.core.AprLifecycleListener"/>
  <!-- Prevent memory leaks due to use of particular java/javax APIs-->
  <Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener"/>
  <Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener"/>
  <Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener"/>

  <!-- Global JNDI resources
       Documentation at /docs/jndi-resources-howto.html
  -->
  <GlobalNamingResources>
    <!-- Editable user database that can also be used by
         UserDatabaseRealm to authenticate users
    -->
    <Resource auth="Container" description="User database that can be updated and saved" factory="org.apache.catalina.users.MemoryUserDatabaseFactory" name="UserDatabase" pathname="conf/tomcat-users.xml" type="org.apache.catalina.UserDatabase"/>
  </GlobalNamingResources>

  <!-- A "Service" is a collection of one or more "Connectors" that share
       a single "Container" Note:  A "Service" is not itself a "Container",
       so you may not define subcomponents such as "Valves" at this level.
       Documentation at /docs/config/service.html
   -->
  <Service name="Catalina">

    <!--The connectors can use a shared executor, you can define one or more named thread pools-->
    <!--
    <Executor name="tomcatThreadPool" namePrefix="catalina-exec-"
        maxThreads="150" minSpareThreads="4"/>
    -->


    <!-- A "Connector" represents an endpoint by which requests are received
         and responses are returned. Documentation at :
         Java HTTP Connector: /docs/config/http.html
         Java AJP  Connector: /docs/config/ajp.html
         APR (HTTP/AJP) Connector: /docs/apr.html
         Define a non-SSL/TLS HTTP/1.1 Connector on port 8080
    -->
    <Connector connectionTimeout="20000" port="8080" protocol="HTTP/1.1" redirectPort="8443"/>
    <!-- A "Connector" using the shared thread pool-->
    <!--
    <Connector executor="tomcatThreadPool"
               port="8080" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8443" />
    -->
    <!-- Define a SSL/TLS HTTP/1.1 Connector on port 8443
         This connector uses the NIO implementation. The default
         SSLImplementation will depend on the presence of the APR/native
         library and the useOpenSSL attribute of the
         AprLifecycleListener.
         Either JSSE or OpenSSL style configuration may be used regardless of
         the SSLImplementation selected. JSSE style configuration is used below.
    -->
    <!--
    <Connector port="8443" protocol="org.apache.coyote.http11.Http11NioProtocol"
               maxThreads="150" SSLEnabled="true">
        <SSLHostConfig>
            <Certificate certificateKeystoreFile="conf/localhost-rsa.jks"
                         type="RSA" />
        </SSLHostConfig>
    </Connector>
    -->
    <!-- Define a SSL/TLS HTTP/1.1 Connector on port 8443 with HTTP/2
         This connector uses the APR/native implementation which always uses
         OpenSSL for TLS.
         Either JSSE or OpenSSL style configuration may be used. OpenSSL style
         configuration is used below.
    -->
    <!--
    <Connector port="8443" protocol="org.apache.coyote.http11.Http11AprProtocol"
               maxThreads="150" SSLEnabled="true" >
        <UpgradeProtocol className="org.apache.coyote.http2.Http2Protocol" />
        <SSLHostConfig>
            <Certificate certificateKeyFile="conf/localhost-rsa-key.pem"
                         certificateFile="conf/localhost-rsa-cert.pem"
                         certificateChainFile="conf/localhost-rsa-chain.pem"
                         type="RSA" />
        </SSLHostConfig>
    </Connector>
    -->

    <!-- Define an AJP 1.3 Connector on port 8009 -->
    <Connector port="8009" protocol="AJP/1.3" redirectPort="8443"/>


    <!-- An Engine represents the entry point (within Catalina) that processes
         every request.  The Engine implementation for Tomcat stand alone
         analyzes the HTTP headers included with the request, and passes them
         on to the appropriate Host (virtual host).
         Documentation at /docs/config/engine.html -->

    <!-- You should set jvmRoute to support load-balancing via AJP ie :
    <Engine name="Catalina" defaultHost="localhost" jvmRoute="jvm1">
    -->
    <Engine defaultHost="localhost" name="Catalina">

      <!--For clustering, please take a look at documentation at:
          /docs/cluster-howto.html  (simple how to)
          /docs/config/cluster.html (reference documentation) -->
      <!--
      <Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"/>
      -->

      <!-- Use the LockOutRealm to prevent attempts to guess user passwords
           via a brute-force attack -->
      <Realm className="org.apache.catalina.realm.LockOutRealm">
        <!-- This Realm uses the UserDatabase configured in the global JNDI
             resources under the key "UserDatabase".  Any edits
             that are performed against this UserDatabase are immediately
             available for use by the Realm.  -->
        <Realm className="org.apache.catalina.realm.UserDatabaseRealm" resourceName="UserDatabase"/>
      </Realm>

      <Host appBase="webapps" autoDeploy="true" name="localhost" unpackWARs="true">

        <!-- SingleSignOn valve, share authentication between web applications
             Documentation at: /docs/config/valve.html -->
        <!--
        <Valve className="org.apache.catalina.authenticator.SingleSignOn" />
        -->

        <!-- Access log processes all example.
             Documentation at: /docs/config/valve.html
             Note: The pattern used is equivalent to using pattern="common" -->
        <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs" pattern="%h %l %u %t "%r" %s %b" prefix="localhost_access_log" suffix=".txt"/>

      <Context docBase="JavaSecondWeb" path="/JavaSecondWeb" reloadable="false" source="org.eclipse.jst.jee.server:JavaSecondWeb"/>
      <Context docBase="JavaFirstWeb" path="/JavaFirstWeb" reloadable="false" source="org.eclipse.jst.jee.server:JavaFirstWeb"/>
      <Context docBase="JavaThirdWeb" path="/JavaThirdWeb" reloadable="false" source="org.eclipse.jst.jee.server:JavaThirdWeb"/>
      <Context docBase="jspSample" path="/jspSample" reloadable="false" source="org.eclipse.jst.jee.server:jspSample"/>
      <Context docBase="WebFirst" path="/WebFirst" reloadable="false" source="org.eclipse.jst.jee.server:WebFirst"/>
      <Context docBase="SampleMVC" path="/SampleMVC" reloadable="false" source="org.eclipse.jst.jee.server:SampleMVC"/>
      <Context docBase="Web" path="/Web" reloadable="false" source="org.eclipse.jst.jee.server:Web"/>
      <Context docBase="SampleMVC" path="/SampleMVC" reloadable="false" source="org.eclipse.jst.jee.server:SampleMVC"/>
      </Host>
    </Engine>
  </Service>
</Server>

たしかに、『<Context docBase="SampleMVC" path="/SampleMVC" reloadable="false" source="org.eclipse.jst.jee.server:SampleMVC"/>』が2つあるし...1つ消し去り保存。

再び、サーバーを起動。

f:id:ts0818:20170731221325j:plain

起動しました!

でも、あいかわらず、404エラー!Eclipseを再起動したところ、『http://localhost:8080/プロジェクト名/コントローラー名』で無事表示!

f:id:ts0818:20170731223602j:plain

診断結果も表示!

f:id:ts0818:20170731223559j:plain

 

本当は怖いEclipse自動変換

Servletの一部で誤って自動変換をしたところ、 

f:id:ts0818:20170731224347j:plain

というエラーが!

package servlet;

import java.io.IOException;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import model.Health;
import model.HealthCheckLogic;

/**
 * Servlet implementation class HealthCheck
 */
@WebServlet("/HealthCheck")
public class HealthCheck extends HttpServlet {
	private static final long serialVersionUID = 1L;

    /**
     * @see HttpServlet#HttpServlet()
     */
    public HealthCheck() {
        super();
    }

	/**
	 * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
	 */
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		// 『http://localhost:8080/Web/HealthCheck』でアクセスで初めに呼び出さる
		// 『/WEB-INF/jsp/healthCheck.jsp』が表示される
    RequestDispatcher dispatcher = request.getRequestDispatcher("/WEB-INF/jsp/healthCheck.jsp");
    dispatcher.forward(request, response);
	}

	/**
	 * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
	 */
	protected void doPost(ServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		// formからの値を取得
		String weight = request.getParameter("weight");
		String height = request.getParameter("height");

		// Healthクラスのインスタンスに値をセット
		Health health = new Health();
		health.setHeight(Double.parseDouble(height));
		health.setWeight(Double.parseDouble(weight));

		// BMIを計算
		HealthCheckLogic healthCheckLogic = new HealthCheckLogic();
		healthCheckLogic.execute(health);

		// 値をセット
		request.setAttribute("health", health);

		//『/WEB-INF/jsp/healthCheckResult.jsp』に飛ぶ
		RequestDispatcher dispatcher = request.getRequestDispatcher("/WEB-INF/jsp/healthCheckResult.jsp");
		dispatcher.forward(request, response);
	}

}

犯人は、doPost()メソッドの第一引数が、変わってしまっていたことでした。

protected void doPost(ServletRequest request, HttpServletResponse response)

正しくは、

protected void doPost(HttpServletRequest request, HttpServletResponse response)

となります。自動変換は便利ですが、意図せぬ変換がなされてしまうときがあるので注意が必要ですね。