Java | サーブレット

Java

Springを触っていてサーブレットに関して知識が乏しい問題に行き着いたのと、kindleの本でイイのがあったので勉強してみた記録です。

参考書籍

JSPサーブレット: Javaサーバーサイド 実践Java言語シリーズ

丁寧でおススメです(kindle unlimitedで読めます)

サーブレットとJSP

  • サーブレットでできることはJSPでもできる
  • JSPでできることはサーブレットでもできる
  • では、なぜサーブレットをなぜ利用するのか?
    • 役割分担できるから
      • サーブレットがプログラム処理
      • JSPがレスポンス出力

jspでは<% %>の間にJavaプログラムを書くことができます。

web.xml

  • サーブレットクラスを登録
    • servlet
  • ルーティングを登録
    • servlet-mapping
  • 外部リソースの参照
    • resource-ref
  • フィルターの登録
    • filter
      • マッピング
        • filter-mapping

サーブレットクラスの作成

  • HttpServletクラスを継承する
    • doGetかdoPostのオーバーライド
  • パラメータの取得
    • getParameter(key);
  • レスポンス
    • response.getWriter()が使える

JSP

  • パラメータの取得
    • requestオブジェクトを利用して取得できる
      • request.getParameter(key);
        • JSPが事前に用意している
  • レスポンス
    • out.println(value);
      • outもrequest同様にインスタンス化不要で利用可能

サーブレットとJSPの連携

  • サーブレットからJSPに値を渡す
    • request.setAttribute(key,value);
  • JSPで値を受け取る
    • request.getAttribute(key);
  • サーブレットからJSPのファイルを呼び出す
    • getServletContext();
    • getRequestDispatcher();
    • forward(request,response);

超簡単なサンプル作成

以下のような簡単なアプリケーションを作成する

  • データベースからレコードを取得する
  • データベースにレコードを登録する
  • フィルターの利用

データベースのアクセスと、フィルターに関しては参考書籍に記載はありませんのでご注意ください。

プロジェクトの新規作成

新規Mavenプロジェクトを作成

Archetypeでmaven-archetype-webappを選択します

次にpomに依存関係を追加します

  • Servlet-api
  • PostgreSQL JDBC Driver
  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>
    <!-- 追加 -->
	<dependency>
	    <groupId>javax.servlet</groupId>
	    <artifactId>servlet-api</artifactId>
	    <version>2.5</version>
	    <scope>provided</scope>
	</dependency>	
	<dependency>
	    <groupId>org.postgresql</groupId>
	    <artifactId>postgresql</artifactId>
	    <version>42.4.1</version>
	</dependency>  
  </dependencies>

maven build compileしておきます。

私はstsでプロジェクトを作成したのですが、ここでsrc/main/javaとかがmissingになっていることに気が付きました。

プロジェクトのビルドバスからOrder and Exportタブを開き、JRE System LibraryとMaven Dependenciesにチェックを入れる

これでOK

単純なGetメソッドでJSPと連携する

新規サーブレットクラスを作成すると、ほぼ出来上がった状態でクラスが作成される
doGetメソッドを以下のように修正する。

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
	// jspに出力を依頼する
	getServletContext().getRequestDispatcher("/kakine/get.jsp").forward(request, response);
	//response.getWriter().append("Served at: ").append(request.getContextPath());
}

jspファイルをsrc/main/webapp/kakineに下記のように作成します

<html>
<body>
<h2>GETのサンプル</h2>
</body>
</html>

web.xmlにてルーティングの設定を行おうと思いますが、サーブレットクラスを登録すると自動的に簡単な設定がされているので確認してみます

<web-app>
  <display-name>Archetype Created Web Application</display-name>
  <servlet>
  	<servlet-name>GetSample</servlet-name>
  	<display-name>GetSample</display-name>
  	<description></description>
  	<servlet-class>com.kakine.GetSample</servlet-class>
  </servlet>
  <servlet-mapping>
  	<servlet-name>GetSample</servlet-name>
  	<url-pattern>/GetSample</url-pattern>
  </servlet-mapping>
</web-app>

GetSampleクラスが登録されており、/GetSampleというアクセスに対して紐づけられています

実行しますのでサーバータブの追加と削除でプロジェクトを追加します。
私はTomcatを利用しています。kakineが追加されている状態

これで実行して、http://localhost:8080/kakine/GetSampleにアクセスします
※kakineの部分はプロジェクト名

文字化けしているのでjspファイルを修正します

<%@ page contentType="text/html;charset=UTF-8" %> 
<!DOCTYPE html>
<html lang="ja">
<body>
<h2>GETのサンプル</h2>
</body>
</html>

次にサーブレットから値を渡して、jspで表示してみます

doGetメソッドを以下のように修正します

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
	request.setAttribute("name", "垣根提督");
	request.setAttribute("level", 5);
	getServletContext().getRequestDispatcher("/kakine/get.jsp").forward(request, response);
}

jspから受け取る時は用意されているrequestオブジェクトが利用できます

<%@ page contentType="text/html;charset=UTF-8" %> 
<!DOCTYPE html>
<html lang="ja">
	<body>
		<h2>GETのサンプル</h2>
		<p>名前 = <%=request.getAttribute("name") %></p>
		<p>レベル = <%=request.getAttribute("level") %></p>
	</body>
</html>

データベースからレコードを取得する

色々やり方はあるかと思いますが、今回はJNDIを利用してみます

webappフォルダにMETA-INFフォルダを作成してcontext.xmlを作成します

<?xml version="1.0" encoding="UTF-8"?>
<Context>
	<Resource 
		name="jdbc/postgres" 
		auth="Container" 
		type="javax.sql.DataSource"
		driverClassName="org.postgresql.Driver" 
		url="jdbc:postgresql://localhost:5432/postgres"
		username="postgres" 
		password="ps" 
		maxActive="20" 
		maxIdle="10" 
		maxWait="-1"
	/>
</Context>

次にweb.xmlを修正します。resource-refを使って定義したデータベース接続情報を指定しています。

<web-app>
  <display-name>Archetype Created Web Application</display-name>
  <servlet>
  	<servlet-name>GetSample</servlet-name>
  	<display-name>GetSample</display-name>
  	<description></description>
  	<servlet-class>com.kakine.GetSample</servlet-class>
  </servlet>
  <servlet-mapping>
  	<servlet-name>GetSample</servlet-name>
  	<url-pattern>/GetSample</url-pattern>
  </servlet-mapping>
  <!-- 追加 -->
  <resource-ref>
	  <description>Postgres Connection</description>
	  <res-ref-name>jdbc/kakine</res-ref-name>
	  <res-type>javax.sql.DataSource</res-type>
	  <res-auth>Container</res-auth>
  </resource-ref>
</web-app>

データベースの準備については省略しますが、今回はdockerでpostgre環境を用意しています。

version: '3.7'
services:
  postgre:
      image: postgres:latest
      ports:
          - 5432:5432
      container_name: test_postgre
      volumes:
      - ./data:/var/lib/postgresql/data
      # 初期化用のシェルを格納しておきます。
      - ./setup:/docker-entrypoint-initdb.d
      environment: 
          POSTGRES_USER: kakine
          POSTGRES_PASSWORD: teitoku
          POSTGRES_DB: school

以下のようなレコードがitemsテーブルに入っている想定です

INSERT INTO items(name) VALUES 
    ('figma SP-020 とある魔術の禁書目録 ミサカ 電撃屋通販<20,000体>限定品'),
    ('ミサカ盛り'),
    ('とある自治体のストラップ')
;

次にdoGetメソッドを修正します。(普通はフレームワークを利用することがほとんどなので、以下のような書き方はしません)

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

	List<String> result = new ArrayList<>();
	
	try {
		Context context = new InitialContext();
		DataSource dataSource = (DataSource) context.lookup("java:comp/env/jdbc/kakine");
		try(
			Connection con = dataSource.getConnection();
			Statement statement = con.createStatement();
			ResultSet resultSet = statement.executeQuery("select * from items");
			){
			while (resultSet.next()) {
				result.add(resultSet.getString("name"));//カラム名
			}
		}
	} catch (NamingException e) {
		e.printStackTrace();
	} catch (SQLException e) {
		e.printStackTrace();
	}
	request.setAttribute("items", result);
	getServletContext().getRequestDispatcher("/kakine/get.jsp").forward(request, response);
}

jspでは簡易的に表示させておきます

<%@ page contentType="text/html;charset=UTF-8" %> 
<!DOCTYPE html>
<html lang="ja">
	<body>
		<h2>GETのサンプル</h2>
		<%=request.getAttribute("items") %>
	</body>
</html>

データベースからレコードが取得できていることが確認できました。

データベースに登録する

次はdoPostメソッドを利用してデータベースにレコードを登録してみます。
駆け足でやっていきます

get.jspに登録フォームを追加します

<%@ page contentType="text/html;charset=UTF-8" %> 
<!DOCTYPE html>
<html lang="ja">
	<body>
		<h2>GETのサンプル</h2>
		<%=request.getAttribute("items") %>
		
		<form method="post" action="GetSample">
			<input type="text" name="item">
			<button>登録</button>
		</form>
		
	</body>
</html>

GetSample.javaのdoPostメソッドを修正します

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
	// データベース登録時の文字化けを防ぐため文字コードを指定する
	request.setCharacterEncoding("UTF-8");
	// postしたパラメータを取得する
	String item = request.getParameter("item");
	// データベースに登録する
	try {
		Context context = new InitialContext();
		DataSource dataSource = (DataSource) context.lookup("java:comp/env/jdbc/kakine");
		try(
			Connection con = dataSource.getConnection();
			// プリペアドステートメントを利用します。
			PreparedStatement statement = con.prepareStatement("INSERT INTO items(name) VALUES (?);");
			){
			statement.setString(1, item);
			statement.execute();
		}
	} catch (NamingException e) {
		e.printStackTrace();
	} catch (SQLException e) {
		e.printStackTrace();
	}
	doGet(request, response);
}

フォームに入力して登録を押下します

またdoGetが呼ばれるのでページが更新されます

入力した内容が登録されていることがわかります

フィルター

SpringSecurityでよく出てくるフィルターについて触って終わりにします。

フィルターを利用することでGETやPOSTの処理の間にフィルターの処理を挟むことができます。
jwtのトークンのチェックなどをする時などに利用しました。

Filterを実装したクラスを作成します

3つのメソッドをオーバーライドしますがdoFilterメソッドのみ処理を記載していきます

public class SampleFilter implements Filter{

	@Override
	public void init(FilterConfig filterConfig) throws ServletException {
		// TODO Auto-generated method stub
	}

	@Override
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
			throws IOException, ServletException {
		System.out.println(this.getClass().getName() + "フィルターが呼ばれました");
		// 次のフィルターを呼ぶ
		chain.doFilter(request, response);
	}

	@Override
	public void destroy() {
		// TODO Auto-generated method stub
	}

}

もう一個Filterの実装クラスを作成しておきます

public class NextFilter implements Filter {

	@Override
	public void init(FilterConfig filterConfig) throws ServletException {
		// TODO Auto-generated method stub	
	}

	@Override
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
			throws IOException, ServletException {
		System.out.println(this.getClass().getName() + "フィルターが呼ばれました");
		// 次のフィルターを呼ぶ
		chain.doFilter(request, response);
	}

	@Override
	public void destroy() {
		// TODO Auto-generated method stub	
	}

}

web.xmlにフィルタークラスの登録とフィルターのマッピングを行います

<web-app>
  <display-name>Archetype Created Web Application</display-name>
  <servlet>
  	<servlet-name>GetSample</servlet-name>
  	<display-name>GetSample</display-name>
  	<description></description>
  	<servlet-class>com.kakine.GetSample</servlet-class>
  </servlet>
  <servlet-mapping>
  	<servlet-name>GetSample</servlet-name>
  	<url-pattern>/GetSample</url-pattern>
  </servlet-mapping>
  <resource-ref>
	  <description>Postgres Connection</description>
	  <res-ref-name>jdbc/kakine</res-ref-name>
	  <res-type>javax.sql.DataSource</res-type>
	  <res-auth>Container</res-auth>
  </resource-ref>
  <!-- 追加 -->
  <filter>
  	<filter-name>SampleFilter</filter-name>
  	<filter-class>com.kakine.SampleFilter</filter-class>
  </filter>
  <filter>
  	<filter-name>NextFilter</filter-name>
  	<filter-class>com.kakine.NextFilter</filter-class>
  </filter>
  <!-- 順番に定義される -->
  <filter-mapping>
  	<filter-name>SampleFilter</filter-name>
  	<url-pattern>/*</url-pattern>
  </filter-mapping>
  <filter-mapping>
  	<filter-name>NextFilter</filter-name>
  	<url-pattern>/*</url-pattern>
  </filter-mapping>
</web-app>
  • 全てのアクセスに対してSampleFilterとNextFilterが実行される
  • 順番はSampleFilter→NextFilter

適当に画面を表示させるとログが出力されています

フィルターが順番に正しく動いていることが確認できました。

終わりに

サーブレットを使って以下の事を行いました

  • JSP連携
    • サーブレットで値を渡す
    • JSPで値を取得する
  • データアクセス
    • レコードの取得
    • レコードの登録
  • フィルター
    • フィルターチェイン

またweb.xmlがアプリケーションと密接な関係になっていることも確認できました。

今回のソースコードはGithubにあげております

関連記事

コメント

タイトルとURLをコピーしました