タイトル: JSP
SEOタイトル: JSP (JavaServer Pages) の構文と現代的な使い方(EL / JSTL / Thymeleaf 移行)
| この記事の要点 |
|
JSP とは
JSP (JavaServer Pages) は、HTML 内に Java コードや EL 式を埋め込んで動的ページを生成するサーバサイドテンプレート技術です。実行時にはサーブレットに変換され、JVM 上で動きます。Java EE (現 Jakarta EE)の標準仕様で、Servlet とセットで使われてきました。
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head><title>Hello JSP</title></head>
<body>
<h1>Welcome, ${user.name}!</h1>
<ul>
<c:forEach var="item" items="${items}">
<li>${item.title} - ${item.price}円</li>
</c:forEach>
</ul>
</body>
</html>
主な構文要素
| 構文 | 意味 | 例 |
|---|---|---|
<%@ page %> | ページディレクティブ | 文字コード・import など |
<%@ include %> | 静的 include | コンパイル時に取り込み |
<%@ taglib %> | タグライブラリ宣言 | JSTL / カスタムタグ |
<%! ... %> | 宣言 (Servlet クラスのフィールド/メソッド) | <%! int counter = 0; %> |
<% ... %> | スクリプトレット (Java 文) | <% int n = 10; %> |
<%= expr %> | 式 (出力) | <%= user.getName() %> |
<%-- comment --%> | JSP コメント (出力されない) | HTML コメント は出る |
${expr} | EL 式 | ${user.name} |
<c:if> / <c:forEach> | JSTL コアタグ | 条件分岐・繰り返し |
ページディレクティブと文字コード
<%@ page language="java"
contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"
import="java.util.*, com.example.model.User"
session="true"
errorPage="/WEB-INF/error.jsp" %>
<%-- import を複数 --%>
<%@ page import="java.util.List" %>
<%@ page import="java.util.Map" %>
<%-- 別ファイルを静的に取り込み (コンパイル時) --%>
<%@ include file="header.jsp" %>
<%-- 動的 include (実行時) --%>
<jsp:include page="header.jsp" />
スクリプトレットと式 (旧スタイル)
<%@ page contentType="text/html;charset=UTF-8" %>
<%@ page import="java.util.*" %>
<%-- 宣言: クラスのフィールドになる --%>
<%! private int hitCounter = 0; %>
<%-- スクリプトレット: _jspService メソッド内のローカル --%>
<%
String name = (String) request.getAttribute("name");
List<String> items = (List<String>) request.getAttribute("items");
hitCounter++;
%>
<h1>Hello, <%= name %></h1>
<p>このページの表示回数: <%= hitCounter %></p>
<ul>
<% for (String item : items) { %>
<li><%= item %></li>
<% } %>
</ul>
<%-- ❌ ロジックが散らかる。現代では避ける --%>
EL (Expression Language) と JSTL (推奨スタイル)
スクリプトレットを書かず、EL + JSTL で表現するのが現代の JSP の作法です。テンプレートとロジックを分離できます。
<%@ page contentType="text/html;charset=UTF-8" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<%-- EL でリクエスト属性 / セッション / プロパティアクセス --%>
<h1>Hello, ${user.name}!</h1>
<p>年齢: ${user.age}</p>
<p>住所: ${user.address.city}</p>
<%-- 条件分岐 --%>
<c:if test="${user.admin}">
<p>管理者です</p>
</c:if>
<c:choose>
<c:when test="${user.age >= 20}">大人</c:when>
<c:when test="${user.age >= 13}">中高生</c:when>
<c:otherwise>子供</c:otherwise>
</c:choose>
<%-- 繰り返し --%>
<ul>
<c:forEach var="item" items="${items}" varStatus="s">
<li>${s.count}: ${item.title} - <fmt:formatNumber value="${item.price}" type="currency"/></li>
</c:forEach>
</ul>
<%-- 関数 --%>
<p>アイテム数: ${fn:length(items)}</p>
<p>大文字: ${fn:toUpperCase(user.name)}</p>
<%-- 日付フォーマット --%>
<p>登録日: <fmt:formatDate value="${user.createdAt}" pattern="yyyy/MM/dd HH:mm"/></p>
EL のスコープ参照
EL は名前の前に明示しなくても各スコープを順に探します(pageScope → requestScope → sessionScope → applicationScope):
<%-- 任意のスコープから自動探索 --%>
${user.name}
<%-- スコープ明示 --%>
${requestScope.user.name}
${sessionScope.cart.items}
${applicationScope.config.title}
<%-- リクエストパラメータ --%>
${param.q} <%-- ?q=xxx --%>
${paramValues.tags[0]} <%-- 配列 --%>
<%-- Cookie / Header --%>
${cookie.JSESSIONID.value}
${header['User-Agent']}
${initParam.appName} <%-- web.xml の init-param --%>
サーブレット側との連携
// MVC: Servlet がコントローラ、JSP がビュー
@WebServlet("/users")
public class UserListServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
// モデル
List<User> users = userService.findAll();
// リクエストスコープに格納
req.setAttribute("users", users);
req.setAttribute("title", "ユーザ一覧");
// JSP に転送
req.getRequestDispatcher("/WEB-INF/views/user-list.jsp")
.forward(req, resp);
}
}
カスタムタグ (Tag File)
<%-- /WEB-INF/tags/userCard.tag --%>
<%@ tag pageEncoding="UTF-8" %>
<%@ attribute name="user" required="true" type="com.example.User" %>
<div class="user-card">
<strong>${user.name}</strong>
<span>${user.email}</span>
</div><%-- 使う側 --%>
<%@ taglib prefix="my" tagdir="/WEB-INF/tags" %>
<c:forEach var="u" items="${users}">
<my:userCard user="${u}" />
</c:forEach>
Jakarta EE への移行
Jakarta EE 9(2020 年)から名前空間が javax.* → jakarta.* に変わり、JSP は 3.0 → 3.1 になりました。Tomcat 10+ は jakarta 系のみ対応。
| 項目 | Java EE / Tomcat 9 | Jakarta EE / Tomcat 10+ |
|---|---|---|
| パッケージ | javax.servlet.* | jakarta.servlet.* |
| JSTL taglib URI | http://java.sun.com/jsp/jstl/core | jakarta.tags.core |
| JSP 仕様 | 2.3 | 3.1 |
Thymeleaf への移行が主流
Spring Boot 系で新規に JSP を採用する理由はほとんどありません。以下の理由で Thymeleaf が標準的に使われます:
- JSP はSpring Boot の自動設定対象外(追加設定が必要)
- JSP はブラウザで直接プレビューできない(独自タグでデザイナとの分業しづらい)
- Thymeleaf は
<h1 th:text="${title}">サンプル</h1>のようにHTML として有効 - セキュリティ(XSS)デフォルト挙動が Thymeleaf の方が安全
FAQ
Q: スクリプトレットを禁止するには?
A: web.xml に <scripting-invalid>true</scripting-invalid> を追加。チームの規約として強制できる。
Q: EL で出力すると XSS は大丈夫?
A: ${...} はエスケープしない。安全に出すには <c:out value="${...}"/> または fn:escapeXml()。
Q: JSP のホットリロード
A: Tomcat 等は JSP を変更すれば自動再コンパイル。本番では development=false でビルド時のみコンパイルにする方が高速。