Export veřejného rozhraní modulu

Export veřejného rozhraní modulu

Veřejné rozhranní modulu představují beany označené marker interfacem PublicInterface. Při startu modulu se ModuleContext / WebModuleContext postará o to, že se beany takto označené PublicInterfacem zaregistrují zároveň jako singletony root kontextu.

Tím pádem se stanou viditelné pro další moduly. Tím, že má každý modul vlastní aplikační kontext, je vyřešen problém konfliktů jmen bean - nicméně jakmile vybrané beany (veřejné rozhranní) opět slučujeme v root kontextu, je nutné na konflikty opět brát ohled.

Platí následující logika:

  1. beana veřejného rozhranní se do root kontextu registruje pod jménem „jmenoBeany” jen pokud pod tímto názvem ještě nebyla zaregistrována jiná beana
  2. pokud pod tímto jménem již existuje beana, zaregistruje se pouze pod názvem „jmenoModulu_jmenoBeany”, zároveň se pro původní beanu vytvoří ještě jedna registrace s rozšířeným názvem „jmenoModulu_jmenoBeany” pro její mateřský modul

Pokud tedy dva moduly konfigurované v sitemap nebudou mít konfliktní názvy bean, budeme moci s beanamy veřejného rozhraní pracovat prostřednictvím jejich „jednoduchých” jmen. Pokud ke konfliktu dojde, je třeba pracovat s rozšířenými jmény - respektive s konfliktní beanou modulu, který naběhne jako první můžeme pracovat i s pomocí jejího „jednoduchého” názvu.

Zkopírovat odkaz na sekciNapojení na rozhranní jiného modulu

Zbývá dořešit otázku jak se nejlépe napojit na rozhraní jiného modulu. Pokud váš modul závisí na nějakém externím rozhraní (třeba databázovém zdroji), je potřeba, abyste toto explicitně uvedli v API vašeho modulu implementaci metody getModuleDependencies(), ve které vrátíte seznam bean a jejich typů, na kterých závisíte. Závislost může být buď povinná - což znamená, že bez jejího splnění nebude modul vůbec funkční, nebo volitelná a to znamená, že modul bude funkční, ale nebude poskytovat některé své služby.

Ukázka implementace metody v případě závislosti na připojení do databáze (DataSource):

java
1 @Override2protected List<ExternalDependency> getDependencies(ModuleContext moduleConfiguration) {3    List<ExternalDependency> dependencies = super.getDependencies(moduleConfiguration);4    addDataSourceDependencyRequired(dependencies);5    return dependencies;6}

Pokud závisíte na nějakém jiném externím rozhraní je implementace podobná:

java
1 @Override2protected List<ExternalDependency> getDependencies(ModuleContext moduleConfiguration) {3    List<ExternalDependency> dependencies = super.getDependencies(moduleConfiguration);4    dependencies.add(5     new ExternalSpringBeanDependency("myLocalBean", MockPublicInterfaceBean.class, moduleConfiguration)6    );7    return dependencies;8}

Alternativně je možné závislosti uvést pomocí Java anotací:

java
1 @ExternalDependencies(2	required = {3		@RequiredExternalDependency(name = "somePublicInterface", type = MockPublicInterfaceBean.class)4	},5	optional = {6		@OptionalExternalDependency(name = "something", type = ServletConfig.class, whatWillNotWork = "Something")7	}8)9public class MyModule extends CpsSpringModuleAbstract {10	11}

V případě, že definovaná externí závislost je volitelná, na úrovni aplikační logiky je třeba přítomnost beany kontrolovat takto:

java
1 ExternalDependency.isMissing(myService)

beana je totiž obalená proxy i když cílová dependece neexistuje. Podmínka myService == null by tedy byla vždy false.

Pokud v systému existují jednoduché vazby a jednu konkrétní deklarovanou závislost splňuje pouze jediná beana z root kontextu, dojde automaticky ke správnému spárování (funguje správně i anotační autowiring Springu).

Pokud je situace složitější a není možné, aby systém správně identifikoval cílovou beanu veřejného rozhraní je nutné mu dodat mapování v následujícím formátu:

xml
1 <injectedResources>2    <connection>c.edee</connection>3    <connection as="customDataSource" txManager="customTransactionManager">c.jachym</connection>4    <externalPublicInterfaceBeanName as="localBeanName"/>5</injectedResources>

Pozor!!! Pokud uvedete v injectedResources pouze jedinou konekci, bude tato použita ve všech @Autowired polích a to i přesto, že neobsazuje výchozí názvy dataSource a transactionManager. Pokud chcete, aby v modulu byla zároveň k dispozici i defaultní konekce je nutné definovat v injectedResources i její mapování. Jinak je při autowirování preferována bean přímo injectovaná do kontextu modulu. Proto je v tomto příkladu uvedeno i <connection>c.edee</connection>.

Tímto zápisem provedeme registraci lokálních bean do aplikačního kontextu modulu, které odkazují na správné beany v root kontextu. Z výše uvedeného zápisu je patrné, že v root kontextu se pokusí nalézt beana s názvem externalPublicInterfaceBeanName a pokusí se zaregistrovat do aplikačním kontextu modulu pod aliasem localBeanName.

V případě mapování konekcí je zápis přizpůsoben tomu, jak s databázovými zdroji pracuje CPS. Framework se pokusí najít datasource s názvem c.jachym a registrovat jej jako interní beanu pod názvem customDataSource a zpřístupnit i transakční manažer pod názvem customTransactionManager. Pokud bychom chtěli ponechat "standardní" názvy (dataSource a transactionManager), stačí nám následující zápis:

xml
1 <injectedResources>2    <connection>c.jachym</connection>3</injectedResources>

Zkopírovat odkaz na sekciSouvislost závislostí mezi moduly a pořadím jejich nabíhání

Dříve bylo nutné pořadí modulů upravovat dle toho, jaké závislosti mezi sebou měly. Tato podmínka se ukázala časem jako neudržitelná a byl vytvořen mechanismus, který toto omezení řeší.

V případě, že modul závisí na nějaké beaně, která ještě v kontextu není dostupná, vytvoří modulární systém proxy, která splňuje požadovaný kontrakt třídy a tato proxy se použije při autowiringu daného modulu, který tuto závislost vyžaduje.

Jakmile se dokončí start všech modulů, začne systém řešit oživení prázdných proxy, které po cestě vytvořil a vkládá do nich delegáty odkazující se na reálné beany v kontextu, které provádějí moduly požadovanou logiku.

Pokud se nepodaří nalézt existující beanu, která požadovaný kontrakt splňuje, systém se zachová takto:

  1. v případě povinných závislostí (required) - vyhlásí a zaloguje chybu a zastaví spouštění CPS
  2. v případě nepovinných závislostí - zaloguje danou informaci a nechá CPS ožít, pokud by došlo k volání libovolné metody na vytvořených proxy, dojde k runtime vyjímce (BeansException) a modul se musí podle toho zachovat

Této funkcionality lze využít i v případě, že se deklarují závislosti až v konfiguraci sitemap.xml. Stačí do sekce injectedResources k dané beaně dodefinovat atribut type - např. takto:

xml
1 <injectedResources>2    <!-- verejna beana s nazvem "agreementService" je v modulu pouzita s nazvem dle "as" - proxy bude implementovat dany interface -->3    <agreementService as="agreementService" type="com.fg.gdpr.service.AgreementService"/>4    <!-- od CPS v8.4.0 je atribut "as" nepoviny pokud je hodnota stejna s bean name -->5    <agreementOpinionService type="com.fg.gdpr.service.AgreementOpinionService"/>6</injectedResources>

Tímto způsobem lze ovšem definovat pouze nepovinné závislosti modulu.

Zkopírovat odkaz na sekciKontrola splnění nepovinné závislosti

Pro nepovinné závislosti dojde k jejich autowiringu, ale při jejich použití pak funkce selže. Aby bylo možné v logice případně odlišit, že daná bean je neaktivní proxy, vznikla statická metoda:

com.fg.webapp.cps.v1.modules.ExternalDependency#isMissing