139
150

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

JavaEE使い方メモ(Servlet・JSP・EL式)

Last updated at Posted at 2014-12-30

環境構築は こちら

コードは GitHub に上げてます。
https://github.com/opengl8080-javaee-samples/servlet-jsp

#Servlet とは
Java で Web アプリを作るときの基礎となる仕組み・フレームワーク。

Java EE は知らないけど、 Servlet なら知っているという人は多いかと。

Java で Web アプリを作る場合、 Servlet を直接実装することは基本的に無い。普通は、フレームワークを使う(Struts, JSF, JAX-RS, Spark, etc...)。
しかし、フレームワークは Servlet の上で動いているので、フレームワークを使いこなすためには Servlet の知識が必要になるときがある。また、フレームワークが対応していない機能を補完するために、 Servlet が提供する API をプログラマが直接触る機会は結構多い(HttpServletRequestHttpServletResponse など)。

#Hello World
あえて web.xml を使った基本的な方法で Servlet を実装する。

コンテキストルートを servlet にして Web プロジェクトを作成する

HelloServlet.java
package sample.javaee.servlet;

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

public class HelloServlet extends HttpServlet {
    
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        try (PrintWriter writer = response.getWriter()) {
            writer.println("<h1>Hello Servlet!!</h1>");
        }
    }
}
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.1"
         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_3_1.xsd">
  
  <servlet>
    <servlet-name>HelloServlet</servlet-name>
    <servlet-class>sample.javaee.servlet.HelloServlet</servlet-class>
  </servlet>
  
  <servlet-mapping>
    <servlet-name>HelloServlet</servlet-name>
    <url-pattern>/hello</url-pattern>
  </servlet-mapping>
  
</web-app>

プロジェクトを実行して、 Web ブラウザで http://localhost:8080/servlet/hello を開く。

javaee-servlet.JPG

  • Servlet は、 HttpServlet クラスを継承したクラスを作成する。
  • リクエストのあった URL と実行する Servlet のマッピングは、 web.xml に記述する。
    • <servlet> タグを使って、 Servlet クラスを宣言する。
    • <servlet-mapping> タグを使って、 Servlet と URL をマッピングする。
  • HttpServlet クラスが持つ do***() メソッドをオーバーライドすると、 Servlet の URL に送られてきた HTTP リクエストを受け取ることができる。
    • *** の部分は、 HTTP で定義されているメソッド(GET, POST, PUT, DELETE など)が対応している。
    • オーバーライドしていない HTTP メソッドでリクエストが飛んでくると、 HttpServlet のデフォルトの実装が 405 - Method Not Allowed を返すようになっている。

#Servlet のライフサイクル
Servlet のインスタンスがいつ生成されて、いつ破棄されるのかについて。

LifeCycleServlet.java
package sample.javaee.servlet;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class LifeCycleServlet extends HttpServlet {

    @Override
    public void init() throws ServletException {
        System.out.println("init() : hash=" + this.hashCode());
    }
    
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) {
        System.out.println("doGet() : hash=" + this.hashCode());
    }

    @Override
    public void destroy() {
        System.out.println("destroy() : hash=" + this.hashCode());
    }
}
web.xml
  <servlet>
    <servlet-name>LifeCycleServlet</servlet-name>
    <servlet-class>sample.javaee.servlet.LifeCycleServlet</servlet-class>
  </servlet>
  
  <servlet-mapping>
    <servlet-name>LifeCycleServlet</servlet-name>
    <url-pattern>/lifecycle</url-pattern>
  </servlet-mapping>

Web ブラウザで http://localhost:8080/servlet/lifecycle を開き、何度か F5 を実行したあと GlassFish を停止させる。

GlassFishのログ出力
情報:   init() : hash=1092915953, thread=http-listener-1(4)
情報:   doGet() : hash=1092915953, thread=http-listener-1(4)
情報:   doGet() : hash=1092915953, thread=http-listener-1(1)
情報:   doGet() : hash=1092915953, thread=http-listener-1(5)
情報:   doGet() : hash=1092915953, thread=http-listener-1(3)
情報:   Server shutdown initiated
情報:   Unregistered com.sun.enterprise.glassfish.bootstrap.osgi.EmbeddedOSGiGlassFishImpl@70e02081 from service registry.
情報:   FileMonitoring shutdown
情報:   JMXStartupService: Stopped JMXConnectorServer: null
情報:   JMXStartupService and JMXConnectors have been shut down.
情報:   destroy() : hash=1092915953, thread=RunLevelControllerThread-1419038746038
  • Servlet は、最初に URL にアクセスされたタイミングでインスタンスが生成される。
    • このとき、 init() メソッドがコールバックされる。
  • 一度生成された Servlet のインスタンスは、破棄されることなく使いまわされる。
    • このとき、インスタンスを使用するスレッドは毎回異なる。
    • よって、 Servlet を実装するときは、スレッドセーフにしなければならない。
  • 一定時間 Servlet にアクセスが無かったり、サーバーが停止するときに Servlet のインスタンスは破棄される。
    • このとき、 destroy() メソッドがコールバックされる。

#HTTP リクエストの情報を取得する

RequestParameterServlet.java
package sample.javaee.servlet;

import java.io.BufferedReader;
import java.io.IOException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class RequestParameterServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
        String queryParam = req.getParameter("queryParameter");
        System.out.println("queryParam=" + queryParam);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        try (BufferedReader br = req.getReader()) {
            br.lines().forEach(System.out::println);
        }
    }
}
web.xml
  <servlet>
    <servlet-name>RequestParameterServlet</servlet-name>
    <servlet-class>sample.javaee.servlet.RequestParameterServlet</servlet-class>
  </servlet>

  <servlet-mapping>
    <servlet-name>RequestParameterServlet</servlet-name>
    <url-pattern>/request-param-servlet</url-pattern>
  </servlet-mapping>
html/request.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Serlvet Request</title>
  </head>
  <body>
    <form action="/servlet/request-param-servlet" method="GET">
      <input type="text" name="queryParameter" />
      <input type="submit" value="GET" />
    </form>
    
    <form action="/servlet/request-param-servlet" method="POST">
      <input type="text" name="textbox" />
      <textarea name="textarea"></textarea>
      <input type="submit" value="POST" />
    </form>
  </body>
</html>

Web ブラウザで、 http://localhost:8080/servlet/html/request.html を開く。

javaee-servlet.JPG

GET ボタンを押下

GlassFishコンソール出力
情報:   queryParam=get parameter

POST ボタンを押下

GlassFishコンソール出力
情報:   textbox=textbox&textarea=textarea%0D%0Avalue
  • HTTP リクエストの情報は、 HttpServletRequest から取得できる。
  • クエリパラメータは、 getParameter() メソッドで取得できる。
  • リクエストボディは、 getReader() メソッドで取得した BufferedReader から取得できる。
    • リクエストボディの取得は、 getInputStream() メソッドでも取得可能。
    • どちらか一方を先に呼び出した場合、他方のメソッドは使えなくなる(IllegalStateException がスローされる)。
  • これら以外にも、 HTTP ヘッダーやクライアントの情報(IP アドレスなど)も、 HttpServletRequest から取得できる。

#Servlet 初期化時にパラメータを渡す

web.xml
  <servlet>
    <servlet-name>InitParamServlet</servlet-name>
    <servlet-class>sample.javaee.servlet.InitParamServlet</servlet-class>
    <init-param>
      <param-name>hoge</param-name>
      <param-value>HOGE</param-value>
    </init-param>
  </servlet>

  <servlet-mapping>
    <servlet-name>InitParamServlet</servlet-name>
    <url-pattern>/init-param</url-pattern>
  </servlet-mapping>
InitServlet.java
package sample.javaee.servlet;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class InitParamServlet extends HttpServlet {

    @Override
    public void init(ServletConfig config) throws ServletException {
        String hoge = config.getInitParameter("hoge");
        System.out.println("hoge=" + hoge);
    }
    
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response){
    }
}

Web ブラウザから http://localhost:8080/servlet/init-param にアクセスすると、以下のようにコンソールに出力される。

GlassFishコンソール出力
情報:   hoge=HOGE
  • web.xml<servlet> タグ内で、 <init-param> タグを設定すると、 Servlet 初期化時に任意のパラメータを渡すことができる。
  • Servlet 側は、 ServletConfig を受け取る init() メソッドをオーバーライドすることで、このパラメータを受取ることができる。

#サーバー起動時に Servlet の初期化処理を実行する

web.xml
  <servlet>
    <servlet-name>FirstStartupServlet</servlet-name>
    <servlet-class>sample.javaee.servlet.FirstStartupServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
  </servlet>
  
  <servlet>
    <servlet-name>SecondStartupServlet</servlet-name>
    <servlet-class>sample.javaee.servlet.SecondStartupServlet</servlet-class>
    <load-on-startup>2</load-on-startup>
  </servlet>
  
  <servlet>
    <servlet-name>ThirdStartupServlet</servlet-name>
    <servlet-class>sample.javaee.servlet.ThirdStartupServlet</servlet-class>
    <load-on-startup>3</load-on-startup>
  </servlet>
FirstStartupServlet.java
package sample.javaee.servlet;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;

public class FirstStartupServlet extends HttpServlet {

    @Override
    public void init() throws ServletException {
        System.out.println("FirstStartupServlet.init()");
    }
}

SecondStartupServlet.java, ThirdStartupServlet.java は出力文字列が違うだけで、同じ実装なので省略。

サーバーを再起動する。

GlassFishコンソール出力
情報:   FirstStartupServlet.init()
情報:   SecondStartupServlet.init()
情報:   ThirdStartupServlet.init()
  • web.xml<servlet> タグ内で、 <load-on-startup> タグを設定することで、サーバー起動時に Servlet の初期化処理を実行させることができる。
  • このとき、 <load-on-startup> タグで指定した順序で Servlet の初期化処理が実行される。

#URL のマッピング

web.xml
  <servlet>
    <servlet-name>UrlPatternWildCardServlet</servlet-name>
    <servlet-class>sample.javaee.servlet.UrlPatternWildCardServlet</servlet-class>
  </servlet>

  <servlet-mapping>
    <servlet-name>UrlPatternWildCardServlet</servlet-name>
    <url-pattern>/wildcard/*</url-pattern>
  </servlet-mapping>
  
  <servlet>
    <servlet-name>UrlPatternExtensionServlet</servlet-name>
    <servlet-class>sample.javaee.servlet.UrlPatternExtensionServlet</servlet-class>
  </servlet>

  <servlet-mapping>
    <servlet-name>UrlPatternExtensionServlet</servlet-name>
    <url-pattern>*.sample</url-pattern>
  </servlet-mapping>

##動作確認

URL 実行される Servlet
/servlet/wildcard UrlPatternWildCardServlet
/servlet/wildcard/test UrlPatternWildCardServlet
/servlet/test.sample UrlPatternExtensionServlet
/servlet/test/test.sample UrlPatternExtensionServlet
/servlet/wildcard/test.sample UrlPatternWildCardServlet
  • <url-pattern> にはワイルドカード * を使用することができる。

#別の Servlet に処理を委譲する

web.xml
  <servlet>
    <servlet-name>FromServlet</servlet-name>
    <servlet-class>sample.javaee.servlet.FromServlet</servlet-class>
  </servlet>

  <servlet-mapping>
    <servlet-name>FromServlet</servlet-name>
    <url-pattern>/forward/from</url-pattern>
  </servlet-mapping>
  
  <servlet>
    <servlet-name>ToServlet</servlet-name>
    <servlet-class>sample.javaee.servlet.ToServlet</servlet-class>
  </servlet>

  <servlet-mapping>
    <servlet-name>ToServlet</servlet-name>
    <url-pattern>/forward/to</url-pattern>
  </servlet-mapping>
FromServlet.java
package sample.javaee.servlet;

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

public class FromServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("FromServlet.doGet()");
        req.getRequestDispatcher("/forward/to").forward(req, resp);
    }
}
ToServlet.java
package sample.javaee.servlet;

import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class ToServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
        System.out.println("ToServlet.doGet()");
    }
}

Web ブラウザで http://localhost:8080/servlet/forward/from にアクセスする。

GlassFishコンソール出力
情報:   FromServlet.doGet()
情報:   ToServlet.doGet()
  • request.getRequestDispatcher("<処理を委譲する Servlet のパス>").forward(request, response) で、指定した Servlet に処理を委譲することができる。
  • これをフォワードと呼ぶ。

#非同期処理を実装する
##基本

web.xml
  <servlet>
    <servlet-name>AsyncServlet</servlet-name>
    <servlet-class>sample.javaee.servlet.AsyncServlet</servlet-class>
    <async-supported>true</async-supported>
  </servlet>

  <servlet-mapping>
    <servlet-name>AsyncServlet</servlet-name>
    <url-pattern>/async</url-pattern>
  </servlet-mapping>
AsyncServlet.java
package sample.javaee.servlet;

import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.AsyncContext;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class AsyncServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        System.out.println("AsyncServlet start.");
        
        AsyncContext ctx = req.startAsync();
        
        ctx.start(() -> {
            try (PrintWriter pw = ctx.getResponse().getWriter()) {
                Thread.sleep(5000);
                System.out.println("async process.");
                pw.println("<h1>Async Process</h1>");
            } catch (IOException | InterruptedException ex) {
                ex.printStackTrace();
            } finally {
                ctx.complete();
            }
        });
        
        resp.getWriter().println("<h1>AsyncServlet</h1>");
        
        System.out.println("AsyncServlet end.");
    }
}

Web ブラウザで http://localhost:8080/servlet/async にアクセスする。

GlassFishコンソール出力
情報:   AsyncServlet start.
情報:   AsyncServlet end.
情報:   async process.

5秒ほど経過してから、画面が表示される

javaee-servlet.JPG

  • 非同期処理を実装する場合は、 Servlet の定義に <asyncasync-supported> を追加して true を指定する。
    • もし Servlet の処理の前に Filter が処理を挟む場合は、その Filter にも async-supported を設定する必要がある。
  • request.startAsync()AsyncContext のインスタンスを取得する。
  • AsyncContext#start(Runnable) で、非同期処理を開始する。
  • 非同期処理中に HttpServletRequestHttpServletResponse を取得したい場合は、 AsyncContext から取得する。
  • 非同期処理が完了したら、 AsyncContext#complete() メソッドを実行して非同期処理が完了したことをコンテナに知らせる(知らせないとタイムアウトまで待つことになる)。

###レスポンスは非同期処理が完了されるまでコミットされない
「非同期」とあるので、まるで非同期処理が完了する前にクライアントにレスポンスが返されるのかと勘違いしそうになるけど、そうではない。
レスポンスは、 AsyncContext#complete() が実行されるまでクライアントに返されることはない(コミットされない)。

Java™ Servlet Specification Version 3.1 - 2.3.3.3 Asynchronous processing (P.2-11)

The response isn't committed till complete (see below) is called on the AsyncContext.

日本語訳版はこちら→ サーブレット3.0仕様書邦訳版

その応答はAsyncContext上でcompleteが呼ばれるまで(下記参照)コミットされない。

つまり、クライアントは非同期の分も含めて、処理が完了するまで待たされる。

非同期処理にするメリットは、サーブレットの実行に割り当てられているスレッドをコンテナがさっさと回収して、別のリクエストの処理に割り当てられて無駄が少なくなる、という点。

前述の日本語訳版の仕様書より抜粋

(前略)
サーブレット内での待機は非効率な使い方である。
(中略)
多くのスレッドをアクセス待ちの状態でブロックさせ、ウェブ・コンテナ全体のスレッドの枯渇とサービス品質劣化をもたらす。

サーブレット第3版では要求の非同期処理を導入しており、そのスレッドをコンテナに返して他のタスクを実行できるようにしている。

クライアントを待たせたくないのであれば、自分でスレッドを作って非同期処理を実装する必要がある。
その場合は、作成したスレッドからコンテナが管理しているリソース(HttpServletRequest など)にするのは避けたほうが良い。
その辺については Servletがスレッドを生成してはいけないのか | code up を参照。

リソースを非同期処理中で使いたいのであれば、 Java EE 7 で追加された Concurrency Utilities for Java EE を使うのがいいのかもしれない。
参考:JavaEE - Concurrency Utilities for Java EEをつかってみる - Qiita

##非同期処理のイベントを監視する

AsyncServlet.java
package sample.javaee.servlet;

import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.AsyncContext;
import javax.servlet.AsyncEvent;
import javax.servlet.AsyncListener;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class AsyncServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        System.out.println("AsyncServlet start.");
        
        AsyncContext ctx = req.startAsync();
        
        ctx.addListener(new AsyncListenerImpl());
        
        ctx.start(() -> {
            try (PrintWriter pw = ctx.getResponse().getWriter()) {
                Thread.sleep(5000);
                System.out.println("async process.");
                pw.println("<h1>Async Process</h1>");
            } catch (IOException | InterruptedException ex) {
                ex.printStackTrace();
            } finally {
                ctx.complete();
            }
        });
        
        resp.getWriter().println("<h1>AsyncServlet</h1>");
        
        System.out.println("AsyncServlet end.");
    }
    
    private static class AsyncListenerImpl implements AsyncListener {

        @Override
        public void onStartAsync(AsyncEvent event) throws IOException {
            System.out.println("onStartAsync");
        }
        
        @Override
        public void onComplete(AsyncEvent event) throws IOException {
            System.out.println("onComplete");
        }

        @Override
        public void onTimeout(AsyncEvent event) throws IOException {
            System.out.println("onTimeout");
        }

        @Override
        public void onError(AsyncEvent event) throws IOException {
            System.out.println("onError");
        }
    }
}
  • AsyncListener を実装したクラスを作成して、 AsyncContext#addListener() で登録する。
  • 非同期処理のイベントごとに、各メソッドがコールバックされる。

#セッション管理

web.xml
  <servlet>
    <servlet-name>SessionServlet</servlet-name>
    <servlet-class>sample.javaee.servlet.SessionServlet</servlet-class>
  </servlet>

  <servlet-mapping>
    <servlet-name>SessionServlet</servlet-name>
    <url-pattern>/session/*</url-pattern>
  </servlet-mapping>
SessionServlet.java
package sample.javaee.servlet;

import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

public class SessionServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
        if (this.notExistsSession(req)) {
            this.createSession(req);
        }
        
        if ("/delete".equals(req.getPathInfo())) {
            this.deleteSession(req);
        } else {
            this.countUp(req);
        }
    }
    
    private boolean notExistsSession(HttpServletRequest request) {
        return request.getSession(false) == null;
    }
    
    private void createSession(HttpServletRequest request) {
        HttpSession session = request.getSession();
        session.setAttribute("count", 0);
        
        System.out.printf("session is created. id=%s%n", session.getId());
    }

    private void deleteSession(HttpServletRequest request) {
        HttpSession session = request.getSession(true);
        session.invalidate();
        
        System.out.printf("session is deleted. id=%s%n", session.getId());
    }

    private void countUp(HttpServletRequest request) {
        HttpSession session = request.getSession(false);
        int count = (int)session.getAttribute("count");
        session.setAttribute("count", ++count);
        
        System.out.printf("count up. id=%s, count=%d%n", session.getId(), count);
    }
}

Web ブラウザで、 http://localhost:8080/servlet/session/ に何度かアクセスする。

GlassFishコンソール出力
情報:   session is created. id=7d32d5e4a2958576fd139692be1a
情報:   count up. id=7d32d5e4a2958576fd139692be1a, count=1
情報:   count up. id=7d32d5e4a2958576fd139692be1a, count=2
情報:   count up. id=7d32d5e4a2958576fd139692be1a, count=3
情報:   count up. id=7d32d5e4a2958576fd139692be1a, count=4

ブラウザの Cookie を見ると、 JSESSIONID という名前でセッションの ID が保存されている。

javaee-servlet.JPG

http://localhost:8080/servlet/session/delete にアクセスする。

GlassFishコンソール出力
情報:   session is deleted. id=7d32d5e4a2958576fd139692be1a

もう一度 http://localhost:8080/servlet/session/ にアクセスする。

GlassFishコンソール出力
情報:   session is created. id=7d6023ef3e12cc524fd29fa7f5ad
情報:   count up. id=7d6023ef3e12cc524fd29fa7f5ad, count=1
  • request.getSession(boolean) でセッションを取得する。
    • true を渡した場合、セッションが存在しないとセッションが新規に作成される。
    • false を渡した場合、セッションが存在しないと null が返される。
  • セッションの ID はブラウザに保存される(普通は Cookie)。
    • ブラウザは、リクエストのたびにこの ID をサーバーに渡す。
    • サーバーは、受け取った ID をもとにクライアントを識別してセッションを管理する。
  • session.invalidate() でセッションを破棄できる。

#セッションタイムアウトの時間を設定する

web.xml
  <session-config>
    <session-timeout>60</session-timeout>
  </session-config>
  • <session-config> タグを web.xml に記述することで、セッションタイムアウトの時間を設定できる。
  • 値は分数を指定する。
  • デフォルトは 60 (1時間)。
  • -1 を指定した場合は、セッションタイムアウト無しになる。

#アプリケーション起動・終了時に処理を実行する
##基本

web.xml
  <listener>
    <listener-class>sample.javaee.servlet.listener.WebAppListener</listener-class>
  </listener>
WebAppListener.java
package sample.javaee.servlet.listener;

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;

public class WebAppListener implements ServletContextListener {

    @Override
    public void contextInitialized(ServletContextEvent event) {
        System.out.println("contextInitialized()");
    }

    @Override
    public void contextDestroyed(ServletContextEvent event) {
        System.out.println("contextDestroyed()");
    }
}

GlassFish を起動する。

GlassFishコンソール出力
情報:   contextInitialized()

GlassFish を停止する。

GlassFishコンソール出力
情報:   contextDestroyed()
  • ServletContextListneer を実装したクラスを作成し、 <listener> タグを使って web.xml に登録する。
  • アプリケーション起動・終了時に、 contextInitialized()contextDestroyed() がそれぞれ呼ばれる。

##パラメータを渡す

web.xml
  <context-param>
    <param-name>hoge</param-name>
    <param-value>HOGE</param-value>
  </context-param>
  
  <listener>
    <listener-class>sample.javaee.servlet.listener.WebAppListener</listener-class>
  </listener>
WebAppListener.java
package sample.javaee.servlet.listener;

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;

public class WebAppListener implements ServletContextListener {

    @Override
    public void contextInitialized(ServletContextEvent event) {
        String value = (String)event.getServletContext().getInitParameter("hoge");
        System.out.println("contextInitialized() hoge=" + value);
    }

    @Override
    public void contextDestroyed(ServletContextEvent event) {
        System.out.println("contextDestroyed()");
    }
}

GlassFish を起動する。

GlassFishコンソール出力
情報:   contextInitialized() hoge=HOGE
  • <contex-param> タグを web.xml に記述することで、 ServletContextEvent から値を取得することができる。

#セッションの作成・終了時に処理を実行する

web.xml
  <listener>
    <listener-class>sample.javaee.servlet.listener.SessionListener</listener-class>
  </listener>
SessionListener.java
package sample.javaee.servlet.listener;

import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;

public class SessionListener implements HttpSessionListener {

    @Override
    public void sessionCreated(HttpSessionEvent event) {
        System.out.printf("sessionCreated() id=%s%n", event.getSession().getId());
    }

    @Override
    public void sessionDestroyed(HttpSessionEvent event) {
        System.out.printf("sessionDestroyed() id=%s%n", event.getSession().getId());
    }
}

先ほど作成した SessionServlet にアクセスしてみる。

GlassFishコンソール出力
情報:   sessionCreated() id=7f5d729e7319528e4a8316d16555
情報:   session is created. id=7f5d729e7319528e4a8316d16555
情報:   count up. id=7f5d729e7319528e4a8316d16555, count=1
情報:   sessionDestroyed() id=7f5d729e7319528e4a8316d16555
情報:   session is deleted. id=7f5d729e7319528e4a8316d16555
  • HttpSessionListener を実装したクラスを作成して、 <listener> タグを使って web.xml に登録する。
  • セッションの作成と削除のときに、 sessionCreated()sessionDestroyed() がそれぞれ呼ばれる。
  • 引数の HttpSessionEvent から、対象の HttpSession オブジェクトを取得できる。

#リクエストの前後で処理を挟む

web.xml
  <filter-mapping>
    <filter-name>RequestFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>
RequestFilter.java
package sample.javaee.servlet.filter;

import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

public class RequestFilter implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("RequestFilter before");
        chain.doFilter(request, response);
        System.out.println("RequestFilter after");
    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }

    @Override
    public void destroy() {
    }
}

http://localhost:8080/servlet/test.sample にアクセスする。

GlassFishコンソール出力
情報:   RequestFilter before
情報:   UrlPatternExtensionServlet.doGet()
情報:   RequestFilter after
  • Filter を実装したクラスを作成して、 <filter> タグで web.xml に登録する。
  • <filter-mapping> タグで、フィルターを適用する URL パターンをマッピングする。
  • URL パターンにマッチするリクエストがあると、 doFilter() メソッドが実行される。
    • FilterChain#doFilter() メソッドで、次の処理へ進むことができる(呼ばなくても良い)。

#トップページを指定する

web.xml
  <welcome-file-list>
    <welcome-file>welcome.html</welcome-file>
  </welcome-file-list>
welcome.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Servlet JSP Sample</title>
  </head>
  <body>
    <h1>Welcome Page</h1>
  </body>
</html>

Web ブラウザで http://localhost:8080/servlet にアクセスする。

javaee-servlet.JPG

  • <welcome-file-file> タグで、ルートにアクセスしたときに表示するパスを指定できる。
  • <welcome-file> タグは複数記述できる。
    • 複数記述した場合は、先頭から順番に存在をチェックして、最初に存在見つかったファイルが表示される。
  • ファイルだけでなく、以下のように Servlet にマッピングしたパスも指定可能。
web.xml
  <welcome-file-list>
    <welcome-file>hello</welcome-file>
  </welcome-file-list>

#エラーが発生したときのページを指定する
##HTTP のステータスコードごとにページを指定する

web.xml
  <error-page>
    <error-code>404</error-code>
    <location>/error/404.html</location>
  </error-page>
404.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>404 Not Found</title>
  </head>
  <body>
    <h1>404 Not Found</h1>
  </body>
</html>

Web ブラウザを開いて、 http://localhost:8080/servlet/xxx にアクセスする

javaee-servlet.JPG

  • <error-page> タグで、エラーが発生したときに表示するページを指定できる。
  • <error-code> タグを指定すると、 HTTP のステータスコードごとにページを指定できる。

##スローされた例外ごとにページを指定する

web.xml
  <servlet>
    <servlet-name>NullPointerServlet</servlet-name>
    <servlet-class>sample.javaee.servlet.NullPointerServlet</servlet-class>
  </servlet>
  
  <servlet-mapping>
    <servlet-name>NullPointerServlet</servlet-name>
    <url-pattern>/null</url-pattern>
  </servlet-mapping>
  
  <error-page>
    <exception-type>java.lang.NullPointerException</exception-type>
    <location>/error/null.html</location>
  </error-page>
NullPointerServlet.java
package sample.javaee.servlet;

import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class NullPointerServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
        throw new NullPointerException("NullPointerServlet");
    }
}
null.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>NullPointerException</title>
  </head>
  <body>
    <h1>NullPointerException!!</h1>
  </body>
</html>

Web ブラウザで http://localhost:8080/servlet/null にアクセスする。

javaee-servlet.JPG

  • <exception-type> タグで、例外ごとにエラーページを指定できる。

#アノテーションで設定する
Servlet 3.0 からは、 web.xml で記述していた設定をアノテーションで指定することができる。

##Servlet

AnnotatedServlet.java
package sample.javaee.servlet;

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

@WebServlet("/annotated")
public class AnnotatedServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
        System.out.println("AnnotatedServlet.doGet()");
    }
}

Web ブラウザで http://localhost:8080/servlet/annotated にアクセスする。

GlassFishコンソール出力
情報:   AnnotatedServlet.doGet()

設定できるパラメータについては API ドキュメント を参照。

##Listener

AnnotatedListener.java
package sample.javaee.servlet.listener;

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;

@WebListener
public class AnnotatedListener implements ServletContextListener {

    @Override
    public void contextInitialized(ServletContextEvent sce) {
        System.out.println("AnnotatedListener.contextInitialized()");
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
    }
}

GlassFish を起動する。

GlassFishコンソール出力
情報:   AnnotatedListener.contextInitialized()

##Filter

AnnotatedFilter.java
package sample.javaee.servlet.filter;

import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;

@WebFilter("/*")
public class AnnotatedFilter implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("AnnotatedFilter before");
        chain.doFilter(request, response);
        System.out.println("AnnotatedFilter after");
    }
    
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {}
    @Override
    public void destroy() {}
}

Web ブラウザで http://localhost:8080/servlet にアクセスする。

GlassFishコンソール出力
情報:   AnnotatedFilter before
情報:   AnnotatedFilter after

ただし、アノテーション指定の場合は Filter の順序を定義できない。
順序を定義したい場合はこちら を参照。

#JSP
##JSP とは
JavaServer Pages の略。
ビュー(画面)を動的に作るための仕組み(テンプレートエンジン)。 HTML タグと JSP が持つタグなどを使って画面を記述できる。

Servlet は MVC の C (Controller) を担い、 JSP は V (View) を担う。
しかし、書き方によってはゴリゴリ Java の処理を書くことができるため、魔窟のような JSP も世の中には存在する。

ただし、 Java EE 6 からは、 Facelets という別のテンプレートエンジンがメインとなっている(JSP は無くなってはいない)。

##Hello World

jsp/hello.jsp
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Hello JSP</title>
  </head>
  <body>
    <h1>Hello JSP!!</h1>
  </body>
</html>

Web ブラウザで、 http://localhost:8080/servlet/jsp/hello.jsp にアクセスする。

javaee-servlet.JPG

  • 普通に HTML を記述することができる。
  • 拡張子を .jsp にして保存し、直接 URL をアクセスすれば JSP として実行される。

##JSP は Servlet の実装に変換されて実行される
JSP にアクセスがあると、サーバーは JSP を Servlet の実装に変換する。
GlassFish の場合、以下のフォルダを見ると生成された Servlet の実装を確認できる。

<GlassFish 本体が存在するフォルダ>\domains\<ドメイン名>\generated\jsp\<アプリケーション名>\

以下が、実際に出力された Servlet のソースコード。

hello_jsp.java
package org.apache.jsp.jsp;

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

public final class hello_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("<!DOCTYPE html>\r\n");
      out.write("<html>\r\n");
      out.write("  <head>\r\n");
      out.write("    <meta charset=\"UTF-8\">\r\n");
      out.write("    <title>Hello JSP</title>\r\n");
      out.write("  </head>\r\n");
      out.write("  <body>\r\n");
      out.write("    <h1>Hello JSP!!</h1>\r\n");
      out.write("  </body>\r\n");
      out.write("</html>\r\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);
    }
  }
}

単純に HTML で記述した JSP ファイルは、上記のように Servlet による実装に置き換えられてから実行され、処理結果がクライアントに返されている。

##ページのエンコーディングを指定する

encoding.jsp
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>JSP Page</title>
  </head>
  <body>
    <h1>日本語</h1>
  </body>
</html>

HTML の <meta> タグを使って文字コードを指定しているが、このままで日本語を記述すると文字化けが発生する。

javaee-servlet.JPG

これは、 JSP が HTML を生成するときの文字コードがデフォルトで ISO-8859-1 になっているのが原因。

HTML を出力するときの文字コードは、以下のように指定する。

encoding.jsp
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>JSP Page</title>
  </head>
  <body>
    <h1>日本語</h1>
  </body>
</html>

javaee-servlet.JPG

##任意の Java コードを埋め込む

scriptlet.jsp
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Hello JSP</title>
  </head>
  <body>
    <% String msg = "Hello Scriptlet!!"; %>
    <h1><%=msg %></h1>
  </body>
</html>

javaee-servlet.JPG

  • <% %>スクリプトレットと呼び、任意の Java コードを記述できる。
  • <%= %> で任意の Java コードを評価した結果を HTML 中に埋め込むことができる。

##JSP 内で暗黙的に使用できる変数

implicit.jsp
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Hello JSP</title>
  </head>
  <body>
    <% out.println("<h1>Implicit Objects</h1>"); %>
  </body>
</html>

javaee-servlet.JPG

  • JSP 内では、宣言せずに暗黙的に使用できる変数が存在する。
    • 上記例の場合は、 out という変数を使用している。
    • これの実体は PrintWriter で、 HTML へ任意の文字列を出力できる。

out 以外にも、以下のような変数を使用できる。

変数 説明
out PrintWriter HTML に任意の文字列を出力できる。
request HttpServletRequest 現在のリクエストオブジェクト。
response HttpServletResponse 現在のレスポンスオブジェクト。
pageContext PageContext 現在の JSP ページのコンテキストを持つオブジェクト。
session HttpSession 現在のセッションオブジェクト。
application ServletContext アプリの ServletContext オブジェクト。
page HttpJspPage JSP から生成された Servlet のオブジェクト。

##Java のクラスをインポートする

java_import.jsp
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<%@page import="java.util.List" %>
<%@page import="java.util.Arrays" %>
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Hello JSP</title>
  </head>
  <body>
    <ul>
      <%
      List<String> list = Arrays.asList("hoge", "fuga", "piyo");

      for (String value : list) {
          out.println("<li>" + value + "</li>");
      }
      %>
    </ul>
  </body>
</html>

javaee-servlet.JPG

  • <%@page import="..." %> で、任意の Java クラスをインポートできる。

##コメントを記述する

comment.jsp
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Hello JSP</title>
  </head>
  <body>
    <!--HTML comment-->
    <%-- JSP comment --%>
  </body>
</html>

javaee-servlet.JPG

  • <%-- --%> で JSP のコメントを記述できる。
  • JSP のコメントは、 HTML に出力されない。

##別のファイルを埋め込む

embedded.jsp
<%@include file="hello.jsp" %>

javaee-servlet.JPG

  • <%@include file="..." %> で、任意のファイルを JSP 内に埋め込むことができる。

##Servlet から JSP に処理を移す

JspServlet.java
package sample.javaee.servlet;

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("/jsp-servlet")
public class JspServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("JspServlet.doGet()");
        req.getRequestDispatcher("jsp/hello.jsp").forward(req, resp);
    }
}

Web ブラウザで http://localhost:8080/servlet/jsp-servlet にアクセスする。

GlassFishコンソール出力
情報:   JspServlet.doGet()

javaee-servlet.JPG

  • Servlet と同じ要領でフォワードすることができる。

##EL 式
###EL 式とは
Expression Language の略。
式言語とも呼ばれ、スクリプトレットよりも簡潔な記述で演算結果の出力などができる。

最初は JSP の一部だったが、 Java EE 7 からは独立した仕様となった。
Facelets でも使える。

###基本

el.jsp
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>JSP Page</title>
  </head>
  <body>
    <h1>${"Hello Expression Language"}</h1>
  </body>
</html>

javaee-servlet.JPG

  • ${<評価したい式>} で記述する。
  • 評価結果は、そのまま HTML 上に出力される。

###演算する

el2.jsp
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>JSP Page</title>
  </head>
  <body>
    <h1>100 + 10 + 1 = ${100 + 10 + 1}</h1>
  </body>
</html>

javaee-servlet.JPG

  • ${} の中には、任意の演算式を記述できる。

###暗黙的に参照できるオブジェクト

el3.jsp
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>JSP Page</title>
  </head>
  <body>
    <h1>${pageContext.request.serverName}</h1>
  </body>
</html>

javaee-servlet.JPG

  • JavaBeans の仕様に従って命名された getter メソッドは、 get を省略してプロパティ名だけで参照することができる。
  • スクリプトレットと同じように、 EL 式内では暗黙的に参照できる変数が存在する。
変数名 説明
sessionScope セッションスコープ
requestScope リクエストスコープ
applicationScope アプリケーションスコープ
pageSope ページスコープ
pageContext 現在のページのコンテキスト
param リクエストパラメータのマップ
paramValues リクエストパラメータ(配列)のマップ
header HTTP のヘッダ
headerValues HTTP のヘッダ(配列)
cookie クッキー(マップ)

###各スコープへのアクセス

ELScopeServlet.java
package sample.javaee.servlet;

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("/el-scope-servlet")
public class ELScopeServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.setAttribute("hoge", "HOGE");
        req.getSession().setAttribute("fuga", "FUGA");
        req.getServletContext().setAttribute("piyo", "PIYO");
        
        req.setAttribute("same", "RequestValue");
        req.getSession().setAttribute("same", "SessionValue");
        req.getServletContext().setAttribute("same", "ApplicationValue");
        
        req.getRequestDispatcher("jsp/el4.jsp").forward(req, resp);
    }
}
el4.jsp
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>JSP Page</title>
  </head>
  <body>
    <h1>${requestScope.hoge} - ${sessionScope.fuga} - ${applicationScope.piyo}</h1>
    <h2>${hoge} - ${fuga} - ${piyo}</h2>
    
    <ul>
      <li>${same}
      <li>${requestScope.same}
      <li>${sessionScope.same}
      <li>${applicationScope.same}
    </ul>
    
    <% pageContext.setAttribute("same", "PageScopeValue"); %>
    
    <h3>${same}</h3>
  </body>
</html>

Web ブラウザで http://localhost:8080/servlet/el-scope-servlet にアクセスする。

javaee-servlet.JPG

  • requestScopesessionScopeapplicationScope は、それぞれ HttpServletRequestHttpSessionServletContextsetAttribute() でセットする。
  • 各スコープに保存された値は、 EL 式上ではいきなりキーに指定した名前で参照することができる。
  • 異なるスコープに同じ名前で保存されている値を EL 式で参照した場合、より狭いスコープで設定されている値が取得される。

###配列・リストへのアクセス

el5.jsp
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<%@page import="java.util.*" %>
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>JSP Page</title>
  </head>
  <body>
    <%
    String[] array = {"hoge", "fuga", "piyo"};
    List<String> list = Arrays.asList("HOGE", "FUGA", "PIYO");
    
    pageContext.setAttribute("array", array);
    pageContext.setAttribute("list", list);
    %>
    
    <h2>${array[0]}, ${array[1]}, ${array[2]}</h2>
    <h2>${list[0]}, ${list[1]}, ${list[2]}</h2>
    
  </body>
</html>

javaee-servlet.JPG

  • 配列、 List は、 [] を使ったインデックス指定で要素にアクセスできる。

###マップのエントリへのアクセス

el6.jsp
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<%@page import="java.util.*" %>
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>JSP Page</title>
  </head>
  <body>
    <%
    Map<String, String> map = new HashMap<String, String>();
    map.put("hoge", "HOGE");
    map.put("fuga", "FUGA");
    map.put("piyo", "PIYO");
    
    pageContext.setAttribute("map", map);
    %>
    
    <h2>${map.hoge} - ${map["fuga"]} - ${map['piyo']}</h2>
    
  </body>
</html>

javaee-servlet.JPG

  • Map の各エントリへのアクセスには、 map.キー名map["キー名"] のいずれかの方法が利用できる。

##標準タグライブラリ(JSTL)を使用する

jstl.jsp
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>JSP Page</title>
  </head>
  <body>
    <c:set var="flag" value="true" />
    
    <c:if test="${flag}">
      flag is true
    </c:if>
  </body>
</html>

Web ブラウザで http://localhost:8080/servlet/jsp/jstl.jsp を表示する。

javaee-servlet.JPG

  • JSP には、標準タグライブラリ(JSP Standard Tag Library)というロジックなどを扱うためのタグが用意されている。
  • タグライブラリを使う場合は、 <%@taglib prefix="..." uri="..." %> で使用するタグを宣言する。
    • prefix 属性には、タグを使用するときの名前空間を指定する。
    • uri 属性には、使用するタグを識別するための URI を指定する。
    • URI に指定する値は、 jar ファイルに入っている *.tld ファイルを見ると書いている。

c.tld

javaee-servlet.JPG

c.tld
<taglib xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-jsptaglibrary_2_1.xsd"
    version="2.1">
    
  <description>JSTL 1.2 core library</description>
  <display-name>JSTL core</display-name>
  <tlib-version>1.2</tlib-version>
  <short-name>c</short-name>
  <uri>http://java.sun.com/jsp/jstl/core</uri>

(略)
  • 標準カスタムタグには、以下の5種類が存在する。
名前 説明 URI
core if や for など、最もよく使うロジック処理用のタグが用意されている。 http://java.sun.com/jsp/jstl/core
i18n-capable formatting i18n 対応したフォーマット出力などのタグが用意されている。 http://java.sun.com/jsp/jstl/fmt
functions 文字列操作ようのタグが用意されている。 http://java.sun.com/jsp/jstl/functions
sql データベース接続用のタグが用意されている。 http://java.sun.com/jsp/jstl/sql
xml XML の解析、処理するためのタグが用意されている。 http://java.sun.com/jsp/jstl/xml

##複数のJSPで共通する宣言をまとめる

web.xml
  <jsp-config>
    <jsp-property-group>
      <url-pattern>*.jsp</url-pattern>
      <page-encoding>UTF-8</page-encoding>
      <include-prelude>/jsp/taglib.jsp</include-prelude>
    </jsp-property-group>
  </jsp-config>
taglib.jsp
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
jsp-config.jsp
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>JSP Page</title>
  </head>
  <body>
    <c:set var="flag" value="true" />
    <c:set var="message" value="日本語" />
    
    <c:if test="${flag}">
      ${message}
    </c:if>
  </body>
</html>

Web ブラウザで http://localhost:8080/servlet/jsp/jsp-config.jsp を開く。

javaee-servlet.JPG

  • web.xml<jsp-config> タグを使用することで、複数ファイルで共通する設定を記述することができる。
  • <url-pattern> で指定したパターンにマッチする URL にアクセスがあった場合に限り、ここで指定した共通設定が適用される。
  • <include-prelude> で指定したファイルは、全ての JSP ファイルの先頭に埋め込まれる。ここで標準タグライブラリの使用宣言を記述することで、各ページでの読み込み宣言を省略できる。

#セキュリティ
Servlet では、特定の URL へのアクセスに対して認証による制限を設けることができる。

このアクセス制御は、ロール単位に行える。
つまり、「/admin/* という URL に対しては admin というロールを持ったユーザしかアクセスできない」といった制御ができる。

Servlet が定めているのはアクセス制御のところだけで、ユーザの定義方法やユーザとロールの紐付けはアプリケーションサーバー毎に方法が異なる。

##ユーザを定義する

  • Web ブラウザを開いて http://localhost:4848/ にアクセスする。
  • [Configurations] → [server-config] → [Security] → [Realms] → [file] を選択する。

javaee-servlet.JPG

  • [Manage Users] をクリックする。
  • [New...] をクリックして、以下のユーザーを追加する。
User ID Group List New Password
admin admin_group admin
user user_group user

javaee-servlet.JPG

###レルム
ここで定義したものをレルムと呼ぶ。

レルムとは、ユーザ名とパスワードの組み合わせのように、 Webアプリケーションのユーザを一意に定めるための"データベース"と、 認証された各ユーザに付与されているロールの一覧を列挙するものの両方を合わせたものを指します。

Tomcat5 サーブレット/JSP コンテナ - レルム設定方法

つまり、複数のユーザ情報をまとめてレルムという単位で管理する。
アプリケーションからは、使用するレルムを指定する。

##ロールを定義する

web.xml
  <security-role>
    <role-name>admin_role</role-name>
  </security-role>
  
  <security-role>
    <role-name>user_role</role-name>
  </security-role>
  • 管理者用のロールと、一般ユーザ用のロールを定義している。

##ユーザ(グループ)とロールを紐付ける。

glassfish-web.xml
  <security-role-mapping>
    <role-name>admin_role</role-name>
    <group-name>admin_group</group-name>
  </security-role-mapping>

  <security-role-mapping>
    <role-name>user_role</role-name>
    <principal-name>user</principal-name>
    <group-name>admin_group</group-name>
  </security-role-mapping>
  • ユーザおよびグループとロールの紐付けは、 GlassFish の場合は glassfish-web.xml で定義する。
    • glassfish-web.xmlWEB-INF の直下に配置する。
  • ユーザとロールを紐付ける場合は <principal-name> タグを使用する。
  • グループとロールを紐付ける場合は、 <group-name> タグを使用する。
  • 上記設定は、以下のような意味になる。
    • グループ「admin_group」に所属するユーザは、ロール「admin_role」を持つ。
    • user というユーザとグループ「admin_group」に所属ユーザは、ロール「user_role」を持つ。

##アクセス制御を定義する

web.xml
  <security-constraint>
    <web-resource-collection>
      <web-resource-name>User Resource</web-resource-name>
      <url-pattern>/secure/user/*</url-pattern>
      <http-method>GET</http-method>
    </web-resource-collection>
    <auth-constraint>
      <role-name>user_role</role-name>
    </auth-constraint>
  </security-constraint>
  
  <security-constraint>
    <web-resource-collection>
      <web-resource-name>Admin Resource</web-resource-name>
      <url-pattern>/secure/admin/*</url-pattern>
      <http-method>GET</http-method>
    </web-resource-collection>
    <auth-constraint>
      <role-name>admin_user</role-name>
    </auth-constraint>
  </security-constraint>
  • <security-constraint> タグで、1つのアクセス制御を定義できる。
  • <web-resource-collection> タグで、アクセス制御の対象となる URL パターンや HTTP メソッドを指定できる。
    • <http-method> は省略可。省略した場合は、全てのメソッドが制限の対象になる。
    • <web-resource-collection><url-pattern><http-method> は複数指定可能。
  • <auth-constraint> タグで、そのリソースにアクセスしていいロールを指定する。
    • <role-name> タグは複数指定可。
  • 上記設定は、以下のような意味になる。
    • /secure/user/* への GET リクエストは、 user_role ロールを持つユーザだけが許可される。
    • /secure/admin/* への GET リクエストは、 admin_role ロールを持つユーザだけが許可される。

##ログイン方法と使用するレルムを指定する

web.xml
  <login-config>
    <auth-method>BASIC</auth-method>
    <realm-name>file</realm-name>
  </login-config>
  • <auth-method> でログイン方法を定義する。
    • ここでは、 BASIC 認証を指定している。
  • <realm-name> で、使用するレルムを指定している。
    • ここでは、先ほどユーザ定義を追加した file レルムを指定している。

ログイン方法には、以下のようなものが使える。

設定値 ログイン方法
BASIC BASIC 認証
DIGEST DIGEST 認証
FORM HTML の form タグを使った認証
SSL SSL 認証

##ページを作成する
Web ページの下に、 secure/user/user.htmlsecure/admin/admin.html を作成する。

##動作確認
Web ブラウザで http://localhost:8080/servlet/secure/admin/admin.html を開く。

BASIC 認証用のログインダイアログが表示されるので、 admin ユーザでログインする。

javaee-servlet.JPG

javaee-servlet.JPG

続いて、そのまま http://localhost:8080/servlet/secure/user/user.html を開く。

javaee-servlet.JPG

ログインダイアログが表示されることなく、ページが表示される。

次に、ブラウザを一旦終了させてから、 http://localhost:8080/servlet/secure/user/user.html にアクセスする。

javaee-servlet.JPG

再びログインダイアログが表示されるので、今度は user ユーザでログインする。

javaee-servlet.JPG

続いて http://localhost:8080/servlet/secure/admin/admin.html にアクセスする。

javaee-servlet.JPG

403 で弾かれる。

#参考

139
150
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
139
150

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?