Jak si JBPM rozumí se Springem
V posledním článku Jak jsme použili JBPM jsem popsal, jak a proč jsme použili JBPM. Dnes se podělím o naše zkušenosti s integrací se Springem.
Spring a JBPM se v jednom z projektů, kde jsme oba použili, stýkají ve dvou místech:
- Konfigurace – můžeme například potřebovat, aby JBPM použil pro svůj běh již existující nakonfigurovanou hibernate session factory, kterou používá i zbytek naší aplikace.
- Spouštění akcí – chceme, aby akce byly součástí našeho spring kontextu.
Spring Modules
Začali jsme tím, že jsme použili springmodules-jbpm31, který již řeší oba výše zmíněné body. Takto může vypadat kousek definice spring kontextu:
...
<!-- jBPM configuration -->
<bean id="jbpmConfiguration"
class="org.springmodules.workflow.jbpm31.LocalJbpmConfigurationFactoryBean">
<property name="sessionFactory" ref="sessionFactory" />
<property name="configuration" value="classpath:jbpm.cfg.xml" />
<property name="createSchema" value="false" />
<property name="processDefinitionsResources">
<list>
<value>classpath:createArticle.xml</value>
<value>classpath:updateArticle.xml</value>
</list>
</property>
</bean>
...
Jak springmodules řeší druhý bod, tedy spouštění akcí? Podlě mě ne příliš šikovně. Představme si akci, která posílá uživateli email. Chceme použít Ioc k nastavení reference na objekt, který bude obstarávat posílání emailů. Springmodules toto řeší použitím bean proxy, které řekneme id našeho action handleru ve spring kontextu. Deklarace takové akce pak vypadá nějak takto:
... <action name="myAction" config-type="bean" class="org.springmodules.workflow.jbpm31.JbpmHandlerProxy"> <targetBean>sendEmailActionHandler</targetBean> <factoryKey>jbpmConfiguration</factoryKey> </action> ...
Všimněte si atributu config-type
. Pokud je jeho hodnota
bean
, pak JBPM při vykonávání akce vytváří novou instanci
třídy specifikované v atributu class
. Poté nastavuje této
instanci její vlastnosti na základě jmen a obsahů vnořených tagů. Tedy ve
výše uvedeném případě je vytvořena nová instance třídy
org.springmodules.workflow.jbpm31.JbpmHandlerProxy
a jsou na ni
zavolány metody setTargetBean("sendEmailActionHandler")
a
setFactoryKey("jbpmConfiguration")
, což jsou vlastnosti nutné
k fungování této proxy.
Malý trik
Zdá se vám tento způsob složitý? Mi se zdál složitý až zbytečně moc. Jednak takto sestavené akce hyzdí definici procesu. Dále jste si jistě všimli, že zde chybí definice těla posílaného emailu a adresa příjemce. Nenašel jsem lepší způsob, jak mít tyto dvě hodnoty přímo v definici procesu, než je ukládat jako procesní proměnné. Proto jsem hledal způsob, jak být schopen definovat akci ve stylu:
... <action name="myAction" config-type="bean" class="itplace.jbpm.SendEmailActionHandler"> <emailAddress>recipient@server.com</emailAddress> <emailBody>hello</factoryKey> </action> ...
Samotná akce pak bude vypadat takto:
package itplace.jbpm;
import org.jbpm.graph.def.ActionHandler;
import org.jbpm.graph.exe.ExecutionContext;
public class SendEmailActionHandler implements ActionHandler {
private EmailSender emailSender;
private String emailAddress;
private String emailBody;
public void execute(ExecutionContext executionContext) throws Exception {
emailSender.send(emailAddress, emailBody);
}
public void setEmailSender(EmailSender emailSender) {
this.emailSender = emailSender;
}
public void setEmailAddress(String emailAddress) {
this.emailAddress = emailAddress;
}
public void setEmailBody(String emailBody) {
this.emailBody= emailBody;
}
}
K identifikací našich akci ve spring kontextu jsme použili konvenci pojmenovávání:
- pokud je hodnota atributu
class
napříkladitplace.jbpm.SendEmailActionHandler
, pak budeme ve spring kontextu hledat bean s idsendEmailActionHandler
– tedy jednoduché jméno třídy s malým písmenkem na začátku. Definice naši akce ve spring kontextu tedy bude:
... <bean id="sendEmailActionHandler" class="itplace.jbpm.SendEmailActionHandler" singleton="true"> <property name="emailSender" ref="emailSender"/> </bean> ...
Nyní potřebujeme JBPM donutit, aby nevytvářelo novou instanci třídy
akce, ale aby se použil bean definovaný ve spring kontextu s identifikací
odpovídající konvenci. Poté už JBPM může standardně nastavit vlastnosti
emailAddress
a emailBody
.
JBPM si ve třídě Delegation
drží statickou mapu objektů
typu Instantiator. Instantiator je zodpovědný za vytváření instancí akcí.
Klíčem v této mapě je String
a JBPM v této mapě hledá
Instantiator podle obsahu atributu config-type
. Standardně je pod
klíčem bean
v mapě uložený BeanInstantiator. Mým cílem bylo
tento rozšířit.
Nejprve jsem si vytvořil třídu, která mi umožní obecně manipulovat s mapou instantiatorů:
package itplace.jbpm;
import java.util.Map;
import org.jbpm.instantiation.Delegation;
import org.jbpm.instantiation.Instantiator;
public class AdditionalInstantiatorsDeclaration extends Delegation {
public void setInstantiators(Map<String, Instantiator> instantiators) {
instantiatorCache.putAll(instantiators);
}
}
instantiatorCache
je ona zmíněná statická mapa, do které
nyní můžeme přidat libovolný instantiator pod libovolným jménem. Náš
nový instantiator vypadá nějak takto:
package itplace.jbpm;
import org.jbpm.instantiation.BeanInstantiator;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
public class SpringInstantiator extends BeanInstantiator
implements BeanFactoryAware {
private BeanFactory beanFactory;
protected Object newInstance(Class clazz) {
String beanName = clazz.getSimpleName();
beanName = beanName.substring(0, 1).toLowerCase()
+ beanName.substring(1);
if (beanFactory.containsBean(beanName)) {
return beanFactory.getBean(beanName);
}
return super.newInstance(clazz);
}
public void setBeanFactory(BeanFactory beanFactory) {
this.beanFactory = beanFactory;
}
}
SpringInstantiator
implementuje rozhraní
BeanFactoryAware
, takže se spring postará o vložení bean
factory. A pak jsme rozšířili standardní BeanInstantiator
a
změnili chování metody newInstance
, která nyní nejprve hledá
v bean factory bean podle konvence a pokud jej nenajde pouze vytvoří novou
instanci.
Do spring kontextu je tedy zapotřebí přidat následující:
...
<bean id="additionalInstantiatorsDeclaration"
class="itplace.jbpm.AdditionalInstantiatorsDeclaration">
<property name="instantiators">
<map>
<entry>
<key><value>bean</value></key>
<bean class="itplace.jbpm.SpringInstantiator" />
</entry>
</map>
</property>
</bean>
...
Je ještě vhodné říct springu, že JBPM je závislý na beanu
additionalInstantiatorsDeclaration
, stačí přidat
depends-on
atribut do JBPM konfigurace:
...
<!-- jBPM configuration -->
<bean id="jbpmConfiguration" depends-on="additionalInstantiatorsDeclaration"
class="org.springmodules.workflow.jbpm31.LocalJbpmConfigurationFactoryBean">
...
Závěr
Viděli jste, jak jednoduše lze JBPM integrovat se Springem. Také jste si možná udělali obrázek o zdařilosti a praktičnosti spring jbpm modulu. Způsob, který jsem vám předvedl, naprosto vyhovuje našim požadavkům a funguje opravdu velmi dobře.