Domů > Java > Jak si JBPM rozumí se Springem

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:

  1. 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.
  2. 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říklad itplace.jbpm.SendEmailActionHandler, pak budeme ve spring kontextu hledat bean s id sendEmailActionHandler – 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.

Zdroje:

Jan Šmuk Java , ,

  1. Bez komentářů.
  1. Žádné zpětné odkazy
Musíte být přihlášen k poslání komentáře.