Print Friendly, PDF & Email

前回はMVCモデルについて概略を学びました。

今回はコントローラーの役目を果たすことが多いサーブレットについて学んでいきましょう。

サーブレットとは?

Java Servletとは、Javaで書かれたプログラムであり、Webサーバ上で動きます

以前はJava Appletというクライアント(ブラウザ)側で動くJavaプログラムがあり、対で説明されることがありました。

サーブレットの概念は下図1の通りです。

ブラウザからアプリケーションサーバにリクエストが送られると初回のみServletコンテナがJavaのクラスファイルを実行してサーブレットのインスタンスを作り、レスポンスを返します。

それ以降、サーブレットのインスタンスはアプリケーションサーバのメモリに常駐し、リクエストに対してレスポンスを返します

リクエストメソッドに応じてgetにはdoGetメソッド、postにはdoPostメソッドを呼び出します

図3.1サーブレットの概念

サーブレットでHello World

ここでは、ひとまずサーブレットだけを単体で実行してみることにします。

JavaSEでも一番最初に学んだ「Hello World」をやってみましょう。

以下のようなサーブレットだけを作成して実行してみてください。

package p03;

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet(urlPatterns = {"/HelloServlet1"})
public class HelloServlet1 extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        System.out.println("Hello World");
    }
}
  • 13行目の記述にはどういう意味がありましたか?
あなたの答え:

すると真っ白なブラウザの画面が表示されましたね。

(1回目は先述の通りサーブレットのインスタンスを作ったり、あるいは場合によってはグラスフィッシュを立ち上げていますので時間がかかりますが、2回目以降は早くなるはずです。試してみてください。)

「Hello World」 はどこに表示されているのでしょうか?

実は、下図2のように「出力」のタブに「GlassFish Server x.x.x」というタブがあり、このログとして表示されているのでした。

図3.2 GlassFishのログ

このログは、JavaSEでいうところのコンソールです。

GlassFish Serverのタブではプリントデバッグを行うことができます。

また、各種エラーが表示されるところですから今後も頻繁に目にすることになるはずです。

ここからサーブレットの中身の解説に入りますが、決り文句は省略して、urlPatternsとdoGetメソッドという2つのポイントを解説します。

urlPatterns

10行目を再掲します。

@WebServlet(urlPatterns = {"/HelloServlet1"})

ここで注目は、urlPatterns です。

urlPatterns はこのサーブレットがどのようなURLの指定で呼び出せるかを規定しています。

ブラウザのアドレスバーを見ると以下のようになっていますね。

「http://localhost:8080/03_JavaWebText/HelloServlet1」

カンの良い方は、 urlPatterns と複数形になっていることに気づかれた方もいらっしゃるかもしれません。

複数のURLのパターンを指定できるようになっています。

urlPatterns を以下のように書き足してみましょう。

@WebServlet(urlPatterns = {"/HelloServlet1","/sample"})

そうすると以下のURL指定でもこのサーブレットが実行できるはずです。

「http://localhost:8080/03_JavaWebText/sample」

即ち、 urlPatternはクラス名とは無関係に付けることができます。

しかし、当社の新人エンジニア研修では特段の理由がない限り、urlPatternは1つだけ、クラス名と同じものを用います

なお、同じプロジェクトの中に同じurlPatternを指定するとサーブレットも、JSPもHTMLさえも実行できなくなります

みなさんもサーブレットをコピーして、うっかり urlPattern を書き換えるのを忘れていると起こります。

(しかも、コピーした後 urlPattern を書き換えてもキャッシュに元の urlPattern が残ることがあります。その場合にはプロジェクトの「消去」が必要になりますので方法は講師にお尋ねください)

一度経験しておけば安心ですので、ぜひ、試してみてください。

コンテキストルート

なお、urlPatternの先頭の“/”を忘れるとGlassFishサーバーのログにエラーが出るはずですので試してみてください。

サーブレットが呼び出される際のURLは以下のようになっています。

http://ホスト名:ポート番号/プロジェクト名/urlPattern

この時、以下のポート番号までがサーバのURLです。

「http://localhost:8080/」

また、プロジェクト名までをコンテキストルートといいます。

「http://localhost:8080/03_JavaWebText/」

プロジェクト名はコンテキスト名と呼ばれることもあります。

したがって上記のコンテキスト名は「03_JavaWebText」です。

contextとは英語で文脈という意味です。

文脈が変われば言葉の意味が変わるように、プロジェクトが変われば同じ urlPatternでも呼び出されるサーブレットが変わります。

さらに、以下がファイルのURLになります。

「http://localhost:8080/03_JavaWebText/sample」

Servletクラス

サーブレットはHttpServletクラスを継承して宣言されます。

HttpServletクラスに関して当社の新人エンジニア研修では特に深入りはしません。

JavaSEのときのような詳細な説明がほしい方はJakarta EE 8 仕様 APIを御覧ください。

doGetメソッド

次にdoGetメソッドを見ていきます。

doGetメソッドは HttpServletクラスのdoGetメソッドをオーバーライドしているためアノテーションを付けています。

このメソッド内で起きる可能性のある例外を呼び出し元に投げるためにthrowsキーワードが使われていますが、決り文句ですので深入りはしません。

doGetメソッドの引数は、HttpServletRequest型の変数名requestというインスタンスとHttpServletResponse型の変数名responseというインスタンスです

それぞれ、リクエストとレスポンスでやり取りされるオブジェクトのフィールドやメソッドが含まれています。

リクエストとレスポンスについては、今回の新人エンジニア研修で折に触れて紹介することになりますので先を急ぐことにします。

以下にdoGetメソッドを再掲します。

@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {
    System.out.println("Hello World");
}

では「Hello World」をブラウザに表示するにはどうすればよいでしょうか?

それには、JSPにデータを渡す方法とJSPのEL【Expression Language】を使って表示する方法を学ぶ必要があります。

※本当はサーブレット単体でもHTMLの出力は可能ですが、MVCを学ぶという本研修の趣旨からは外れますので解説しません。

興味のある方はご自分で調べてください。

属性とは?

あるサーブレットからJSPへデータを渡すには属性というアプリケーション・サーバーのデータ領域を使います

スコープ【scope】には範囲という意味があるということを私達はJavaSEで学びました。

JavaEEにはデータの有効範囲の異なる4種類のスコープがあるのですが、今回はそのうちスコープが狭い方から2番めのリクエスト属性(Request Attributes)を使います。(本研修ではあともう一つセッション属性(Session Attributes)というものをあとで学びます)

サーブレットではrequestのsetAttributeメソッドを使いインスタンスを属性に保存します。

set(セットする)Attribute(属性を)という意味のメソッドです。

属性には、文字列はもちろん、オブジェクトであれば何でも入ります

また、1つだけではなく複数のオブジェクトを格納することができます。

複数のオブジェクトを格納できますからそれぞれに名前をつけて区別する必要があります。

下図3のnameが名前、attributeがオブジェクトになります。

図3.4 リクエスト属性のイメージ

そして、requestのforwardメソッドを使って指定のJSPにフォワード処理をします。

ちょうどサッカーのフォワードがボールを前に前に運ぶイメージでしょうか。

JSPでは、ELを使ってその値をリクエスト属性から取り出してHTMLを組み立てます。

HTMLファイルがブラウザにレスポンスされて皆さんの画面に表示されます。

次のようなサーブレットを作成して実行してみましょう。

package p03;

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet(urlPatterns = {"/Hello"})
public class HelloServlet2 extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        request.setAttribute("message", "Hello World");
        request.getRequestDispatcher("03Servlet/result.jsp").forward(request, response);
    }
}
  • 18行目のように複数のメソッドを“.”でつなぐ書き方を何といいましたか?
あなたの答え:

ポイントは、17、18行目です。

まず、17行目でリクエスト属性という保存領域にmessageという名前で"Hello World"という文字列のインスタンスを格納しています。

request.setAttribute("message", "Hello World");

("request. "と入力して暫く待つとさまざまなメソッドが表示されて面食らいますが本研修で使用するのはほんの一部ですから安心ください)

このメソッド名をあえて日本語に訳せば、set(セットする)Attribute(属性を)という名前のメソッドです。

requestのsetAttributeメソッドの引数は2つあり1つ目が名前、2つ目がオブジェクトです。

オブジェクトは名前を変えれば複数保存できます。

この名前が遷移先のjspで変数のように利用できるわけです。

リクエスト属性の特徴

リクエスト属性とはその名の通り、1回のリクエストとレスポンスの間だけ有効な記憶領域です。

レスポンスを返し終わると消えてしまう記憶領域ですのでメモリを圧迫しません。

他のプログラミング言語ではリクエスト変数と呼ばれることもあります。

調べてみましょう

属性は全部で4つあります。

この研修では2つしか使いませんが、残りの2つも余裕があれば調べてみましょう。

調べた結果のメモ:

フォワードとは

フォワードとは、本研修の場合、処理中のサーブレットからJSPに処理を移す(画面を切り替える)ことです。

JSPに画面表示の処理を任せてしまうのがフォワードだとお考え下さい。

以下の1行でフォワードが成立します。

request.getRequestDispatcher("03Servlet/result.jsp").forward(request, response);

画面を切り替える方法としてあとの章でリダイレクトというものも出てきますので比較を通じておいおい理解いただければ大丈夫です。

このメソッドの前半部分でrequestのgetRequestDispatcherメソッドを使っています

request.getRequestDispatcher("03Servlet/result.jsp")

「ブラウザにデータを戻すのであれば、 request ではなく、response のメソッドを使うのではないか?」そう思った人もいるかも知れません。

しかし上図3をよく見ると、サーブレットからJSPにデータを渡してからレスポンスをしているのでこれで良いのですね。

このフォワードのメソッド名はget(ゲットする)Request(リクエストを)Dispatcher (送るものを)という程度の意味です。

送り先のURLは引数として文字列で渡します。

以下のようにコンテキストルート以下の部分を記述します。

"03Servlet/result.jsp"

今回この処理はメソッドチェーンを使い1行で書いています。

2行で書くこともできますが、シンプルですからこの書き方に慣れてください。

なお、以下の部分は決り文句と考えていただいて結構です。

.forward(request, response)

ELを使ったリクエスト属性のオブジェクトの取り出し方

JSPには、HTMLの定型文をすべて消して、ELだけを記述することにします。

論点を明確にするためです。

下記のようにJSPのELは$で始めて波括弧の中にリクエスト属性に付けた名前を書けば表示されます。

(この例では”message"です)

 ${message}

このとき、もちろんJSPだけを実行しても何も表示されません。

サーブレットからJSPにフォワードしないとHello Worldが表示されないことを確認してください。

なお、変数に$マークを付けるのはIT業界の古くからの習慣です。 

古くはBasic、そしてLinuxのシェルスクリプトやPHPでも$は変数の目印として使われます。

知識として覚えておいて損はありません。

JSPで日本語を扱う

先のプログラムを少しだけ変更して日本語で挨拶させてみます。

以下のサンプルコードを実行してみましょう。

package p03;

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet(urlPatterns = {"/Konnichiwa"})
public class HelloServlet3 extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        request.setAttribute("message", "こんにちは");
        request.getRequestDispatcher("03Servlet/result.jsp").forward(request, response);
              
    }
}

するとブラウザには「?????」のように文字化けして表示されたのではないでしょうか?

この文字化けを解消するにはJSPに文字コードとしてUTF-8を指定します。

以下のサンプルコードをご覧ください。

<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>JSP Page</title>
    </head>
    <body>
        ${message}
    </body>
</html>

実験1

上記サンプルコードのmessageにIDEの名前変更機能は有効でしょうか?

実験結果のメモ:

実験2

1行目にはJSPの、5行目にはHTMLの文字コード指定があります。

どちらが必須の文字コード指定でしょうか?

実験結果のメモ:

JSPの正体はサーブレットである

ここまで、サーブレットとJSPはあたかも別物として説明してきました。

ControllerとViewを明確に分けるという意味ではその理解にも一理あります。

しかし、エンジニアの皆さんは一歩進んでJSPも実はサーブレットなのだということを知っておきましょう。

JSPもサーブレットに変換されてからサーバーサイドで動くのです。

先の、ELが一行だけ書かれたJSPをサーブレットに変換すると以下のようになります。(やり方は講師にお尋ねください)

なお、大変複雑なJavaコードですが、理解する必要はありません。

<ans_jsp.java>

package org.apache.jsp._03Servlet;

import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.jsp.*;

public final class ans_jsp extends org.apache.jasper.runtime.HttpJspBase
    implements org.apache.jasper.runtime.JspSourceDependent {

  private static final JspFactory _jspxFactory = JspFactory.getDefaultFactory();

  private static java.util.List<String> _jspx_dependants;

  private org.glassfish.jsp.api.ResourceInjector _jspx_resourceInjector;

  public java.util.List<String> getDependants() {
    return _jspx_dependants;
  }

  public void _jspService(HttpServletRequest request, HttpServletResponse response)
        throws java.io.IOException, ServletException {

    PageContext pageContext = null;
    HttpSession session = null;
    ServletContext application = null;
    ServletConfig config = null;
    JspWriter out = null;
    Object page = this;
    JspWriter _jspx_out = null;
    PageContext _jspx_page_context = null;

    try {
      response.setContentType("text/html");
      response.setHeader("X-Powered-By", "JSP/2.3");
      pageContext = _jspxFactory.getPageContext(this, request, response,
      			null, true, 8192, true);
      _jspx_page_context = pageContext;
      application = pageContext.getServletContext();
      config = pageContext.getServletConfig();
      session = pageContext.getSession();
      out = pageContext.getOut();
      _jspx_out = out;
      _jspx_resourceInjector = (org.glassfish.jsp.api.ResourceInjector) application.getAttribute("com.sun.appserv.jsp.resource.injector");

      out.write((java.lang.String) org.apache.jasper.runtime.PageContextImpl.evaluateExpression("${ans}", java.lang.String.class, (PageContext)_jspx_page_context, null));
      out.write('\n');
    } catch (Throwable t) {
      if (!(t instanceof SkipPageException)){
        out = _jspx_out;
        if (out != null && out.getBufferSize() != 0)
          out.clearBuffer();
        if (_jspx_page_context != null) _jspx_page_context.handlePageException(t);
        else throw new ServletException(t);
      }
    } finally {
      _jspxFactory.releasePageContext(_jspx_page_context);
    }
  }
}
  • 答えを出力するためのELが上のソースコードの何行目になったかわかりますか?
あなたの答え:

JSPをサーブレットに変換したソースコードを理解する必要はありません。

しかし、JSPで上手く表示されないときにサーブレットにしてコードを解析すると問題が解決することもあります。

実験2

以下のソースコードにはJSPのコメントとHTMLのコメントがあります。

このファイルを実行して「ページのソースを表示」を選択してみてください。

何か分かったことはありますか?

また、なぜそのような違いがあるのでしょうか?

<comment.jsp>

<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>JSP Page</title>
    </head>
    <body>
        <%-- これはJSPのコメントです --%>
        <!--これはHTMLのコメントです-->
    </body>
</html>

実験結果のメモ:

例題1

以下のサーブレットを実行したときにブラウザには何が出力されますか?

なお、各ファイルはそれぞれ正しい構成でプロジェクト内にあるものとします。

package p03;

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;


@WebServlet(urlPatterns = {"/AdditionServlet"})
public class AdditionServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        
        int num1 = 1, num2 = 2;
        
        request.setAttribute("ans", num1 + num2);
        request.getRequestDispatcher("03Servlet/ans.jsp").forward(request, response);
    }
}
${ans}

オブジェクトに数値が格納できる理由をJavaSEで学んだ言葉を使い説明しなさい。

あなたの答え:

例題2

上記のELを以下のように書き換えるとします。

何が出力されるか予想しなさい。

${ans + 1}

上記結果になる理由を JavaSEで学んだ言葉を使い説明しなさい 。

あなたの答え:

今回はサーブレットからJSPにデータを渡して表示する方法について学びました。

次回は、フォームから送ったデータをサーブレットで受け取る方法を学んでいきます。

そうすれば、ブラウザからのデータをサーブレットで受けて、JSPで表示する一連のMVCの処理が書けるようになります。

<まとめ:隣の人に正しく説明できたらチェックを付けましょう>

□ Java Servletとは、Javaで書かれたプログラムであり、Webサーバ上で動く

□ サーブレットのインスタンスはアプリケーションサーバのメモリに常駐し、リクエストに対してレスポンスを返す

□ リクエストメソッドに応じてgetにはdoGetメソッド、postにはdoPostメソッドが呼び出される

□ GlassFish Serverのタブではプリントデバッグを行うことができ、また、各種エラーが表示される

□ urlPatternsはこのサーブレットがどのようなURLの指定で呼び出せるかを規定しているが本研修では基本的にクラス名と同じにしている

□ 同じプロジェクトの中に全く同じurlPatternを指定したサーブレットが複数あると、JSPもHTMLさえも実行できなくなる

□ doGetメソッドの引数は、HttpServletRequest型の変数名requestというインスタンスとHttpServletResponse型の変数名responseというインスタンスの2つである

□ サーブレットからJSPへデータを渡すにはリクエスト属性というデータ領域を使うのが一般的である

□ requestのsetAttributeメソッドの引数は2つあり、1つ目が名前、2つ目がオブジェクトである

□ requestのgetRequestDispatcherメソッドを使いJSPにフォワード処理する

□ JSPのELは$で始めて波括弧の中にリクエスト属性に付けた名前を書く

□ JSPもサーブレットに変換されて実行される

インターネットでサンプルコードを探す際の注意点

学習が進んでシステム開発演習をするようになるといろいろなサンプルコードをインターネット検索することもあるでしょう。

その際に、以下の9~11行目のような<% %>で囲われた記述を目にすることもあるでしょう。

これはスクリプトレット【 scriptlet 】といいます。

script=プログラム、let=短い

ということで文字通りJSPの中に短いJavaのプログラムが書ける仕様です。

先に学んだ日本語を表示させるプログラムをJSP1つに書くと以下のようになります。

<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>JSP Page</title>
    </head>
    <body>
        <%
            request.setAttribute("message", "こんにちは");
        %>
        ${message}
    </body>
</html>

しかし、当社の新人エンジニア研修では基本的にスクリプトレットをやりません。

なぜなら、JSPにロジックを書くことはMVCモデルの考え方に反し保守性を低下させるから、というのは以前お話した通りです。

JavaWebアプリケーション目次に戻る