Spring Java Config and ProxyFactoryBean

I really like spring remoting. I really like spring java config. Recently I had to marry the two. Here is how.

I have a value-object:

public class Datum
        implements Serializable
    {
    private int id;
    private String name;

    public Datum(int id, String name)
        {
        this.id = id;
        this.name = name;
        }

    public int getId()
        {
        return id;
        }

    public String getName()
        {
        return name;
        }

    @Override
    public String toString()
        {
        return "Datum{" + "id=" + id + "name=" + name + '}';
        }

    }

I have a service that produces Datum objects. The service interface is:

public interface IDatumProducer
    {
    public Datum produce();
    }

and a sample implementation might be:

public class RandomDatumProducerImpl
        implements IDatumProducer
    {
    private static final Logger log =
            LoggerFactory.getLogger(RandomDatumProducerImpl.class);

    private Random rnd = new Random(System.currentTimeMillis());

    @Override
    public Datum produce()
        {
        int i = rnd.nextInt();
        Datum datum = new Datum(i, String.valueOf(i));
        log.info("Produced {}", datum);
        return datum;
        }

    }

Here is a client for the service:

public class DatumProducerClient
    {
    private static final Logger log =
            LoggerFactory.getLogger(DatumProducerClient.class);

    private final IDatumProducer producer;

    public DatumProducerClient(IDatumProducer producer)
        {
        this.producer = producer;
        }

    @PostConstruct
    public void init()
        {
        while (true)
            {
            try
                {
                Datum datum = producer.produce();
                log.info("Got {}", datum);
                Thread.sleep(1000L);
                }
            catch (InterruptedException ex)
                {
                log.error("Interrupted", ex);
                }
            }
        }
    }

Now I want to run the DatumProducerClient and the IDatumProducer on different machines. The client should connect remotely to the producer, invoke the produce method, and do its stuff.

We can expose the service easily using Spring. The spring remoting (JMS) manual section gives the spring configuration XML to do this, but no example for doing this in Java Config. Turns out there is one small detail to take care of when using Java config.

Producer (Server)

Here is the application config for the producer side:

@Configuration
public class ProducerConfig
    {

    @Bean
    public AbstractMessageListenerContainer messageListenerContainer()
        {
        SimpleMessageListenerContainer container =
                new SimpleMessageListenerContainer();
        container.setConnectionFactory(jmsConnectionFactory());
        container.setDestination(jmsQueue());
        container.setMessageListener(jmsInvokerServiceExporter());
        container.setConcurrentConsumers(3);
        return container;
        }

    @Bean
    public JmsInvokerServiceExporter jmsInvokerServiceExporter()
        {
        JmsInvokerServiceExporter serviceExporter =
                new JmsInvokerServiceExporter();
        serviceExporter.setService(datumProducer());
        serviceExporter.setServiceInterface(IDatumProducer.class);
        return serviceExporter;
        }

    @Bean
    public ConnectionFactory jmsConnectionFactory()
        {
        ActiveMQConnectionFactory baseConnectionFactory =
                new ActiveMQConnectionFactory();
        baseConnectionFactory.setBrokerURL("tcp://localhost:61616");
        return baseConnectionFactory;
        }

    @Bean
    public Queue jmsQueue()
        {
        ActiveMQQueue queue = new ActiveMQQueue("dataChannel");
        return queue;
        }

    @Bean
    public IDatumProducer datumProducer()
        {
        return new RandomDatumProducerImpl();
        }

    }

And here is a main method that kicks off our application.

public class PublisherMain
    {
    public static void main(String[] args)
        {
        AnnotationConfigApplicationContext ctx =
                new AnnotationConfigApplicationContext(ProducerConfig.class);
        }
    }

Consumer (Client)

Here is how the consumer is wired up.

@Configuration
public class ConsumerConfig
    {

    @Bean
    public DatumProducerClient datumProducerClient()
        {
        return new DatumProducerClient(datumProducer());
        }

    @Bean
    public IDatumProducer datumProducer()
        {
        // producerFactory.getObject() will return null if we don't define
        // it as a separate bean; if we copy over the code from
        // jmsInvokerProxyFactoryBean here, it won't work: the proxy won't
        // get "enhanced" by spring, and its getObject() method will always
        // return null.
        JmsInvokerProxyFactoryBean producerProxy = jmsInvokerProxyFactoryBean();
        return (IDatumProducer) producerProxy.getObject();
        }

    // We can't fold this code into datumProducer above
    // The proxy bean must be defined separately, otherwise spring won't get
    // a chance to enhance it.
    @Bean
    public JmsInvokerProxyFactoryBean jmsInvokerProxyFactoryBean()
        {
        JmsInvokerProxyFactoryBean producerProxy = new JmsInvokerProxyFactoryBean();
        producerProxy.setServiceInterface(IDatumProducer.class);
        producerProxy.setConnectionFactory(jmsConnectionFactory());
        producerProxy.setQueue(jmsQueue());
        return producerProxy;
        }

    @Bean
    public ConnectionFactory jmsConnectionFactory()
        {
        ActiveMQConnectionFactory baseConnectionFactory =
            new ActiveMQConnectionFactory();
        baseConnectionFactory.setBrokerURL("tcp://localhost:61616");
        return baseConnectionFactory;
        }

    @Bean
    public Queue jmsQueue()
        {
        ActiveMQQueue queue = new ActiveMQQueue("dataChannel");
        return queue;
        }
    }

and here is how the main method on the client side looks like:

public class ConsumerMain
    {
    public static void main(String[] args)
        {
        AnnotationConfigApplicationContext ctx =
                new AnnotationConfigApplicationContext(ConsumerConfig.class);
        }
    }

The important bit to note here is that the JmsInvokerProxyFactoryBean is configured as a bean in its own right. This allows spring to “enhance” it at runtime to do all the proxying for us. If this were not a separate bean, things would not work. For e.g., the following code won’t work:

// Wrong way to use a ProxyFactoryBean
    @Bean
    public IDatumProducer datumProducer()
        {
        JmsInvokerProxyFactoryBean producerProxy = new JmsInvokerProxyFactoryBean();
        producerProxy.setServiceInterface(IDatumProducer.class);
        producerProxy.setConnectionFactory(jmsConnectionFactory());
        producerProxy.setQueue(jmsQueue());
        return (IDatumProducer) producerProxy.getObject(); // Wrong: getObject always returns null
        }

That’s all!

Advertisements
Post a comment or leave a trackback: Trackback URL.

Comments

  • Robert Kish  On February 25, 2012 at 3:57 am

    Regarding the producerFactory.getObject() returning null, if you don’t let Spring “enhance” it… If you first call producerProxy.afterPropertiesSet();
    then producerProxy.getObject() will not return null.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: