読者です 読者をやめる 読者になる 読者になる

Challenge Java EE !

Java EEを中心に趣味や仕事における開発メモを書いています。

いまいさんのJSFを自分なりに試してみた

JSF

いつもJenkinsオジサマの画像関連Twitterでお世話になっているIntelliJ IDEAマスターこと@masanobuimaiさんが何とJSF2.2を触られていて、色々もやもやされていました。

「あー自分も最初の頃そういう疑問を多々思っていた」

と思い出しつつ

「あれっ、今もモヤっとしてるじゃん! (゚д゚) ガタッ」

と机に足をぶつけながら読んでました。

人間慣れとは恐ろしいもので、いつの間にか細かいことを気にしなくなってて、初心忘れるべからずだなぁ…と改めて思いつつ、何となくつぶやいたら無言のプレッシャーが(^^;

ということで、GitHubにある今井さんのコード

https://github.com/masanobuimai/JSFSample/tree/01-managed-beans

を拝見しながら、自分なりに考えたもの・仕事で使ってるようなもので書いてみました。

https://github.com/kikutaro/JSFSample-01-managed-beans

2014/6/24 追記
@megascusさんが「あるべき姿」を書かれていました(>_<)



自分が書いた以下コードは、いまいさんのブログにあったようにCDIを使うと~という流れで書いたため、少し微妙な感じです。

私も色々意識せずシンプルに普通に書くとmegascusさんのようになるかなと思います!

画面挙動

僕は今井さんのようにリッチではなく、Java EEが使える有償のIntelliJは持っていないので(´・ω・`) NetBeansを利用して、まずはいまいさんの元コードを動かしてみました。

起動するとメニューが出て

f:id:kikutaro777:20140624021213j:plain

一番上のPlainを選択すると以下画面となります。数値は乱数のようです。

f:id:kikutaro777:20140624021245j:plain

計算ボタンを押下すると、足し算をして結果を下に表示します。

f:id:kikutaro777:20140624021312j:plain

計算時には、左辺が偶数であればその旨をメッセージとして表示し、右辺が奇数であればその旨をメッセージとして表示する仕様のようです。

f:id:kikutaro777:20140624021447j:plain

f:id:kikutaro777:20140624021453j:plain

f:id:kikutaro777:20140624021459j:plain

画像だけみると、メッセージの感じが伝わりにくいですが、1つ前に入ってた値に対するメッセージとなってます。

管理ビーン

f:id:kikutaro777:20140624022104j:plain

用語…確かにいつもフワフワしてます。英語では主に

  • Backing Bean
  • Managed Bean

ですが、日本語だと人によってマチマチで

  • バッキングビーン
  • バックビーン
  • 管理ビーン
  • 管理対象Bean

とか…英語・カタカナも混じったり(^^;さらに面倒なのはJSFとCDIの両者に存在するため

  • JSF管理対象Bean
  • CDI管理対象Bean

などの接頭辞が(^^;

自分のブログも統一できていないのですが、今日は以降、管理ビーン(CDIの)で合わせておきます。

Getter/Setter

f:id:kikutaro777:20140624022611j:plain

管理ビーンでGetter/Setterの嵐になるのが嫌、というのは誰もが皆同じですね(^^;

Lombokは無しで、とのことですが、私は初めてJava EE 6で開発したPrjでLombokを使わずにやっていましたが、IDEで自動生成できるとはいえGetter/Setterが何ともうっとうしくて、それ以降のプロジェクトではLombokを利用しています。

ということで私が書いた版ではLombok使いました。以下はCalcクラスの例です。

package com.jsfsample.calc;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;

@Getter @Setter
@AllArgsConstructor
public class Calc {
    private long left;
    private long right;
    private long answer;
}

CDI(Contexts and Dependency Injection)

f:id:kikutaro777:20140624023152j:plain

この辺は色々モヤモヤしますよね(^^;

個人的には今回のサンプルレベルであれば、あまりInjectを利用することもなく、必要に応じてnewしてもいいのかなぁと思います。

でもそれだと上記モヤモヤの回答にならないので、CDIを積極的に使う姿勢でやってみます。

ListのオブジェクトをInjectできる?

Listやプリミティブ型などは直接Injectできないため、いまいさんが書かれているようにProducerを用意する必要があり、定義してみました。

package com.jsfsample.calc;

import java.util.LinkedList;
import java.util.List;
import java.util.Random;
import javax.enterprise.inject.Produces;

public class CalcProducer {
    
    @Produces
    public List<Calc> getCalcList(){
        return new LinkedList<>();
    }
}

ちなみに元のコードをみるとコンストラクタでnewしてますが、多分自分もそうします(^^;Producerをあえて使うと上記になる感じです。

Injectできるのは管理ビーンだけ?

元文章を読んでいると、InjectできるクラスはCDIで@RequestScopedのようなアノテーションがつけられたCDI管理ビーンだけ…のような認識をされているのかなと(もし違ってたらすみません(>_<;;)

この辺、私も昔そう思っていたのですが、Inject対象はCDI管理ビーンでなくてもでき、普通のPOJOなんかもInjectできます。

ただ、そうするためにはbeans.xmlを利用して以下bean-discover-modeの定義が必要です。

<?xml version="1.0" encoding="UTF-8"?>
<beans 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/beans_1_1.xsd"
       bean-discovery-mode="all">
</beans>

ただし、NetBeansでbeans.xmlを追加した場合、デフォルトはannotatedとなっており、コメントをみると強く推奨しています。

<?xml version="1.0" encoding="UTF-8"?>
<beans 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/beans_1_1.xsd"
       bean-discovery-mode="annotated">
</beans>

この辺の理由は@n_agetsuさんが詳しく細かくまとめられているので、そちらをどうぞ(^^;

Java EE環境におけるCDIのデフォルト化

というか、CDIやるなら同じく@n_agetsuさんの以下が大変参考になります。

少し脱線しましたが、戻ります。

最初にLombokうんぬんの部分で定義した私のCalcはLombok関連のアノテーションのみで、単なるPOJOですが、上記beans.xmlでall指定していれば

@Inject
private Calc calc;

でInjectされます。

ただし今回の元コードでCalcをnewしてるのはresetメソッドで、newした際に乱数を当て込んでいます。

この処理は自分のCalcクラスだと、コンストラクタに実装してもいい気がするのですが、今回は先ほど定義したProducer用のクラスを使って以下のようにしました。

package com.jsfsample.calc;

import java.util.LinkedList;
import java.util.List;
import java.util.Random;
import javax.enterprise.inject.Produces;

public class CalcProducer {
    
    @Produces
    public List<Calc> getCalcList(){
        return new LinkedList<>();
    }
    
    @Produces
    public static Calc getCalc(){
        Random rand = new Random(System.currentTimeMillis());
        return new Calc(
                Math.abs(rand.nextInt() % 10) + 1,
                Math.abs(rand.nextInt() % 10) + 1,
                0
        );
    }
}

ここも大げさな書き方してますが、元々のコードのresetメソッドでやっているように普通にnewすると思います(^^;あえてProducer使ったら、という例かなと。

staticメソッドにしている理由は後ほど。

Producerでスコープを指定

f:id:kikutaro777:20140624030430j:plain

上記にあるRequestScopedへViewScopedのInjectは私も自信がないです。

が、一応動きました(^^;いいのかなぁ…。

ちなみにやった方法としては、先ほどProducerでListをInjectできるようにした部分でスコープを指定しただけです。

package com.jsfsample.calc;

import java.util.LinkedList;
import java.util.List;
import java.util.Random;
import javax.enterprise.inject.Produces;
import javax.faces.view.ViewScoped;

public class CalcProducer {
    
    @Produces @ViewScoped
    public List<Calc> getCalcList(){
        return new LinkedList<>();
    }
}

元ネタは以下サイトの「7. Producer method scopes」です。

Java EE CDI Producer methods tutorial

管理ビーンであるCalcViewクラスはRequestScopedになっています。

@Named
@RequestScoped
@Getter @Setter
public class CalcView extends AbstractBean{
    @Inject
    private Calc calc;
    @Inject
    private List<Calc> results;

    //略

この書き方はあまりやったことないので、実際にはCalcViewをViewScopedにしておくかなぁ。

抽象クラスに画面共通処理を定義

1画面1管理ビーンを基本にしていますが、実際のプロジェクトでは管理ビーンの雛型として抽象クラスを設けています。
さきのコードでネタばれしてますが、今回の定義だと以下のような抽象クラスです。

package com.jsfsample.calc.plain;

import javax.faces.application.FacesMessage;
import javax.faces.context.FacesContext;

public abstract class AbstractBean {
    protected void message(String clientId, String message){
        FacesContext.getCurrentInstance().addMessage(
                clientId, 
                new FacesMessage(message)
        );
    }
}

で、管理ビーンのCalcViewクラスはAbstractBeanを継承しています。

package com.jsfsample.calc.plain;

import com.jsfsample.calc.Calc;
import com.jsfsample.calc.CalcProducer;
import java.util.List;
import javax.annotation.PostConstruct;
import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.inject.Named;
import lombok.Getter;
import lombok.Setter;

@Named
@RequestScoped
@Getter @Setter
public class CalcView extends AbstractBean{
    @Inject
    private Calc calc;
    @Inject
    private List<Calc> results;
    
    @PostConstruct
    public void init(){
        //今回は使ってないですが普通使います(^^;
    }
    
    public void calc(){
        long l = calc.getLeft();
        long r = calc.getRight();
        long a = l + r;
        calc.setAnswer(a);
        
        if (l % 2 == 0) message("left", "左辺 " + l + " は偶数です");
        if (r % 2 != 0) message("right", "右辺 " + r + " は奇数です");
        
        results.add(0, calc);
        calc = CalcProducer.getCalc();
    }
}

messageメソッドの部分で使っています。

メッセージ処理は元コードではFaceUtilsクラスを定義して、flashを利用されていますが、今回は上記のようにしました。
あまりよくないのはわかっているのですが、メッセージを動的に生成したり変えたりしたい、というのはよくあって、わりと上記のように管理ビーンから制御してしまうことが今のプロジェクトでは多いです。モデルがビューを気にするんじゃないよ、と言われるのはわかるのですが…。

……

管理ビーン用の抽象クラスのメリットは今回の例だと1画面しかないのでわかりにくいですが、普段のプロジェクトだと、画面IDや画面遷移処理を定義させています。

また、各画面で必ず実装してね、というものは抽象メソッドとしておいて、継承先の管理ビーンで書く形をとっています。

元コードにあるresetメソッドがCalcViewクラスにないですが、Calcインスタンスの生成をProducer使ったため、ここではそれを普通のメソッドとして呼び出してます(いいのか(^^;)
なのでstaticメソッドにしていました。ここは微妙ですね。

Facelet

最後にビューですが元コードでHTMLのtableタグで書かれていた部分をFaceletっぽく変えました。

<h:form prependId="false">
    <h:panelGrid columns="3">
        <h:inputText id="left" value="#{calcView.calc.left}" />
        <h:outputText value="+" />
        <h:inputText id="right" value="#{calcView.calc.right}" />
                
        <h:message for="left" />
        <h:outputText value="&nbsp;" />
        <h:message for="right" />
    </h:panelGrid>

    <!-- 略 -->
</h:form>

あとレンダリングされた際にformのIDがつかないようにprependId属性をfalseにしています。

formのid属性をしていない場合、適当なidがふられて、form内のコンポーネントのidがわかりにくくなります。

prependId属性を指定いない場合(あるいはtrueの場合)
f:id:kikutaro777:20140624040429j:plain

prependId属性をfalseにした場合
f:id:kikutaro777:20140624040435j:plain

j_idt5というのが付くか付かないかですね。

コード

ということで、トータルなコードはGitHubにあげました。
https://github.com/kikutaro/JSFSample-01-managed-beans

動作確認

戻るボタンを忘れてしまいましたが(^^;
動作としては同じになりました。

f:id:kikutaro777:20140624035742j:plain

f:id:kikutaro777:20140624035750j:plain

f:id:kikutaro777:20140624035755j:plain

f:id:kikutaro777:20140624035801j:plain

まとめ

f:id:kikutaro777:20140624034811j:plain

言葉が刺さりますね(^^;JSFに限った話ではなく、Java EE全般な気もします。

Java EEを利用してる方々はだいぶ増えてる感じがするので、うちではこうやってるよ、的な情報がもっと出てくるといいなぁとm(_ _)m

明日(というか今日)朝イチで顧客打合せなんだけど…(^^;ちょっと夜遊びをしすぎました。

にほんブログ村 IT技術ブログへ
にほんブログ村
にほんブログ村 IT技術ブログ Javaへ
にほんブログ村