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:

Post a Comment

Please note that this is not a support forum for Tapestry. Requests for help will be deleted. Please subscribe to the Tapestry user mailing list if you are in need of support, or contact me directly for professional (for pay) support.

Spammers: Don't bother. I delete your comments and it's a waste of time for both of us. 垃圾邮件发送者:不要打扰。我删除您的评论和它的时间对我们双方的浪费