Version 10

    bold means it needs to be fixed

     

    JBoss Microcontainer - Component models

     

    Introduction

     

    Looking at the current state of Java, we can see that POJOs (1) rule the land again.

    Its dominance stretches from enterprise apps to middleware services. At JBoss we were known for our modular JMX (2) based kernel (3). Application server was nothing more than a bunch of flexible MBeans (2) and a powerful MicroKernel in the middle.

    But, as you could feel the change is coming, we still wanted to be ahead of the pack. Hence the Microcontainer project was born.

     

    The JBoss Microcontainer (4) project is about many things.

    Just to name a few, Microcontainer's puzzles are ranging from reflection abstraction, virtual file system, simple state machine and all the way to transparent AOP integration, new classloading layer, deployment framework, OSGi framework implementation.

    I'll try to address them all over a small series of articles here at DZone.

    This, the first one, being about Microcontainer's component models.

     

    What is a component model

     

    What do we consider component model?

    First we need to determine what is our component.

    To consider that a model, we need to declare what kind of interactions we allow.

     

    Previously mentioned JMX MBeans is one of them.

    MBeans being components, and its interactions are executing mbean operations,

    referencing attributes, setting attributes and declaring explicit dependencies between named mbeans.

     

    As mentioned, we had that already with MicroKernel.

    And as expected, Microcontainer brings extensive POJO support.

    Default behavior / interactions in Microcontainer is what you also normally get from any other IoC (5) container,

    similar to MBeans, operations are plain method invocations, attributes are setters/getters and explicit dependencies are still present.

    Having only that would mean we didn't get much further than just relieving the pain of declaring MBeans, hence it's only logical to expect something more.

    Since there is many new interesting features that we introduced, but this article is more about introduction to different component model interaction, I'll leave that for the next article in the series.

     

    OK, so far we have MBeans and our custom POJOs.

    Currently there is many existing POJO component models out there, Guice (6) and Spring (7) being among the most popular. Having nice integration with those was also one of our important goals.

     

    Demo environment setup

     

    Let's now turn to our demo.

    In order to get a slight feeling for how demo works, I'll describe parts that constitute the demo.

     

    All source code can be found at this location of our Subversion repository:

    Project is fully mavenized, so it should be easy adjust it to your IDE.

     

    Let's just quickly go over the sub-projects that make this article's demo and describe it's usage.

    Once we're fully over the series of articles we can have a more detailed look at what certain sub-project does.

     

    JBoss Microcontainer Demos, sub-projects relevant for this article:

    • bootstrap (as the name suggest, it bootstraps Microcontainer with demo code)

    • jmx  (adds the JMX notion to demo's bootstrap)

    • models (source code of our components / services)

     

    The demo has only one variable you need to set - demos home - but even this one can be optional if you checked-out your project into \projects\demos directory.

    Else you need to set system property demos.home (e.g. -Ddemos.home=).

     

    If all is right, you should now be able to run JMXMain class as a main class.

    Make sure you include models sub-project classpath, since some of the services require additional classes on the classpath, a bit more then what jmx sub-project expects.

    Once Microcontainer is booted it starts to scan ${demos.home}/sandbox directory for any changes.

    Now all we need is to provide a deployable unit and drop it there.

     

    Models

     

    And this is what our models sub-project is all about. You can quickly try if everything is in place, by building models sub-project (mvn package) and drop it into the sandbox.

    You should get some nice error-less output on the console otherwise something went wrong.

     

    OK, but lets first go over what exactly does this models sub-project do, where is the integration code and then try deploying it once again.

     

    If we look at the models src/main/resources/META-INF directory, we'll see plenty of -beans.xml resource files and one -service.xml.

    Each with meaningful name matching source code package from models's src/main/java/org/jboss/demos/models.

     

    Let's dissect them one by one. Starting at the one's that have no dependencies.

     

    <deployment xmlns="urn:jboss:bean-deployer:2.0">
    
      <bean name="PlainPojo" class="org.jboss.demos.models.plain.Pojo"></bean>
    
      <beanfactory name="PojoFactory" class="org.jboss.demos.models.plain.Pojo">
        <property name="factoryClass">org.jboss.demos.models.plain.PojoFactory</property>
      </beanfactory>
    
    </deployment>
    

     

    This is a simple Micrcocontainer beans descriptor file. Anyone who crossed paths with some IoC should be familiar with it.

    And, as I already mentioned, I'll follow up on more advanced usage in the next article.

     

    I mentioned nice Spring integration, this next file shows what we have done.

     

    <beans xmlns="urn:jboss:spring-beans:2.0">
    
      <!-- Adding @Spring annotation handler -->
      <bean id="SpringAnnotationPlugin" class="org.jboss.spring.annotations.SpringBeanAnnotationPlugin" ></bean>
    
      <bean id="SpringPojo" class="org.jboss.demos.models.spring.Pojo"></bean>
    
    </beans>
    

     

    Note that file's namespace is different from previous Microcontainer beans plain-beans.xml file.

    urn:jboss:spring-beans:2.0 namespace points to our version of Spring schema port,

    meaning you can describe your beans Spring style, but it's the Microcontainer that's gonna deploy then, not Spring's bean factory notion.

     

    public class Pojo extends AbstractPojo implements BeanNameAware
    {
       private String beanName;
    
       public void setBeanName(String name)
       {
          beanName = name;
       }
    
       public String getBeanName()
       {
          return beanName;
       }
    
       public void start()
       {
          if ("SpringPojo".equals(getBeanName()) == false)
             throw new IllegalArgumentException("Name doesn't match: " + getBeanName());
       }
    }
    

     

    Although SpringPojo bean has a dependency on Spring lib, but it's only there to expose we can even mock some of the Spring's callback behavior,

    see SpringBeanAnnotationPlugin for more details, w/o having Spring lib in Microcontainer's classpath.

     

    Since we introduced Spring integration, let's have a look at Guice integration.

    As Guice users know, Guice is all about types matching. Configuration of Guice beans is done via Modules.

    Meaning, in order to provide beans, one most implement a Module.

     

    <deployment xmlns="urn:jboss:bean-deployer:2.0">
    
      <bean name="GuicePlugin" class="org.jboss.guice.spi.GuiceKernelRegistryEntryPlugin">
        <constructor>
          <parameter>
            <array elementClass="com.google.inject.Module">
              <bean class="org.jboss.demos.models.guice.PojoModule"></bean>
            </array>
          </parameter>
        </constructor>
      </bean>
    
    </deployment>
    

     

    Two important parts to watch from this file are PojoModule and GuiceKernelRegistryEntryPlugin.

    The first one is where we configure our beans

     

    public class PojoModule extends AbstractModule
    {
       private Controller controller;
    
       @Constructor
       public PojoModule(@Inject(bean = KernelConstants.KERNEL_CONTROLLER_NAME) Controller controller)
       {
          this.controller = controller;
       }
    
       protected void configure()
       {
          bind(Controller.class).toInstance(controller);
          bind(IPojo.class).to(Pojo.class).in(Scopes.SINGLETON);
          bind(IPojo.class).annotatedWith(FromMC.class).toProvider(GuiceIntegration.fromMicrocontainer(IPojo.class, "PlainPojo"));
       }
    }
    

     

    where the second one is where the integration with Microcontainer lies

     

    public class GuiceKernelRegistryEntryPlugin implements KernelRegistryPlugin
    {
       private Injector injector;
    
       public GuiceKernelRegistryEntryPlugin(Module... modules)
       {
          injector = Guice.createInjector(modules);
       }
    
       public void destroy()
       {
          injector = null;
       }
    
       public KernelRegistryEntry getEntry(Object name)
       {
          KernelRegistryEntry entry = null;
          try
          {
             if (name instanceof Class<?>)
             {
                Class<?> clazz = (Class<?>)name;
                entry = new AbstractKernelRegistryEntry(name, injector.getInstance(clazz));
             }
             else if (name instanceof Key)
             {
                Key<?> key = (Key<?>)name;
                entry = new AbstractKernelRegistryEntry(name, injector.getInstance(key));
             }
          }
          catch (Exception ignored)
          {
          }
          return entry;
       }
    }
    

     

    See how we create Injector from Modules and then do lookup on it for matching beans.

     

    In mbeans-service.xml we declare legacy usage of MBean.

     

    <server>
    
       <mbean code="org.jboss.demos.models.mbeans.Pojo" name="jboss.demos:service=pojo">
         <attribute name="OtherPojo"><inject bean="PlainPojo"></inject></attribute>
       </mbean>
    
    </server>
    

     

    Interesting bit to detect here is injection of plain POJO into MBean.

    By that introducing our first different component models interaction. Yay!

     

    In order to allow for MBean deployment via Microcontainer, a whole new component model handling code had to be written.

    See system-jmx-beans.xml for more details. The code from this file lives in JBossAS source code: system-jmx sub-project.

    One note here, this is currently only possible with JBoss's JMX implementation, since system-jmx code uses some implementation details.

     

    OK, we already deployed MBeans, but what about if we want to expose existing POJO also as MBeans, registering them into a mbean server.

     

    <deployment xmlns="urn:jboss:bean-deployer:2.0">
    
      <bean name="AnnotatedJMXPojo" class="org.jboss.demos.models.jmx.AtJmxPojo"></bean>
    
      <bean name="XmlJMXPojo" class="org.jboss.demos.models.mbeans.Pojo">
        <annotation>@org.jboss.aop.microcontainer.aspects.jmx.JMX(exposedInterface=org.jboss.demos.models.mbeans.PojoMBean.class, registerDirectly=true)</annotation>
      </bean>
    
      <bean name="ExposedPojo" class="org.jboss.demos.models.jmx.Pojo"></bean>
    
      <bean name="AnnotatedExposePojo" class="org.jboss.demos.models.jmx.ExposePojo">
        <constructor>
          <parameter><inject bean="ExposedPojo"></inject></parameter>
        </constructor>
      </bean>
    
    </deployment>
    

     

    Doing that, as you can see from looking at any of the beans in this file, is as simple as annotating beans with @JMX annotation.

    You can either expose bean directly or even its property.

     

    <deployment xmlns="urn:jboss:bean-deployer:2.0">
    
      <bean name="XMLLoginConfig" class="org.jboss.demos.models.old.XMLLoginConfig"></bean>
    
      <bean name="SecurityConfig" class="org.jboss.demos.models.old.SecurityConfig">
        <property name="defaultLoginConfig"><inject bean="XMLLoginConfig"></inject></property>
      </bean>
    
      <bean name="SecurityChecker" class="org.jboss.demos.models.old.Checker">
        <property name="loginConfig"><inject bean="jboss.security:service=XMLLoginConfig"></inject></property>
        <property name="securityConfig"><inject bean="jboss.security:service=SecurityConfig"></inject></property>
      </bean>
    
    </deployment>
    

     

    Here we can see how you can use any of the injection mechanisms, injecting plain pojo or from mbean server.

     

    One of the injection options is also to use type injection also sometimes called autowiring.

     

    <deployment xmlns="urn:jboss:bean-deployer:2.0">
    
      <bean name="FromGuice" class="org.jboss.demos.models.plain.FromGuice">
        <constructor><parameter><inject bean="PlainPojo"></inject></parameter></constructor>
        <property name="guicePojo"><inject></inject></property>
      </bean>
    
      <bean name="AllPojos" class="org.jboss.demos.models.plain.AllPojos">
        <property name="directMBean"><inject bean="jboss.demos:service=pojo"></inject></property>
        <property name="exposedMBean"><inject bean="jboss.demos:service=ExposedPojo"></inject></property>
        <property name="exposedMBean"><inject bean="jboss.demos:service=ExposedPojo"></inject></property>
      </bean>
    
    </deployment>
    

     

    FromGuice bean gets Guice bean injected via type matching, where PlainPojo is injected with common name injection.

    We then test if Guice binding works as expected:

     

    public class FromGuice
    {
       private IPojo plainPojo;
       private org.jboss.demos.models.guice.Pojo guicePojo;
    
       public FromGuice(IPojo plainPojo)
       {
          this.plainPojo = plainPojo;
       }
    
       public void setGuicePojo(org.jboss.demos.models.guice.Pojo guicePojo)
       {
          this.guicePojo = guicePojo;
       }
    
       public void start()
       {
          if (plainPojo != guicePojo.getMcPojo())
             throw new IllegalArgumentException("Pojos are not the same: " + plainPojo + "!=" + guicePojo.getMcPojo());
       }
    }
    

     

    This only leaves us with alias component model.

    Even though alias is quite trivial feature, in order to implement it as true dependency,

    it has to be introduced as a new component model inside Microcontainer.

    Implementation details are part of AbstractController source code.

     

    <deployment xmlns="urn:jboss:bean-deployer:2.0">
    
      <alias name="SpringPojo">springPojo</alias>
    
    </deployment>
    

     

    Here we map SpringPojo name to springPojo alias.

    The beauty of having alias as true component model is that it doesn't matter when real bean is deployed.

    Meaning alias will wait in non-installed state until real bean triggers it.

     

    We're done!

     

    We've seen how we can deploy simple Microcontainer beans, legacy MBeans, Guice POJOs, Spring beans and aliases.

    And since all of this is controlled by Microcontainer, we saw how easy we can mix and match this different component models.

    Injecting POJOs into MBeans and vice versa. Looking up Guice beans and injecting them into Microcontainer beans, using Microcontainer beans to wire Guice beans.

    Deploying Spring custom xml - apart from changing namespace - with Microcontainer and then using this beans as if they were Microcontainer beans.

     

    I can easily say, with the level of abstraction we put in our component model design, sky is the limit on what we can handle.

    An example of this is the upcoming OSGi services, but that's another story, another article.

     

    Stayed tuned for detailed Microcontainer IoC article.

     

    References: