Spring managed Hibernate interceptor in JPA

I have been trying to teach Hibernate injecting dependencies into Entities (I know, there is magic @Configurable annotation, I wanted to try it without magic). It is quite easy to do it using Hibernate interceptor (for example like this). But there is one drawback. It is not straightforward to inject interceptor into Hibernate when JPA abstraction is in the way.

It is simple to define interceptor in persistence.xml using hibernate.ejb.interceptor property. But it is only possible to specify class name, you can not inject Spring bean. If you read documentation to Spring LocalContainerEntityManagerFactoryBean there is no possibility to specify the interceptor bean there neither. But there is a way how to achieve it. First of all, you have to redefine Hibernate PersistenceProvider.

public class ConfigurableHibernatePersistence extends HibernatePersistence {
	private Interceptor interceptor;
	public Interceptor getInterceptor() {
		return interceptor;
	}

	public void setInterceptor(Interceptor interceptor) {
		this.interceptor = interceptor;
	}

	@SuppressWarnings("unchecked")
	@Override
	public EntityManagerFactory createContainerEntityManagerFactory(PersistenceUnitInfo info, Map map) {
		Ejb3Configuration cfg = new Ejb3Configuration();
		Ejb3Configuration configured = cfg.configure( info, map );
		postprocessConfiguration(info, map, configured);
		return configured != null ? configured.buildEntityManagerFactory() : null;
	}

	@SuppressWarnings("unchecked")
	protected void postprocessConfiguration(PersistenceUnitInfo info, Map map, Ejb3Configuration configured) {
		if (this.interceptor != null)
		{
			if (configured.getInterceptor()==null || EmptyInterceptor.class.equals(configured.getInterceptor().getClass()))
			{
				configured.setInterceptor(this.interceptor);
			}
			else
			{
				throw new IllegalStateException("Hibernate interceptor already set in persistence.xml ("+configured.getInterceptor()+")");
			}
		}
	}
}

(source)

Here we override method createContainerEntityManagerFactory in order to add postprocessConfiguration method call. In this method, it is possible to change Hibernate configuration as needed. Now the only thing to be done is configuring Spring to use our new PersistenceProvider. It is quite simple.

	<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
		<property name="persistenceUnitName" value="testPU" />
		<property name="dataSource" ref="dataSource" />
		<property name="persistenceProvider">
			<bean class="net.krecan.javacrumbs.jpa.interceptor.ConfigurableHibernatePersistence">
				<property name="interceptor">
					<bean class="net.krecan.javacrumbs.jpa.interceptor.SpringInjectingInterceptor"/>
				</property>
			</bean>
		</property>
	</bean>

And that all folks, we have Spring configured interceptor even when using JPA abstraction. It would be better if similar classes were in Spring or Hibernate, but it’s just few lines of code so feel free to copy them if you need to. If you know easier way how to do it, please mention it in the comments below.

Skrytý drahokam Springu

Dnes jsem koukal na rozhovor s Rodem Johnsonem, v kterém mluvil o novinkách ve Springu 3.0. Mezi řečí se zmínil o anotaci @Configurable, která je už ve Springu 2.5 a označil ji za skrytý drahokam. Protože jsem o něčem takovém slyšel poprvé, tak jsem se podíval do dokumentace a docela mě to zaujalo. (Teda, teď koukám, že jsem se o něčem podobném zmiňoval už před půl rokem zde, ach ta paměť). V zásadě jde o to, že obvykle funguje dependency injection jen u bean, které spravuje Spring. Takže například nejde použít u JPA entit a podobně. Což je docela škoda, protože pak musíme dělat umělou servisní vrstvu a objektový model tím docela trpí. Máme na jednu stranu třídy, které drží stav (entity) a na druhou stranu servisní třídy, v kterých je jen kód a stavu mají pomálu. Ale ve správném objektovém programování bychom měli mít třídy, které kombinují oboje. Takže například objekt User by měl mít metodu remove(), která by ho smazala z databáze. Jenže to by znamenalo, že by každá instance této třídy musela mít v sobě odkaz na EntityManager nebo ještě lépe DAO, což neumíme injektnout.

Možná si říkáte, že jsem se zbláznil, že to je ošklivé. Musím se přiznat, že mi je tento přístup taky cizí, ale asi je to jen otázka zvyku. Nejspíš jsem jen zvyklý na to procedurální programování, kde mám servisní procedury v singletonu a jim předhazuji hloupé datové objekty, které se o sebe neumějí postarat samy.

Anotace @Configurable je tu právě od toho, aby nám usnadnila injektování i do bean, které nejsou spravovány Springem. Předvedu na příkladu. Mám třídu Bean, které se dá podstrčit nějaký text.

@Configurable
public class Bean {
	private String text;
	public String getText() {
		return text;
	}
	public void setText(String text) {
		this.text = text;
	}
}

Mým cílem je zprovoznit tento test.

	public void testIt()
	{
		Bean bean = new Bean();
		assertEquals("Hallo",bean.getText());
	}

Všimněte si, že vytvářím novou instanci a koukám se, co je v ní nastaveno za text. Podle všeho by tam mělo být null. Ale pokud správně poladím Spring, tak tam může být cokoliv.

Prvním krokem je samozřejmě anotace @Configurable, která označí beanu jako kandidáta na injektování. V druhém kroku musím nastavit, co chci injektnout. Mohu například zvolit klasickou XML konfiguraci.

	<bean class="net.krecan.configurable.Bean" scope="prototype">
		<property name="text" value="Hallo"/>
	</bean>

	<context:spring-configured/>
	
	<context:load-time-weaver/

Zde si všimněme několika věcí. Například toho, že Spring pozná to kam má co injektnout automaticky, podle jména třídy. Nicméně dají se použít i vlastní jména. Pak si všimněme použití rozsahu „protoype“. To dává smysl, já mohu new zavolat kolikrát chci (i když tam to protoype nedáte, tak se to chová stejně, takhle je to jen hezčí). No a pak už nám zbývá jen magie. Pomocí prvního kouzelného slůvka <context:spring-configured/> zařídíme, aby se použil AspectJ aspekt, který to všechno zařídí. Ano, musí se použít AspectJ, zázraky ani Spring nedovede. Zatím.

Druhé kouzelné slůvko <context:load-time-weaver/> nastaví AspectJ, tak aby si podle potřeby při startu aplikace upravil třídy. Aby to totiž všechno fungovalo, tak je potřeba upravit bytecode. A to buď při kompilaci nebo při startu aplikace. Já jsem zvolil druhou variantu, která bohužel také vyžaduje, abyste to celé spouštěli s atributem virtuálního stroje -javaagent:spring-agent-2.5.6.jar. Pokud si kód budete zkoušet sami, tak na to prosím nezapomeňte.

A to je vše, testy projdou bez problému. Vidíme, že Spring umí injektovat i do tříd, nad kterými na první pohled nemá kontrolu. A to aniž bychom nějak zvlášť museli študovat AspectJ. Všechno už máme pěkně připraveno. Samozřejmě, uvedený příklad je umělý, ale pokud bychom našli dost odvahy, tak můžeme bez problémů injektnout EntityManager přímo do entit. A to může být docela užitečné.

Zdrojový kód je ke stažení zde, obzvláště doporučuji extra zvrhlou třídu MagicBean. No a pokud chcete umět Spring tak skvěle jako já, tak se mi ozvěte, můžu vás to naučit :-).

WebBeans

Včera jsem si četl dokumentaci k WebBeans. A ač opravdu nemám rád EJB a i k anotacím se stavím dost rezervovaně, docela mě to zaujalo. Je tam pár zajímavých nápadů, které mi docela rozšířily obzory.

Ale abych začal od začátku. WebBeans jsou projekt, který se s největší pravděpodobností dostane do Java EE 6 jako JSR-299. Ideově to celé vychází ze Seamu, ostatně dané JSR vede Gavin King.

Nebudu se zabývat úvodem, radši se podívejte do originální dokumentace nebo sem (česky). Radši napíšu jen co mě zaujalo. No nejvíc asi to, že se díky WebBeans mohou stát EJB i JSF snadněji použitelnějšími. Stejně jako Seam propojují obě technologie, což se může docela hodit. Navíc podporují injektování i normálních POJO, což je taky velký skok vpřed. Nicméně to vypadá, že se EJB nezbavíme, budeme je muset použít pro deklarativní transakce a podobně. Ale proč ne, ve verzi 3.1 vypadají docela snesitelně.

Vlastní anotace
Z technického hlediska mě zaujala míra použití anotací. Z dokumentace to vypadá, že se každý problém dá vyřešit napsáním si vlastní anotace. Doposud jsem anotace spíš jen pasivně používal než je vytvářel. Ale ve WebBeans bude tvorba vlastních anotací na denním pořádku. Uvedu příklad. Představte si, že z nějakého zvrhlého důvodu potřebujete injektovat do bean náhodné číslo. První co potřebujete udělat, je nějaká továrna. Ve WebBeans vyrobíte normální POJO a k metodě přidáte @Produces. Tím frameworku řeknete, že to je továrna na inty. No ale int není zrovna moc konkrétní typ a je velká pravděpodobnost, že se vám v aplikaci objeví továren na inty víc, takže je musíte nějak rozlišit. Tak si napíšete vlastní anotaci @Random.

@ApplicationScoped
public class Generator {

    private Random random = new Random( System.currentTimeMillis() );
    
    @Produces @Random int next() {
        return random.nextInt(100);
    }

}

Když pak chcete injektnout náhodné číslo někam jinam, tak jen použijete tu samou anotaci.

@Random int randomNumber

Není to nic světoborného, Spring to umí taky, ale tady je to primární způsob konfigurace. Navíc můžete nad deklaraci beany hodit anotaci, že je jen pro testovací prostředí, nebo pro zákazníka z Kanady a framework se vám postará o to, aby se použily pro dané prostředí vhodné implementace.

InjectionPoint
Další zajímavá věc je InjectionPoint. Představte si následující kód.

@Current Logger log;

Tím říkám, že chci injektnout logger. Obvykle ale potřebujeme mít v každé třídě Logger s jinou kategorií (jménem). Ve WebBeans uděláte toto.

class LogFactory {

   @Produces Logger createLogger(InjectionPoint injectionPoint) { 
      return Logger.getLogger(injectionPoint.getMember().getDeclaringClass().getName()); 
   }

}

InjectionPoint vám poskytne informace o tom, kam se vytvářená instance injektuje. Musím se přiznat, že jsem se pár hodin pokoušel udělat to samé ve Springu. Nakonec se mi za cenu neuvěřitelných prasáren podařilo, ale hezké to vůbec nebylo.

Dekorátor
Zajímavým nápadem jsou dekorátory. Je to něco jako interceptor, akorát typově kontrolovaný. Nejlépe to zas uvidíte na příkladu. V něm mám rozhraní Account, nějakou jako implementaci nebo dokonce implementace a já chci přidat logování operací větších než nějaká částka. Slušní lidé by si napsali normální wrapper. Pokud jste ale líní, tak můžete použít dekorátor a WebBeans se za vás postarají o ty nudné činnosti.

@Decorator
public abstract class LargeTransactionDecorator 
        implements Account {
    
    @Decorates Account account;
    
    @PersistenceContext EntityManager em;
    
    public void withdraw(BigDecimal amount) {
        account.withdraw(amount);
        if ( amount.compareTo(LARGE_AMOUNT)>0 ) {
            em.persist( new LoggedWithdrawl(amount) );
        }
    }
    
    public void deposit(BigDecimal amount);
        account.deposit(amount);
        if ( amount.compareTo(LARGE_AMOUNT)>0 ) {
            em.persist( new LoggedDeposit(amount) );
        }
    }
    
}

Všimněte si prosím dvou věcí. První je, že je třída abstraktní, takže nemusím implementovat ty metody, které mě nezajímají. Druhá věc je injektnutí obalované instance pomocí anotace @Decorates. Pozor, dekorátor se nedá použít jako náhrada za AOP, jak jsem původně myslel. K tomu aby se to dalo použít, potřebujete rozhraní, které budete implementovat, takže třeba na transakce je to nepoužitelné. Ale nápad je to zajímavý.

Abych to shrnul. Pořád si myslím, že pro složitější konfiguraci je XML lepší. Mám pro to i jeden argument. V XML mohu mít víc instancí stejné třídy s různou konfigurací. To se může často hodit. U anotací platí vztah jedna třída jedna konfigurace. Nicméně to vypadá, že jsem se svým názorem v menšině, takže se s anotacemi musím asi smířit. Rozhodně se vyplatí si alespoň přečíst tu dokumentaci, něco se třeba naučíte. Čte se docela dobře a je i rozumně krátká. Z mého pohledu jsou WebBeans framework, který by teoreticky, za velmi nepravděpodobných podmínek, při vhodné konstelaci planet, až se mi to skoro nechce ani napsat, mohl na čas nabídnou funkce, které vám Spring nenabídne. Tím se oproti ostatním módním frameworkům docela liší.

Update (10.10.2009): Ještě link na zajímavý článek