5. října 2007

AOP proxy objekty - volání metody v rámci jedné třídy

Dlouho jsem přemýšlel nad nějakým výstižným názvem tohoto příspěvku, ale nic moc jsem nevymyslel. Zkusím tedy popsat o co přesně jde.
Mám třídu, která má dvě metody. Obě metody se mají pouštět v transakci, každá metoda může nebo nemusí mít svoje vlastní nastavení transakční definice. Pro účely ukázky jsem si vymyslel následující příklad:

public interface DocumentFacade {
/**
* Metoda pridava vice dokumentu.
*/
public DocumentIdsFacadeResult addDocuments(DocumentsDTO doc);

/**
* Metoda pridava pouze jeden dokument, tato metoda je volana metodou addDocuments().
*/
public DocumentIdsFacadeResult addDocument(DocumentsDTO doc);
}

<bean id="DbTransactionInterceptor"
class="org.springframework.transaction.interceptor.TransactionInterceptor">
<property name="transactionManager" ref="dbTransactionManager" />
<property name="transactionAttributes">
<props>
<prop key="add*">PROPAGATION_REQUIRED,-Exception</prop>
</props>
</property>
</bean>

<bean id="serviceProxyCreator"
class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<property name="beanNames">
<list>
<idref bean="documentFacade" />
</list>
</property>
<property name="interceptorNames">
<list>
<idref bean="DbTransactionInterceptor" />
</list>
</property>
</bean>

Z požadavků zákazníka vyplývá následující chování - pokud se bude vkládat více dokumentů, tak výsledek každého vložení dokumentu bude nezávislý (atomický) od ostatních vložení dokumentů. Když tedy budu přidávat dva dokumenty a během prvního se objeví nějaká chyba ve zpracování, tak vložení druhého dokumentu to nemůže ovlivnit. Jinak řečeno každé volání metody addDocument() bude ve své vlastní transakci.

Pro méně zkušené uživatele AOP (mezi ně se řadím i já) se může zdát, že předcházející požadavek je automaticky vyřešen nastavením transakcí - obě metody odpovídají nastaveným transakčním atributům (viz bean serviceProxyCreator). A zde je právě kámen úrazu. Pokud bychom volali jednotlivé metody nezávisle (např. z kontroleru), tak vše bude chodit bez problémů, ale vzhledem k tomu, že jedna metoda je volána z druhé metody stejné třídy resp. objektu, tak nedochází ke vstupu do objektu přes proxy (jsme pořád v tom samém objektu) a tudíž se neaktivuje transakční nastavení pro druhou metodu. Pro náš příklad to znamená, že vše se provede pouze v jedné transakci definované pro první metodu, tedy addDocuments().
Toto samozřejmě není problém jen transakcí, ale obecně všech AOP proxy objektů.

Jaká jsou možná řešení? Z dostupných informací jsem našel následující řešení:
  • Refaktorovat strukturu volání, tak aby nedocházelo k volání v rámci jedné třídy. Toto řešení se může nakonec ukázat jako nejvíce použitelné pro případy, které nelze vyřešit následujícím řešením.

  • Použít programové transakční vymezení pomocí TransactionTemplate. Toto je celkem elegantní řešení pokud nechceme "uměle" refaktorovat kód. Problém může ovšem být v případě, kdy potřebujeme nastavit transakci přes více zdrojů (např. databáze a JCR). Pro tento případ je pak nutné použít JtaTransactionManager.
    Možná by šla ještě vnořená implementace TransactionTemplate, ale toto jsem nezkoušel. Velká výhoda nastavení transakcí ve Springu je ta, že infrastrukturní věci jako transakce mohu definovat deklarativně a mimo vlastní kód - zde tyto výhody ztrácím.

  • Programově se dostat k proxy objektu a uměle nasimulovat volání metody z vně objektu. Přesně tomuto řešení nerozumím (více informací je možné nalézt zde), ale každopádně je to už řešení, které je pevně spojeno se Springem a vlastním kódem.


Já osobně jsem zatím vždy použil druhý způsob.

2 komentáře:

Anonymní řekl(a)...

TransactionTemplate nám nezabraňuje využívat deklarativní transakce Springu. Toto jsem nikde nevyčetl, ale zkusmo použil na jednom projektu, kde mi to úspěšně funguje.

Začal jsem nejdřív implementovat transakce pomocí TransactionTemplate. Pak jsem ovšem potřeboval vytvořit transakci (ach to změnové řízení ;)) nad dvěma menšími transakcemi.

Zkusil jsem zapojit AOP a zastřešit oboje volání metod jednou deklarativně definovanou transakcí a voila, ono to fungovalo.

Petr Jůza řekl(a)...

Já jsem to možná napsal nepřesně, ale myslel jsem to tak, že když použiji programové řízení transakcí, tak tím ztrácím ty výhody, které bych měl, kdybych to dělal deklarativním způsobem.
Míchání programového a deklarativního způsobu jsem také zkoušel a šlo to.