SPR-3926
requests a @Service
annotation for Spring Remoting
configuration, and makes for an interesting foray into Spring's
annotation support.
The goal I had in mind in my initial attempt at tackling this was to create an annotation which would allow specification of the common Spring Remoting configuration supporting the four remoting technologies:
These translate into the ServiceType
enumeration, which
@Service
annotated services use to specify their desired
remoting technology.
public enum ServiceType {
, BURLAP, HESSIAN, RMI
HTTP}
Spring makes the task easy, as configuration for each of these requires no more than the identification of the service interface.
The @Service
annotation needs to define only two key
pieces of information: the remoting technology to use, and the service
interface to expose.
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Service {
serviceType() default ServiceType.HTTP;
ServiceType
Class<?> serviceInterface();
}
An InstantiationAwareBeanPostProcessorAdapter
handles
interpreting @Service
annotated classes by instantiating
the appropriate RemoteExporter
, and initializing its
serviceInterface and serviceName
properties.
public class ServiceAnnotationBeanPostProcessor extends InstantiationAwareBeanPostProcessorAdapter implements PriorityOrdered {
private int order = Ordered.LOWEST_PRECEDENCE - 1;
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
Service service = AnnotationUtils.findAnnotation(bean.getClass(), Service.class);
Object resultBean = bean;
if (null != service) {
if (ServiceType.HTTP == service.serviceType()) {
= new HttpInvokerServiceExporter();
HttpInvokerServiceExporter httpInvokerServiceExporter .setServiceInterface(service.serviceInterface());
httpInvokerServiceExporter.setService(bean);
httpInvokerServiceExporter.afterPropertiesSet();
httpInvokerServiceExporter= httpInvokerServiceExporter;
resultBean
} else if (ServiceType.HESSIAN == service.serviceType()) {
= new HessianServiceExporter();
HessianServiceExporter hessianServiceExporter .setServiceInterface(service.serviceInterface());
hessianServiceExporter.setService(bean);
hessianServiceExporter.afterPropertiesSet();
hessianServiceExporter= hessianServiceExporter;
resultBean
} else if (ServiceType.BURLAP == service.serviceType()) {
= new BurlapServiceExporter();
BurlapServiceExporter burlapServiceExporter .setServiceInterface(service.serviceInterface());
burlapServiceExporter.setService(bean);
burlapServiceExporter.afterPropertiesSet();
burlapServiceExporter= burlapServiceExporter;
resultBean
} else if (ServiceType.RMI == service.serviceType()) {
= new RmiServiceExporter();
RmiServiceExporter rmiServiceExporter .setServiceInterface(service.serviceInterface());
rmiServiceExporter.setService(bean);
rmiServiceExporter.setServiceName(beanName);
rmiServiceExportertry {
.afterPropertiesSet();
rmiServiceExporter} catch (RemoteException remoteException) {
throw new FatalBeanException("Exception initializing RmiServiceExporter", remoteException);
}
= rmiServiceExporter;
resultBean }
}
return resultBean;
}
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
public void setOrder(int order) {
this.order = order;
}
@Override
public int getOrder() {
return order;
}
}
The ServiceAnnotationBeanPostProcessor
must be added to
the appropriate bean definition registry, which in this case occurs in
AnnotationConfigUtils#registerAnnotationConfigProcessors
.
if (!registry.containsBeanDefinition(SERVICE_ANNOTATION_PROCESSOR_BEAN_NAME)) {
= new RootBeanDefinition(ServiceAnnotationBeanPostProcessor.class);
RootBeanDefinition def .setSource(source);
def.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
def.add(registerPostProcessor(registry, def, SERVICE_ANNOTATION_PROCESSOR_BEAN_NAME));
beanDefs}
That's all there is to it. To take the new @Service
annotation for a test run, a test service interface is defined, and a
test implementation is written and annotated.
public interface DateService {
public Date getDate();
}
@Service(serviceInterface = DateService.class, serviceType = ServiceType.HTTP)
public class DateServiceImpl implements DateService {
@Override
public Date getDate() {
return new Date();
}
}
DateServiceImpl
uses the @Service
annotation to identify DateService
as its service
interface, and Spring's HTTP invoker as the remoting technology. The
Spring configuration on the server side is now quite simple.
<?xml version="1.0" encoding="ISO-8859-1"?>
beans xmlns="http://www.springframework.org/schema/beans"
< xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd">
context:annotation-config />
<
bean id="/DateService" class="com.earldouglas.springremoting.DateServiceImpl" />
<
beans> </
The service is linked in at the client in the usual way, and neither the client nor the server's application context notice the difference.
It would be interesting and probably worthwhile to explore JAX-RPC,
JAX-WS, and JMS in addition the four remoting technologies already
supported by the new @Service
annotation, and may make for
a future post.
As usual, the Spring Reference is a great resource for learning more.