Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce annotation-based configuration for remoting [SPR-3926] #8606

Closed
spring-issuemaster opened this issue Sep 26, 2007 · 13 comments
Closed

Introduce annotation-based configuration for remoting [SPR-3926] #8606

spring-issuemaster opened this issue Sep 26, 2007 · 13 comments

Comments

@spring-issuemaster
Copy link
Collaborator

@spring-issuemaster spring-issuemaster commented Sep 26, 2007

Janos Haber opened SPR-3926 and commented

Please introduce annotation-based configuration for remoting -- for example:

@Service
@ExportAs({ServiceType.HESSIAN, ServiceType.BURLAP})
public class MyService {
    // ...
 }

or another way.


Attachments:

Issue Links:

  • #17238 Add support for automatic remote export of services using Spring Remoting

17 votes, 19 watchers

@spring-issuemaster
Copy link
Collaborator Author

@spring-issuemaster spring-issuemaster commented Mar 7, 2009

James Earl Douglas commented

Here's a first crack at this feature.

Added Service and ServiceType to org.springframework.remoting
Added ServiceAnnotationBeanPostProcessor to org.springframework.beans.factory.annotation
Added handling of ServiceAnnotationBeanPostProcessor to AnnotationConfigUtils in org.springframework.context.annotation

A class to be exposed as a remoting service is annotated with @Service(serviceInterface = ..., serviceType = ServiceType.HTTP), where serviceInterface specifies the interface that the service will be exposed as and serviceType specifies HTTP, HESSIAN, BURLAP, or RMI

Then annotation configuration is enabled in the context, and the bean is configured with the name it will be exposed as:
<context:annotation-config />
<bean name="/TestService" class="org.springframework.TestServiceImpl" />

Then it is a simple matter to link the service in at the client in the normal way:
<bean id="testService"
class="org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean">
<property name="serviceUrl" value="http://localhost:8080/SPR-3926/TestService" />
<property name="serviceInterface" value="org.springframework.TestService" />
</bean>

@spring-issuemaster
Copy link
Collaborator Author

@spring-issuemaster spring-issuemaster commented Mar 7, 2009

James Earl Douglas commented

The attached #8606.war may be deployed to a Tomcat server to test exposing a remoting service via the @Service annotation, and the included ServiceTest may be run to test linking the service in a a client.

@spring-issuemaster
Copy link
Collaborator Author

@spring-issuemaster spring-issuemaster commented Mar 9, 2009

James Earl Douglas commented

Here's the source code for the aforementioned war file.

@spring-issuemaster
Copy link
Collaborator Author

@spring-issuemaster spring-issuemaster commented Jun 10, 2009

James Earl Douglas commented

After some very helpful advice from Chris Beams, I have reworked most of the code that I originally wrote.

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.

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-2.5.xsd, which is used by adding <context:remote-export /> to the context configuration:

<xsd:element name="remote-export">
	<xsd:annotation>
		<xsd:documentation><![CDATA[
	Exports classes whose interfaces have been annotated with @Remote as Spring Remoting services.
		]]></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());
}
class RemoteBeanDefinitionParser implements BeanDefinitionParser {
	public BeanDefinition parse(Element element, ParserContext parserContext) {
		RootBeanDefinition bd = new RootBeanDefinition(RemotingExporter.class);
		bd.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
		bd.setSource(parserContext.extractSource(element));
		parserContext.registerBeanComponent(new BeanComponentDefinition(bd, RemotingExporter.class.getName()));
		return 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 {

	@SuppressWarnings("unchecked")
	@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) {
				RemoteExporter remoteExporter = createRemoteExporter(serviceInterface, bean);
				beanFactory.registerSingleton("/" + serviceInterface.getName(), remoteExporter);
			}
		}
	}

	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)) {
					serviceInterface = interfaceClass;
				}
			}
		}
		return serviceInterface;
	}

	private RemoteExporter createRemoteExporter(Class serviceInterface, Object service) {
		RemoteExporter remoteExporter = null;
		Remote remote = AnnotationUtils.findAnnotation(service.getClass(), Remote.class);
		try {
			remoteExporter = (RemoteExporter) remote.serviceExporter().newInstance();
			remoteExporter.setService(service);
			remoteExporter.setServiceInterface(serviceInterface);
			if (remoteExporter instanceof RmiServiceExporter) {
				((RmiServiceExporter) remoteExporter).setServiceName(serviceInterface.getName());
			}
			if (remoteExporter instanceof InitializingBean) {
				((InitializingBean) remoteExporter).afterPropertiesSet();
			}
		}
		catch (Exception e) {
			e.printStackTrace();
		}
		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" />
@spring-issuemaster
Copy link
Collaborator Author

@spring-issuemaster spring-issuemaster commented Oct 27, 2009

Lukas Herman commented

I have found useful to perform additional checks before calling beanFactory.getBean(beanName);. Without isAbstract() checking it may fail with BeanIsAbstractException.

...
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
		for (String beanName : beanFactory.getBeanDefinitionNames()) { 
                        BeanDefinition beanDef = beanFactory.getBeanDefinition(beanName);
                        if (beanDef.isAbstract()) {
                                continue;
                        }
			Object bean = beanFactory.getBean(beanName);
			Class serviceInterface = findServiceInterface(bean);
			if (serviceInterface != null) {
				RemoteExporter remoteExporter = createRemoteExporter(serviceInterface, bean);
				beanFactory.registerSingleton("/" + serviceInterface.getName(), remoteExporter);
			}
		}
	}
...
@spring-issuemaster
Copy link
Collaborator Author

@spring-issuemaster spring-issuemaster commented Oct 28, 2009

Lukas Herman commented

In a typical usage scenario, where set of @Service objects is defined in root application context, and web.xml defines DispatcherServlet for remoting only, there is a need for extensibility point to look up bean definitions in parent context. And possibility to redefine the way how the resulting singleton is registered seems important.
This implementation registers remote service under /name specified in @Service annotation, fallbacking to service interface class name. Seems like sensible default for me.

It also unpacks TargetClassAware proxy classes before looking up Service annotations.

public class RemotingExporter implements BeanFactoryPostProcessor {
    protected final Log log = LogFactory.getLog(getClass());

    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        for (String beanName : BeanFactoryUtils.beanNamesIncludingAncestors(beanFactory)) {
            BeanDefinition beanDef;
            try {
                beanDef = getBeanDefinition(beanName, beanFactory);
            } catch (NoSuchBeanDefinitionException e) {
                log.warn(e.getMessage());
                continue;
            }
            if (beanDef.isAbstract()) {
                continue;
            }
            Object bean = beanFactory.getBean(beanName);
            Class serviceInterface = findServiceInterface(bean);
            if (serviceInterface != null) {
                RemoteExporter remoteExporter = createRemoteExporter(serviceInterface, bean);
                beanFactory.registerSingleton("/" + getExporterBeanName(bean), remoteExporter);
            }
        }
    }

    protected BeanDefinition getBeanDefinition(String beanName, ConfigurableListableBeanFactory beanFactory) throws BeansException {
        BeanDefinition beanDef = null;
        do {
            try {
                beanDef = beanFactory.getBeanDefinition(beanName);
            } catch (NoSuchBeanDefinitionException e) {
                final BeanFactory parent = beanFactory.getParentBeanFactory();
                if (parent != null && parent instanceof ListableBeanFactory) {
                    beanFactory = (DefaultListableBeanFactory) parent;
                } else {
                    throw e;
                }
            }
        } while (beanDef == null && beanFactory != null);
        return beanDef;
    }

    protected String getExporterBeanName(Object service) {
        Class serviceClass;
        // unpack proxy interfaces
        if (service instanceof TargetClassAware) {
            serviceClass = ((TargetClassAware) service).getTargetClass();
        } else {
            serviceClass = service.getClass();
        }
        if (AnnotationUtils.isAnnotationDeclaredLocally(Service.class, serviceClass)) {
            Annotation serviceAnnotation = serviceClass.getAnnotation(Service.class);
            Object serviceAnnotationValue = AnnotationUtils.getValue(serviceAnnotation);
            if ("".equals(serviceAnnotationValue)) {
                for (Class interfaceClass : service.getClass().getInterfaces()) {
                    if (AnnotationUtils.isAnnotationDeclaredLocally(Remote.class, interfaceClass)) {
                        return interfaceClass.getName();
                    }
                }
            } else {
                return serviceAnnotationValue.toString();
            }
        }
        // no annotations found
        return service.toString();
    }

    private Class findServiceInterface(Object service) {
        Class serviceInterface = null;
        Class serviceClass;
        // unpack proxy interfaces
        if (service instanceof TargetClassAware) {
            serviceClass = ((TargetClassAware) service).getTargetClass();
        } else {
            serviceClass = service.getClass();
        }
        if (AnnotationUtils.isAnnotationDeclaredLocally(Service.class, serviceClass)) {
            for (Class interfaceClass : serviceClass.getInterfaces()) {
                if (AnnotationUtils.isAnnotationDeclaredLocally(Remote.class, interfaceClass)) {
                    serviceInterface = interfaceClass;
                }
            }
        }
        return serviceInterface;
    }

    private RemoteExporter createRemoteExporter(Class serviceInterface, Object service) {
        RemoteExporter remoteExporter = null;
        Remote remote = AnnotationUtils.findAnnotation(service.getClass(), Remote.class);
        try {
            remoteExporter = (RemoteExporter) remote.serviceExporter().newInstance();
            remoteExporter.setService(service);
            remoteExporter.setServiceInterface(serviceInterface);
            if (remoteExporter instanceof RmiServiceExporter) {
                ((RmiServiceExporter) remoteExporter).setServiceName(serviceInterface.getName());
            }
            if (remoteExporter instanceof InitializingBean) {
                ((InitializingBean) remoteExporter).afterPropertiesSet();
            }
        }
        catch (Exception e) {
            log.error(e);
        }
        return remoteExporter;
    }
}
@spring-issuemaster
Copy link
Collaborator Author

@spring-issuemaster spring-issuemaster commented Apr 27, 2010

Henno Vermeulen commented

Looks very promising. I see two issues:

  • Coupling to @Service annotation. I would like to directly expose my DAO's which have an @Repository annotation or expose other classes that don't have an annotation at the moment.
  • Each service specifies which service exporter it uses. Suppose I use RMI for all my services but decide to switch to http. Then I would need to adjust each annotation.

Is there also a way to use classpath scanning to automate the remote lookup of the same interfaces? This would be very useful for us to avoid boilerplate XML code for DAO's at the client side. I don't think it would be logical to do this with a component-scan + post processor because there are no custom classes to instantiate at the client, only the proxies. But the same classpath scanning mechanism as component-scan could be used.

@spring-issuemaster
Copy link
Collaborator Author

@spring-issuemaster spring-issuemaster commented Apr 27, 2010

Henno Vermeulen commented

Especially if there would be support for automatic client-side lookup by scanning interfaces with @Remote annotations, it isn't logical to have the @Remote annotation include a server side implementation class (the exporter to use) instead of only the type of remoting protocol.

@spring-issuemaster
Copy link
Collaborator Author

@spring-issuemaster spring-issuemaster commented Apr 27, 2010

Henno Vermeulen commented

Another use case would be to have one interface with multiple (remoted) implementations...
The whole service concept is closely related to the way Spring-DM and OSGI exposes and looks up services in an OSGI environment. Unfortunately I again have boilerplate code for exposal and lookup of my DAO services because I use Spring-DM at the client side in Eclipse RCP. For Spring-DM my perfect solution would re-use the same annotation ("@Remote" but possibly renamed to @ServiceInterface) to automatically expose and possibly lookup my DAO's as OSGi services...

@spring-issuemaster
Copy link
Collaborator Author

@spring-issuemaster spring-issuemaster commented Apr 29, 2010

Henno Vermeulen commented

I tested your RemotingExporter and it works fine for a simple test.
The code is very easy to adjust to my liking to use any annotation with a "value" instead of only @Service and to configure it with one RemoteExporter class to use for all remotely exposed beans. If you want multiple services of the same interface, it is also easily adjusted to expose it using for example the service name instead of the interface name.

So far so good, but I found one showstopping issue in case you rely on Spring AOP/autowiring magic: the postProcessBeanFactory method is called even before beans are instantiated. The line Object bean = beanFactory.getBean(beanName); eagerly instantiates a bean even if it doesn't need to be exposed! This can have unwanted side effects. When I added the RemotingExporter as a bean to my Spring context, the org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor did not inject an entityManager in my DAO's anymore even though I nowhere used the @Remote annotation.
This problem can be solved by calling Object bean = beanFactory.getBean(beanName); AFTER looking for the @Remote annotation by adjusting the findServiceInterface to use the Class instead of the bean: findServiceInterface(Class.forName(beanDef.getBeanClassName())).

Unfortunately, when I DO use the @Remote annotation the bean doesn't get post processed. We also have many aspects that hook into our DAO beans and I don't think that these are applied either.

I don't know what would be the best way to solve this problem. I will probably try to apply the RemotingExporter after the entire context is created.
Another way would be to use a BeanPostProcessor instead of a BeanFactoryPostProcessor but I don't know if and how a BeanPostProcessor should add new beans instead while keeping the original intact.

@spring-issuemaster
Copy link
Collaborator Author

@spring-issuemaster spring-issuemaster commented Apr 29, 2010

Henno Vermeulen commented

Okay, here is my version of RemotingExporter which works correctly with BeanPostProcessors and AOP by doing the export AFTER the Spring context is refreshed. I'm not sure if this could give problems so use at your own risk!!!

Services are still exported under the name of the interface.
I did rename @Remote to @ServiceInterface and removed the remoting protocol dependency but these changes are very easy to revert if needed. I gave the @ServiceInterface annotation a value which is not used yet (might be used in the future for the bean name at the client side for a RemotingImporter...)

import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ApplicationContextEvent;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.event.ContextStartedEvent;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.remoting.rmi.RmiServiceExporter;
import org.springframework.remoting.support.RemoteExporter;
import org.springframework.stereotype.Service;

/**
 * Provides automatic remote export of services found inside a Spring context.
 * 
 * <p>
 * When a bean is annotated with {@link Service} <em>and</em> implements an
 * interface annotated with {@link ServiceInterface}, the bean will be exported
 * as a service with this interface. This is done <em>after</em> the Spring
 * context is refreshed or started so that we are sure that any
 * {@link BeanPostProcessor}s and AOP advise is applied.
 * 
 * <p>
 * This bean can be configured with any {@link RemoteExporter}. When using an
 * {@link RmiServiceExporter} the service will be exposed under the complete
 * name of the service interface as returned by {@link Class#getName()}.
 * 
 * <p>
 * Based on RemotingExporter from <a
 * href=http://jira.springframework.org/browse/SPR-3926>Jira issue SPR-3926</a>
 * 
 * @author James Douglas
 * @author Henno Vermeulen
 */
public class RemotingExporter implements BeanFactoryPostProcessor,
        ApplicationListener<ApplicationContextEvent>, DisposableBean {

    private static final Log LOG = LogFactory.getLog(RemotingExporter.class);

    private Class<? extends RemoteExporter> exporterClass;
    private Class<? extends Annotation> serviceAnnotationType;

    private List<ServiceInfo> serviceInfos = new ArrayList<ServiceInfo>();
    private List<DisposableBean> disposables = new ArrayList<DisposableBean>();

    public RemotingExporter() {
        this(RmiServiceExporter.class);
    }

    public RemotingExporter(Class<? extends RemoteExporter> exporterClass) {
        this(exporterClass, Service.class);
    }

    public RemotingExporter(Class<? extends RemoteExporter> exporterClass,
            Class<? extends Annotation> serviceAnnotationType) {
        this.serviceAnnotationType = serviceAnnotationType;
        this.exporterClass = exporterClass;
    }

    /**
     * {@inheritDoc}
     * 
     * We look for the service annotation on the bean class found in the
     * {@link BeanDefinition} before any {@link BeanPostProcessor}s or AOP
     * advice is applied.
     */
    @Override
    public void postProcessBeanFactory(
            ConfigurableListableBeanFactory beanFactory) throws BeansException {
        LOG.info("processing beans for remote export");
        for (String beanName : beanFactory.getBeanDefinitionNames()) {
            BeanDefinition beanDef = beanFactory.getBeanDefinition(beanName);
            if (beanDef.isAbstract()) {
                LOG.info("skipping abstract bean '" + beanName + "'");
                continue;
            }
            Class<?> serviceInterface = null;
            Class<?> serviceClass;
            try {
                serviceClass = Class.forName(beanDef.getBeanClassName());
                serviceInterface = findServiceInterface(serviceClass);
            } catch (ClassNotFoundException e) {
                throw new RuntimeException("class of bean " + beanName
                        + " not found", e);
            }
            if (serviceInterface != null) {
                LOG.debug("found service bean with name '" + beanName
                        + "' and interface " + serviceInterface);
                serviceInfos.add(new ServiceInfo(beanName, serviceInterface,
                        serviceInterface.getName()));
            } else {
                LOG
                        .debug("skipping bean because no remotable interface was found '"
                                + beanName + "'");
            }
        }
    }

    protected BeanDefinition getBeanDefinition(String beanName,
            ConfigurableListableBeanFactory beanFactory) throws BeansException {
        BeanDefinition beanDef = null;
        do {
            try {
                beanDef = beanFactory.getBeanDefinition(beanName);
            } catch (NoSuchBeanDefinitionException e) {
                final BeanFactory parent = beanFactory.getParentBeanFactory();
                if (parent != null && parent instanceof ListableBeanFactory) {
                    beanFactory = (DefaultListableBeanFactory) parent;
                } else {
                    throw e;
                }
            }
        } while (beanDef == null && beanFactory != null);
        return beanDef;
    }

    private Class findServiceInterface(Class serviceClass) {
        Class serviceInterface = null;
        if (AnnotationUtils.isAnnotationDeclaredLocally(
                getServiceAnnotationType(), serviceClass)) {
            for (Class interfaceClass : serviceClass.getInterfaces()) {
                if (AnnotationUtils.isAnnotationDeclaredLocally(
                        ServiceInterface.class, interfaceClass)) {
                    serviceInterface = interfaceClass;
                }
            }
        }
        return serviceInterface;
    }

    private void createExporters(ApplicationContext context) {
        LOG.debug("exposing " + serviceInfos.size()
                + " services that were found.");
        for (ServiceInfo service : serviceInfos) {
            Object bean = context.getBean(service.getBeanName());
            LOG.debug("creating remote exporter for bean '"
                    + service.getBeanName() + "'");
            RemoteExporter remoteExporter = createRemoteExporter(service
                    .getBeanInterface(), bean, service.getExposedServiceName());
            if (remoteExporter instanceof DisposableBean) {
                disposables.add((DisposableBean) remoteExporter);
            }
        }
    }

    private RemoteExporter createRemoteExporter(Class serviceInterface,
            Object service, String serviceName) {
        LOG.info("Exposing service " + service + " using service name "
                + serviceName + " and interface " + serviceInterface.getName());
        try {
            RemoteExporter remoteExporter = getExporterClass().newInstance();
            remoteExporter.setService(service);
            remoteExporter.setServiceInterface(serviceInterface);
            if (remoteExporter instanceof RmiServiceExporter) {
                ((RmiServiceExporter) remoteExporter)
                        .setServiceName(serviceName);
            }
            if (remoteExporter instanceof InitializingBean) {
                ((InitializingBean) remoteExporter).afterPropertiesSet();
            }
            return remoteExporter;
        } catch (Exception e) {
            throw new RuntimeException(
                    "exception while creating remote exporter", e);
        }
    }

    public void setExporterClass(Class<? extends RemoteExporter> exporterClass) {
        this.exporterClass = exporterClass;
    }

    public Class<? extends RemoteExporter> getExporterClass() {
        return exporterClass;
    }

    public void setServiceAnnotationType(
            Class<? extends Annotation> serviceAnnotationType) {
        this.serviceAnnotationType = serviceAnnotationType;
    }

    public Class<? extends Annotation> getServiceAnnotationType() {
        return serviceAnnotationType;
    }

    @Override
    public void onApplicationEvent(ApplicationContextEvent event) {
        if ((event instanceof ContextRefreshedEvent)
                || (event instanceof ContextStartedEvent)) {
            createExporters(event.getApplicationContext());
        }
    }

    @Override
    public void destroy() throws Exception {
        for (DisposableBean bean : disposables) {
            bean.destroy();
        }
    }

    private static final class ServiceInfo {
        private String beanName;
        private String exposedServiceName;
        private Class beanInterface;

        public ServiceInfo(String beanName, Class beanInterface,
                String exposedServiceName) {
            this.beanName = beanName;
            this.beanInterface = beanInterface;
            this.exposedServiceName = exposedServiceName;
        }

        public String getExposedServiceName() {
            return exposedServiceName;
        }

        public String getBeanName() {
            return beanName;
        }

        public Class getBeanInterface() {
            return beanInterface;
        }
    }

}
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.springframework.stereotype.Component;

/**
 * Indicates that an annotated class is a "Service" interface (e.g. a business
 * service facade).
 * 
 * <p>
 * This annotation serves as a specialization of {@link Component @Component},
 * allowing for interface classes to be autodetected through classpath scanning.
 * 
 * @author Henno Vermeulen
 * @see Component
 * @see org.springframework.context.annotation.ClassPathBeanDefinitionScanner
 */
@Target( { ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface ServiceInterface {

    /**
     * The value may indicate a suggestion for a logical component name, to be
     * turned into a Spring bean in case of an autodetected component.
     * 
     * @return the suggested component name, if any
     */
    String value() default "";

}
@spring-issuemaster
Copy link
Collaborator Author

@spring-issuemaster spring-issuemaster commented Aug 17, 2013

Adam Walczak - WALCZAK.IT commented

inspired by Henno Vermeulens code I wrote an implementation of both the exporter and accessor (importer) for RMI and Hessian with support for Java Configuration:

https://bitbucket.org/walczak_it/prodoko-base/src/HEAD/prodoko-remoting?at=master

Hessian implementation also has support for authentication. You can use it like this:

@Configuration
@ComponentScan("com.prodoko.remoting.testservices")
@EnableHessianExport
public class HessianServerConfig {

}

The above will export all @Serive annotated components which implement an @ServiceInterface annotated interface.

Now you can connect with them by just autowireing their interfaces with the following client configuration:
(we assume in this example that the String dispatcher servlet with the exposed some services on the local server was mapped to the /api/* path)

@Configuration
@EnableHessianAccess(basePackages="com.prodoko.remoting.testservices",
        host="localhost", port=8080, pathPreffix="api/")
public class HessianClientConfig {

}

u can also pass the authentication data via annotation

@Configuration
@EnableHessianAccess(basePackages="com.prodoko.remoting.testcontext", pathPreffix="api/",
        host="localhost", port=8080, useSsl=true, username="someusr", password="somepass")
public class SecureHessianClientConfig {
}

or you can pass the client connection properties via a properties file (this requires Spring 4.0.0.M2)

@Configuration
@PropertySource(value="file:conn.properties")
@EnableHessianAccess(basePackages="com.prodoko.remoting.testservices", pathPreffix="api/")
public class SecureHessianClientWithoutConnDataConfig {
    
}

the file in this case must have properties with the following names:

prodoko.remoting.hessian.host = localhost
prodoko.remoting.hessian.port = 8080
prodoko.remoting.hessian.username = someusr
prodoko.remoting.hessian.password = somepass
prodoko.remoting.hessian.useSsl = true

I used the properties file approach because I couldn't think of any better way to pass these params :o)
In my app I actually programmatically pass a Properties map with params from a login form to application context before loading it.

Any suggestions for a better way to pass client connection parameters would be welcomed.

@spring-issuemaster
Copy link
Collaborator Author

@spring-issuemaster spring-issuemaster commented Jun 2, 2016

Juergen Hoeller commented

I'm afraid this is not likely to happen at this point: Our focus is rather different these days, and we generally recommend remote exposure for specific service facades only... Also, we'd rather suggest custom REST-oriented protocols over HTTP, avoiding the pitfalls and security vulnerabilities of Java deserialization (with HTTP Invoker and RMI Invoker) as well as the dependencies on vendor-driven protocols (Hessian and Burlap).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
2 participants
You can’t perform that action at this time.