Announcement Announcement Module
Collapse
No announcement yet.
programmatic configuration of the application context Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • programmatic configuration of the application context

    From time to time I read criticisms about the fact that Spring has to be configured through long xmls, and that there is no easy-to-use programmatic api. While I certainly don't hate xml configs, and I appreciate the approach Spring has taken for its xml configuration (a simple format, consistent through every layer and using common javabeans conventions) I sometimes realize that I'm suffering from the lack of refactoring and navigation capabilities on the config (I work with Intellij IDEA, I imagine that the eclipse IDE provides good answers to all of this).

    So I tried experimenting with a couple of ideas with regards to a programmatic configuration, and I sketched up a class that allows simpler programmatic config for Spring contexts: I'd like to know if there's other people that could be interested in this approach, that's why I'm talking about it here.

    What I have now is a class named
    Code:
    JavaRefreshableApplicationContext
    which is a subclass of
    Code:
    AbstractRefreshableApplicationContext
    . It's an abstract class, and users must subclass it in order to define a concrete application context: only one method has to be implemented, this one:

    Code:
    protected abstract void doLoadBeanDefinitions();
    What can be done in the implementation of the method is defining beans just as one normally does with the xml config. I have been experiencing with various options, all trying to obtain the following:

    1) not having to code too much
    2) being able to define an application context with clean and concise code
    3) possibly, being able to take advantage of modern ide features (auto-completion, documentation, refactoring, etc.)

    so in a normal
    Code:
    doLoadBeanDefinitions
    implementation one can currently use three different "styles" for bean definition:

    1) a "type-safe" one, with code such as this one:

    Code:
    MarriedWoman linda = ( MarriedWoman )bean( MarriedWoman.class );
    linda.setChildren( list().with( bean( NoisyChild.class ) )
                             .and( bean( QuietChild.class ) )
                             .and( bean( "Marcus", NoisyChild.class ) )
                     );
    linda.setName( "Linda" );
    
    MarriedMan john = ( MarriedMan )bean( "John", MarriedMan.class );
    john.setWife( linda );
    here we can see:
    • the method call bean( MarriedWoman.class ) which defines an anonymous bean (i.e.: with id automatically defined by the container) of class "MarriedWoman", and returns a typed instance of the bean definition.
    • The typed definition is assigned to an instance of the MarriedWoman class which is not a "real" instance: it is only used in order to define bean dependencies. For this reason, calls to its setter methods are not real calls to the MarriedWoman implementation, but result in bean dependencies and properties defined in the context
    • For this reason linda.setName( "Linda" ); is the equivalent of
      Code:
      <property name="name" value="Linda"/>
    • For this same reason john.setWife( linda ); defines a dependency between the bean with (Spring) name "John" and the bean with (java) name "linda". This is the equivalent of an anonymous inner bean set with the <property> tag.
    • The list() method is something I tried in order to reduce the amount of code one has to write while defining lists in cases like this one: it only returns a particular instance of a List defining also a couple of "with" and "and" methods which add the element to the list and return the list itself. I don't know if I really like this, but it surely seems more readable to me: one can just as well use ArrayList instances he or she prefers.
    • Since the objects returned by bean() calls are typed, one can program the application context while benefitting from IDEA auto-completion, documentation (both for methods provided by JavaRefreshableApplicationContext and methods provided by the business domain (or business services) MarriedWoman, MarriedMan, QuietChild, etc. classes).

    Overall, the previous code is the equivalent of the following:
    Code:
    <bean id="John" class="com.blahblahblah.blabla.MarriedMan">
        <property name="wife">
            <bean class="com.blahblahblah.blabla.MarriedWoman">
                <property name="name" value="Linda"/>
                <property name="children>
                    <list>
                        <bean class="com.blahblahblah.blabla.NoisyChild"/>
                        <bean class="com.blahblahblah.blabla.QUietChild"/>
                        <bean id="Marcus" class="com.blahblahblah.blabla.NoisyChild"/>
                    </list>
                </property>
            </bean>
        </property>
    </bean>
    This approach is based on programmatic use of AOP techniques: it's the first time in my life I use these kinds of tools, so my implementation is really rough, for now. In particular since this is based on Spring AOP it has it's same limitations with respect to final classes and so on: I also had a problem with acegi classes since they come from a signed jar, so I must better investigate in this area.

    2) a "general" (un-typed) one which is more similar to the xml approach, such as the following:

    Code:
    bean&#40; "linda", MarriedWoman.class &#41;
        .property&#40; "name", "Linda" &#41;
        .property&#40; "children", list&#40;&#41;.with&#40; NoisyChild.class &#41;
                                     .and&#40; QuietChild.class &#41; &#41;
        .property&#40; "husband", bean&#40; "John", MarriedMan.class &#41; &#41;
        .replaceMethod&#40; "prepareDinner", "cook" &#41;
        .initMethod&#40; "wakeUp" &#41;
        .lazyInit&#40; true &#41;
        .singleton&#40; false &#41;;
    This should be self-explanatory to anyone who has ever programmed a Spring context, and should give not too bad feelings about this technique. This does not provide type-safe properties and dependencies, but provides IDE autocompletion and documentation of the various property, initMethod and so on. It's java, and it's reasonably concise.

    The bean method returns an instance of an interface called SpringBean which provides methods for nearly all the features one can set on a bean in the normal Spring xml configuration except the "parent" one. This particular one can be set while using one of the variants of the bean() method, which leads us to the third style (which is the one that I like the least but must be used for those cases:

    3) an approach led by the polymorphic variants of the bean method.

    I introduced a number of bean(...) methods with different arguments in order to receive a bean definition instance which has already set a number of features:

    Code:
    SpringBean bean&#40; Class clazz &#41;;
    SpringBean bean&#40; String id, Class clazz &#41;;
    SpringBean bean&#40; String id, SpringBean parent &#41;;
    SpringBean bean&#40; String id, Class clazz, String initMethod, String destroyMethod &#41;;
    ...
    since this is java code, people can define application context subclasses which add specific configuration methods that succintly define beans of a particular class, or with particular sets of attributes pre-defined. As an example and in order to provide some practical tools, I have defined methods such as the following:

    Code:
    SpringBean abstractBean&#40; Class clazz &#41;;
    ...other "abstractBean" variants...
    SpringBean prototype&#40; Class clazz &#41;;
    ...other "prototype" variants...
    SpringBean autowired&#40; Class clazz &#41;;
    ...other "autowired" variants...
    // the following returns instances of SpringBean only and does not try to create a typed mock of the given class&#58; 
    // it is useful for those cases when a mock is problematic
    SpringBean unmocked&#40; Class clazz &#41;; 
    ...other "unmocked" variants...
    The three styles shown above can be mixed and matched at will, so one can write for example:

    Code:
    MarriedWoman linda = &#40; MarriedWoman &#41;prototype&#40; "linda", MarriedWoman.class, "wakeUp", "fallAsleep" &#41;;
    linda.setDetails&#40; properties&#40;&#41;.with&#40; "eyes", "blue" &#41;.and&#40; "weight", "49Kg" &#41; &#41;;
    &#40; &#40;SpringBean&#41;linda &#41;
        .dependsOn&#40; "Mommy" &#41;
        .constructor&#40; constructorArgs&#40;&#41;.with&#40; "Linda" &#41;.and&#40; someBean &#41; &#41;
        .lookupMethod&#40; "newChild", someFactory &#41;;
    (which makes it clear that one could create an horrible mess too, with this system).

    In general this leads to configurations which are a bit less visually structured, but are more compact, and not less readable, thanks to the fact the're done in java (so syntax highlighting has its role, and the fact the eye is accustomed to java code makes it even easier).

    What I find a little bit annoying is the amount of casts one has to type when doing assignments of bean() returned objects to variables, and the ones one has to type when he/she wants to use the SpringBean interface of a bean that was previously referred by a typed handle. That's why I'm experiencing with, uh, a

    4) fourth style: scripted.

    Ok, I admit I haven't gone very far in this direction, mostly because
    • I don't like very much scripting languages, all the untyped stuff and so on
    • The jakarta bean scripting framework interface for integrating scripting languages seems to be strangely designed and the various scripting languages implement it in unflexible ways (the interface is designed so badly that an experienced developer such as Pat Niemeyer, the author of Beanshell, writes "I don't quite understand these compile methods.[...]" in its implementation of it). For example there does not seem to be a common way for an implementation to throw an exception that carries information about line and column where an error in the script was detected. On the contrary, when one does call the "eval" method in order to pass a script to the underlying engine, it has to send also... two "line" and "column" attributes, which should... indicate where the script is located: the result is that beanshell prints out those same values, in order to indicate where an error in the script occurred.
    • I have to work, eat, sleep and meet my girlfriend sometimes

    Anyway, I have implemented a subclass of the said JavaRefreshableApplicationContext which is called
    Code:
    BSFRefreshableApplicationContext
    . This more or less works in the only case I've tested, and that is:
    • I took one of my most complex java unit cases, and made a beanshell script out of it (basically I stripped everything except the body of the doLoadBeanDefinitions method
    • I defined a context:

      Code:
      BSFRefreshableApplicationContext ctx = new BSFRefreshableApplicationContext&#40;&#41;;
      ctx.setConfigLocations&#40; new String&#91;&#93; &#123; "classpath&#58;it/stratosfera/java/spring/context/applicationContext.bsh" &#125; &#41;;
      ctx.refresh&#40;&#41;;
      and then I did some pretty normal tests with the results, such as:

      Code:
      Map map = &#40; Map &#41;ctx.getBeansOfType&#40; MarriedWoman.class &#41;;
      MarriedWoman linda = &#40; MarriedWoman &#41;map.values&#40;&#41;.iterator&#40;&#41;.next&#40;&#41;;
      assertEquals&#40; "Linda", linda.getName&#40;&#41; &#41;;
      List children = linda.getChildren&#40;&#41;;
      assertEquals&#40; 3, children.size&#40;&#41; &#41;;
      This did produce a nice green bar, which made me happy, because I basically used the same code both for the context (in beanshell) and the test (in java).
    • I then tried to take advantage of what the scripting languages have been done for, and removed all type declarations and casts. This basically reduced the context declaration to the following (note that all bean methods are called on a reference "s", this could be avoided with a beanshell object import):

      Code:
      import it.stratosfera.java.spring.context.*;
      
      linda = s.bean&#40; MarriedWoman.class &#41;;
      linda.setChildren&#40; s.list&#40;&#41;.with&#40; s.bean&#40; NoisyChild.class &#41; &#41;
                                 .and&#40; s.bean&#40; QuietChild.class &#41; &#41;
                                 .and&#40; s.bean&#40; "Marcus", NoisyChild.class &#41; &#41;
      &#41;;
      linda.setName&#40; "Linda" &#41;;
      john = s.bean&#40; "John", MarriedMan.class &#41;;
      john.setWife&#40; linda &#41;;
      
      auntJulie = s.bean&#40; MarriedWoman.class &#41;;
      friendRachel = s.bean&#40; "friendRachel", PersonSupport.class &#41;;
      auntJulie.setName&#40; "auntJulie" &#41;;
      
      auntJulie.setOpinionsOn&#40; s.map&#40;&#41;.with&#40; friendRachel, "obnoxious" &#41; &#41;;
      Map rachelOpinions = new HashMap&#40;&#41;;
      grandPa = s.bean&#40; PersonSupport.class &#41;;
      
      rachelOpinions.put&#40; grandPa, "lovely" &#41;;
      friendRachel.setOpinionsOn&#40; rachelOpinions &#41;;
      
      john.setRelatives&#40; s.map&#40;&#41;.with&#40; "aunt", auntJulie &#41;.and&#40; "grandPa", grandPa &#41; &#41;;
      
      marriageRegistry = s.bean&#40; MarriageRegistry.class &#41;;
      
      marriageRegistry.setRegistry&#40; s.map&#40;&#41;.with&#40; linda, john &#41;
                                            .and&#40; grandPa, friendRachel &#41; &#41;;
      
      grandPa.setConcerns&#40; s.set&#40;&#41;.with&#40; "football" &#41;.and&#40; "money" &#41;.and&#40; linda &#41; &#41;;
      
      linda.setDetails&#40; s.properties&#40;&#41;.with&#40; "eyes", "blue" &#41;.and&#40; "weight", "49Kg" &#41; &#41;;

    which is a purposely messy and relatively complex example that anyway shows how in this case we're free from casts and types (with the goods and the bads, and I find the bads are worse than the goods): in my case (IDEA) I'm also "free" from syntax highlighting, refactoring, documentation and auto-completion so I hereby declare this style as unpractical, even if it has a certain charme (one could integrate different contexts written in different scripting languages at the same time, whoa! :| ).

    So, this is pretty much all: does this sound promising or somewhat interesting to anyone?

    Bye,
    Davide Baroncelli.

  • #2
    Davide

    I have considered a Java config option. There's even some code in the sandbox (org.springframework.beans.factory.Configurer), although I haven't done much on it lately.

    I appreciate the effort you've taken here. I'd like to continue a discussion of this, either here or offline. I'll try to look more closely at your proposal next week.

    Rgds
    Rod

    Comment


    • #3
      would be great

      Rod, Davide,

      The option to configure spring contexts in java instead of xml and being able to benefit from code completion would be really great !!
      I am looking forward to this.

      Maarten

      Comment


      • #4
        Of course, also an option is to make the xml edtior more intelligent by using a Schema better. For example, if I click on a class attribute value, the editor should know that it is a Java class and allow me to navigate to it in the Java editor.

        Since many IDEs, like Eclipse have the plumbing for this, it is possible. But, adding this is not trivial.

        And another option is to allow use of an XML short cut language, one writes using prose adding hints where needed, and then a transformer creates the target XML. I guess the 'little language' pattern.

        I think too much is made of the XML pointy language 'problem'. In the HTML authoring world, they don't complain, the tools do the heavy lifting, and many people have no problem manipulating the DHTML technologies directly.

        BTW, I would like easier programmatic configuration too.

        Comment


        • #5
          I think too much is made of the XML pointy language 'problem'.
          So do I, in general.

          However, I agree that a strong programmatic option will be a valuable choice. And it must be possible to mix and match. It would be good to do this in the 1.3 timeframe. Also note that there will be further XML simplication (behind the attribute shortcuts introduced in 1.2) in 1.3.

          This will most likely involve some use of schema, allowing users to define new namespaces and tags. This could also be used to introduce new library tags, such as:
          Code:
          <j2ee&#58;jndi id="ds" jndiName="myDataSource" />

          Comment


          • #6
            The idea seems pretty nice. What's the conclusion of the (offline) discussion? Have you published the code?

            tia,

            Comment


            • #7
              Originally posted by alexandrupopescu
              The idea seems pretty nice. What's the conclusion of the (offline) discussion? Have you published the code?
              No, I didn't (yet): I took a look at what Rod has put in the sandbox and found it has similarities with what I've done: as soon as i have the time I'd like to test what I've done with bigger configurations (I have a couple of fairly complex ones to convert, but somehow didn't find the time to do it). If all goes well and anyone is interested in the code I'll publish it.

              In general I do agree with what other people has said in this thread: XML config is not a problem, but I also think than a more practical java option than what is present now in spring should be provided because it can be provided.

              (an idea plugin wouldn't be bad, wither )

              Comment

              Working...
              X