In my first attempt at SPR-3926, I
ended up with a @Service
annotation which was wholly
separate from Spring's existing @Service
annotation, and
required either a confusing coexistence or an inappropriate integration
of the two. It also required implementations of service interfaces to be
annotated as Spring Remoting services, which is arguably less intuitive
than annotating the service interfaces themselves as Spring Remoting
services.
After some very helpful advice from Chris Beams, I have reworked most
of the original code. To avoid stepping all over the intended function
of the existing @Service
annotation, I created the
@Remote
annotation:
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Remote {
Class<?> serviceExporter() default HttpInvokerServiceExporter.class;
}
To use the new annotation, a service interface is annotated with
@Remote
, optionally specifying
serviceExporter
. The optional serviceExporter
may be any of the
HttpInvokerServiceExporterRmiServiceExporter
,
HessianServiceExporter
, or
BurlapServiceExporter
classes, and defaults to
HttpInvokerServiceExporter
if not specified. The reason I
chose to allow direct specification of the RemoteExporter
class as the serviceExporter
is so future implementations
of remoting technologies (JSON, etc.) will be supported without
modification to the @Remote
definition. The alternative to
this is to maintain the previous pattern of an enumerated type which is
later mapped to corresponding RemoteExporter
implementations.
An implementation of the service interface then simply needs to be
annotated with @Service
, so it will be picked up by
<context:component-scan />
during application context
initialization.
A new element definition named remote-export
is needed
in spring-context-3.1.xsd
,
which is used by adding <context:remote-export />
to
the context configuration:
<xsd:element name="remote-export">
<xsd:annotation>
<xsd:documentation><![CDATA[
@Remote as Spring Remoting services.
Exports classes whose interfaces have been annotated with ]]></xsd:documentation>
</xsd:annotation>
</xsd:element>
To handle the new remote-export
element, I created
RemoteBeanDefinitionParser
and added it to
ContextNamespaceHandler.init()
:
public void init() {
...
registerBeanDefinitionParser("remote-export", new RemoteBeanDefinitionParser());
}
public BeanDefinition parse(Element element, ParserContext parserContext) {
= new RootBeanDefinition(RemotingExporter.class);
RootBeanDefinition bd .setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
bd.setSource(parserContext.extractSource(element));
bd.registerBeanComponent(new BeanComponentDefinition(bd, RemotingExporter.class.getName()));
parserContextreturn null;
}
RemoteBeanDefinitionParser
registers a
RemotingExporter
, which looks for @Remote
beans in the context, and wires them up with the appropriate service
exporters:
public class RemotingExporter implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
for (String beanName : beanFactory.getBeanDefinitionNames()) {
Object bean = beanFactory.getBean(beanName);
Class<?> serviceInterface = findServiceInterface(bean);
if (serviceInterface != null) {
= createRemoteExporter(serviceInterface, bean);
RemoteExporter remoteExporter .registerSingleton("/" + serviceInterface.getName(), remoteExporter);
beanFactory}
}
}
private Class<?> findServiceInterface(Object service) {
Class<?> serviceInterface = null;
if (AnnotationUtils.isAnnotationDeclaredLocally(Service.class, service.getClass())) {
for (Class<?> interfaceClass : service.getClass().getInterfaces()) {
if (AnnotationUtils.isAnnotationDeclaredLocally(Remote.class, interfaceClass)) {
= interfaceClass;
serviceInterface }
}
}
return serviceInterface;
}
private RemoteExporter createRemoteExporter(Class<?> serviceInterface, Object service) {
= null;
RemoteExporter remoteExporter Remote remote = AnnotationUtils.findAnnotation(service.getClass(), Remote.class);
try {
= (RemoteExporter) remote.serviceExporter().newInstance();
remoteExporter .setService(service);
remoteExporter.setServiceInterface(serviceInterface);
remoteExporterif (remoteExporter instanceof RmiServiceExporter) {
((RmiServiceExporter) remoteExporter).setServiceName(serviceInterface.getName());
}
if (remoteExporter instanceof InitializingBean) {
((InitializingBean) remoteExporter).afterPropertiesSet();
}
} catch (Exception e) {
.printStackTrace();
e}
return remoteExporter;
}
}
In short, a class which implements a @Remote
-annotated
interface may be instantiated and exposed as a Spring Remoting service
by including only two lines in the context configuration:
context:remote-export />
<context:component-scan base-package="path.to.service.annotated.implementation" /> <
There are trade-offs in both this and my previous method, but with the architectural improvements here it would be hard to argue going back.