Martin Tibenský
30.5.2011

Custom Spring Namespace + Embedded Groovy pre Business DSL



Spring umožňuje pre svoju xml konfiguráciu okrem hromady dodávaných namespacov (jdbc, context, tx…) zaregistrovať aj vlastný namespace handler a tým používať custom xml tagy pre vytváranie bean, prípadne iné zasahovanie do contextu. V AspectWorks to používame napríklad pre konfiguráciu validátorov atp. Sila tohoto prístupu je však omnoho väčšia, v dnešnom článku si ukážeme malý príklad z praxe.

Boilerplate kód pre vlastný spring namespace pozostáva zo súborov META-INF/spring.handlers

http://www.company.com/schema/ourSchema=com.company.OurNamespaceHandler

 a META-INF/spring.schemas

http://www.company.com/schema/ourSchema.xsd=META-INF/schema/ourSchema.xsd

 V súbore META-INF/schema/ourSchema.xsd definujme štandardným spôsobom XML schému:

<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns="http://www.company.com/schema/ourSchema" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:beans="http://www.springframework.org/schema/beans" targetNamespace="http://www.hypotecnibanka.cz/schema/scoreProc" elementFormDefault="qualified" attributeFormDefault="unqualified">
<xsd:import namespace="http://www.springframework.org/schema/beans" />
<xsd:element name="expr">
  <xsd:complexType>
    <xsd:simpleContent>
      <xsd:extension base="xsd:string">
        <xsd:attribute name="result" use="required" type="xsd:token" />
        <xsd:attribute name="id" type="xsd:ID" />
        <xsd:attribute name="name" type="xsd:string" />
      </xsd:extension>
    </xsd:simpleContent>
  </xsd:complexType>
</xsd:element>
<xsd:element name="foo">
  <xsd:complexType>
    <xsd:sequence minOccurs="1" maxOccurs="1">
      <xsd:element ref="expr" minOccurs="1" maxOccurs="unbounded" />
    </xsd:sequence>
  </xsd:complexType>
</xsd:element> 

V jave je potrebné implementovať dva interfacei: NamespaceHandler a BeanDefinitionParser. Spring framework nám samozrejme poskytuje potrebné abstraktné implementácie, ktoré je jednoduché upraviť:

public class OurNamespaceHandler extends NamespaceHandlerSupport {
  public void init() {
    ScoreBeanDefinitionParser parser = new OurBeanDefinitionParser();
    registerBeanDefinitionParser("foo", parser);
  }
}

Väčšinu práce vykonáva BeanDefinitionParser:

public class ScoreBeanDefinitionParser extends AbstractBeanDefinitionParser {
  @Override 
  protected AbstractBeanDefinition parseInternal(Element scoring, ParserContext parserContext) {
    BeanDefinitionBuilder factory = BeanDefinitionBuilder.genericBeanDefinition(FooServiceImpl.class);
    factory.addPropertyValue("exprs", processExprs(DomUtils.getChildElementsByTagName(scoring, "expr")));
    
    return factory.getBeanDefinition();
  } 
  
  private ManagedList<BeanDefinition> processExpressions(List<Element> expressions) {
    ManagedList<BeanDefinition> ret = new ManagedList<BeanDefinition>(expressions.size());
    for (Element element : expressions) {
      BeanDefinitionBuilder factory = BeanDefinitionBuilder.genericBeanDefinition(ExpressionProcessor.class);
      factory.addPropertyValue("expression", element.getTextContent());
      factory.addPropertyValue("result", CredScore.valueOf(element.getAttribute("result")));
      ret.add(factory.getBeanDefinition());
    } 
    
    return ret; 
}

Z kódu je zrejmé, že Spring kontajner vytvorí beany dvoch tried, FooServiceImpl a ExpressionProcessor. Implementácii ExpressionProcessora sa bude venovať druhá časť článku. Pre samotné vyhodnocovanie expressionov môže dobre poslúžiť akýkoľvek expression language (SPEL, MVEL, …). V našom konkrétnom prípade použijeme groovy, keďže má k expression jazykom blízko ale jeho syntax umožňuje silnejiše vyjadrovanie. ExpressionProcessor v základe implementujeme:

public class ExpressionProcessor {
  private String result;
  private String expr;
  
  public String getResult() {
    GroovyShell groovy = new GroovyShell();
    //priklad nastavenia premennej
    groovy.setVariable("foo", "bar");
    Boolean eval = (Boolean) groovy.evaluate(expr);
    if (eval) { return result; }
    log.debug("Evaluated to false, returning null");
    
    return null; 
  }
}

 V príklade je viditeľné využitie embedded Groovy, pomocou GroovyShell, nastavenie premennej do kontextu a vyhodnotenie expressionu. DSL jazyk v celej svojej paráde potom môže vyzerať nasledovne:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:proc="http://www.company.com/schema/ourSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.company.com/schema/ourSchema http://www.company.com/schema/scoreProc/ourSchema.xsd">
  <proc:foo>
    <proc:expr result="L">foo == bar</proc:expr>
    <proc:expr result="M">foo != bar</proc:expr>
  </proc:foo>
</beans>

Toto je už napríklad pre business analytika pochopiteľný formát a jednoducho do neho pridá ďalšie pravidlá atď. Oproti vytváraniu DSL napríklad v čistom Groovy (postup je popísaný napríklad v knihe Groovy for Domain-Specific Languages) má tento postup výhodu v tom, že je relatívne nenáročný najmä pre vývojára, ktorý je Spring zručný a možnosť nasadiť ľubovoľný expression language zvyšuje flexibilitu.

Vaše emailová adresa nebude zveřejněna

Komentáře

Děkujeme za váš komentář
Další
  • X

    Mě se více osvědčilo použití dekorátoru. Tedy standardní bean definice (s výhodami abstrakce, identifikace, atd. ) a definované atributy na které reagoval místo BeanDefinitionParser, ale BeanDefinitionDecorator. Dostanu už inicializovanou beanu a jen doplním to co potřebuji. Je ale pravda, že jsem tím řešil složitou konfiguraci a ne DSL.

  • Josef

    Good one !