В данной статье я покажу, как можно реализовать прозрачный, с точки зрения клиента, роутинг вызывов удаленных сервисов на примере 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/"/">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, или реализовать независящий от протокола реализации, а, возможно, и смесь протоколов, в случае когда удаленный сервисы могут реализовывать сервисы на разных протоколах.