В браузере есть способ хранить и передавать некоторую дополнительную информацию, которая не присутствует в параметрах при вызове GET или не хранится в теле запроса POST. Данная функциональность реализована с помощью кук.
Для браузера это позволяет создать некоторое постоянное хранилище данных на стороне клиента, с помощью которых сервер может идентифицировать клиента.
Spring Remoting можно реализовать поверх протокола http, однако ни одного упоминания про куки я не нашел. Попробуем реализовать их самостоятельно.
Пусть есть некий сервис, опубликованный с помощью spring remoting:
package foo;
public interface FooService {
public String hello(String name);
}
package foo;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
public class FooServiceImpl {
private Log logger = LogFactory.getLog(getClass());
public String hello(String name) {
logger.info("Greeting "+name);
return "Hello "+name;
}
}
<bean id="fooService" class="foo.FooServiceImpl">
<bean name="/FooService" class="org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter">
<property name="service" ref="fooService"/>
<property name="serviceInterface" value="foo.FooService"/>
</bean>
На клиенте мы получаем ссылку на сервис:
<bean id="fooService" class="org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean">
<property name="serviceUrl" value="http://${server.address}:${server.port}/server/remoting/FooService"/>
<property name="serviceInterface" value="foo.FooService"/>
</bean>
Для начала реализуем способ хранения кук на клиенте. Так как клиент — swing-приложение, то в качестве хранителя можно использовать обычный синглтон. Все куки будем хранить в карте, ключ — имя куки.
package foo;
import java.util.*;
import java.io.Serializable;
public class CookiesHolder {
private Map<String, Serializable> cookies;
public void addCookie(String name, Serializable o) {
if (cookies == null) {
cookies =new HashMap<String, Serializable>();
}
cookies.put(name, o);
}
public Serializable getCookie(String name) {
return cookies!=null ? cookies.get(name) : null;
}
public Set<String> getCookiesNames() {
if (cookies==null) {
return Collections.emptySet();
}
else {
return cookies.keySet();
}
}
}
<bean id="cookieHolder" class="core.service.remote.CookiesHolder" />
Далее необходим способ передать куки на сервер при вызове любого метода. Изучая класс org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean, я наткнулся на метод
/**
* Set the RemoteInvocationFactory to use for this accessor.
* Default is a {@link DefaultRemoteInvocationFactory}.
* <p>A custom invocation factory can add further context information
* to the invocation, for example user credentials.
*/
public void setRemoteInvocationFactory(RemoteInvocationFactory remoteInvocationFactory) {
this.remoteInvocationFactory =
(remoteInvocationFactory != null ? remoteInvocationFactory : new DefaultRemoteInvocationFactory());
}
По описанию выглядит как то, что нам нужно. Если изучить код
RemoteInvocationFactory, мы увидим единственный метод:
RemoteInvocation createRemoteInvocation(MethodInvocation methodInvocation);
Очевидно, что RemoteInvocation содержит данные о том, какой метод вызывается, какие параметры в него передаются. Посмотрим на код RemoteInvocation. Сразу же бросается в глаза поле
private Map attributes;
смотрим описание:
/**
* Add an additional invocation attribute. Useful to add additional
* invocation context without having to subclass RemoteInvocation
* Attribute keys have to be unique, and no overriding of existing
* attributes is allowed.
* The implementation avoids to unnecessarily create the attributes
* Map, to minimize serialization size.
* @param key the attribute key
* @param value the attribute value
* @throws IllegalStateException if the key is already bound
*/
public void addAttribute(String key, Serializable value) throws IllegalStateException {
…
Итак, при вызове метода сервиса, есть возможность, переопределив RemoteInvocationFactory, поместить в RemoteInvocation дополнительные атрибуты, в частности, наши куки:
package foo;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.remoting.support.DefaultRemoteInvocationFactory;
import org.springframework.remoting.support.RemoteInvocation;
import java.io.Serializable;
public class CookiesBasedRemoteInvocationFactory extends DefaultRemoteInvocationFactory {
private CookiesHolder cookiesHolder;
@Override
public RemoteInvocation createRemoteInvocation(MethodInvocation methodInvocation) {
final RemoteInvocation invocation = super.createRemoteInvocation(methodInvocation);
final Application instance = Application.getInstance();
CookiesHolder holder;
if (instance!=null) {
holder = instance.getCookiesHolder();
}
else {
holder = new CookiesHolder();
}
for (String name : holder.getCookiesNames()) {
final Object value = holder.getCookie(name);
if (value instanceof Serializable) {
invocation.addAttribute(name, (Serializable) value);
}
}
return invocation;
}
public void setCookiesHolder(CookiesHolder cookiesHolder) {
this.cookiesHolder = cookiesHolder;
}
}
теперь изменим конфигурацию ссылки на сервис:
<bean id='cookiesBasedRemoteInvocationFactory' class="foo.CookiesBasedRemoteInvocationFactory"/>
<bean id="fooService" class="org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean">
<property name="serviceUrl" value="http://${server.address}:${server.port}/server/remoting/FooService"/>
<property name="serviceInterface" value="foo.FooService"/>
<property name="remoteInvocationFactory" ref="cookiesBasedRemoteInvocationFactory"/>
</bean>
Таким образом, любой вызов метода сервиса (в частности hello()) будет передавать на сервер наши куки. Осталось их получить на сервере. Будем действовать аналогичным способом.
Создаем структуру для хранения кук:
package foo;
import org.springframework.util.Assert;
public class ServerCookiesHolder {
private static ThreadLocal contextHolder = new ThreadLocal();
public static void clearContext() {
contextHolder.set(null);
}
public static CookiesHolder getContext() {
if (contextHolder.get() == null) {
contextHolder.set(new CookiesHolder());
}
return (CookiesHolder) contextHolder.get();
}
public static void setContext(CookiesHolder context) {
Assert.notNull(context, "Only non-null CookiesHolder instances are permitted");
contextHolder.set(context);
}
}
Изучаем HttpInvokerServiceExporter, находим метод setRemoteInvocationExecutor, который устанавливает обработчика вызова метода. Переопределяем обработчик:
package foo;
import org.springframework.remoting.support.DefaultRemoteInvocationExecutor;
import org.springframework.remoting.support.RemoteInvocation;
import java.lang.reflect.InvocationTargetException;
import java.util.Map;
import java.util.Set;
public class CookiesBasedRemoteInvocationExecutor extends DefaultRemoteInvocationExecutor {
@Override
public Object invoke(RemoteInvocation invocation, Object targetObject) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
final Map map = invocation.getAttributes();
if (map!=null) {
final Set<String> set = map.keySet();
final CookiesHolder cookiesContext = ServerCookiesHolder.getContext();
for (String name : set) {
cookiesContext.addCookie(name, invocation.getAttribute(name));
}
}
return super.invoke(invocation, targetObject);
}
}
Изменяем конфигурацию сервиса:
<bean id="cookiesBasedRemoteInvocationExecutor" class="foo.CookiesBasedRemoteInvocationExecutor"/>
<bean name="/FooService" class="org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter">
<property name="service" ref="fooSearcher"/>
<property name="serviceInterface" value="foo.FooService"/>
<property name="remoteInvocationExecutor" ref="cookiesBasedRemoteInvocationExecutor"/>
</bean>
Вуаля. Проверяем. Измененный код сервиса:
public String hello(String name) {
logger.info("Greeting "+ServerCookiesHolder.getContext().getCookie("realName"));
return "Hello "+name;
}
…
Код вызова сервиса на клиенте:
public void test() {
CookiesHolder.setCookie("realName", "Foo");
fooService.hello("Fred");
}