lastaflute / lasta-di

DI Container for LastaFlute, forked from Seasar as Java8
Apache License 2.0
9 stars 1 forks source link

Research, Fess's SearchEngineClient refers LoginAssist as getComponent() #44

Open jflute opened 1 month ago

jflute commented 1 month ago

// Japanese here

from DBFlute Slack:

Lastaflute 2.0.0などでCreatorなどで読み込まれるコンポーネントのタイミングが変わるような変更があったりするでしょうか?
コンポーネント([SearchEngineClient](https://github.com/codelibs/fess/blob/master/src/main/resources/esclient.xml#L5))の初期化で、それの@PostConstructで処理しているときに、FessLoginAssistを[getComponent](https://github.com/codelibs/fess/blob/master/src/main/java/org/codelibs/fess/es/client/SearchEngineClient.java#L778)しているのですが、smart deployがwarmのときは取得できるのですが、coolで取得できなくなったので、何が原因なのかと思いまして…。lastaflute 1.2.7のときには、coolとwarmのどちらも取得できてました。jakarta対応のSNAPSHOTのときは、大丈夫だったと思うのですが、今回、Fessを2.0.0系にしたら、coolで動く、GitHub Actionsで実行しているテストが失敗してしまいまして。
lastaflute 2 and warm: ok
lastaflute 2 and cool: fail
lastaflute 1 and warm: ok
lastaflute 1 and coll: ok
のような状況で、coolでcreatorで読み込まれているものが取れないのだろう…のような状態でした。

...

[@shinsuke](https://dbflute.slack.com/team/UAS1W6J4E)
 おや、りょうかいです。フィードバックありがとうございます。
パッとは思いつかないので、コード差分追うのと、test環境で似た構成を実際に作って再現してみます。
コードはほぼ1も2も同じなので、ちょと不思議ですね…
実際に取得できなかったときの例外メッセージとスタックトレースを見せてもらってもよろしいでしょうか?

...

ありがとうございます。そうですよね…。SNAPSHOTのときにはcoolでも起きていなかったので、最近の何かの変更で、何かを踏んでいるのかもしれないですね…。
スタックトレースはJSONで読みにくいですが、[こんな感じ](https://github.com/codelibs/fess-test-ui/actions/runs/11425442060/job/31787135498#step:3:1707)です。
warmにすると、AssistのCreatorが先に読み込まれるのですが、coolにすると、Creatorより先にSearchEngineCLientの@PostConstructの処理が呼ばれるようになったので、まだ、理由がわかってません…。一応、coolのときには、Creatorの呼び出し順があとになるだけで、あとでCreatorは呼ばれてはいるようでした。

...

取り急ぎ、超単純環境で試してみましたが、再現しませんね…
o Di xmlにてWhiteLoginReferenceClientを定義。
o WhiteLoginReferenceClientにてLoginAssistをgetComponent()。
o java8, java21, hot,warm,coolと全ての組み合わせで起動してログ確認
↓
すべて正常に動作
[https://github.com/lastaflute/lastaflute-test-fortress/blob/jakarta21/src/main/jav[…]org/docksidestage/bizfw/whitebox/WhiteLoginReferenceClient.java](https://github.com/lastaflute/lastaflute-test-fortress/blob/jakarta21/src/main/java/org/docksidestage/bizfw/whitebox/WhiteLoginReferenceClient.java#L19)
もうちょいfessならではの環境があるかもなので、もうちょい試行錯誤してみます。

...
...

PrimaryLoginManager経由でもどちらでも結果は変わらず:
@PostConstruct
public void open() {
    System.out.println("@@@: open() before getComponent(assist)");

    //PrimaryLoginManager loginManager = ComponentUtil.getComponent(PrimaryLoginManager.class);
    //System.out.println("@@@: result (loginManager) => " + loginManager);

    FessLoginAssist loginAssist = ComponentUtil.getComponent(FessLoginAssist.class);
    System.out.println("@@@: result (loginAssist) => " + loginAssist);
根本的にQuick Componentがまだ初期化されていない状態になってる。
SearchEngineClientの場合、Di xmlの階層が深いので、そこを疑って試行錯誤してみる。
(fessをシンプルにしていくか、fortressを複雑にしていくか)

..

SearchEngineClientじゃなくて、fortressのシンプルクラスをfessに入れて試してみた。
esclientとかログイン処理とか関係ない感じで、新しいAssistクラスに新しいClientクラスで。
fess :: 再現する
fortress21 :: 再現しない
かなーり、違いを排除したけど結果に違いが出る。
でも、まだまだ違いはあるので、徐々に排除していく。
(新しいローカルブランチで、どんどんファイルを削除したりで近づけていく)
jflute commented 1 month ago

そもそもJava8のfortressにて、coolのときのQuick Componentの初期化タイミングが、JSON Managerの前後になってる。 厳密には、Objective Configの後に一部コンポーネントが初期化されて、その後JSON Manager。

2024-10-21 16:07:52,178 [main] INFO  (LaContainerFactory@show():158) - ...Reading       convention.xml (recycle)
2024-10-21 16:07:52,178 [main] INFO  (LaContainerFactory@show():158) - ...Reading       customizer.xml (recycle)
2024-10-21 16:07:52,184 [main] INFO  (LaContainerFactory@show():158) - ...Reading   customizer.xml (recycle)
2024-10-21 16:07:52,296 [main] INFO  (ObjectiveConfig@showBootLogging():242) - [Objective Config]
2024-10-21 16:07:52,300 [main] INFO  (ObjectiveConfig@showBootLogging():243) -  fortress_config.properties extends [fortress_thymeleaf_config.properties, fortress_env.properties]
2024-10-21 16:07:52,302 [main] INFO  (ObjectiveConfig@showBootLogging():246) -  checkImplicitOverride=true, propertyCount=45
2024-10-21 16:07:52,372 [main] DEBUG (LaLogger@log():118) - Registering component definition of class(org.docksidestage.app.biz.onionarc.application.OnionarcSeaAppService[onionarc_application_onionarcSeaAppService]).
2024-10-21 16:07:52,386 [main] DEBUG (LaLogger@log():118) - Registering component definition of class(org.docksidestage.app.biz.onionarc.infrastructure.OnionarcSeaInfraRepository[onionarc_domain_repository_onionarcSeaRepository]).
2024-10-21 16:07:52,390 [main] DEBUG (LaLogger@log():118) - Registering component definition of class(org.docksidestage.app.biz.onionarc.domain.service.OnionarcSeaDomainService[onionarc_domain_service_onionarcSeaDomainService]).
2024-10-21 16:07:52,393 [main] DEBUG (LaLogger@log():118) - Registering component definition of class(org.docksidestage.app.biz.cleaneg.infrastructure.CleanegSeaInfraRepository[cleaneg_infrastructure_cleanegSeaInfraRepository]).
2024-10-21 16:07:52,768 [main] INFO  (SimpleJsonManager@showBootLogging():147) - [JSON Manager]
2024-10-21 16:07:52,770 [main] INFO  (SimpleJsonManager@showBootLogging():148) -  realJsonParser: GsonJsonEngine
2024-10-21 16:07:52,780 [main] INFO  (SimpleJsonManager@showBootLogging():150) -  mapping: {CAMEL_TO_LOWER_SNAKE, [ImmutableList]}
2024-10-21 16:07:52,900 [main] DEBUG (LaLogger@log():118) - Registering component definition of class(org.docksidestage.app.web.RootAction[rootAction]).
2024-10-21 16:07:52,974 [main] DEBUG (LaLogger@log():118) - Registering component definition of class(org.docksidestage.app.web.lido.auth.LidoAuthAction[lido_auth_lidoAuthAction]).
2024-10-21 16:07:52,994 [main] DEBUG (LaLogger@log():118) - Registering component definition of class(org.docksidestage.app.web.lido.product.LidoProductDetailAction[lido_product_lidoProductDetailAction]).
2024-10-21 16:07:53,009 [main] DEBUG (LaLogger@log():118) - Registering component definition of class(org.docksidestage.app.web.lido.product.price.LidoProductPriceAction[lido_product_price_lidoProductPriceAction]).
2024-10-21 16:07:53,035 [main] DEBUG (LaLogger@log():118) - Registering component definition of class(org.docksidestage.app.web.lido.product.LidoProductListAction[lido_product_lidoProductListAction]).
2024-10-21 16:07:53,041 [main] DEBUG (LaLogger@log():118) - Registering component definition of class(org.docksidestage.app.web.lido.mypage.LidoMypageAction[lido_mypage_lidoMypageAction]).
...

この挙動自体なんで?って感じ。

Java21の場合は、さらにObjective Configの後にJSON Managerを待たずしてすべてのQuick Componentが初期化されている。

2024-10-21 16:12:47,381 [main] INFO  (ObjectiveConfig@showBootLogging():242) - [Objective Config]
2024-10-21 16:12:47,381 [main] INFO  (ObjectiveConfig@showBootLogging():243) -  fortress_config.properties extends [fortress_thymeleaf_config.properties, fortress_env.properties]
2024-10-21 16:12:47,382 [main] INFO  (ObjectiveConfig@showBootLogging():246) -  checkImplicitOverride=true, propertyCount=45
2024-10-21 16:12:47,405 [main] DEBUG (LaLogger@log():118) - Registering component definition of class(org.docksidestage.app.biz.onionarc.application.OnionarcSeaAppService[onionarc_application_onionarcSeaAppService]).
2024-10-21 16:12:47,414 [main] DEBUG (LaLogger@log():118) - Registering component definition of class(org.docksidestage.app.biz.onionarc.infrastructure.OnionarcSeaInfraRepository[onionarc_domain_repository_onionarcSeaRepository]).
2024-10-21 16:12:47,416 [main] DEBUG (LaLogger@log():118) - Registering component definition of class(org.docksidestage.app.biz.onionarc.domain.service.OnionarcSeaDomainService[onionarc_domain_service_onionarcSeaDomainService]).
2024-10-21 16:12:47,417 [main] DEBUG (LaLogger@log():118) - Registering component definition of class(org.docksidestage.app.biz.cleaneg.infrastructure.CleanegSeaInfraRepository[cleaneg_infrastructure_cleanegSeaInfraRepository]).
2024-10-21 16:12:47,557 [main] DEBUG (LaLogger@log():118) - Registering component definition of class(org.docksidestage.app.web.RootAction[rootAction]).
2024-10-21 16:12:47,581 [main] DEBUG (LaLogger@log():118) - Registering component definition of class(org.docksidestage.app.web.lido.auth.LidoAuthAction[lido_auth_lidoAuthAction]).
2024-10-21 16:12:47,595 [main] DEBUG (LaLogger@log():118) - Registering component definition of class(org.docksidestage.app.web.lido.product.LidoProductDetailAction[lido_product_lidoProductDetailAction]).
2024-10-21 16:12:47,599 [main] DEBUG (LaLogger@log():118) - Registering component definition of class(org.docksidestage.app.web.lido.product.price.LidoProductPriceAction[lido_product_price_lidoProductPriceAction]).
2024-10-21 16:12:47,613 [main] DEBUG (LaLogger@log():118) - Registering component definition of class(org.docksidestage.app.web.lido.product.LidoProductListAction[lido_product_lidoProductListAction]).
2024-10-21 16:12:47,615 [main] DEBUG (LaLogger@log():118) - Registering component definition of class(org.docksidestage.app.web.lido.mypage.LidoMypageAction[lido_mypage_lidoMypageAction]).
2024-10-21 16:12:47,627 [main] DEBUG (LaLogger@log():118) - Registering component definition of class(org.docksidestage.app.web.lido.following.LidoFollowingAction[lido_following_lidoFollowingAction]).
...

そもそも、Java8とJava21で、Rich Componentの初期化順序もちょい違う。 JSON Managerの初期化が、Primary Cipherの後だったり前だったり。

lastaflute_core.xmlの定義順で言うと、Java8の方が変、PrimaryCipherを追い抜いてJsonManagerが初期化されている。

    <component name="primaryCipher" class="org.lastaflute.core.security.SimplePrimaryCipher"/>
    <component name="timeManager" class="org.lastaflute.core.time.SimpleTimeManager"/>
    <component name="jsonManager" class="org.lastaflute.core.json.SimpleJsonManager"/>
jflute commented 1 month ago

fortressからlastaflute_core+timeManager.xmlを削除したら再現したー。 java21でもjava8でも同じく再現。

起動時のログで、lastaflute_core+timeManager.xmlの直後にcoolが読まれてるので怪しいと思って消してたら。

2024-10-21 16:32:06,243 [main] INFO  (LaContainerFactory@show():158) - ...Reading wx_fess_app.xml
2024-10-21 16:32:06,246 [main] INFO  (LaContainerFactory@show():158) - ...Reading   convention.xml
2024-10-21 16:32:06,249 [main] INFO  (LaContainerFactory@show():158) - ...Reading     embedded_convention.xml
2024-10-21 16:32:06,260 [main] INFO  (LaContainerFactory@show():158) - ...Reading   lastaflute.xml
2024-10-21 16:32:06,263 [main] INFO  (LaContainerFactory@show():158) - ...Reading     lastaflute_core.xml
2024-10-21 16:32:06,266 [main] INFO  (LaContainerFactory@show():158) - ...Reading       lastaflute_assist.xml
2024-10-21 16:32:06,269 [main] INFO  (LaContainerFactory@show():158) - ...Reading         lastaflute_director.xml
2024-10-21 16:32:06,304 [main] INFO  (LaContainerFactory@show():158) - ...Reading       lastaflute_core+timeManager.xml
2024-10-21 16:32:06,306 [main] INFO  (LaContainerFactory@show():158) - ...Reading       smart/cooldeploy-autoregister.xml
2024-10-21 16:32:06,310 [main] INFO  (LaContainerFactory@show():158) - ...Reading         convention.xml
2024-10-21 16:32:06,311 [main] INFO  (LaContainerFactory@show():158) - ...Reading           embedded_convention.xml
2024-10-21 16:32:06,314 [main] INFO  (LaContainerFactory@show():158) - ...Reading         creator.xml
2024-10-21 16:32:06,318 [main] INFO  (LaContainerFactory@show():158) - ...Reading           convention.xml (recycle)
2024-10-21 16:32:06,318 [main] INFO  (LaContainerFactory@show():158) - ...Reading           customizer.xml
2024-10-21 16:32:06,320 [main] INFO  (LaContainerFactory@show():158) - ...Reading             lastafw_customizer.xml
2024-10-21 16:32:06,329 [main] INFO  (LaContainerFactory@show():158) - ...Reading             embedded_customizer.xml
2024-10-21 16:32:06,331 [main] INFO  (LaContainerFactory@show():158) - ...Reading               tx_customizer.xml
2024-10-21 16:32:06,345 [main] INFO  (LaContainerFactory@show():158) - ...Reading           lastafw_creator.xml
...

lastaflute_core+timeManager.xmlを消すと、coolの読み込みが後になった。 つまり、lastaflute.xmlの初期化の時点ではまだ存在しないということになる。

2024-10-21 16:33:34,212 [main] INFO  (LaContainerFactory@show():158) - ...Reading wx_fess_app.xml
2024-10-21 16:33:34,216 [main] INFO  (LaContainerFactory@show():158) - ...Reading   convention.xml
2024-10-21 16:33:34,219 [main] INFO  (LaContainerFactory@show():158) - ...Reading     embedded_convention.xml
2024-10-21 16:33:34,233 [main] INFO  (LaContainerFactory@show():158) - ...Reading   lastaflute.xml
2024-10-21 16:33:34,235 [main] INFO  (LaContainerFactory@show():158) - ...Reading     lastaflute_core.xml
2024-10-21 16:33:34,238 [main] INFO  (LaContainerFactory@show():158) - ...Reading       lastaflute_assist.xml
2024-10-21 16:33:34,241 [main] INFO  (LaContainerFactory@show():158) - ...Reading         lastaflute_director.xml
2024-10-21 16:33:34,295 [main] INFO  (LaContainerFactory@show():158) - ...Reading     lastaflute_db.xml
2024-10-21 16:33:34,298 [main] INFO  (LaContainerFactory@show():158) - ...Reading       jta.xml
2024-10-21 16:33:34,314 [main] INFO  (LaContainerFactory@show():158) - ...Reading     lastaflute_web.xml
2024-10-21 16:33:34,320 [main] INFO  (LaContainerFactory@show():158) - ...Reading       lastaflute_core.xml (recycle)
2024-10-21 16:33:34,320 [main] INFO  (LaContainerFactory@show():158) - ...Reading       lastaflute_db.xml (recycle)
2024-10-21 16:33:34,320 [main] INFO  (LaContainerFactory@show():158) - ...Reading       convention.xml (recycle)
2024-10-21 16:33:34,351 [main] INFO  (LaContainerFactory@show():158) - ...Reading   tx_aop.xml
2024-10-21 16:33:34,353 [main] INFO  (LaContainerFactory@show():158) - ...Reading     jta.xml (recycle)
2024-10-21 16:33:34,355 [main] INFO  (LaContainerFactory@show():158) - ...Reading   trial_fess.xml
2024-10-21 16:33:34,359 [main] INFO  (LaContainerFactory@show():158) - ...Reading smart/cooldeploy-autoregister.xml
2024-10-21 16:33:34,361 [main] INFO  (LaContainerFactory@show():158) - ...Reading   convention.xml (recycle)
2024-10-21 16:33:34,361 [main] INFO  (LaContainerFactory@show():158) - ...Reading   creator.xml
...
jflute commented 1 month ago

なんとなく予感していたんだけど、そもそもrichの方が先でしょうから、richの中でquickを参照しても順番的に参照できないんじゃないかな?と。

DIコンテナとしてはlazyに初期化されるのでは?ってところもあるけど、 rich to quickはそもそもやらない参照なので、そこは考慮されていないんじゃなかろうか?

たまたまrichの途中で先にquickが初期化されても動いてるので、うまく制御すれば問題ないのかもだけど... そもそもやはりrichからquickの参照は推奨されることではないので、そこに依存しないような実装の方が良いかも。

例えば... o richにLoginPasswordEncryptorを作成 (PrimaryCipherのoneway()を使う) o FessLoginAssistにてencrytPassword()をオーバーライドしてLoginPasswordEncryptorを利用 o SearchEngineClientのopen()でもLoginPasswordEncryptorを利用 (辻褄を合わせる)

もしくは本来は、open()でやってることは、CurtainBeforeHookでやる方が良いのではないだろうか?

jflute commented 1 month ago

原因まとめ:

fortressにて、lastaflute_core+timeManager.xmlを削除すると再現した。 (java8でもjava21でも両方とも)

+timeManagerが存在する状態だと、+timeManagerのコンポーネント初期化が早いタイミングで実行され、 それにともなってかわからないが Quick Component (ActionやAssistたち) の初期化もそのタイミングで実行されていた。 ゆえに、その後の Rich Component 初期化ではすでにAssistが存在するのでgetComponent()できてきた。

fortress自体があまりオーソドックスではない状態になっていたので最初再現してなかったと言える。 どちらかというと... 「Rich Component の初期化時点ではまだ Quick Component は存在しない」 って方がオーソドックスと捉えることができるのでは?

これは、(試してないけど)恐らくSeasarでも同じなのかなと。

Quick Componentの初期化がRich Componentの途中に混じっても正常に動いていたので、 何かしら明示的な設定で最初に初期化するようにすれば良いのかも知れないが難しそう。

また、必要とされた時点でlazyに初期化していくようにできれば良いのかもだが、それも難しそう。 (richからrich, quickからquick, quickからrichでは既にそのようになっているが、richからquickはない)

そもそも、Rich ComponentからQuick Componentへの参照というのが非推奨なので、 それをOKにしちゃうような機能は設けたくないというところではある。 ただ現状では「たまたまうまくいっちゃう時がある」という感じになっている。

Fessで「とあるときから落ちるようになった」というのは、 恐らくDi xml回りで何かしらの構成変更があって、それでLasta Diの初期化タイミングに影響が出たのだと思われる。 それが何きっかけなのかは不明。

追記(2024/10/21): sugayaさんより

なるほど!それだったんですね。ありがとうございます。Fessのプラグインとして、別Jarファイルで+.xmlで追加していたのを、今回マージしてました…。
あちこち見直して修正するのは、すぐには厳しいので、元の+.xmlを戻して、ひとまず、回避しようと思います。
jflute commented 1 month ago

Fessでの回避の提案:

言葉の前提

Rich Component (rich) :: Di xmlに定義しているコンポーネント (appパッケージ以外のもの) Quick Component (quick) :: ActionやAssistなどappパッケージ配下で自動登録されるコンポーネント

提案1 // 手軽に修正パターン

open()の中では、LoginAssistを使うのではなくPrimaryCipherをDIしてoneway()を呼ぶようにする。 (この場合、richからrichになるので、getComponent()じゃなくて@Resourceで普通にDI)

TypicalLoginAssist@encryptPassword()と同じことをする、というイメージ。

@Override
public String encryptPassword(String plainPassword) {
    return primaryCipher.oneway(plainPassword);
}

メリット: rich から quick の参照が無くなる デメリット: 「ログインで使ってる暗号化方式」を使うというニュアンスが無くなり、辻褄合わせになる。 (万が一、FessLoginAssistでencryptPassword()をオーバーライドしてやり方を変えた場合に追従できない)

提案2 // 理想的なクラス構成だけどちょい面倒パターン

LoginPasswordCipherという Rich Component を新たに作成して、両方でそれを使う。 LoginPasswordCipherでは、primaryCipherのoneway()を使う。

FessLoginAssistでは、encryptPassword()をオーバーライドしてLoginPasswordCipherを使う。 SearchEngineClient@open()でも、LoginPasswordCipherを使う。

メリット: どちらもログイン用の暗号化処理を使うという統一ができている、LastaFluteとしては推奨の形 デメリット: 新しくクラスを作るのでちょこっと面倒

提案3 // もっと理想的なクラス構成だけどもっと面倒パターン

(Fessでやっている処理を完全に把握しているわけではないので、この案は本当にできるかは不明)

open()でやっている(一部の!?)ことを、CurtainBeforeHookに持っていく。 LastaFluteとしては、「コンポーネントの初期化」と「アプリとしての準備」は分けられるように、 CurtainBeforeHookという機能を設けている。

「DIコンテナとしてはすべて初期化が終わっている、かつ、Tomcatはまだ公開していない」というタイミング、 このタイミングで CurtainBeforeHook が呼ばれるので、DIコンテナのリソースをすべて使える状態でじっくり準備処理できる。

// FessCurtainBeforeHook.java https://github.com/codelibs/fess/blob/master/src/main/java/org/codelibs/fess/mylasta/direction/sponsor/FessCurtainBeforeHook.java

SearchEngineClient@insertBulkData()を、 「コンポーネントの初期化」というよりも「アプリとしての準備」と捉えることができるのであれば、 LastaFluteとしては、CurtainBeforeHookの方で実装するのが推奨ではある。

(ただ、別のコンポーネントがinsertBulkData()に依存してて処理の順番に依存があったりすると大変かも)

提案4 // 現状のままの辻褄合わせパターン

何かしらのきっかけで、Quick Component の初期化タイミングが変わっちゃったので、 まだ最初に初期化されるように Di xml を細工する。(+...xmlのダミーを作るとか)

でもこれは推奨しない。これから提案1や2の方が良いと思われる。

jflute commented 1 month ago

そもそものLasta Diの挙動分析:

o SingletonLaContainerFactoryにてLaContainerFactory@create()を呼び... o メインのapp.xmlの読み込みの前に lasta_di.xml の読み込みを行い... o lasta_di.xmlにて、smartdeploy.xmlがincludeされ... o smartdeploy.xmlにて、coolならcooldeploy.xmlがincludeされ... o cooldeploy.xmlにて、LaContainerFactoryCoolConfiguratorが登録され... o LaContainerFactoryにて、LaContainerFactoryCoolConfiguratorのconfigure()が呼ばれ... o LaContainerFactoryCoolProviderがproviderとして採用され... o create()が呼ばれ...先にsuper.create() (lasta_di.xmlの普通の読み込み) を呼んでから... o smart/cooldeploy-autoregister.xml がJava上でincludeされる → Quick Componentはrootのコンポーネントとしてincludeされるってのは、rootのコンテナーでincludeされるから

ただ、この時点では Di xml に include されただけで、まだ初期化されるわけじゃない。 Di xmlを読み込んでContainerインスタンスの構造が構築されただけ。 LaContainerImplのdescendantsにaddされただけ。

その後の順番は?最後になると思うけど。 だからこそ、オーソドックスなときは、すべてのRich Componentの初期化後にずらーっと初期化される。

o その後、app.xml (or test_app.xml) の読み込みが行われる。

同じくまだ初期化されているわけではない。

o もろもろ読み込んだ後で、初期化が行われる o 初期化の順番は読み込みの順番のはずだが... o Redeinerがあると、順序がちょっと変わると思われる

(続く: Redeiner回り)

jflute commented 1 month ago

Redefiner, 初期化のタイミングだと思ったら、Containerの構築で組み込まれている

1: LaContainerFactory@create()
2: LaContainerFactory@doCreate()
3: LaContainerProvider@create(path)
(LaContainerDefaultProvider@create(path))

LaContainerDefaultProvider@create()にて、

final LaContainer container = LdiStringUtil.isEmpty(path) ? new LaContainerImpl() : build(path, classLoader);

ということで、build()の方を経由して...getBuilder()でbuild()しておるが...

final LaContainer container = getBuilder(ext).build(realPath, classLoader);
...

protected LaContainerBuilder getBuilder(String ext) {
    final String componentName = ext + "Redefined";
    final LaContainer configurationContainer = LaContainerFactory.getConfigurationContainer();
    if (configurationContainer != null && configurationContainer.hasComponentDef(componentName)) {
        return (LaContainerBuilder) configurationContainer.getComponent(componentName);
    }
    return LaContainerFactory.getDefaultBuilder();
}

このbuilderが RedefinableXmlLaContainerBuilder になる。

xmlRddefinedという名前のコンポーネントは、redefiner.xmlにて定義されているが...

<components>
    <component name="xmlRedefined" class="org.lastaflute.di.redefiner.core.RedefinableXmlLaContainerBuilder" />
    <component class="org.lastaflute.di.redefiner.core.RedefinableResourceResolver" />
</components>

redefiner.xmlは、lasta_di.xmlにて固定でincludeされているので...

<components>
    <include path="redefiner.xml"/>
    <include path="smartdeploy.xml"/>
</components>

必ずこのRedefinableXmlLaContainerBuilderが使われる。

jflute commented 1 month ago
LaContainerBuilder // build()メソッドたちを定義するインターフェース
 ^
AbstractLaContainerBuilder // build(String path, ClassLoader classLoader)の基本実装
 ^
DiXmlLaContainerBuilder // build(String path)の基本実装、parse(LaContainer parent, String path)がメイン
 ^
RedefinableXmlLaContainerBuilder // ↑のparse(LaContainer parent, String path)などをオーバーライド

RedefinableXmlLaContainerBuilderのParser Override:

@Override
protected SaxHandlerParser createSaxHandlerParser(LaContainer parent, String path) {
    final SaxHandlerParser parser = super.createSaxHandlerParser(parent, path);
    final TagHandlerContext context = parser.getSaxHandler().getTagHandlerContext();
    context.addParameter(PARAMETER_BUILDER, this);
    context.addParameter(PARAMETER_BASEPATH, getCurrentBasePath());
    return parser;
}

@Override
protected LaContainer parse(LaContainer parent, String path) {
    pushPath(path);
    try {
        final LaContainer container = super.parse(parent, path); // contains e.g. jta+.xml, jta+userTransaction.xml handling
        mergeContainers(container, path, true); // fixedly tail e.g. jta++.xml
        return container;
    } finally {
        popPath(path);
    }
}

...

protected void mergeContainers(LaContainer container, String path, boolean addToTail) {
    final Set<URL> additionalURLSet = gatherAdditionalDiconURLs(path, addToTail);
    for (Iterator<URL> itr = additionalURLSet.iterator(); itr.hasNext();) {
        final String url = itr.next().toExternalForm();
        if (LaContainerBuilderUtils.resourceExists(url, this)) {
            LaContainerBuilderUtils.mergeContainer(container, LaContainerFactory.create(url));
        }
    }
}
jflute commented 1 month ago

とにかく、RedefinableXmlLaContainerBuilder@mergeContainers()がキーポイントっぽい。 素のContainerインスタンスに対して、追加のDi xmlをマージしている。

protected void mergeContainers(LaContainer container, String path, boolean addToTail) {
    final Set<URL> additionalURLSet = gatherAdditionalDiconURLs(path, addToTail);
    for (Iterator<URL> itr = additionalURLSet.iterator(); itr.hasNext();) {
        final String url = itr.next().toExternalForm();
        if (LaContainerBuilderUtils.resourceExists(url, this)) {
            LaContainerBuilderUtils.mergeContainer(container, LaContainerFactory.create(url));
        }
    }
}

LaContainerBuilderUtils@mergeContainer():

public static void mergeContainer(LaContainer container, LaContainer merged) {
    int size = merged.getChildSize();
    for (int i = 0; i < size; i++) {
        container.include(merged.getChild(i));
    }

    size = merged.getMetaDefSize();
    for (int i = 0; i < size; i++) {
        MetaDef metaDef = merged.getMetaDef(i);
        metaDef.setContainer(container);
        container.addMetaDef(metaDef);
    }

    // cannot override, always add
    size = merged.getComponentDefSize();
    for (int i = 0; i < size; i++) {
        ComponentDef componentDef = merged.getComponentDef(i);
        componentDef.setContainer(container);
        container.register(componentDef);
    }
}

これは難しい...

jflute commented 1 month ago

追いやすいようにコメントを色々と追加 https://github.com/lastaflute/lasta-di/commit/a6826190e6d2b75b3d96196550a61477f8f2c8a6

jflute commented 1 month ago

redefinerの処理が入ることで、なぜ cooldeploy のコンポーネントたちが先に初期化されるのか? がまだわかっていない。

起動ログを見る限り、結構唐突に始まるんだよね。 lastaflute_core.xmlの中の処理としてcooldeploy-autoregister.xmlが読まれるようになる。

2024-10-21 16:32:06,243 [main] INFO  (LaContainerFactory@show():158) - ...Reading wx_fess_app.xml
2024-10-21 16:32:06,246 [main] INFO  (LaContainerFactory@show():158) - ...Reading   convention.xml
2024-10-21 16:32:06,249 [main] INFO  (LaContainerFactory@show():158) - ...Reading     embedded_convention.xml
2024-10-21 16:32:06,260 [main] INFO  (LaContainerFactory@show():158) - ...Reading   lastaflute.xml
2024-10-21 16:32:06,263 [main] INFO  (LaContainerFactory@show():158) - ...Reading     lastaflute_core.xml
2024-10-21 16:32:06,266 [main] INFO  (LaContainerFactory@show():158) - ...Reading       lastaflute_assist.xml
2024-10-21 16:32:06,269 [main] INFO  (LaContainerFactory@show():158) - ...Reading         lastaflute_director.xml
2024-10-21 16:32:06,304 [main] INFO  (LaContainerFactory@show():158) - ...Reading       lastaflute_core+timeManager.xml
2024-10-21 16:32:06,306 [main] INFO  (LaContainerFactory@show():158) - ...Reading       smart/cooldeploy-autoregister.xml
2024-10-21 16:32:06,310 [main] INFO  (LaContainerFactory@show():158) - ...Reading         convention.xml
2024-10-21 16:32:06,311 [main] INFO  (LaContainerFactory@show():158) - ...Reading           embedded_convention.xml
2024-10-21 16:32:06,314 [main] INFO  (LaContainerFactory@show():158) - ...Reading         creator.xml
2024-10-21 16:32:06,318 [main] INFO  (LaContainerFactory@show():158) - ...Reading           convention.xml (recycle)
2024-10-21 16:32:06,318 [main] INFO  (LaContainerFactory@show():158) - ...Reading           customizer.xml
2024-10-21 16:32:06,320 [main] INFO  (LaContainerFactory@show():158) - ...Reading             lastafw_customizer.xml
2024-10-21 16:32:06,329 [main] INFO  (LaContainerFactory@show():158) - ...Reading             embedded_customizer.xml
2024-10-21 16:32:06,331 [main] INFO  (LaContainerFactory@show():158) - ...Reading               tx_customizer.xml
2024-10-21 16:32:06,345 [main] INFO  (LaContainerFactory@show():158) - ...Reading           lastafw_creator.xml
...
jflute commented 1 month ago

redefinerが、追加のcontainerなどで LaContainerFactory@create() を呼んでいる。

だが、LaContainerFactory@create()は本来はroot containerを読み込むためのもののはず。 coolのときは、読み込みの最後でcoolのcontainerがincludeされるようになっているが、 途中でredefiner経由で呼ばれると、redefinerの追加的containerからincludeされることになる。

それで所属のcontainerが変わって、初期化タイミングが変わってしまっているのだと思われる。 このままでいいのか?coolのコンポーネントたちは最終的にどのcontainerの所属になるのか?

別途チケットを起票: cool reading timing is varying by redefiner's addiitional container #45