Tapestry Training -- From The Source

Let me help you get your team up to speed in Tapestry ... fast. Visit howardlewisship.com for details on training, mentoring and support!

Friday, February 27, 2004

Interceptors in HiveMind and Spring

I've been doing more playing with Spring and HiveMind. Like most developers, I'm focusing on what I know and am comfortable with.

I wanted a simple example of using an interceptor. Now, I know Spring has a lot more AOP (Aspect Oriented Programming) goodies inside it, but again, one must start somewhere.

As a starting point, here's the service interface and class:

Adder.java:

package springex;

public interface Adder
{
    public int add(int arg0, int arg1);
}

AdderImpl.java:

package springex.impl;

import springex.Adder;


public class AdderImpl implements Adder
{

    public int add(int arg0, int arg1)
    {
        return arg0 + arg1;
    }

}

In Spring, you create a bean, often refered to as the target, then create a second bean that adds interceptors to the first bean. It ends up looking something like:

springbeans.xml:


<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">

<beans>	
  <bean id="adderImpl" class="springex.impl.AdderImpl"/>
	
  <bean id="debugInterceptor" class="org.springframework.aop.interceptor.DebugInterceptor"/>
			
  <bean id="adder"	class="org.springframework.aop.framework.ProxyFactoryBean">
	
    <property name="proxyInterfaces">
      <value>springex.Adder</value>	
    </property>
			
    <property name="interceptorNames">
      <list>
        <value>debugInterceptor</value>	
      </list>	
    </property>
		
    <property name="target">
      <ref local="adderImpl"/>
    </property>
  </bean>

</beans>

The code for creating the BeanFactory and invoking methods on the bean is pretty simple:

SpringMain.java:

package springex.main;

import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;

import springex.Adder;

public class SpringMain
{

    public static void main(String[] args)
    {
        Resource r = new FileSystemResource("springbeans.xml");

        BeanFactory factory = new XmlBeanFactory(r);

        Adder a = (Adder) factory.getBean("adder");

        System.out.println("adder = " + a);
        System.out.println("result = " + a.add(4, 7));
    }
}

And when you run this ...

Debug interceptor: count=1 invocation=[Invocation: method=[public java.lang.String java.lang.Object.toString()] args=null] target is of class springex.impl.AdderImpl]
Debug interceptor: next returned
adder = springex.impl.AdderImpl@c8f6f8
Debug interceptor: count=2 invocation=[Invocation: method=[public abstract int springex.Adder.add(int,int)] args=[Ljava.lang.Object;@1b9240e] target is of class springex.impl.AdderImpl]
Debug interceptor: next returned
result = 11

You can see the Debug interceptor catching the invocations of toString() as well as add(). It doesn't do a great job on arguments. With some more work, you could create an adviser for the interceptor that would determine which methods to intercept, and which to leave alone.

Now, to do the equivalent in HiveMind. Because interceptors are the main approach to AOP in HiveMind, their useage is very streamlined:

META-INF/hivemodule.xml:

<?xml version="1.0"?>

<module id="springex" version="1.0.0">

  <service-point id="Adder" interface="springex.Adder">
  	<create-instance class="springex.impl.AdderImpl"/>
  	<interceptor service-id="hivemind.LoggingInterceptor"/>	
  </service-point>

</module>

The <interceptor> element references a interceptor factory service that fabricates an interceptor for the service. The factory is responsible for deciding which methods will be enhanded and how, by providing additional code that is invoked before and/or after the service methods are invoked on the core service implementation. You can have a whole stack of interceptors by adding additional <interceptor> elements.

One of the things I like is that there is no way in HiveMind to directly access the service implementation (the equivalent of the adderImpl bean in Spring). It will always be properly buried beneath proxy objects and interceptor objects.

Next, the main class.

HiveMain.java:

package springex.main;

import org.apache.hivemind.Registry;
import org.apache.hivemind.impl.RegistryBuilder;

import springex.Adder;

public class HiveMain
{

    public static void main(String[] args)
    {
        Registry registry = RegistryBuilder.constructDefaultRegistry();

        Adder a = (Adder) registry.getService("springex.Adder", Adder.class);

        System.out.println("adder = " + a);
        System.out.println("result = " + a.add(4, 7));
    }
}

And the runtime output:

adder = <SingletonProxy for springex.Adder(springex.Adder)>
springex.Adder [DEBUG] BEGIN add(4, 7)
springex.Adder [DEBUG] END add() [11]
result = 11

HiveMind does a careful job of converting method parameters into something readable, even when the parameters are arrays. It also shows the return value for non-void methods, and would log any thrown exceptions. The Log4J logger is springex.Adder. This is based on the service id, not the Java class.

Unless toString() is explicitly part of the service interface, HiveMind objects will implement their own version. Here, the object returned from Registry.getSerivce() is a singleton proxy, an object that handles the just-in-time creation of the core service implementation the first time a service method is invoked (and yes, its efficient and thread-safe). Its toString() implementation provides the service id and the service interface.

No comments: