В данной статье я покажу, как можно реализовать прозрачный, с точки зрения клиента, роутинг вызывов удаленных сервисов на примере HttpInvoker.

Задача достаточно просто реализуется: для этого понадобится написать HttpInvokerRoutingProxyFactoryBean, который будет внутри себя скрывать все детали роутинга. Для большей гибкости создадим интерфейс HttpInvokerServiceUrlResolver, что позволит нам реализовать свой алгоритм выбора URL-сервиса.

Реализация HttpInvokerRoutingProxyFactoryBean:


package ru.nikisoft.spring.httpinvoker;

import java.util.HashMap;
import java.util.Map;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean;

public class HttpInvokerRoutingProxyFactoryBean implements FactoryBean, MethodInterceptor, InitializingBean
{
 private Class serviceInterface;
 private Object serviceProxy;
 private Map<String, HttpInvokerProxyFactoryBean> proxies = new HashMap<String, HttpInvokerProxyFactoryBean>();
 private HttpInvokerServiceUrlResolver serviceUrlResolver;
 
 public void afterPropertiesSet()
 {     
  if (getServiceInterface() == null)
  { 
   throw new IllegalArgumentException("Property 'serviceInterface' is required"); 
  }
  
  this.serviceProxy = new ProxyFactory(getServiceInterface(), this).getProxy(getClass().getClassLoader()); 
 }
 
 public Object getObject() throws Exception
 {  
  return this.serviceProxy;
 }

 public Class getObjectType()
 {  
  return serviceInterface;
 }

 public boolean isSingleton()
 {
  return true;
 }

 public Object invoke(MethodInvocation invocation) throws Throwable
 {
  String serviceUrl = serviceUrlResolver.getServiceUrl(invocation);
    
  HttpInvokerProxyFactoryBean proxy = getServiceProxyByUrl(serviceUrl);
    
  return proxy.invoke(invocation);  
 }
 
 public Class getServiceInterface()
 {
  return serviceInterface;
 } 
 
 public void setServiceInterface(Class serviceInterface)
 {
  this.serviceInterface = serviceInterface;
 }
 
 public HttpInvokerServiceUrlResolver getServiceUrlResolver()
 {
  return serviceUrlResolver;
 } 
 
 public void setServiceUrlResolver(HttpInvokerServiceUrlResolver serviceUrlResolver)
 {
  this.serviceUrlResolver = serviceUrlResolver;
 }
 
 private synchronized HttpInvokerProxyFactoryBean getServiceProxyByUrl(String serviceUrl)
 {
  HttpInvokerProxyFactoryBean proxy = proxies.get(serviceUrl);
  
  if (proxy == null)
  {   
   proxy = new HttpInvokerProxyFactoryBean();
   proxy.setServiceInterface(getServiceInterface());  
   proxy.setServiceUrl(serviceUrl);
   
   proxies.put(serviceUrl, proxy);
  }  
  
  return proxy;
 }
}

Объявление интерфейса HttpInvokerServiceUrlResolver:


package ru.nikisoft.spring.httpinvoker;

import org.aopalliance.intercept.MethodInvocation;

public interface HttpInvokerServiceUrlResolver
{
 String getServiceUrl(MethodInvocation invocation);
}

Метод getServiceUrl интерфейса принимает на вход MethodInvocation и реализация данного интерфейса должна вернуть строку содержащую  URL удаленного сервиса.

Пример использования

Интерфейс простейшего удаленного сервиса:


package ru.nikisoft.spring.httpinvoker.test;

public interface IService
{
 void getTest(String branch);
}

Реализация интерфейса HttpInvokerServiceUrlResolver:


package ru.nikisoft.spring.httpinvoker.test;

import org.aopalliance.intercept.MethodInvocation;

public class SimpleHttpInvokerServiceUrlResolver implements HttpInvokerServiceUrlResolver
{
 private String baseUrl;
 
 public String getServiceUrl(MethodInvocation invocation)
 {  
  return getBaseUrl() + "/" + invocation.getArguments()[0];
 }
 
 public String getBaseUrl()
 {
  return baseUrl;
 }
 
 public void setBaseUrl(String baseUrl)
 {  
  if (baseUrl.endsWith("/"))
   baseUrl = baseUrl.substring(0, baseUrl.length() - 1);
   
  this.baseUrl = baseUrl;
 }
}

Дескриптор Spring-контекста:


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="<a href="http://www.springframework.org/schema/beans">http://www.springframework.org/schema/beans</a>"
      xmlns:xsi="<a href="http://www.w3.org/2001/XMLSchema-instance">http://www.w3.org/2001/XMLSchema-instance</a>"
      xmlns:aop="<a href="http://www.springframework.org/schema/aop">http://www.springframework.org/schema/aop</a>"
      xmlns:tx="<a href="http://www.springframework.org/schema/tx">http://www.springframework.org/schema/tx</a>"
      xsi:schemaLocation="<a href="http://www.springframework.org/schema/beans">http://www.springframework.org/schema/beans</a> <a href="http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">http://www.springframework.org/schema/beans/spring-beans-2.5.xsd</a>
           <a href="http://www.springframework.org/schema/aop">http://www.springframework.org/schema/aop</a> <a href="http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">http://www.springframework.org/schema/aop/spring-aop-2.5.xsd</a>
           <a href="http://www.springframework.org/schema/tx">http://www.springframework.org/schema/tx</a> <a href="http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">http://www.springframework.org/schema/tx/spring-tx-2.5.xsd</a>">

 <bean id="serviceUrlResolver" class="ru.nikisoft.spring.httpinvoker.SimpleHttpInvokerServiceUrlResolver">
  
<property name="baseUrl" value="<a href="http://localhost/httpinvoker/&quot;/">http://localhost/httpinvoker/"/</a>>
 </bean>

 <bean id="service" class="ru.nikisoft.spring.httpinvoker.HttpInvokerRoutingProxyFactoryBean">
  
<property name="serviceInterface" value="ru.nikisoft.spring.httpinvoker.test.IService"/>
  
<property name="serviceUrlResolver" ref="serviceUrlResolver"/>
 </bean>

</beans>

Приложение:


package ru.nikisoft.spring.httpinvoker.test;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;

public class Test
{
 /**
  * @param args
  */
 public static void main(String[] args)
 {
  ApplicationContext context = new FileSystemXmlApplicationContext("context.xml");
  IService service = (IService) context.getBean("service");
  
  service.getTest("branch1"); 

  service.getTest("branch2"); 

  service.getTest("branch3"); 

  service.getTest("branch4"); 

  service.getTest("branch5"); 

  service.getTest("branch6");     
 }
}

Как видно из примера, клиентское приложение просто получает объект типа IService и вызывает его методы и не предполагает о том, что каждый вызов метода может быть обработан разными сервисами в зависимости от входных параметров или от результатов какого-либо еще алгоритма.

Данный подход можно аналогичным образом реализовать и для других протоколов, например для Hessian или Burlap, или реализовать независящий от протокола реализации, а, возможно, и смесь протоколов, в случае когда удаленный сервисы могут реализовывать сервисы на разных протоколах.