Yan Hon 博士の著書「JAVA とパターン」は、責任連鎖モデルの説明から始まります。
責任連鎖パターンは、オブジェクトの動作パターンです。責任の連鎖パターンでは、各オブジェクトの子孫への参照によって多くのオブジェクトが接続され、連鎖を形成します。リクエストは、チェーン内のオブジェクトがリクエストの処理を決定するまで、チェーンを上流に渡されます。リクエストを行うクライアントは、チェーン内のどのオブジェクトがリクエストを最終的に処理するかを知りません。そのため、システムはクライアントに影響を与えることなく、動的に再編成して責任を割り当てることができます。
太鼓たたきから始まり花を散らす
太鼓をたたきながら花を渡す、賑やかで緊張感のある酒飲みゲームです。宴会ではゲストが順番に座り、一人が太鼓をたたきますが、太鼓を叩く場所と花を渡す場所は分けられており、公平性を保っています。太鼓の演奏が始まると花束が回り始めます。太鼓が落ちるとすぐに、花束が誰かの手にある場合、その人は水を飲まなければなりません。
たとえば、Jia Mu、Jia She、Jia Zheng、Jia Baoyu、Jia Huan の 5 人は、太鼓と花渡しのゲームに参加し、チェーンを形成します。ドラマーはジア・ムーに花を渡し、花渡しゲームを始めました。下図に示すように、花は賈木から賈社へ、賈社から賈正へ、賈正から賈保玉へ、賈保玉から賈桓へ、賈桓から賈牧へ、というように受け継がれていきました。太鼓の音が止まったら、花を手に持った人が飲み物の注文を実行しなければなりません。
太鼓を叩いて花を広げることは、責任連鎖モデルの適用です。責任の連鎖は、直線、鎖、またはツリー構造の一部である場合があります。
責任連鎖モデルの構造
以下では、責任連鎖パターンの最も単純な実装を使用します。
責任連鎖モデルに関与する役割は次のとおりです。
● 抽象ハンドラー (Handler) ロール: リクエストを処理するためのインターフェースを定義します。必要に応じて、インターフェイスは次のインターフェイスへの参照を設定して返すメソッドを定義できます。この役割は通常、Java 抽象クラスまたは Java インターフェイスによって実装されます。上の図の Handler クラスの集約関係は、特定のサブクラスの参照を次のサブクラスに与えます。抽象メソッド handleRequest() は、リクエストを処理する際のサブクラスの動作を標準化します。
● コンクリート ハンドラー (ConcreteHandler) ロール: リクエストを受信した後、コンクリート ハンドラーはリクエストを処理するか、リクエストを次のパーティに渡すかを選択できます。具象プロセッサは次のホームへの参照を保持しているため、必要に応じて次のホームにアクセスできます。
ソースコード
抽象ハンドラーの役割
次のようにコードをコピーします。
パブリック抽象クラス ハンドラー {
/**
* 後継者責任オブジェクトを保持
*/
保護されたハンドラー後継者。
/**
* リクエストを処理するメソッドを示しますが、このメソッドはパラメータを渡しません。
* ただし、実際にはパラメータを渡すことができます。特定のニーズに応じてパラメータを渡すかどうかを選択できます。
*/
public abstract void handleRequest();
/**
※バリュー方式
*/
public ハンドラー getSuccessor() {
後継者を返す。
}
/**
* 後続の責任オブジェクトを設定するための割り当て方法
*/
public void setSuccessor(ハンドラー後続) {
this.successor = 後継者;
}
}
特定のプロセッサの役割
次のようにコードをコピーします。
public class ConcreteHandler extends Handler {
/**
* 処理メソッド。このメソッドを呼び出してリクエストを処理します。
*/
@オーバーライド
public void handleRequest() {
/**
* 後続の責任オブジェクトがあるかどうかを判断します
* 存在する場合は、リクエストを後続の責任のあるオブジェクトに転送します
*そうでない場合は、リクエストを処理します
*/
if(getSuccessor() != null)
{
System.out.println("リクエストを許可します");
getSuccessor().handleRequest();
}それ以外
{
System.out.println("リクエストを処理しています");
}
}
}
クライアントクラス
次のようにコードをコピーします。
パブリック クラス クライアント {
public static void main(String[] args) {
//責任チェーンを組み立てる
ハンドラー handler1 = new ConcreteHandler();
ハンドラー handler2 = new ConcreteHandler();
handler1.setSuccessor(handler2);
//リクエストを送信する
handler1.handleRequest();
}
}
クライアントが 2 つのハンドラー オブジェクトを作成し、最初のハンドラー オブジェクトの次のハンドラー オブジェクトが 2 番目のハンドラー オブジェクトであることを指定しますが、2 番目のハンドラー オブジェクトには次のホームがないことがわかります。次に、クライアントはリクエストを最初のハンドラー オブジェクトに渡します。
この例の転送ロジックは非常に単純であるため、次の所有者がいる限り、処理のために次の所有者に渡されます。次の所有者がいない場合は、それ自体が処理されます。したがって、最初のハンドラー オブジェクトがリクエストを受信した後、そのリクエストを 2 番目のハンドラー オブジェクトに渡します。 2 番目のハンドラー オブジェクトにはホームがないため、リクエストを単独で処理します。アクティビティのシーケンス図を以下に示します。
使用シナリオ
食事会費用の管理申請という機能を考えてみましょう。
多くの企業では、プロジェクトチームや部門が食事会費用の一部を会社に申請して、プロジェクトチームや部門のメンバーを集めた食事会を開催することができるという特典を設けています。
食事会費用申請の一般的な流れは、まず申請者が申請書に必要事項を記入し、承認を得るためにリーダーに提出します。申請が承認されると、リーダーは申請者に承認の旨を通知します。その後、申請者は財務部門に行き、手数料を徴収します。承認されなかった場合、リーダーは申請者に承認が通らなかったことを通知し、その件は取り下げられます。
たとえば、プロジェクト マネージャーは 500 元の限度で申請を承認でき、部長は 1,000 元の限度で申請を承認できます。
つまり、誰かが食事会費用の請求をすると、プロジェクトマネージャー、部長、部長のいずれかがそれに従って処理することになりますが、最終的にどうなるかは請求した本人にはわかりません。誰が彼の要求を処理しますか? 通常、申請者はプロジェクト マネージャーに申請書を提出し、最終的にはゼネラル マネージャーが彼の要求を処理します。
責任連鎖モデルを使用すると、上記の機能を実現できます。誰かが食事会の費用を要求すると、その要求はプロジェクト マネージャー -> 部門マネージャー -> 部長などのリーダーシップの処理チェーンに渡されます。誰がその要求を処理するかはわかりません。各リーダーは、自分の責任範囲に基づいて、その要求を処理するか、上位のリーダーに引き渡すかを判断します。転送は終了しました。
各リーダーの処理を分離し、別個の責任処理オブジェクトに実装し、それらに共通の抽象的な親責任オブジェクトを提供して、責任チェーンをクライアント上で動的に結合してさまざまな機能要件を達成できるようにする必要があります。 。
ソースコード
抽象ハンドラーロールクラス
次のようにコードをコピーします。
パブリック抽象クラス ハンドラー {
/**
* 次のリクエストを処理するオブジェクトを保持します
*/
protected ハンドラーの後続 = null;
/**
※バリュー方式
*/
public ハンドラー getSuccessor() {
後継者を返す。
}
/**
* リクエストを処理する次のオブジェクトを設定します
*/
public void setSuccessor(ハンドラー後続) {
this.successor = 後継者;
}
/**
* パーティー費用の申請手続き
* @param ユーザー申請者
* @param Fee 申請金額
* @return 成功または失敗の具体的な通知
*/
public abstract String handleFeeRequest(String user, double 料金);
}
特定のプロセッサの役割
次のようにコードをコピーします。
public class ProjectManager extends Handler {
@オーバーライド
public String handleFeeRequest(String user, double 料金) {
文字列 str = "";
// プロジェクト マネージャーの権限は比較的小さく、500 以内しか許可されません
if(手数料 < 500)
{
//テストのため、シンプルにして、Zhang San のリクエストにのみ同意します。
if("張三".equals(user))
{
str = "成功: プロジェクト マネージャーは、[" + ユーザー + "] のディナー パーティの料金に同意しました。金額は " + 料金 + "元";
}それ以外
{
//他に誰も同意しない
str = "失敗: プロジェクト マネージャーは [" + ユーザー + "] のディナー パーティの料金に同意しません。金額は " + 料金 + "元";
}
}それ以外
{
// 500 を超えた場合は、引き続き上位の人に渡して処理してもらいます。
if(getSuccessor() != null)
{
return getSuccessor().handleFeeRequest(ユーザー, 料金);
}
}
文字列を返します。
}
}
次のようにコードをコピーします。
public class DeptManager extends Handler {
@オーバーライド
public String handleFeeRequest(String user, double 料金) {
文字列 str = "";
//部門長の権限は1000以内のみ
if(手数料 < 1000)
{
//テストのため、シンプルにして、Zhang San のリクエストにのみ同意します。
if("張三".equals(user))
{
str = "成功: 部門マネージャーは、[" + ユーザー + "] の食事会料金に同意しました。金額は " + 料金 + "元";
}それ以外
{
//他に誰も同意しない
str = "失敗: 部門マネージャーは [" + ユーザー + "] の食事会の料金に同意しません。金額は " + 料金 + "元";
}
}それ以外
{
// 1000 を超えた場合は、引き続き上位の人に渡して処理してもらいます。
if(getSuccessor() != null)
{
return getSuccessor().handleFeeRequest(ユーザー, 料金);
}
}
文字列を返します。
}
}
次のようにコードをコピーします。
public class GeneralManager extends Handler {
@オーバーライド
public String handleFeeRequest(String user, double 料金) {
文字列 str = "";
//部長は大きな権限を持っているので、こちらからの依頼であれば対応してもらえます。
if(手数料 >= 1000)
{
//テストのため、シンプルにして、Zhang San のリクエストにのみ同意します。
if("張三".equals(user))
{
str = "成功: 総支配人は [" + ユーザー + "] の夕食費用に同意します。金額は " + 料金 + "元";
}それ以外
{
//他に誰も同意しない
str = "失敗: 総支配人は [" + ユーザー + "] のディナー パーティー料金に同意しません。金額は " + 料金 + "元";
}
}それ以外
{
//後続の処理オブジェクトがある場合は、引き続き渡します
if(getSuccessor() != null)
{
return getSuccessor().handleFeeRequest(ユーザー, 料金);
}
}
文字列を返します。
}
}
クライアントクラス
次のようにコードをコピーします。
パブリック クラス クライアント {
public static void main(String[] args) {
//最初に責任チェーンを組み立てます
ハンドラー h1 = new GeneralManager();
ハンドラ h2 = new DeptManager();
ハンドラ h3 = 新しい ProjectManager();
h3.setSuccessor(h2);
h2.setSuccessor(h1);
//テストを開始する
String test1 = h3.handleFeeRequest("張三", 300);
System.out.println("test1 = " + test1);
String test2 = h3.handleFeeRequest("李思", 300);
System.out.println("test2 = " + test2);
System.out.println("-------------------------------------------------");
String test3 = h3.handleFeeRequest("張三", 700);
System.out.println("test3 = " + test3);
String test4 = h3.handleFeeRequest("李思", 700);
System.out.println("test4 = " + test4);
System.out.println("-------------------------------------------------");
String test5 = h3.handleFeeRequest("張三", 1500);
System.out.println("test5 = " + test5);
String test6 = h3.handleFeeRequest("李思", 1500);
System.out.println("test6 = " + test6);
}
}
実行結果は次のとおりです。
純粋および不純な責任連鎖モデル
純粋な責任連鎖モデルでは、特定のプロセッサ オブジェクトが 2 つのアクションのうち 1 つだけを選択できることが必要です。1 つは責任を負い、次の当事者に責任を渡すことです。特定のプロセッサ オブジェクトが何らかの責任を引き受けた後でその責任を引き継ぐことは許可されていません。
純粋な責任連鎖モデルでは、リクエストはハンドラー オブジェクトによって受信される必要がありますが、不純な責任連鎖モデルでは、リクエストはどのレシーバー オブジェクトによっても受信されない可能性があります。
純粋な責任連鎖モデルの実例は見つけるのが難しく、一般に見られる例は不純な責任連鎖モデルの実装です。不純な責任の連鎖はまったく責任の連鎖モデルではないと考える人もいますが、これは理にかなっているかもしれません。しかし、実際のシステムでは、純粋な責任の連鎖を見つけるのは困難です。責任の連鎖は不純であり、責任の連鎖モデルではないと主張する場合、責任の連鎖モデルはあまり意味を持たなくなります。
Tomcat における責任連鎖モデルの適用
ご存知のとおり、Tomcat のフィルターは責任連鎖モデルを使用します。web.xml ファイルで対応する設定を行うだけでなく、フィルターを作成するには javax.servlet.Filter インターフェースを実装する必要もあります。
次のようにコードをコピーします。
public class TestFilter は Filter{ を実装します。
public void doFilter(ServletRequest リクエスト、ServletResponse レスポンス、
FilterChain チェーン) IOException、ServletException をスローする {
chain.doFilter(リクエスト、レスポンス);
}
public void destroy() {
}
public void init(FilterConfig filterConfig) throws ServletException {
}
}
DEBUG モードを使用した場合の結果は次のとおりです。
実際、TestFilter クラスを実際に実行する前に、Tomcat の多くの内部クラスを通過します。ちなみに、Tomcat のコンテナの設定も、赤枠で囲ったクラスから、リクエストがチェーンを経由して渡されることに注目してください。緑の枠で囲ったところにApplicationFilterChainというクラスがあり、ApplicationFilterChainクラスが抽象的なプロセッサの役割を果たしており、具体的なプロセッサの役割は各Filterが担っています。
最初の疑問は、ApplicationFilterChain がすべてのフィルターをどこに保存するのかということです。
答えは、ApplicationFilterChain クラスの ApplicationFilterConfig オブジェクトの配列に保存されます。
次のようにコードをコピーします。
/**
*フィルター。
*/
プライベート ApplicationFilterConfig[] フィルター =
新しい ApplicationFilterConfig[0];
では、ApplicationFilterConfig オブジェクトとは何でしょうか?
ApplicationFilterConfig はフィルター コンテナーです。 ApplicationFilterConfig クラスの宣言は次のとおりです。
次のようにコードをコピーします。
/**
* に役立つ <code>javax.servlet.FilterConfig</code> の実装
* Web アプリケーションの実行時にインスタンス化されるフィルター インスタンスの管理
* が最初に起動されます。
*
* @著者 Craig R. McClanahan
* @version $Id: ApplicationFilterConfig.java 1201569 2011-11-14 01:36:07Z kkolinko $
*/
ApplicationFilterConfig は、Web アプリケーションの初回起動時に自動的にインスタンス化され、Web アプリケーションの web.xml ファイルから設定されたフィルター情報を読み取り、それをコンテナーに読み込みます。
ApplicationFilterChain クラスで作成された ApplicationFilterConfig 配列の長さがゼロであることがわかりました。いつ再割り当てされたのですか?
次のようにコードをコピーします。
プライベート ApplicationFilterConfig[] フィルター =
新しい ApplicationFilterConfig[0];
ApplicationFilterChainクラスのaddFilter()メソッドを呼び出したときです。
次のようにコードをコピーします。
/**
* チェーン内の現在のフィルター数を示す int。
*/
プライベート int n = 0;
パブリック静的最終整数 INCREMENT = 10;
次のようにコードをコピーします。
void addFilter(ApplicationFilterConfig filterConfig) {
// 同じフィルターが複数回追加されるのを防ぎます
for(ApplicationFilterConfig フィルター:フィルター)
if(フィルター==フィルター構成)
戻る;
if (n == フィルター.長さ) {
ApplicationFilterConfig[] newFilters =
new ApplicationFilterConfig[n + INCREMENT];
System.arraycopy(filters, 0, newFilters, 0, n);
フィルター = newFilters;
}
フィルター[n++] = フィルター構成;
}
変数 n は、現在のフィルター チェーン内のフィルターの数を記録するために使用されます。デフォルトでは、n は 0 に等しく、ApplicationFilterConfig オブジェクト配列の長さも 0 に等しいため、addFilter() メソッドが呼び出されるときに使用されます。 1 回目は (n == filters .length) が true で、ApplicationFilterConfig 配列の長さが変更された場合です。次に、 filters[n++] = filterConfig; 変数 filterConfig を ApplicationFilterConfig 配列に入れ、現在のフィルター チェーン内のフィルターの数に 1 を加えます。
では、ApplicationFilterChain の addFilter() メソッドはどこで呼び出されるのでしょうか?
これは、ApplicationFilterFactory クラスの createFilterChain() メソッド内にあります。
次のようにコードをコピーします。
public ApplicationFilterChain createFilterChain
(ServletRequest リクエスト、Wrapper ラッパー、Servlet サーブレット) {
// ディスパッチャのタイプを取得します
DispatcherType ディスパッチャ = null;
if (request.getAttribute(DISPATCHER_TYPE_ATTR) != null) {
dispatcher = (DispatcherType) request.getAttribute(DISPATCHER_TYPE_ATTR);
}
文字列リクエストパス = null;
オブジェクト属性 = request.getAttribute(DISPATCHER_REQUEST_PATH_ATTR);
if (属性 != null){
requestPath = 属性.toString();
}
// 実行するサーブレットがない場合は null を返す
if (サーブレット == null)
戻り値 (null);
ブール値の彗星 = false;
// フィルターチェーンオブジェクトを作成して初期化します
ApplicationFilterChain フィルターチェーン = null;
if (リクエストのインスタンスをリクエスト) {
リクエスト req = (リクエスト) リクエスト;
コメット = req.isComet();
if (Globals.IS_SECURITY_ENABLED) {
// セキュリティ: リサイクルしないでください
filterChain = 新しい ApplicationFilterChain();
if (彗星) {
req.setFilterChain(filterChain);
}
} それ以外 {
filterChain = (ApplicationFilterChain) req.getFilterChain();
if (filterChain == null) {
filterChain = 新しい ApplicationFilterChain();
req.setFilterChain(filterChain);
}
}
} それ以外 {
//リクエストディスパッチャが使用中です
filterChain = 新しい ApplicationFilterChain();
}
filterChain.setServlet(サーブレット);
filterChain.setSupport
(((StandardWrapper)ラッパー).getInstanceSupport());
// この Context のフィルター マッピングを取得します
StandardContext context = (StandardContext) Wrapper.getParent();
FilterMap filterMaps[] = context.findFilterMaps();
// フィルター マッピングがない場合は完了です
if ((filterMaps == null) || (filterMaps.length == 0))
戻り値 (フィルターチェーン);
// フィルター マッピングを照合するために必要な情報を取得します
文字列サーブレット名 =wrapper.getName();
// 関連するパスマップされたフィルターをこのフィルター チェーンに追加します
for (int i = 0; i < filterMaps.length; i++) {
if (!matchDispatcher(filterMaps[i] ,dispatcher)) {
続く;
}
if (!matchFiltersURL(filterMaps[i], requestPath))
続く;
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)
context.findFilterConfig(filterMaps[i].getFilterName());
if (filterConfig == null) {
// FIXME - ログ構成の問題
続く;
}
ブール値 isCometFilter = false;
if (彗星) {
試す {
isCometFilter = filterConfig.getFilter() CometFilter のインスタンス;
} catch (例外 e) {
// 注: getFilter には多くの機能があるため、try catch が存在します。
// 宣言された例外ですが、フィルターは大量に割り当てられます。
// 以前
スロー可能な t = ExceptionUtils.unwrapInvocationTargetException(e);
ExceptionUtils.handleThrowable(t);
}
if (isCometFilter) {
filterChain.addFilter(filterConfig);
}
} それ以外 {
filterChain.addFilter(filterConfig);
}
}
// サーブレット名に一致するフィルターを 2 番目に追加します
for (int i = 0; i < filterMaps.length; i++) {
if (!matchDispatcher(filterMaps[i] ,dispatcher)) {
続く;
}
if (!matchFiltersServlet(filterMaps[i], servletName))
続く;
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)
context.findFilterConfig(filterMaps[i].getFilterName());
if (filterConfig == null) {
// FIXME - ログ構成の問題
続く;
}
ブール値 isCometFilter = false;
if (彗星) {
試す {
isCometFilter = filterConfig.getFilter() CometFilter のインスタンス;
} catch (例外 e) {
// 注: getFilter には多くの機能があるため、try catch が存在します。
// 宣言された例外ですが、フィルターは大量に割り当てられます。
// 以前
}
if (isCometFilter) {
filterChain.addFilter(filterConfig);
}
} それ以外 {
filterChain.addFilter(filterConfig);
}
}
// 完成したフィルターチェーンを返します
戻り値 (フィルターチェーン);
}
上記のコードは、51 行目の前の最初のセクションと 51 行目の後の 2 番目のセクションの 2 つのセクションに分割できます。
最初の段落の主な目的は、ApplicationFilterChain オブジェクトを作成し、いくつかのパラメーターを設定することです。
2 番目の段落の主な目的は、コンテキストからすべてのフィルター情報を取得し、for ループを使用して filterChain.addFilter(filterConfig) を呼び出し、filterConfig を ApplicationFilterChain オブジェクトの ApplicationFilterConfig 配列に入れることです。
では、ApplicationFilterFactory クラスの createFilterChain() メソッドはどこで呼び出されるのでしょうか?
これは、StandardWrapperValue クラスの invoke() メソッドで呼び出されます。
invoke() メソッドは長いため、多くの箇所が省略されています。
次のようにコードをコピーします。
public Final void invoke(リクエストリクエスト、レスポンスレスポンス)
IOException、ServletExceptionをスローします{
...中間コードを省略 // このリクエストのフィルター チェーンを作成します
ApplicationFilterFactory ファクトリ =
ApplicationFilterFactory.getInstance();
アプリケーションフィルターチェーンフィルターチェーン =
Factory.createFilterChain(リクエスト、ラッパー、サーブレット);
...中間コードを省略
filterChain.doFilter(request.getRequest()、response.getResponse());
...中間コードを省略
}
通常のプロセスは次のようになります。
StandardWrapperValue クラスの invoke() メソッドで ApplicationFilterChai クラスの createFilterChain() メソッドを呼び出します ---> ApplicationFilterChai クラスの createFilterChain() メソッドで ApplicationFilterChain クラスの addFilter() メソッドを呼び出します ---> ApplicationFilterChain クラスの addFilter() メソッド ApplicationFilterConfig 配列に値を割り当てます。
上記のコードによると、StandardWrapperValue クラスの invoke() メソッドは、createFilterChain() メソッドを実行した後、引き続き ApplicationFilterChain クラスの doFilter() メソッドを実行し、その後、internalDoFilter() メソッドが実行されることがわかります。 doFilter() メソッドで呼び出されます。
以下は、internalDoFilter() メソッドのコードの一部です。
次のようにコードをコピーします。
// 次のフィルターがある場合はそれを呼び出します
if (pos < n) {
//次のフィルタを取得し、ポインタを 1 つ下の位置に移動します
//pos 現在の ApplicationFilterChain (現在のフィルター チェーン) が実行するフィルターを識別します
ApplicationFilterConfig filterConfig = フィルター[pos++];
フィルターフィルター = null;
試す {
//現在ポイントされているフィルターのインスタンスを取得します
フィルター = filterConfig.getFilter();
support.fireInstanceEvent(InstanceEvent.BEFORE_FILTER_EVENT,
フィルター、リクエスト、レスポンス);
if (request.isAsyncSupported() && "false".equalsIgnoreCase(
filterConfig.getFilterDef().getAsyncSupported())) {
request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR,
ブール値.FALSE);
}
if(Globals.IS_SECURITY_ENABLED) {
最終的なServletRequest req = リクエスト;
最終的な ServletResponse res = 応答;
校長校長 =
((HttpServletRequest) 要求).getUserPrincipal();
Object[] args = new Object[]{req, res, this};
SecurityUtil.doAsPrivilege
("doFilter"、フィルター、クラスタイプ、引数、プリンシパル);
} それ以外 {
// Filter の doFilter() メソッドを呼び出します。
filter.doFilter(リクエスト、レスポンス、これ);
}
ここでの filter.doFilter(request, response, this); は、前に作成した TestFilter の doFilter() メソッドを呼び出します。 TestFilter の doFilter() メソッドは、chain.doFilter(request, response); メソッドの呼び出しを継続し、このチェーンは実際には ApplicationFilterChain であるため、呼び出しプロセスは dofilter の呼び出しと上記の externalDoFilter メソッドの呼び出しに戻ります。実行は、内部のフィルターがすべて実行されるまで継続されます。
2 つのフィルターが定義されている場合、デバッグ結果は次のようになります。