![]() |
Spring features integration classes for remoting support using various technologies. The remoting support eases the development of remote-enabled services, implemented by your usual (Spring) POJOs. Currently, Spring supports the following remoting technologies:
While discussing the remoting capabilities of Spring, we’ll use the following domain model and corresponding services: public class Account implements Serializable{ private String name; public String getName(){ return name; } public void setName(String name) { this.name = name; } } public interface AccountService { public void insertAccount(Account account); public List<Account> getAccounts(String name); } public interface RemoteAccountService extends Remote { public void insertAccount(Account account) throws RemoteException; public List<Account> getAccounts(String name) throws RemoteException; } // the implementation doing nothing at the moment public class AccountServiceImpl implements AccountService { public void insertAccount(Account acc) { // do something... } public List<Account> getAccounts(String name) { // do something... } } We will start exposing the service to a remote client by using RMI and talk a bit about the drawbacks of using RMI. We’ll then continue to show an example using Hessian as the protocol. Using Spring’s support for RMI, you can transparently expose your services through the RMI infrastructure. After having this set up, you basically have a configuration similar to remote EJBs, except for the fact that there is no standard support for security context propagation or remote transaction propagation. Spring does provide hooks for such additional invocation context when using the RMI invoker, so you can for example plug in security frameworks or custom security credentials here. Using the Of course, we first have to set up our service in the Spring container: <bean id="accountService" class="example.AccountServiceImpl"> <!-- any additional properties, maybe a DAO? --> </bean> Next we’ll have to expose our service using the <bean class="org.springframework.remoting.rmi.RmiServiceExporter"> <!-- does not necessarily have to be the same name as the bean to be exported --> <property name="serviceName" value="AccountService"/> <property name="service" ref="accountService"/> <property name="serviceInterface" value="example.AccountService"/> <!-- defaults to 1099 --> <property name="registryPort" value="1199"/> </bean> As you can see, we’re overriding the port for the RMI registry. Often, your application
server also maintains an RMI registry and it is wise to not interfere with that one.
Furthermore, the service name is used to bind the service under. So right now, the
service will be bound at
Our client is a simple object using the public class SimpleObject { private AccountService accountService; public void setAccountService(AccountService accountService) { this.accountService = accountService; } // additional methods using the accountService } To link in the service on the client, we’ll create a separate Spring container, containing the simple object and the service linking configuration bits: <bean class="example.SimpleObject"> <property name="accountService" ref="accountService"/> </bean> <bean id="accountService" class="org.springframework.remoting.rmi.RmiProxyFactoryBean"> <property name="serviceUrl" value="rmi://HOST:1199/AccountService"/> <property name="serviceInterface" value="example.AccountService"/> </bean> That’s all we need to do to support the remote account service on the client. Spring
will transparently create an invoker and remotely enable the account service through the
Hessian offers a binary HTTP-based remoting protocol. It is developed by Caucho and more information about Hessian itself can be found at http://www.caucho.com. Hessian communicates via HTTP and does so using a custom servlet. Using Spring’s
<servlet> <servlet-name>remoting</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>remoting</servlet-name> <url-pattern>/remoting/*</url-pattern> </servlet-mapping> You’re probably familiar with Spring’s Alternatively, consider the use of Spring’s simpler In the newly created application context called <bean id="accountService" class="example.AccountServiceImpl"> <!-- any additional properties, maybe a DAO? --> </bean> <bean name="/AccountService" class="org.springframework.remoting.caucho.HessianServiceExporter"> <property name="service" ref="accountService"/> <property name="serviceInterface" value="example.AccountService"/> </bean> Now we’re ready to link in the service at the client. No explicit handler mapping is
specified, mapping request URLs onto services, so Alternatively, create a <bean name="accountExporter" class="org.springframework.remoting.caucho.HessianServiceExporter"> <property name="service" ref="accountService"/> <property name="serviceInterface" value="example.AccountService"/> </bean> In the latter case, define a corresponding servlet for this exporter in <servlet> <servlet-name>accountExporter</servlet-name> <servlet-class>org.springframework.web.context.support.HttpRequestHandlerServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>accountExporter</servlet-name> <url-pattern>/remoting/AccountService</url-pattern> </servlet-mapping> Using the <bean class="example.SimpleObject"> <property name="accountService" ref="accountService"/> </bean> <bean id="accountService" class="org.springframework.remoting.caucho.HessianProxyFactoryBean"> <property name="serviceUrl" value="http://remotehost:8080/remoting/AccountService"/> <property name="serviceInterface" value="example.AccountService"/> </bean> We won’t discuss Burlap, the XML-based equivalent of Hessian, in detail here, since it
is configured and set up in exactly the same way as the Hessian variant explained above.
Just replace the word One of the advantages of Hessian and Burlap is that we can easily apply HTTP basic
authentication, because both protocols are HTTP-based. Your normal HTTP server security
mechanism can easily be applied through using the <bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"> <property name="interceptors" ref="authorizationInterceptor"/> </bean> <bean id="authorizationInterceptor" class="org.springframework.web.servlet.handler.UserRoleAuthorizationInterceptor"> <property name="authorizedRoles" value="administrator,operator"/> </bean> This is an example where we explicitly mention the
As opposed to Burlap and Hessian, which are both lightweight protocols using their own slim serialization mechanisms, Spring HTTP invokers use the standard Java serialization mechanism to expose services through HTTP. This has a huge advantage if your arguments and return types are complex types that cannot be serialized using the serialization mechanisms Hessian and Burlap use (refer to the next section for more considerations when choosing a remoting technology). Under the hood, Spring uses either the standard facilities provided by the JDK or
Apache Setting up the HTTP invoker infrastructure for a service object resembles closely the
way you would do the same using Hessian or Burlap. Just as Hessian support provides the
To expose the <bean name="/AccountService" class="org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter"> <property name="service" ref="accountService"/> <property name="serviceInterface" value="example.AccountService"/> </bean> Such an exporter definition will be exposed through the Alternatively, create an <bean name="accountExporter" class="org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter"> <property name="service" ref="accountService"/> <property name="serviceInterface" value="example.AccountService"/> </bean> In addition, define a corresponding servlet for this exporter in <servlet> <servlet-name>accountExporter</servlet-name> <servlet-class>org.springframework.web.context.support.HttpRequestHandlerServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>accountExporter</servlet-name> <url-pattern>/remoting/AccountService</url-pattern> </servlet-mapping> If you are running outside of a servlet container and are using Oracle’s Java 6, then you
can use the built-in HTTP server implementation. You can configure the
<bean name="accountExporter" class="org.springframework.remoting.httpinvoker.SimpleHttpInvokerServiceExporter"> <property name="service" ref="accountService"/> <property name="serviceInterface" value="example.AccountService"/> </bean> <bean id="httpServer" class="org.springframework.remoting.support.SimpleHttpServerFactoryBean"> <property name="contexts"> <util:map> <entry key="/remoting/AccountService" value-ref="accountExporter"/> </util:map> </property> <property name="port" value="8080" /> </bean> Again, linking in the service from the client much resembles the way you would do it when using Hessian or Burlap. Using a proxy, Spring will be able to translate your calls to HTTP POST requests to the URL pointing to the exported service. <bean id="httpInvokerProxy" class="org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean"> <property name="serviceUrl" value="http://remotehost:8080/remoting/AccountService"/> <property name="serviceInterface" value="example.AccountService"/> </bean> As mentioned before, you can choose what HTTP client you want to use. By default, the
<property name="httpInvokerRequestExecutor"> <bean class="org.springframework.remoting.httpinvoker.HttpComponentsHttpInvokerRequestExecutor"/> </property> Spring provides full support for standard Java web services APIs:
In addition to stock support for JAX-WS in Spring Core, the Spring portfolio also features Spring Web Services, a solution for contract-first, document-driven web services - highly recommended for building modern, future-proof web services. Spring provides a convenient base class for JAX-WS servlet endpoint implementations -
/** * JAX-WS compliant AccountService implementation that simply delegates * to the AccountService implementation in the root web application context. * * This wrapper class is necessary because JAX-WS requires working with dedicated * endpoint classes. If an existing service needs to be exported, a wrapper that * extends SpringBeanAutowiringSupport for simple Spring bean autowiring (through * the @Autowired annotation) is the simplest JAX-WS compliant way. * * This is the class registered with the server-side JAX-WS implementation. * In the case of a Java EE 5 server, this would simply be defined as a servlet * in web.xml, with the server detecting that this is a JAX-WS endpoint and reacting * accordingly. The servlet name usually needs to match the specified WS service name. * * The web service engine manages the lifecycle of instances of this class. * Spring bean references will just be wired in here. */ import org.springframework.web.context.support.SpringBeanAutowiringSupport; @WebService(serviceName="AccountService") public class AccountServiceEndpoint extends SpringBeanAutowiringSupport { @Autowired private AccountService biz; @WebMethod public void insertAccount(Account acc) { biz.insertAccount(acc); } @WebMethod public Account[] getAccounts(String name) { return biz.getAccounts(name); } } Our The built-in JAX-WS provider that comes with Oracle’s JDK 1.6 supports exposure of web
services using the built-in HTTP server that’s included in JDK 1.6 as well. Spring’s
In this scenario, the endpoint instances are defined and managed as Spring beans
themselves; they will be registered with the JAX-WS engine but their lifecycle will be
up to the Spring application context. This means that Spring functionality like explicit
dependency injection may be applied to the endpoint instances. Of course,
annotation-driven injection through <bean class="org.springframework.remoting.jaxws.SimpleJaxWsServiceExporter"> <property name="baseAddress" value="http://localhost:8080/"/> </bean> <bean id="accountServiceEndpoint" class="example.AccountServiceEndpoint"> ... </bean> ... The @WebService(serviceName="AccountService") public class AccountServiceEndpoint { @Autowired private AccountService biz; @WebMethod public void insertAccount(Account acc) { biz.insertAccount(acc); } @WebMethod public List<Account> getAccounts(String name) { return biz.getAccounts(name); } } Oracle’s JAX-WS RI, developed as part of the GlassFish project, ships Spring support as part of its JAX-WS Commons project. This allows for defining JAX-WS endpoints as Spring-managed beans, similar to the standalone mode discussed in the previous section - but this time in a Servlet environment. Note that this is not portable in a Java EE 5 environment; it is mainly intended for non-EE environments such as Tomcat, embedding the JAX-WS RI as part of the web application. The difference to the standard style of exporting servlet-based endpoints is that the
lifecycle of the endpoint instances themselves will be managed by Spring here, and that
there will be only one JAX-WS servlet defined in Check out https://jax-ws-commons.java.net/spring/ for details on setup and usage style. Spring provides two factory beans to create JAX-WS web service proxies, namely
<bean id="accountWebService" class="org.springframework.remoting.jaxws.JaxWsPortProxyFactoryBean"> <property name="serviceInterface" value="example.AccountService"/> <property name="wsdlDocumentUrl" value="http://localhost:8888/AccountServiceEndpoint?WSDL"/> <property name="namespaceUri" value="http://example/"/> <property name="serviceName" value="AccountService"/> <property name="portName" value="AccountServiceEndpointPort"/> </bean> Where Accessing the web service is now very easy as we have a bean factory for it that will
expose it as <bean id="client" class="example.AccountClientImpl"> ... <property name="service" ref="accountWebService"/> </bean> From the client code we can access the web service just as if it was a normal class: public class AccountClientImpl { private AccountService service; public void setService(AccountService service) { this.service = service; } public void foo() { service.insertAccount(...); } }
It is also possible to expose services transparently using JMS as the underlying
communication protocol. The JMS remoting support in the Spring Framework is pretty basic
- it sends and receives on the The following interface is used on both the server and the client side. package com.foo; public interface CheckingAccountService { public void cancelAccount(Long accountId); } The following simple implementation of the above interface is used on the server-side. package com.foo; public class SimpleCheckingAccountService implements CheckingAccountService { public void cancelAccount(Long accountId) { System.out.println("Cancelling account [" + accountId + "]"); } } This configuration file contains the JMS-infrastructure beans that are shared on both the client and server. <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="connectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory"> <property name="brokerURL" value="tcp://ep-t43:61616"/> </bean> <bean id="queue" class="org.apache.activemq.command.ActiveMQQueue"> <constructor-arg value="mmm"/> </bean> </beans> On the server, you just need to expose the service object using the
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="checkingAccountService" class="org.springframework.jms.remoting.JmsInvokerServiceExporter"> <property name="serviceInterface" value="com.foo.CheckingAccountService"/> <property name="service"> <bean class="com.foo.SimpleCheckingAccountService"/> </property> </bean> <bean class="org.springframework.jms.listener.SimpleMessageListenerContainer"> <property name="connectionFactory" ref="connectionFactory"/> <property name="destination" ref="queue"/> <property name="concurrentConsumers" value="3"/> <property name="messageListener" ref="checkingAccountService"/> </bean> </beans> package com.foo; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Server { public static void main(String[] args) throws Exception { new ClassPathXmlApplicationContext(new String[]{"com/foo/server.xml", "com/foo/jms.xml"}); } } The client merely needs to create a client-side proxy that will implement the agreed
upon interface ( <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="checkingAccountService" class="org.springframework.jms.remoting.JmsInvokerProxyFactoryBean"> <property name="serviceInterface" value="com.foo.CheckingAccountService"/> <property name="connectionFactory" ref="connectionFactory"/> <property name="queue" ref="queue"/> </bean> </beans> package com.foo; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Client { public static void main(String[] args) throws Exception { ApplicationContext ctx = new ClassPathXmlApplicationContext( new String[] {"com/foo/client.xml", "com/foo/jms.xml"}); CheckingAccountService service = (CheckingAccountService) ctx.getBean("checkingAccountService"); service.cancelAccount(new Long(10)); } } You may also wish to investigate the support provided by the Lingo project, which (to quote the homepage blurb) "… is a lightweight POJO based remoting and messaging library based on the Spring Framework’s remoting libraries which extends it to support JMS." Refer to the Spring AMQP Reference Document Spring Remoting with AMQP section for more information. The main reason why auto-detection of implemented interfaces does not occur for remote
interfaces is to avoid opening too many doors to remote callers. The target object might
implement internal callback interfaces like Offering a proxy with all interfaces implemented by the target usually does not matter in the local case. But when exporting a remote service, you should expose a specific service interface, with specific operations intended for remote usage. Besides internal callback interfaces, the target might implement multiple business interfaces, with just one of them intended for remote exposure. For these reasons, we require such a service interface to be specified. This is a trade-off between configuration convenience and the risk of accidental exposure of internal methods. Always specifying a service interface is not too much effort, and puts you on the safe side regarding controlled exposure of specific methods. Each and every technology presented here has its drawbacks. You should carefully consider your needs, the services you are exposing and the objects you’ll be sending over the wire when choosing a technology. When using RMI, it’s not possible to access the objects through the HTTP protocol, unless you’re tunneling the RMI traffic. RMI is a fairly heavy-weight protocol in that it supports full-object serialization which is important when using a complex data model that needs serialization over the wire. However, RMI-JRMP is tied to Java clients: It is a Java-to-Java remoting solution. Spring’s HTTP invoker is a good choice if you need HTTP-based remoting but also rely on Java serialization. It shares the basic infrastructure with RMI invokers, just using HTTP as transport. Note that HTTP invokers are not only limited to Java-to-Java remoting but also to Spring on both the client and server side. (The latter also applies to Spring’s RMI invoker for non-RMI interfaces.) Hessian and/or Burlap might provide significant value when operating in a heterogeneous environment, because they explicitly allow for non-Java clients. However, non-Java support is still limited. Known issues include the serialization of Hibernate objects in combination with lazily-initialized collections. If you have such a data model, consider using RMI or HTTP invokers instead of Hessian. JMS can be useful for providing clusters of services and allowing the JMS broker to take care of load balancing, discovery and auto-failover. By default: Java serialization is used when using JMS remoting but the JMS provider could use a different mechanism for the wire formatting, such as XStream to allow servers to be implemented in other technologies. Last but not least, EJB has an advantage over RMI in that it supports standard role-based authentication and authorization and remote transaction propagation. It is possible to get RMI invokers or HTTP invokers to support security context propagation as well, although this is not provided by core Spring: There are just appropriate hooks for plugging in third-party or custom solutions here. The This section describes how to use the Invoking RESTful services in Java is typically done using a helper class such as Apache
HttpComponents String uri = "http://example.com/hotels/1/bookings"; PostMethod post = new PostMethod(uri); String request = // create booking request content post.setRequestEntity(new StringRequestEntity(request)); httpClient.executeMethod(post); if (HttpStatus.SC_CREATED == post.getStatusCode()) { Header location = post.getRequestHeader("Location"); if (location != null) { System.out.println("Created new booking at :" + location.getValue()); } } RestTemplate provides higher level methods that correspond to each of the six main HTTP methods that make invoking many RESTful services a one-liner and enforce REST best practices.
Table 21.1. Overview of RestTemplate methods
The names of The Objects passed to and returned from these methods are converted to and from HTTP
messages by Each method takes URI template arguments in two forms, either as a String result = restTemplate.getForObject( "http://example.com/hotels/{hotel}/bookings/{booking}", String.class,"42", "21"); using variable length arguments and Map<String, String> vars = Collections.singletonMap("hotel", "42"); String result = restTemplate.getForObject( "http://example.com/hotels/{hotel}/rooms/{hotel}", String.class, vars); using a To create an instance of
The previous example using Apache HttpComponents uri = "http://example.com/hotels/{id}/bookings"; RestTemplate template = new RestTemplate(); Booking booking = // create booking object URI location = template.postForLocation(uri, booking, "1"); To use Apache HttpComponents instead of the native RestTemplate template = new RestTemplate(new HttpComponentsClientHttpRequestFactory());
The general callback interface is public <T> T execute(String url, HttpMethod method, RequestCallback requestCallback, ResponseExtractor<T> responseExtractor, String... urlVariables) // also has an overload with urlVariables as a Map<String, String>. The public interface RequestCallback { void doWithRequest(ClientHttpRequest request) throws IOException; } and allows you to manipulate the request headers and write to the request body. When using the execute method you do not have to worry about any resource management, the template will always close the request and handle any errors. Refer to the API documentation for more information on using the execute method and the meaning of its other method arguments. For each of the main HTTP methods, the The String URI variants accept template arguments as a String variable length argument
or as a restTemplate.getForObject("http://example.com/hotel list", String.class); will perform a GET on The UriComponents uriComponents = UriComponentsBuilder.fromUriString( "http://example.com/hotels/{hotel}/bookings/{booking}").build() .expand("42", "21") .encode(); URI uri = uriComponents.toUri(); Or specify each URI component individually: UriComponents uriComponents = UriComponentsBuilder.newInstance() .scheme("http").host("example.com").path("/hotels/{hotel}/bookings/{booking}").build() .expand("42", "21") .encode(); URI uri = uriComponents.toUri(); Besides the methods described above, the Perhaps most importantly, the HttpHeaders requestHeaders = new HttpHeaders(); requestHeaders.set("MyRequestHeader", "MyValue"); HttpEntity<?> requestEntity = new HttpEntity(requestHeaders); HttpEntity<String> response = template.exchange( "http://example.com/hotels/{hotel}", HttpMethod.GET, requestEntity, String.class, "42"); String responseHeader = response.getHeaders().getFirst("MyResponseHeader"); String body = response.getBody(); In the above example, we first prepare a request entity that contains the
Objects passed to and returned from the methods public interface HttpMessageConverter<T> { // Indicate whether the given class and media type can be read by this converter. boolean canRead(Class<?> clazz, MediaType mediaType); // Indicate whether the given class and media type can be written by this converter. boolean canWrite(Class<?> clazz, MediaType mediaType); // Return the list of MediaType objects supported by this converter. List<MediaType> getSupportedMediaTypes(); // Read an object of the given type from the given input message, and returns it. T read(Class<T> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException; // Write an given object to the given output message. void write(T t, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException; } Concrete implementations for the main media (mime) types are provided in the framework
and are registered by default with the The implementations of An An An An An An Web applications often need to query external REST services those days. The very nature of HTTP and synchronous calls can lead up to challenges when scaling applications for those needs: multiple threads may be blocked, waiting for remote HTTP responses.
The previous // async call Future<ResponseEntity<String>> futureEntity = template.getForEntity( "http://example.com/hotels/{hotel}/bookings/{booking}", String.class, "42", "21"); // get the concrete result - synchronous call ResponseEntity<String> entity = futureEntity.get();
ListenableFuture<ResponseEntity<String>> futureEntity = template.getForEntity( "http://example.com/hotels/{hotel}/bookings/{booking}", String.class, "42", "21"); // register a callback futureEntity.addCallback(new ListenableFutureCallback<ResponseEntity<String>>() { @Override public void onSuccess(ResponseEntity<String> entity) { //... } @Override public void onFailure(Throwable t) { //... } });
See the
|