学校の課題で**Java Servletを使ってなんか作ろう!**みたいなお話が舞い込んできたので、それのお話。
僕はLaravelをよく使っていて、それのように使えるなぁと感じたので対応させながら紹介します。
Servletがコントローラのような立ち位置
package servlets;
import models.Room;
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 java.io.IOException;
import java.sql.SQLException;
import java.util.List;
@WebServlet("/")
public class RoomServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
List<Room> rooms;
try {
Class.forName("org.apache.derby.jdbc.EmbeddedDriver");
rooms = Room.index();
} catch (SQLException | ClassNotFoundException e) {
request.setAttribute("error", e);
request.getRequestDispatcher("/WEB-INF/jsp/views/rooms/room-list.jsp").forward(request, response);
return;
}
request.setAttribute("rooms", rooms);
request.getRequestDispatcher("/WEB-INF/jsp/views/rooms/room-list.jsp").forward(request, response);
}
}
@WebServlet
アノテーションでルーティングができる。
他にもweb.xml
を書き換えてルーティングすることもできないけど、書く量が少し増えるので面倒くさい。
doGet
メソッドはHTTPリクエストのGETに反応して処理をする。
request
にはパラメータとかリクエストヘッダとか、必要なものが詰め込まれている。
response
にはgetWriter()
とかあるので、Servletからレンダリングもできる。
request.setAttribute("attributeName", value)
で内部的(?)にパラメータを追加して、request.getRequestDispatcher("/path/to/page.jsp")
などを使えば他のServletやJSPに処理を移すことができる。移した先でrequest.getAttribute("attributeName")
とすると、取得できる。
request.setAttribute(String, obj)
は何でも渡せる。
request.getAttribute(String)
の返り値はObject型なので、値を受け取って、アップキャストする必要がある。
他にもdoPost()
、doPut()
、doDelete()
などがあるのでREST APIもわりかし簡単につくれますね。
JSPはテンプレートエンジンのように使える
<%@ page import="models.Room" %>
<%@ page import="java.util.List" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<jsp:include page="/WEB-INF/jsp/views/base/head.jsp">
<jsp:param name="title" value="スレッド一覧"/>
</jsp:include>
<body class="bg-light">
<jsp:include page="/WEB-INF/jsp/views/base/navber.jsp"/>
<div class="container">
<h1 class="font-weight-bold">スレッド一覧</h1>
<% if (request.getAttribute("error") != null) {
Exception e = (Exception) request.getAttribute("error");
%>
<div class="text-danger">
<%=e.getMessage()%>
</div>
<% } else {
List<Room> rooms = (List<Room>) request.getAttribute("rooms");
%>
<div class="row">
<%
for (Room it : rooms) {
request.setAttribute("room", it);
%>
<div class="col-12 mb-3">
<jsp:include page="/WEB-INF/jsp/components/room-card.jsp"/>
</div>
<% } %>
</div>
<% } %>
</div>
</body>
</html>
<jsp:include page="path/to/page.jsp" />
を使って他のJSPを埋め込める。この埋め込みは他ファイルのJSPをそのまま追記してレンダリングされるような感じになるので、Vue.jsの単一ファイルコンポーネントのような気持ちで使うことができる。
<jsp:include>
タグの子要素として<jsp:param>
タグを使えばパラメータを渡すこともできる。
こちらはServletのrequest.setAttribute()
と同義で、埋め込まれたJSPからはrequest.getAttribute()
を使って読み込む。
Vue.jsの<slot>
のように、UIの大枠をひとつのJSPとして作り、その中の子要素としてViewを入れるという使い方は厳しそう。
また、テンプレート構文(というのは正しくなさそうだが)はJavaの構文をそのまま使う。
新たにテンプレート構文を覚える労力は必要無いが、Flaskのようなシンプルさはなく、ごちゃついた印象を受けた。
また、Java構文が何でも使えてしまうのでJSPに何でも書けてしまう。
自重しないとPHPのようになるので気をつけたほうがいいかも。
...JSPですべてを作るというのなら止めはしないが。
また、WEB-INF/
ディレクトリはブラウザからはアクセスできないディレクトリになる。
埋め込んだり、ディスパッチャに使われる前提であったりするJSPはWEB-INF/
ディレクトリに配置すべきだろう。
Filterがミドルウェアみたいな感じ
Filterはルーティングされているクラスに処理が渡る前に処理を挟むことができる。
例えば、ログインができていなかったらログインページにリダイレクトさせたり、文字エンコーディングを矯正させたり。
package filters;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;
@WebFilter(filterName = "CharacterEncodingFilter")
public class CharacterEncodingFilter implements Filter {
public void destroy() {
}
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
req.setCharacterEncoding("UTF-8");
resp.setContentType("text/html; charset=UTF-8");
chain.doFilter(req, resp);
}
public void init(FilterConfig config) throws ServletException {
}
}
doFilter()
が肝心なフィルタ処理部分。
引数のreq
はHttpServletRequest
クラスへアップキャストすることもできた。
詳しくは調べていないのでこれ以上はわからないけど、色々楽しめそう。
Filterを効かすにはweb.xml
ファイルで<url-pattern>
を指定したりする。
モデル層は頑張って設計しよう
Java EEにはLaravelのartisanのようなCLIは持っていないし、デザインパターンも考えられていない。
ので、ServletとJSP、Filterから使われるいわゆる「モデル」となる部分については自分で設計する必要がある。
ここの味付けによって好きなアーキテクチャでアプリケーション全体を構築することができるだろう。
自分で好きなアーキテクチャを構築する必要がある分、手間も責任もプログラマにかかってきてしまう。
感想
デプロイの手間は似たようなもん
結局webアプリはwebサーバーの機能を借りて運用することがほとんど。
PythonのFlaskなんかは一応サーバーも持ってるけど、本番環境でやるときはuWSGIでwebサーバーと連携させることがほとんどだし、Tomcatなどを使うくらいは許容範囲かなと思う。
手間がすごい
Laravelのような便利なものになれていると素のJava EEだけでは面倒に感じちゃう。
フロントエンドを充実させたくば、自分でwebpackなり、nodeなりを設定する必要があるし、認証システムやCSRF保護などの機能が無いので、自分でプログラミングする必要がある。
あと、JSPにはエスケープ機能も無いのでXSSの危険性もある。
作られた時代が違うから比べるもんじゃないと思うけど。
ただ、セッションはかなり手軽に簡単に扱うことができたので、認証システムはいらないけど、アクセスしてきたユーザはある程度識別したいというようなケースの場合は困らないかも。
けど長く動かすとなると...
メンテ状況が不安
Java EEの直近のリリースは2017年9月21日らしい。1
あとTomcat。
Tomcat 8.5.47を動かすのにJDK 13を指定したら未対応であるという旨のエラーが出た。
JRE 1.8を指定することで事なきを得ているが不安ではある。
Tomcat 92は試していないので、もしかしたら対応しているかも。
まとめ
講義でやることなんて古くて仕方ないだろうと思ってたけど、Servletはよくできているなーと思った。
けど、Laravelとかのほうが機能が豊富で作りやすいから使いたい!とは思わなかった。