• 検索結果がありません。

第 12 章 CDI ー新しいオブジェクトの作り方 Jakarta CDI Context and Dependency Injection は コンテキストと依存性注入 と訳され ていますが 要は new演算子を使わずにオブジェクトを取得する仕組みのことです コンストラクタがどうなっているのか知ら

N/A
N/A
Protected

Academic year: 2022

シェア "第 12 章 CDI ー新しいオブジェクトの作り方 Jakarta CDI Context and Dependency Injection は コンテキストと依存性注入 と訳され ていますが 要は new演算子を使わずにオブジェクトを取得する仕組みのことです コンストラクタがどうなっているのか知ら"

Copied!
42
0
0

読み込み中.... (全文を見る)

全文

(1)

12

CDI ー新しいオブジェクトの作り方

 CDI(Context and Dependency Injection)は「コンテキストと依存性注入」と訳され ていますが、要は、new演算子を使わずにオブジェクトを取得する仕組みのことです。

コンストラクタがどうなっているのか知らなくてもよく、生成や廃棄の手間も必要な いのでウェブシステム全体で広く使われています。このサービスを提供してくれるサ ブシステムをCDI実装とかCDIサービスといいます。この章では、CDIの使い方につい て解説します。

1 CDIとは ...

2 Conversation(会話)スコープ ...

3 複数ページをまとめるFlowスコープ ...

4 インジェクトできるようにする@Produces ...

5 CDIビーンの実装を使い分ける ...

6 まとめ ...

Jakarta

(2)

12

 JavaEEを使うウェブアプリケーション開発では、必要なオブジェクトを

new

で作成する 代わりにCDI(Context and Dependency Injection)サービスから取得することができます。

なぜそのような操作が必要なのか、最初にそのあたりから解説します。

 まず、次は、

Game

クラスで

Dice

型のオブジェクトを作成する例です。

public class Game {

Dice dice = new Dice(); // プログラマがnewでオブジェクトを作る

public void CreateGame(){

start(dice);

} ・・・

 オブジェクト作成により、

Game

クラスと

Dice

クラスには依存関係が生じます。という のも、

Game

クラスは実行のために

Dice

クラスが必須となり、また、

Dice

クラスは引数の ないコンストラクタを持つ、という実装を変えることができなくなるからです。

 一方、次はCDI(Context and Dependency Injection)サービスにより

Dice

型オブジェク トを取得する例です。

public class Game { @Inject

Dice dice; // CDIからオブジェクトを受け取る

public void CreateGame(){

start(dice);

} ・・・

 

@Inject

は、「変数にオブジェクトを代入する」というCDIのアノテーションです。これ だけで

Dice

型のオブジェクトが

dice

にセットされます。

Game

クラスと

Dice

クラスの依 存関係がなくなるわけではありませんが、コンストラクタについての情報が含まれていな い分、依存関係は弱くなります。つまり、疎結合(loose coupling)になるわけです。

Section Section Section Section Section S Sectition Se Sectctioionn Section S i

1

S ti

CDIとは

Jakarta

トルツメ

Jakarta Context and Dependency Injection で索引語にする

(3)

12

 また、

@Inject

を書くことで依存関係が発生するので、これを依存性注入(Dependency Injection)といいます。用語が堅苦しいため、「インジェクトする」とか「インジェクション」

などと簡略に表現することがあるので、覚えておいてください。

 ただ、CDIの効用はこれだけではありません。それを知るにはコンテキスト(Context)に ついても理解する必要があります。コンテキストとは「プログラムの実行状況に関する情報 全体」を指す言葉ですが、CDIでのコンテキストとは

@RequestScoped

のような「スコープ アノテーションで指定するオブジェクトの存続状態」を指します。次の例題で具体例を示 します。

1.1 CDIを使ってみる

 この例題では、次のように[カウントアップボタン]を 押すと、そのたびに表示される値が 1 ずつ増えていきま す。Sample17-01プロジェクトを開いて実行してみてく ださい。

 なお、このJSFページは次のような簡単なものです。

カウントアップ画面 sample17-01/index.xhtml 例題1

1 <h:body>

2 <h:form>

3 <h1>CDIによるカウントアップ</h1>

4 <h:commandButton action="#{bb.next()}" value="カウントアップ"/>

5 #{bb.value}

6 </h:form>

7 </h:body>

 カウントアップボタンをクリックすると、バッキングビーンの

next

メソッドを実行しま す。5行目でバッキングビーンの変数

value

を画面に表示しているので、

next

メソッドを クリックしたとき、この値が 1 増やされる、ということがわかります。

 バッキングビーンを次に示します。

(4)

12

例題1(続き):CDIを使う sample17-01/beans/Bb.java 1 @Named

2 @RequestScoped

3 public class Bb{

4 private Integer value;

5 @Inject

6 private Counter counter; // カウントアップ機能を持つCounterオブジェクト 7 public String next(){

8 value=counter.countup(); // counterを+1して、その値を得る

9 return null; // 元の画面を再表示する

10 }

11 // セッターとゲッター(省略)

12 }

 

next

メソッドを見ると、

value

の値を直接増やすのではなく、

counter

という変数の

countup

メソッドを実行して、その戻り値を

value

に代入しています(8行目)。この変数

counter

は、6行目を見ると

Counter

型のオブジェクトであることがわかります。

 実は、

Counter

型のオブジェクトは内部にカウンタを持っていて、

countup

メソッドを 実行するたびに 1 増やした値を返します。その値をv

alue

に代入していたので、画面で値 が 1 ずつ増えていたのです。

 しかし、

Counter

型のオブジェクトはいつ作成したのでしょうか。本来なら、次のよう な文がなければいけないところです。

Counter counter = new Counter(); // Counter型のオブジェクトを作る

 そこで5行目の

@Inject

に注目してください。

@Inject

private Counter counter; // カウントアップ機能を持つCounterオブジェクト

 

@Inject

は、JavaEEシステム(CDIサービス)に依頼して、オブジェクトをインジェクト してもらう(作成して変数

counter

に代入してもらう)という意味です。これでオブジェク トをインジェクトしているので、8行目のようにいきなり、オブジェクトのメソッドを使う ことができるのです。

value=counter.countup(); // countup()は+1したカウンタの値(整数)を返す Jakarta EE

(5)

12

 最初は不思議な感じがしますが、ウェブシステムでは、疎結合にしておいてシステムの 修正をやりやすくするために、インジェクトによる方法が広く使われています。

 ここでインジェクトされた

Counter

クラスは、次のような簡単なクラスです。

例題1(続き):インジェクトされるクラス sample17-01/beans/Counter.java

1 @SessionScoped

2 public class Counter implements Serializable{

3 private Integer n=0; // 初期値を0にしておく 4 public Integer countup() {

5 return ++n; // nの値を1増やして返す

6 }

7 // セッターとゲッター(省略)

8 }

 フィールド変数

n

(初期値は0)があり、

countup()

メソッドはその値を+1して返すだけ の機能です。

 ここで、

Counter

クラスにスコープアノテーション(

@SessionScoped

)が付いている ことに注意してください。スコープアノテーションが付いているクラスは、CDIサービス に管理されるCDIビーンとなり、インジェクションできるクラスになります。

 条件がゆるいので、ほとんどのクラスは、スコープアノテーションを付けてCDIビーン にすることができます。

【CDIビーンにできるクラスの基本的な条件】

・具象クラスであること

・引数なしのデフォルトコンストラクタを持つこと

・static付のインナークラスではないこと

  な お、

WEB-INF

フ ォ ル ダ に

beans.xml

フ ァ イ ル を 作 成 し、

bean-discovery- mode="all"

と指定しておくと、上の条件を満たす全てのクラスが、スコープアノテー ションがなくても、自動的にCDIビーンとみなされるようになります。

 JETに含まれる

builder

プロジェクトやアーキタイプを使ってプロジェクトを作成する と、

bean-discovery-mode="all"

と設定した

web.xml

ファイルが自動生成されます。

(6)

12

独立したコンテキスト

 Counterクラスのスコープは、

@SessionScoped

を指定しています。というのも、

@RequestScoped

だと継続的なカウントアップができないからです。

@RequestScoped

では、ウェブで[カウントアップ]ボタンが押されるたびにオブジェクトが生成され、ウェ ブを再表示した後で消去されるため n の値が毎回クリアされます。

 一方、バッキングビーンのスコープアノテーションを見てください。

@RequestScoped

になっています。つまり、[カウントアップ]ボタンが押されるたびに、オブジェクトが再 作成される設定です。これでは

counter

も毎回クリアされてしまうのではと思うのですが、

動かしてみてわかったように、そうはなりませんでした。

 きちんとカウントアップされたことからわかるように、バッキングビーンは毎回消えて しまっても、

counter

変数は消えずに残っていたのです。これは、

counter

のスコープ とバッキングビーンのスコープがそれぞれ独立していて、互いに影響されないからです。

これを、互いに「独立したコンテキストを持つ」といいます。

1.2 CDIのスコープアノテーション

 次の9つのスコープアノテーションが定義されています。

スコープアノテーション 関連 意 味

@RequestScoped HTTP 1回のrequest-responseの間

@SessionScoped HTTP 1回のセッションの間

@ConversationScoped HTTP 複数ページの遷移の間

@ApplicationScoped HTTP アプリケーションが実行されている間

@Dependent HTTP インジェクト先のスコープを引き継ぐ

@Singleton シングルトン(1つのオブジェクトしか生成されないクラス。

EJBにも同じアノテーションがあり、メソッドへの同時アクセス の排他制御機能が付加されている)

@ViewScoped JSF 同じJSFページにアクセスしている間

@FlowScoped JSF 1つのJSFページグループ(Flow)にアクセスしている間

@Transactional DB処理 1回のトランザクション処理

 CDIビーンにはこれらのアノテーションを付けて、オブジェクトの状態や寿命(スコープ)

を設定できます。これをオブジェクトのコンテキストといいます。CDIビーンは、単にイ ンジェクトできるだけでなく、コンテキストを持つことができるという特徴があります。

 なお、まだ、解説の済んでいないスコープアノテーションについては、この後のページ で解説しています。

(7)

12

1.3 FacesContextとServletContext

 

@Inject

を使って、JSFの関連オブジェクト(JSF アーティファクトという)をインジェ クトできます。また、EL式としてJSFページの中でも使用できます。

 後で解説するように、CDIには、どんなオブジェクトでもインジェクトできるようにする 仕組みがありますが、JSFアーティファクトは、最初から組み込みでインジェクトできるよ うになっているオブジェクトです。

 代表的なものとして、FacesContextとServletContextがあります(注)

 FacesContextは、プログラム実行時点におけるJSFの実行パラメータとそれらを操作 するメソッドの集合体です。したがって、FacesContextを利用すると、JSFの実行パラ メータを取得・設定できます。また、JSFはサーブレットの上に構築されているので、

ServletContextを利用すると、サーブレットレベルでのいろいろな情報を取得・設定でき ます。

(1)ServletContext

絶対パスに変換する sample17-01A/beans/FileReader.java 例題2

1 @SessionScoped

2 public class FileReader implements Serializable {

3 @Inject

4 private ServletContext context;

5

6 // テキストファイルをすべて読み出して文字列にして返す 7 public String readAllText(String fpath) {

8 String path = context.getRealPath(fpath); // 絶対パスに変換する 9 // 読み出し処理

10 Path p = Paths.get(path);

11 String data = "";

12 try {

13 byte[] bytes = Files.readAllBytes(p); // バイト列として読み込む 14 data = new String(bytes, "UTF-8"); // 文字列に変換する 15 } catch (IOException ex) {

16 System.out.println(e.getMessage());

17 }

18 return data;

19 }

20 }

(8)

12

 このクラスの

readAllText

メソッドは、テキストファイルのパス(

fpath

)を受け取っ て全内容を読み出し、文字列として返します。

 ただし、ファイルはJSFアプリケーション内のフォルダである

resources

フォルダに格 納されているので、

fpath

は、

"/resources/data/my.txt"

のようなアプリケーショ ンルートを起点とする仮想パスです。しかし、ファイルにアクセスするにはファイルの絶 対パスが必要です。

  そこで、仮 想 パ スから 実 行 環 境 の 絶 対 パ スを 得 るた めに、

ServletContext.

getRealPath()

メソッドを使います。ServletContextオブジェクトはインジェクトする だけで使えるので、例題では3、4行目でそれを行っています。

@Inject

private ServletContext context;

 そして、8行目で次のように仮想パスから絶対パスを取得します。

String path = context.getRealPath(fpath); // 絶対パスに変換する

 10行目以下の枠で囲った部分は、典型的なファイル一括読み出し処理です。

 なお、ServletContextの他のメソッドはJakarta EEのAPIドキュメント(注)で調べること ができます。

(2)FacesContext

 次は、JSFの

/resources/data/

フォルダ内にあるファイルを読み出して表示する処 理です。

1.txt

2.txt

という2つのファイルがあるので、ラジオボタンでどちらを表示す るか選択します。sample17-01Aプロジェクトを実行して、機能を確認してください。

(注) https://jakarta.ee/specifications/platform/9/apidocs/index.html

(9)

12

 この例題では、ラジオボタンを選択した直後に、「ファイルを表示しました」というメッ セージが表示されます。このメッセージは

h:messages

タグにより表示されています。

h:messages

タグは、エラーメッセージを表示するために使ってきましたが、実は特定の メッセージのためのタグではなく、「JSFのメッセージキューに格納されている文字列を表 示する」というのが本来の機能です。

 そこで、例題では、「ファイルを表示しました」というメッセージを表示するために、JSF のメッセージキューに「ファイルを表示しました」という文字列をセットします。このテク ニックは、プログラム実行中での確認・応答などに広く使われていますが、この時使うの がFacesContextです。

例題2(続き)メッセージキューに入れる sample17-01A/beans/Bb.java 1 @Named

2 @RequestScoped

3 public class Bb {

4 private String text; // 表示するテキスト

5 private Integer num; // ファイル番号(ファイル名はno+".txt"となる)

6

7 @Inject

8 FileReader reader; // ファイル読み出しのユーティリティクラス 9 @Inject

10 FacesContext context;

11

12 public String next() {

13 String path = "/resources/data/"+num+".txt"; // 表示するファイル名 14 text = reader.readAllText(path); // ファイルの一括読み出し

15 FacesMessage msg = new FacesMessage("ファイルを表示しました");

16 context.addMessage(null, msg); // メッセージをキューに登録 17 }

// セッター・ゲッターなどを省略

}

 このバッキングビーンは、ファイルデータを一括取得するために、FileReaderクラスの オブジェクトをインジェクトしています。FileReaderには、全ファイル内容を読み出して 文字列にして返す

readAllText()

メソッドがあります。

 ウェブで、コマンドボタンを押すと、バッキングビーンの

next()

メソッドが実行されま す。

next()

メソッドでは、ラジオボタンの値(

num

)から、表示するファイル名を決定し、

readAllText

メソッドで内容を読み出し、

text

に代入します。

(10)

12

13 String path = "/resources/data/"+ num +".txt"; // ファイル名

14 text = reader.readAllText(path); // 一括読み出し

 この時、「ファイルを表示しました」というメッセージを表示させるには、FacesContext を使って、JSFのメッセージキューにメッセージを登録します。15、16行目の処理がそれ です。

15 FacesMessage msg = new FacesMessage("ファイルを表示しました");

16 context.addMessage(null, msg);

 JSFのメッセージキューに登録できるのは、FacesMessageオブジェクトなので、15行目 でそれを作成し、16行目で

addMessage

メソッドを使って登録します。ここで、

context

は、

FacesContextオブジェクトで、9、10行目でインジェクトしていることに注意してください。

 では、最後に、

index.xhtml

を見ておきましょう。

例題2(続き)メッセージを表示する sample17-01A/index.xhtml

1 <h:body>

2 <h1></h1>

3 <h:form id="formId">

4 <h:panelGrid columns="1">

5 <h:selectOneRadio value="#{bb.num}" styleClass="inp">

6 <f:selectItem itemValue="1" itemLabel="杜子春(その1)"/>

7 <f:selectItem itemValue="2" itemLabel="杜子春(その2)"/>

8 </h:selectOneRadio>

9 <h:inputTextarea value="#{bb.text}" cols="60" rows="10"/>

10 <h:commandButton action="#{bb.next()}" />

11 </h:panelGrid >

12 <h:messages layout="table" styleClass="msg"/>

13 </h:form>

14 </h:body>

 

index.xhtml

は、2つのラジオボタン、テキストエリア、コマンドボタンがある簡単な 構成です。そして、最後に

h:messages

タグがあり、これでメッセージを表示します。つ まり

index.xhtml

には、特別な仕掛けは何もありません。JSFのメッセージキューにメッ セージを登録するだけで、

h:messages

タグがそれを表示してくれるわけです。

(3)表示したFacesMessageを消去する方法

 Sample17-01Aを実行すると、表示したメッセージが消えずに残ってしまうことに気がつ

ラジオボタン

メッセージタグ FacesMessage#addMessageメソッド  で索引語に登録

(11)

12

いたと思います。このメッセージを、例えば「2秒間表示したら消す」ようにするには、どう したらいいでしょう。これまでそういう機能はサードパーティのライブラリを利用するし かありませんでしたが、JSF2.3からは、AJAXに追加された機能を使って、同じことがで きるようになりました。

 さて、AJAX機能で呼び出されたバッキングビーンのメソッドは、普通、renderパラメー タで要求されたデータだけをウェブ側に応答として返します。ウェブ側では、それを使っ て部分的な再描画を行います。

 ところが、JSF2.3からは、要求されたデータに加えて、実行してほしいJavascriptプロ グラムを応答に含めることができるようになりました。ウェブ側ではデータを受け取った 直後に、送られてきたJavascriptプログラムを実行します。

 この機能を使って、2秒表示した後にメッセージを消す例を見てみましょう。

例題2(続き)メッセージキューに入れる sample17-01A/beans/Bb.java

AJAXでJavascriptを実行する sample17-01B/index.xhtml 例題3

1 <h:body>

2 <script>

3 function erase() {

4 var msgtag = document.getElementById('formId:msg');

5 msgtag.innerHTML = "";

6 }

7 </script>

8 <h1>AJAXJavascriptを実行する</h1>

9 <h:form id="formId">

10 <h:panelGrid columns="1">

11 <h:selectOneRadio value="#{bb.num}" styleClass="inp">

12 <f:selectItem itemValue="1" itemLabel="杜子春(その1)"/>

13 <f:selectItem itemValue="2" itemLabel="杜子春(その2)"/>

14 <f:ajax event="click" execute="@this"

15 listener="#{bb.next()}" render="txa msg"/>

16 </h:selectOneRadio>

17 <h:inputTextarea id="txa" value="#{bb.text}" cols="60" rows="10"/>

18 </h:panelGrid >

19 <h:messages layout="table" id="msg" styleClass="msg"/>

20 </h:form>

21 < /h:body>

表示したメッセージを消去するJavascript プログラム

トルツメ

(12)

12

※AJAX機能を使うのでコマンドボタンはありません

 

index.xhtml

には、

<script>

タグを使って

erase()

というJavascriptプログラムが埋 め込まれていますが、それ以外はAJAXを使った普通のJSFページです。なお、JavaScript は

h:outputScript

タグを使って、ファイルから読み込むこともできます(⇒3章)。

 

"function erase()"

は関数といい、java言語でのメソッドにあたります。

erase()

関数の機能は、「

id

の値で該当するタグを検索し、その内容を消去する」というものです。

function erase() {

var msgtag = document.getElementById('formId:msg');

msgtag.innerHTML = "";

}

 ちなみに、検索するidの値は

"formId:msg"

となっています。9行目の

h:form

タグの

id

"formId"

、またその子要素である

h:messages

タグのidが

"msg"

ですから、これら を合成した

formId:msg

で検索すると、

h:messages

タグが検索されます。

 ただ、

index.xhtml

には、

erase()

関数を呼び出して起動するプログラムが書かれて いないので、このままでは、何の働きもしません。起動するプログラムは、AJAX呼び出 しの戻り値として、バッキングビーンから送ります。

 そこで、

index.xhtml

f:ajax

タグについても見ておきましょう。

<f:ajax event="click" execute="@this"

listener="#{bb.next()}" render="txa msg"/>

 

f:ajax

はラジオボタンを表す

h:selectOneRadio

タグの中に書かれていて、

event=

"click"

となっているので、ラジオボタンをクリックした時に起動します。そして、

(13)

12

h:selectOneRadio

タグの値をバッキングビーンに送り、

next()

メソッドを起動します。

また、

render="txa msg"

txa

msg

はそれぞれ

h:inputTextarea

h:messages

タグのIDですから、これらのタグの値を受け取って再描画します。

例題3(続き)JavaScriptプログラムを送信する sample17-01B/beans/Bb.java 1 @Named

2 @RequestScoped

3 public class Bb {

4 private String text;

5 private Integer num;

6 @Inject

7 FileReader reader;

8 @Inject

9 FacesContext context;

10

11 private static final String SCRIPT="setTimeout(erase, 2000);";

12

13 public String next() {

14 String path = "/resources/data/"+num+".txt";

15 text = reader.readAllText(path);

16 FacesMessage msg = new FacesMessage("ファイルを表示しました");

17 context.addMessage(null, msg);

18

19 context.getPartialViewContext()

20 .getEvalScripts()

21 .add(SCRIPT);

22

23 return null;

24 }

25 // セッター、ゲッター等を省略 26 }

 バッキングビーンには、JSFページに書いた

erase()

を起動するJavascriptプログラム を追加します。11行目の記述がそれで、

setTimeout

というJavascript関数を使って、2秒 間アイドルした後に

erase()

を起動するという命令です。Javascriptですから、単に文字 列として作成し、

String

型の変数

SCRIPT

に代入しおきます。

 AJAXからの応答にこのプログラムを含める書き方が、19 ~ 21行です。いつもこのよう に書きます。定型的な書き方として覚えておきましょう。なお、ここでも、インジェクト したFacesContextのオブジェクト

context

を使います。

2000ミリ秒(=2秒)後にerase()を実行する、

というJavascriptプログラム

応答(response)に、javascriptを追加す る書き方

(14)

12

context.getPartialViewContext() .getEvalScripts() .add(SCRIPT);

 これで、応答に

SCRIPT

の内容が追加され、ウェブ側で受け取った後、実行されます。

setTimeout

は指定時間アイドルした後で、指示された関数を実行する命令ですから、実 行開始から2秒後に、

erase()

関数が実行されます。

 つまり、メッセージは2秒間だけ表示され、その後、消去されます。smple17-01Bプロジェ クトを実行し、結果を確認してください。

1.4 インジェクションポイント

 

@Inject

を書く場所を、インジェクションポイントといいます。

 普通、インジェクションポイントはフィールド変数ですが、コンストラクタやセッター の引数をインジェクションポイントにすることもできます。

// フィールド変数

@Inject

Counter counter;

// コンストラクタ(引数)

@Inject

public Bb(Counter counter){

this.counter = counter;

}

// セッター(引数)

@Inject

public void setCounter(Counter counter) { this.counter = counter;

}

 ただし、コンストラクタやセッターの引数にインジェクトした場合、プログラマはそれ らのコンストラクタやセッターを直接利用することはできません。オブジェクトをセット するのは、常にCDIサービスの役割だからです。

(15)

12

 例えば、ウェブで買い物をする時は買い物かごに入れる操作をした後、送り先などの情 報入力、支払情報の入力、確認画面を経て一連の操作が終わります。この間、注文と利用 者情報を保持し続ける必要があります。それは、

@SessionScoped

でも可能ですが、も う少し短い期間だけにしたいという時、

@ConversationScoped

が使えます。

 

@ConversationScoped

は、ブラウザとサーバーの間で何往復かする間だけ、オブ ジェクト(バッキングビーン)が存続します。そのため、開始と終了をプログラマが指示し なくてはなりません。

@ConversationScoped

を指定しても、開始の指示をするまでは

@RequestScoped

と同じ働きになります。また、終了の指示をした後も同じです。

2.1 例題の画面推移

 次は会話スコープによるシステムの例です。sample17-02を開いて実行しながら、解説 を読んでください。

 sample17-02は[会話処理をスタートする]ボタンを押した時、会話スコープが開始しま す。画面1、画面2で注文情報を入力し、画面3で確認します。画面3で[購入する]ボタンを 押すまでは会話スコープが続いているので、前の画面に戻ってもデータが残っています(戻 るを押して確認してください)。

Section Section Section Section Section S Sectition Se Sectctioionn Section

S i

2

S ti

Conversation(会話)スコープ

(16)

12

2.2 会話処理の開始と終了

 次は、会話スコープを使用するバッキングビーンのソースコードです。

会話スコープ sample17-02/beans/Bb.java 例題4

1 @Named

2 @ConversationScoped

3 public class Bb implements Serializable{

4 private String product; // 商品名 5 private Integer qty; // 数量 6 private String name; // お名前 7 private String address; // ご住所

8 @Inject // 会話スコープマネージャーをインジェクトする

9 Conversation conv;

10 public String goto_1(){

11 if(conv.isTransient()){ // 会話スコープがすでに開始していないか調べる 12 conv.begin(); // 開始していなければbeginで開始する 13 System.out.println("**1画面 会話スコープ開始 **");

14 }else{

15 System.out.println("**1画面**");

16 }

17 return "view_1.xhtml"; // 画面1(商品と数量の入力)を表示する 18 }

19 public String goto_2(){

20 System.out.println("** 2画面 **");

21 return "view_2.xhtml"; // 画面2(氏名と住所の入力)を表示する 22 }

23 public String goto_3(){

24 System.out.println("** 3画面 **");

25 return "view_3.xhtml"; // 画面3(確認表示)を表示する 26 }

27 public String goto_0(){

28 conv.end(); // 会話処理を終了する

29 System.out.println("** 開始画面 会話スコープ終了 **");

30 return "index.xhtml";

31 }

32 // セッターとゲッター(省略)

33 }

 会話スコープでは、クラスに

@ConversationScoped

アノテーションを付けます。会 話スコープは、会話スコープマネージャーを使って、プログラムで開始と終了を行います。

そのため、CDIサービスから会話スコープマネージャーをインジェクトで取得しておかね ばなりません。

(17)

12

@Inject // 会話スコープマネージャーをインジェクトする

Conversation conv;

 トップページで[会話処理をスタートする]ボタンが押されると10行目の

goto_1

メソッド を実行しますが、これが会話スコープの開始です。

if(conv.isTransient()){ // 会話スコープがすでに開始していないか調べる

conv.begin(); // 開始していなければbeginで開始する

System.out.println("** 1画面 会話スコープ開始 **");

}else{

System.out.println("** 1画面 **");

}

return "view_1.xhtml"; // 画面1(商品と数量の入力)を表示する

 

conv.begin()

で開始できますが、すぐにそうせず、

conv.isTransient()

を使って すでに会話スコープが始まっていないことを確かめてからスタートします(第2画面から第1 画面に戻ってくる場合にもこの

goto_1

メソッドが呼ばれるからです)。

 会話スコープマネージャーの

isTransient

メソッドは、会話スコープがまだ開始され ていない場合に

true

を返します。

 以上で、商品と個数の入力を行う第1画面を表示して会話スコープがスタートします。

 第3画面までは会話スコープの中なので、入力したデータは継続して使用できます。そ のため、前の画面に戻ることも自由にできます。

 そして第3画面の確認表示で[購入する]ボタンを押すと、会話スコープを終了します。

 27行目の

goto_0

メソッドがそれで、次のような内容です。

conv.end(); // 会話処理を終了する

System.out.println("** 開始画面 会話スコープ終了 **");

return "index.xhtml";

 

end

メソッドは、会話スコープを終了します。これにより次の画面(トップページ)を送 信した後でバッキングビーンは廃棄され、入力データも利用できなくなります。

情報の表示

 例題では、実行時の情報をGlassFishサーバーのサーバーログに表示します。サーバー ログを開いて監視しながら実行してください。

Conversation#begin() で索引語に

Conversation#isTransient() で索引語に

Conversation#end() で索引語に

(18)

12 2.3 JSFページの内容

 これらのJSFページを次に示します。コマンドボタンを押したときバッキングビーンの どのメソッドを実行しているかを確認してください。

例題4(続き):会話スコープのトップページ sample17-02/index.xhtml 1 <h:body>

2 <h1>会話スコープ</h1>

3 <h:form>

4 <h:commandButton value="会話処理をスタートする" action="#{bb.goto_1}"/>

5 </h:form>

6 </h:body>

例題4(続き):会話スコープの画面1 sample17-02/view_1.xhtml

1 <h:body>

2 <h1>会話スコープ<画面1</h1>

3 <h:form>

4 <h:panelGrid columns="2">

5 商品名<h:inputText value="#{bb.product}"/>

6 数 量<h:inputText value="#{bb.qty}"/>

7 <h:commandButton value="キャンセル" action="#{bb.goto_0}"/>

8 <h:commandButton value="2画面へ移る" action="#{bb.goto_2}"/>

9 </h:panelGrid>

10 </h:form>

11 </h:body>

(19)

12

例題4(続き):会話スコープの画面2 sample17-02/view_2.xhtml 1 <h:body>

2 <h1>会話スコープ<画面2</h1>

3 <h:form>

4 <h:panelGrid columns="2">

5 お名前<h:inputText value="#{bb.name}"/>

6 ご住所<h:inputText value="#{bb.address}"/>

7 <h:commandButton value="戻る" action="#{bb.goto_1}"/>

8 <h:commandButton value="3画面へ移る" action="#{bb.goto_3}"/>

9 </h:panelGrid>

10 </h:form>

11 </h:body>

例題4(続き):会話スコープの画面3 sample17-02/view_3.xhtml

1 <h:body>

2 <h1>会話スコープ<画面3</h1>

3 <h:form>

4 <h:panelGrid columns="2">

5 お名前:<h:outputText value="#{bb.name}" />

6 ご住所:<h:outputText value="#{bb.address}" />

7 商品名:<h:outputText value="#{bb.product}" />

8 数 量:<h:outputText value="#{bb.qty}" />

9 <h:commandButton value="戻る" action="#{bb.goto_2}" />

10 <h:commandButton value="購入する" action="#{bb.goto_0}" />

11 </h:panelGrid>

12 </h:form>

13 </h:body>

(20)

12

 Flowスコープは、会話スコープとは違った方法で、複数ページにわたるスコープを実現 します。Flowスコープでは、複数のJSFページをまとめて同じフォルダにいれておき、1つ のFlowスコープとします。それらのJSFページを行き来している間は同じスコープ内なの で、ページを移動しても同じバッキングビーンが有効です。

 Flowスコープを作成するには、最初に、フォルダを作成して、その中に関連する複数の JSFページを入れます。そして、作成したフォルダ名がFlowの定義名になります。

 ここでは前節と同じ例を使うので、

order

という名前のフォルダを作成して、関連する JSFページを入れますが、最初にアクセスするJSFページは、フォルダ名と同じ名前(=定 義名)を持つ必要があります。つまり、order.xhtmlとします。

 最後に、order Flowの定義ファイルを作成します。Flow定義を、プログラムで行う方法 もありますが、定義ファイルの方が簡単です。

3.1 orderフォルダとJSFページ

 左図のように、[Webページ]の直下に、orderフォルダを 作成して、その中に関連する3つのJSFページを入れます。

最初にアクセスされるJSFページは、

order.xhtml

とする 必要がありますが、それ以外のページには、任意の名前を 付けることができます。

※フォルダを作成するには、[Webページ]を右クリックして、[新規]

⇒[フォルダ]と選択します。ダイアログが開くので、フォルダ名に orderと入力して[終了]を押します

Section Section Section Section Section S Sectition Se Sectctioionn Section S i

3

S ti

複数ページをまとめるFlowスコープ

3.2 Flow定義ファイルの作成 見出しを

つける

<ここ以下に、P.342 の印をつけた部分を移動してくる>

FLOWスコープ用のフォルダの作成 次に

(21)

12

JSFページ sample17-021/order/order.xhtml 例題5

<h:body>

<h1>Flowスコープ<画面1></h1>

<h:form>

<h:panelGrid columns="2">

商品名<h:inputText value="#{bb.product}" />

数 量<h:inputText value="#{bb.qty}" />

<h:commandButton value="キャンセル" action="order-exit" />

<h:commandButton value="第2画面へ移る" action="order2" />

</h:panelGrid>

</h:form>

</h:body>

sample17-021/order/order2.xhtml <h:body>

<h1>Flowスコープ<画面2></h1>

<h:form>

<h:panelGrid columns="2">

お名前<h:inputText value="#{bb.name}" />

ご住所<h:inputText value="#{bb.address}" />

<h:commandButton value="第3画面へ移る" action="order3" />

<h:commandButton value="戻る" action="order" />

</h:panelGrid>

</h:form>

</h:body>

sample17-021/order/order3.xhtml <h:body>

<h1>Flowスコープ<画面3></h1>

<h:form>

<h:panelGrid columns="2">

商品名:<h:outputText value="#{bb.product}" />

数 量:<h:outputText value="#{bb.qty}" />

お名前:<h:outputText value="#{bb.name}" />

ご住所:<h:outputText value="#{bb.address}" />

<h:commandButton value="購入する" action="order-exit" />

<h:commandButton value="戻る" action="order2" />

</h:panelGrid>

</h:form>

</h:body>

 これらのJSFページは、commandボタンで前後のJSFページを遷移するだけの構成です が、次のリンク先として指定されている

"order-exit"

だけが、JSFページの名前ではな 3.3 JSFページの書き方

つける (続き)

続きの スタイルで

(22)

12

 

"order-exit"

はFlowスコープからの脱出先で、order Flowの定義ファイルで定義され た名前です。

index.xhtml

ファイルの呼び名として定義されています。Flowスコープでは、

脱出先だけは、JSFページ名(

index.xhtml

)で指定できないことになっているのです。

 なお、

index.xhtml

はorderディレクトの外にあり、次のようになっています。

<h:body>

<h1>Flowスコープを始める</h1>

<h:form>

<h:commandLink value="会話処理をスタートする" action="order"/>

</h:form>

</h:body>

※CommandLinkを使っていますが、CommandButtunを使っても同じです  全体の動作は次のようです。

index.xhtml

order.xhtml order2.xhtml order3.xhtml

 次に、定義ファイルです。先ほど作成したorderフォ ルダにJSFファイルと一緒に置くこともできますが、

外部からアクセスできないように、別にフォルダを作 成して、その中に置くのがいいでしょう。

 それには、図のように[WEB-INF]フォルダの中に、もう1つ[order]というフォルダを作 成して、その中に置きます(フォルダ名は、Flowの名前と同じでなければいけません)。定 義ファイル名の名前は、order-flow.xmlです。つまり、Flowの名前に、

"-flow.xml"

を付 けたものになります。

 定義ファイルの内容は、次のようです。

ここから P.340 に移動

(23)

12

例題5(続き) Flow定義ファイル sample17-021/WEB-INF/order/order-flow.xml

<?xml version='1.0' encoding='UTF-8'?>

<faces-config ・・・ 以下省略 ・・・ >

<flow-definition id = "order">

<flow-return id = "order-exit">

<from-outcome>/index</from-outcome>

</flow-return>

</flow-definition>

</faces-config>

 このファイルの形式は、

faces-config.xml

と同じものです。NetBeansで自動生成す るには、次のようにします。

①orderフォルダを右クリックする

②[新規][その他]と選択する ⇒ [新規ファイル]ダイアログが開く

③カテゴリ欄で[JavaServer Faces]、ファイル・タイプ欄で[JSF Faces構成]を選んで  [次>]を押す ⇒ [New JSF Faces構成]ダイアログが開く

④[ファイル名]にorder-flow.xmlと入力して[終了]を押す

 

order-flow.xml

ファイルの内容は、Flowの定義名、脱出先の呼び名、脱出先のJSF ファイルURLを書くだけの単純なものです。他のFlowを定義する時も、青字の部分だけを 変更すればいいでしょう。なお、必要な場合は、2つ以上の脱出先を定義できます。

 最後にバッキングビーンです。バッキングビーンには、@FlowScopedアノテーションを 付け、さらに、Flow名を指定します。後は、普通のバッキングビーンと同じです。

例題5(続き)バッキングビーン sample17-021/beans/Bb.java

@Named

@FlowScoped("order")

public class Bb implements Serializable { private String product; // 商品名

private Integer qty; // 数量

private String name; // 名前

private String address; // 住所

// セッター、ゲッターなどの記載を省略

}

 例題を実行して動作を確認してください。

Flowの定義名 Flowの脱出先の呼び名 Flowの脱出先のJSFファイル

例題の スタイルで

移動 ここまで

3.4 バッキングビーンの書き方 見出しを

つける

(24)

12

会話スコープよりも簡単です。

 なお、複数のFlowスコープを作成しておいて、あるFlowスコープから他のFlowスコー プを呼び出すような使い方もできますが、本書では割愛します。詳細を知りたい場合は、

"JakartEE9 Tutorial"(https://eclipse-ee4j.github.io/jakartaee-tutorial/toc.html)などを参 照してください。

(25)

12

4.1 @Producesの使い方

 既存のクラスとして次のようなMyBeanクラスがあります。

public class MyBean { private String text;

public MyBean(String text) { this.text = text;

}

// セッター・ゲッターを省略

}

 MyBeanクラスは引数のないコンストラクタを持ちません。したがって、MyBeanクラス をCDIビーンとみなすことはできず、残念ながら、次のようにインジェクトする形式で使 うことはできません。

@Inject

private MyBean bean; // コンパイルエラー

 しかし、どこかのCDIビーンの中に、MyBeanのインスタンスを作成して返すメソッドを 作成しておくと、それだけでMyBeanをインジェクトできるようになります。この時、使う のが

@Produces

アノテーションです。次の例を見てください。

インジェクトできるようにする sample17-03/beans/MyFactory.java 例題6

1 @Dependent

2 public class MyFactory { 3 @Produces

4 public MyBean producer() {

5 return new MyBean("こんにちは!");

6 } 7 } Section Section Section Section Section S Sectition Se Sectctioionn Section

S i

4

S ti

インジェクトできるようにする@Produces

(26)

12

 このMyBeanFactoryは、

@Dependent

アノテーションが付いていて、デフォルトコンス トラクタもあるので、明らかにCDIビーンです。

 このようなCDIビーンのクラスの中に、MyBeanオブジェクトを返すメソッドを作成しま す。そして、そのメソッドには@Producesアノテーションを付けておきます。これだけで、

他のクラスで、MyBeanをインジェクトできるようになります。

 実際、次のバッキングビーンでは、MyBeanをインジェクトしています。

例題6(続き) MyBeanをインジェクトする sample17-03/beans/Bb.java 1 @Named

2 @RequestScoped

3 public class Bb implements Serializable { 4 @Inject

5 private MyBean bean;

6 private String data;

7

8 public void next(){

9 data = bean.getText();

10 }

// セッター・ゲッターを省略

}

sample17-03/index.xhtml

<h:body>

<h1>@Produces@Injectできるようにする</h1>

<h:form>

<h:commandButton action="#{bb.next()}" value="表示"/>

<h:outputText value="#{bb.data}"/>

</h:form>

</h:body>

 バッキングビーンBb.

java

は、4、5行目で

MyBean

オブジェクトを

bean

にインジェクト しています。そして、ウェブでコマンドボタンが押されたとき、

next()

メソッドが起動し、

bean

の持つ文字列「こんにちは!」が表示されます。

 sample17-03プロジェクトを実行し、結果を確認してください。

4.2 List<Book>型を返すメソッドに@Producesを付ける

 例えば、CDIビーンの中に、書籍のリストを返すメソッドあったとします。これは

List<Book>

型のオブジェクトを返すので、次のように

@Produces

を付けることができ ます。

トルツメ

115 に差替

(27)

12

List<Book>型を返す sample17-03B/beans/MyBean.java 例題6B

@Dependent

public class MyBean { @Produces

public List<Book> getBookList(){

return Arrays.asList( new Book("杜子春", "芥川龍之介"), new Book("高瀬舟", "森鴎外") );

} }

 すると、次のようにList<Book>型のオブジェクトを@Injectできます。

例題6B(続き)List<Book>をインジェクトする sample17-03B/beans/Bb.java @Named

@RequestScoped

public class Bb implements Serializable { @Inject

private List<Book> books;

// 以下省略

}

 Sample17-03Bプロジェクトは、インジェクトされた このbooksオブジェクトをブラウザに表示します。実 行して、結果を確認してください。

4.3 フィールドに@Producersを付ける

 データベースを操作するために使うEntityManager(詳細は15章)は、

@Inject

で取り込 むことはできず、次のように、

@PersistenceContext

を使って取り込みます。

  // データベース

@PersistenceContext(unitName = "defaultUnit") private EntityManager defaultEm;

 しかし、これに

@Produces

を付けると、オブジェクトの生成に

@Produces

を付けるの 青字にする

120 を挿入

トルツメ

(28)

12

と同じですから、他のクラスでは

@Inject

で取り込むことができるようになります。

@Produces

@PersistenceContext(unitName = "default") private EntityManager defaultEm;

4.4 インジェクトできるロガーを作る

 これまでプログラムの実行状況を見るために

System.out.println

を使ってきまし たが、本来、

java.util.logging

パッケージ(モジュールは

java.logging

です)の

Logger

(ロガー)を使ってログとして記録すべきものです。

 ただ、

Logger

クラスはCDIビーンではないのでインジェクトできません。そこで、

@Produces

を使ってインジェクトできるようにしてみます。

※ Loggerの使い方は、コラム「Loggerの使い方」を参照してください

 すでに見たように、CDIビーンのクラスの中に

Logger

オブジェクトを作成して返すメ ソッドを作って、

@Produces

アノテーションを付けます。全体は次のような形のクラスに なるでしょう。

@Dependent

public class LoggerProducer { @Produces

public Logger createLogger(){

Logger lg = ・・・ // ロガーを作成

return lg; // ロガーを返す

} }

 

Logger

を作成する方法ですが、

Logger

オブジェクトは、

Logger

クラスの

getLogger

メソッドを使って次のように取得します。

Logger lg = Logger.getLogger( ロガーの名前 );

 

getLogger

メソッドの引数はロガーに付ける名前文字列ですが、任意の名前ではなく、

ロガーを使用するクラスの名前(パッケージ名を含む)を使うことが強く推奨されています。

 すると、ロガーを使用する(インジェクト先の)クラス名をどうやって取得するかという 問題がありますが、これについては

jakarta.enterprise.inject.spi

パッケージに

トルツメ

(29)

12

ある

InjectionPoint

が役に立ちます。

 実は、インジェクトされるCDIビーンは、インジェクト先のスコープを知る必要がある ので、インジェクト先の情報を持つオブジェクトをCDIサービスから受け取れるようになっ ています。これが、

InjectionPoint

型のオブジェクトで、

@Inject

を使って簡単に受 け取れます。

@Dependent

public class LoggerProducer { @Inject

InjectionPoint point;

・・・

}

 この

InjectionPoint

オブジェクトを使うと、インジェクト先のクラス名がわかるので それを引数にしてロガーを作成すればよいわけです。

 

InjectionPoint

からインジェクト先のクラス名を得るには、次のようにします。

point.getMember().getDeclaringClass().getName();

 

getMember()

は イ ン ジ ェ クト さ れ た オ ブ ジ ェ クト( こ の 場 合

Logger

)を 返 し、

getDeclaringClass()

はインジェクト先のクラスを返します。最終的に、

getName()

でそのクラス名を取得できます。

 以上から、目的のCDIビーンクラスは次のようになります。

インジェクトできるロガー sample17-04/beans/LoggerProducer.java 例題7

1 @Dependent

2 public class LoggerProducer {

3 @Inject

4 InjectionPoint point;

5 @Produces

6 public Logger getLogger() {

7 String className = point.getMember().getDeclaringClass().getName();

8 Logger logger = Logger.getLogger(className);

9 return logger;

10 }

11 }

※ 3、4行目の@Inject InjectionPoint point;は、Loggerをインジェクトした時に実行されるので、

(30)

12

 この

LoggerProducer

クラスを作っておくと、次のように、目的のクラスにスマートに

Logger

をインジェクトできます。

@Inject Logger log;

 ただし、

Logger

Serializable

インタフェースを実装していないのでシリアライズ できません。そこで、インジェクト先が

@SessionScoped

@ConversationScoped

である場合は、シリアライズ処理から除外するように

transient

修飾子を付けます。

transient

はシリアライズしないという意味です。

@Inject

transient Logger log;

 ロガーの使い方は、コラム「ロガーの使い方」を見てください。

コラム ロガーの使い方

 ロガーでのメッセージ出力は、

info

(情報)、

warning

(警告)、

severe

(エラー)な どに分かれています。どの種類のメッセージでも出力できますが、

setLevel

メソッ ドでどのレベル以上のものを実際に出力するか指定します。

 例えば、次のようにすると "警告メッセージ" と "エラーメッセージ" だけが出力さ れます。

@Inject Logger log;

log.setLeveL(LEVEL_WARNING); // 出力メッセージレベルを「警告」レベルに設定

log.info("情報メッセージ"); // infoレベルメッセージを出力

log.warning("警告メッセージ"); // warningレベルメッセージを出力

log.severe("エラーメッセージ"); // severeレベルメッセージを出力

 

Logger

には、以下に示すようなログレベルがあり、それに応じて記録に使うメ

ソッドが違います。デフォルトは

LEVEL.INFO

になっているので

INFO

WARNINIG

SEVERE

のログが出力されます。

(31)

12

ログレベル定数 意 味 記録メソッド

LEVEL.ALL すべてのログを取る LEVEL.OFF ログを出力しない LEVEL.CONFIG 設定情報に関するログ

LEVEL.FINEST 大量のトレース情報 finest() LEVEL.FINER 特定の処理についての開始、終了情報など finer()

LEVEL.FINE トレース情報ログ fine()

LEVEL.INFO 処理確認のための情報出力のログ info() LEVEL.WARNING 警告レベルのメッセージ。処理は継続可能 warning() LEVEL.SEVERE 重大な障害を示すメッセージ。継続不能 severe()

4.5 後始末をするためのディスポーザー

 

@Produces

でインジェクトできるようにしたオブジェクトのうち、使用後にclose処理 が必要なものがあります。例えば、ファイルやデータベース処理に関係するオブジェクト です。そこで、

@Produces

でインジェクトしたオブジェクトの終了処理を行うのが、ディ スポーザーです。ここでは、ファイル出力するロガーのケースを使って、解説します。

ログをファイルにも出力するようにする

 ログをファイルに出力するのは簡単です。ロガーがログをファイルに送れるようにする ファイルハンドラーを作成し、ファイルハンドラーに、出力形式を指定するためのフォー マッターを指定します。次のようにします。

① Handler handler

    =new FileHandler("w:/temp/log.txt", 1000000, 1, true); // ハンドラーを作成

② handler.setFormatter(new SimpleFormatter()); // フォーマッタを指定

③ logger.addHandler(handler); // ロガーにセットする

①FileHandlerの作成

  ファイルパス、最大ファイルサイズ、ローテーションするファイルの数、追記モードに するかどうか(

true

で追記になる)を指定します。ここでは、wドライブのtempフォルダ に

log.txt

という名前のファイルを作ることとし、最大ファイルサイズは1MB、1つの ログファイルに追記モードで記録するようにしています。

  ログファイルを入れるフォルダ(temp)はすでに存在していなければいけません。また、

絶対パスで指定する必要があります。Macでは、"

/user/<home>/temp/log.txt

" な

(32)

12

どと指定するといいでしょう。なお、最大ファイルサイズを超えると、新しいファイル が開かれます。

②フォーマッタの作成

  フォーマッタは、出力データを整形するためのパーツです。指定しないと、XML形式 になってしまうので、JDKが最初から持っているSimpleFormatterを指定します(フォー マッタは自作できますが、ここでは省略します)。

③ロガーにセットする

 最後に、作成したハンドラーをaddHandlerメソッドでロガーに追加します。

ファイルハンドラーを閉じる

 以上で、ログがファイルに出力されるようになりますが、Loggerをインジェクトしたオ ブジェクトが破棄される時、ファイルハンドラーも閉じておく必要があります。そうしな いと、ログファイルが永遠に書き込み保護の状態のままになり、新しくログを書き込むた びに、新しいログファイルが生成されます。何十ものログファイルが生成されて、期待し たようなログは得られません。

 ファイルハンドラーを閉じるには、ロガーからすべてのハンドラー(コンソールハンドラ、

ファイルハンドラなど)を取得し、それらをすべて閉じます。次のようにします。

Handler[] hs = log.getHandlers(); // ロガーからハンドラの配列を得る

for (Handler h : hs) { // すべてのハンドラをクローズする

h.close();

}

 この部分を、Disposerとして作成します。

 すべてを組み込んだ

FileLoggerProducer

は次のようになります。

参照

関連したドキュメント

て当期の損金の額に算入することができるか否かなどが争われた事件におい

自閉症の人達は、「~かもしれ ない 」という予測を立てて行動 することが難しく、これから起 こる事も予測出来ず 不安で混乱

わかりやすい解説により、今言われているデジタル化の変革と

(自分で感じられ得る[もの])という用例は注目に値する(脚注 24 ).接頭辞の sam は「正しい」と

巣造りから雛が生まれるころの大事な時 期は、深い雪に被われて人が入っていけ

   遠くに住んでいる、家に入られることに抵抗感があるなどの 療養中の子どもへの直接支援の難しさを、 IT という手段を使えば

基準の電力は,原則として次のいずれかを基準として決定するも

自然言語というのは、生得 な文法 があるということです。 生まれつき に、人 に わっている 力を って乳幼児が獲得できる言語だという え です。 語の それ自 も、 から