JSPのカスタムタグを作成するサンプル


jspのカスタムタグを作成することがあったので、そのメモ――というよりサンプルです。ご査収ください(´・ω・｀)

まずは要件定義から! サンプルということで簡単ですが、次のようにまとめてみました。

- データベースの検索結果をプルダウンとして表示する
    - ただしサンプルなので、データベースはモックとして実装する。
- タグ名は`sample:select`。必須属性として`group`を持ち、これをキーにしてデータベースを検索する。

# 事前準備: DBアクセス層とServletを作成する。

DBの検索結果を表示するカスタムタグを作りたいということで、DBへのアクセスとその検索結果を示すクラスを作成します。またjspを表示するためのServletも必要となるので、これも作成します。以下がそのサンプルコードになりますが、__package宣言、import宣言、GetterおよびSetterは冗長なため省略しています。__ただしパッケージ構成については最初に示す、画像をもとに判断してもらえると幸いです(´・ω・｀)

![image](https://qiita-image-store.s3.amazonaws.com/0/105859/e7f4573a-c056-c093-57d5-efc3e496ea65.png)

```java:DB.java
/**
 * データベースへのアクセス・クラス。ただしサンプルのためモック実装とする。
 */
public class DB implements Serializable {    
    private List<Row> rows;
    
    /**
     * コンストラクタ。インスタンスを生成するとともに、モックDBへの初期データ投入を行う。
     */
    public DB() {
        this.rows = new ArrayList<>();
        
        this.rows.add(new Row("group1", "A", "AAA"));
        this.rows.add(new Row("group1", "B", "BBB"));
        this.rows.add(new Row("group1", "C", "CCC"));
        this.rows.add(new Row("group1", "D", "DDD"));
        this.rows.add(new Row("group1", "E", "EEE"));
        
        this.rows.add(new Row("group2", "a", "あああ"));
        this.rows.add(new Row("group2", "b", "いいい"));
        this.rows.add(new Row("group2", "c", "ううう"));
        this.rows.add(new Row("group2", "d", "えええ"));
        this.rows.add(new Row("group2", "e", "おおお"));
    }
    
    /**
     * groupをキーにDBを検索する。
     * @param group groupカラムの値
     * @return  検索結果
     */
    public List<Row> select(String group) {
        return rows.stream()
                .filter(row -> row.getGroup().equals(group))
                .collect(Collectors.toList());
    }
}
```

```java:Row.java
/**
 * com.sample.db.DBクラスが示すデータベースの行をしめすBeanクラス。
 */
final public class Row implements Serializable {
    private String group;
    private String id;
    private String name;

    public Row(String group, String id, String name) {
        this.group = group;
        this.id = id;
        this.name = name;
    }
}
```
```java:SampleServlet.java
/**
 * sample.jspを表示するためのサーブレット。
 */
@WebServlet("/sample")
public class SampleServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        RequestDispatcher dispatcher = request.getRequestDispatcher("/WEB-INF/sample.jsp");
        dispatcher.forward(request, response);
    }
}
```

# タグハンドラを実装する。

前提条件が整ったところで、カスタムタグに対応するJavaクラスであるタグハンドラを実装します。タグハンドラはインターフェイスである`javax.servlet.jsp.tagext.Tag`を実装することにより実現し、そのサンプルコードは以下の通りです。

```java:Select.java
public class Select implements Tag {
    
    private Tag parent;
    private PageContext pageContext;
    private String group;
    
    @Override
    public int doStartTag() throws JspException {
        return Tag.SKIP_BODY; // このタグはBody要素を持たない。
    }
    
    @Override
    public int doEndTag() throws JspException {
        // groupをもとにDBを検索する。
        DB db = new DB();
        List<Row> rows = db.select(this.group);
        
        // DBへの検索結果をもとにselectタグを動的に生成する。
        String select = rows.stream().map(row -> {
            String id = row.getId();
            String name = row.getName();
            
            return String.format("<option value=\"%s\">%s</option>", id, name);
        }).collect(Collectors.joining("", "<select>", "</select>"));
        
        // 生成したselectタグを動的に生成する。
        try {
            JspWriter out = this.pageContext.getOut();
            out.print(select);
        } catch (IOException e) {
            throw new JspException(e);
        }
        
       return Tag.EVAL_PAGE; 
    }
    
    @Override
    public void release() {
    }
    
    @Override
    public Tag getParent() {
        return this.parent;
    }
    
    @Override
    public void setParent(Tag parent) {
        this.parent = parent;
    }
   
    @Override
    public void setPageContext(PageContext pageContext) {
        this.pageContext = pageContext;
    }

    public String getGroup() {
        return group;
    }

    public void setGroup(String group) {
        this.group = group;
    }
}
```

なおここでの「はまりポイント」としては次の二つがありました。

- 属性に対応するフィールドならびにGetterおよびSetterを実装する必要がある。
    - 上記のサンプルでは`private String group;` `getGroup` `setGroup`がそれぞれ存在しないとうまくいかない。
- `JSPWriter`は明示的に`close`しない。

# tldファイルを準備する。

Javaの世界の住人であるタグハンドラが用意できたところで、タグハンドラとjspの世界の住人であるタグを結びつける必要があります。これを設定するのがtldファイルであり、そのサンプルを以下に示します。なお拡張子はtldですが、実態としてはxml形式です。

```xml:sample.tld
<?xml version="1.0" encoding="UTF-8"?>

<taglib 
    xmlns="http://java.sun.com/xml/ns/j2ee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee web-jsptaglibrary_2_0.xsd"
    version="2.0">

  <tlib-version>1.0</tlib-version>
  <short-name>sample</short-name>

  <tag>
    <name>select</name>
    <tag-class>com.sample.tag.Select</tag-class>
    <body-content>empty</body-content>
    <attribute>
        <name>group</name>
        <required>true</required>
    </attribute>
  </tag>
</taglib>
```

# jspファイルを作成してデプロイする。

あとは自作したタグを利用したjspファイルを作成し、サーバにアプリをデプロイします。

```jsp
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<%@taglib uri="sample.tld" prefix="sample" %>

<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>JSP Sample</title>
    </head>
    <body>
        <h1>JSP Sample</h1>
        <p><sample:select group="group1" /></p>
        <p><sample:select group="group2" /></p>
    </body>
</html>
```

最後に結果確認としてWebブラウザなどで`/jsp/sample`にアクセスすると、次のようなソースコードにより表現されるWebページが表示されるはずです。

```html
<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>JSP Sample</title>
    </head>
    <body>
        <h1>JSP Sample</h1>
        <p>
            <select>
                <option value="A">AAA</option>
                <option value="B">BBB</option>
                <option value="C">CCC</option>
                <option value="D">DDD</option>
                <option value="E">EEE</option>
            </select>
        </p>
        <p>
            <select>
                <option value="a">あああ</option>
                <option value="b">いいい</option>
                <option value="c">ううう</option>
                <option value="d">えええ</option>
                <option value="e">おおお</option>
            </select>
        </p>
    </body>
</html>
```

おわり(｀・ω・´)ｼｬｷｰﾝ