CHAPTER 2

image

Remoting

Let’s compare the Ultrabook (Dell’s super thin laptop) to a desktop PC. Most people would probably go for an Ultrabook these days, because it’s lighter, silent, and can be carried nearly everywhere. So does a desktop PC have any advantage at all, apart from its lower price? Yes—it is modular. What would you do with an Ultrabook when its processor breaks or its power connector burns out? These kinds of problems would likely make the Ultrabook unrepairable. But if this happened to a desktop PC’s monitor or power supply, those can be replaced easily. The capability to replace PC components is supported by standardized connectors. They allow us to build a whole PC by using components from different vendors.

Now imagine a big cloud service provider. The infrastructure of this system is closer to PC architecture in terms of our PC vs. Ultrabook metaphor. Enterprise systems are composed of applications that are often implemented in different programming languages and are running on machines located in various parts of the world. It is obvious that these applications need to communicate with each other via common protocols, so that a broken application eventually can be replaced without affecting the whole enterprise.

Spring remoting support is an abstraction umbrella for interapplication communication mechanisms used in the enterprise world. These technologies can be categorized into two basic groups based on their nature: synchronous and asynchronous.

Let’s start with method invocation technologies (synchronous communication):

  • Remote Method Invocation (RMI)
  • HTTPInvoker: Java serialization over HTTP
  • Hessian: Lightweight binary HTTP-based protocol designed by Caucho Technology
  • Burlap: XML-based version of Hessian
  • Java API for XML Web Services (JAX-WS): Replaced JAX-RPC with the introduction of Java EE 5, covered in Chapter 3

And now consider technologies for message passing (asynchronous communication):

  • Java Message Service (JMS): Covered in Chapter 5
  • Advanced Message Queuing Protocol (AMQP): Covered by the Spring AMQP project and out of the scope of this book

As you can see, the Spring umbrella is rich. But each of these technologies also can be used without Spring. So the question is, what benefits do the Spring abstractions provide? For remoting technologies, the benefits are as follows:

  • Decouples remoting logic from application code
  • Propagates a declarative paradigm to configure and expose services
  • Supports various protocols with consistent APIs
  • Translates checked exceptions into runtime exceptions

Let’s explore each of these technologies and Spring’s abstractions in more detail.

Image Note  Hessian and Burlap were included in previous versions of the Enterprise Integration with Spring certification, but were removed from the latest version, which covers Spring 4.0 features. Therefore, they are beyond the scope of this book.

Remote Method Invocation

As its name suggests, Remote Method Invocation (RMI) is used for executing methods located on a remote JVM or system. RMI provides a convenient way to integrate distributed systems. Developers don’t need to convert data models into another format in order to communicate with the remote system. To transfer data between JVMs, serialization is used. Serialization is a standard Java mechanism to convert data and code into binary form, so they can be stored or sent through the wire.

Initial implementations of RMI worked only for inter-JVM communication. They were based on the Java Remote Method Protocol (JRMP) . To make RMI more flexible, the RMI over Internet Inter-Orb Protocol (RMI-IIOP) was developed. This version is based on the Common Object Request Broker Architecture (CORBA ) standard and can be used for intercommunication of diverse systems. The important take-away of this is that RMI-IIOP makes method invocation possible with non-JVM runtime environments.

As mentioned, RMI is used for communication between remote environments potentially located on different hosts. So two sides are involved. One side exposes logic. The second side consumes the exposed RMI method. Both sides are able to communicate via the network, because the JRMP provides an RMI registry. Each side of the communication registers a proxy object into the RMI registry, and these objects then communicate with each other with the help of a standard Java serialization mechanism.

In order to understand each other, the client and server need to have a common contract available on both sides. This contract in the RMI world isn’t anything other than a standard Java interface. The method provider needs to implement this interface, and the RMI client needs it during the remote method call. There are two ways to ensure that this contract is available on both sides:

  • The interface is located in a shared JAR library that would be used by both sides as a dependency.
  • The interface is duplicated with the same signature on both sides.

Each approach has its pros and cons. Extracting into a shared library creates complexity around handling and maintaining that shared library. Duplicating an interface on both sides obviously creates the risk of making the interface incompatible on one of the sides.

It may seem like developers can take any call and easily expose it via RMI without any changes. But this isn’t exactly the case. Certain RMI contract specifics need to be fulfilled:

  • The contract interface needs to extend the java.rmi.Remote interface.
  • The exposing method must throw a java.rmi.RemoteException.
  • The class implementing the RMI method (logic that will be shared) needs to extend java.rmi.server.UnicastRemoteObject.
  • The class implementing the RMI method needs to have an explicit constructor, which must throw java.rmi.RemoteException.
  • The client consuming the RMI method needs to handle the checked exception java.rmi.RemoteException.

Another important part of the contract is serialization. Each parameter, return type, and implementing class itself needs to be serializable. For the service implementation class, this is ensured via extending java.rmi.server.UnicastRemoteObject.

Let’s explore RMI contract specifics in examples. Our shared interface is highlighted in Listing 2-1.

This interface with identical signatures is located in two example projects, 0201-java-rmi-service and 0201-java-rmi-client. Of course, you can use any type as a parameter, as long as it is shared on both sides of the communication and fulfills the serialization requirements. Listing 2-2 shows the service logic that is located in the class implementing the shared interface on the server.

As you can see, all these java.rmi API details are invasive to the application code. It is obvious that exposing a method via RMI wouldn’t always be a refactoring-free operation. You can see that the implementation class itself needs to be serializable (as do the parameters and return value as well). Listing 2-3 shows RMI code that creates the RMI registry and starts the server. It is the last piece of our server example.

In this case, we are creating an RMI registry and thus exposing the RMI method on port 10201. BarService is the RMI name that identifies our method. So it is possible to expose various methods that will be bound to different RMI names. This concept is similar to Spring MVC request mappings.

The next piece of our RMI puzzle is the client side. The name of this example project is 0201-java-rmi-client. Listing 2-4 shows the main client class.

The method callService tries to find the RMI registry on port 10201. If callService can’t find the registry, NotBoundException is thrown. This is another invasive aspect of the pure Java RMI API. The registry is then searched for the RMI object based on the name. Finally, the actual RMI call is performed.

When we run server example 0201-java-rmi-service, it creates an RMI registry and remains running, exposing the service. After running client example 0201-java-rmi-client, it will try to establish a connection to the remote RMI registry and call the remote method. Listing 2-5 shows the console output after running the client example.

You might be wondering where Spring enters the equation and how it helps with the RMI. Let’s focus on that now.

Spring RMI Support

Spring always tries to iron out the API flaws of existing technologies. Java RMI is no exception. Spring allows for noninvasive RMI exposure. This means we can forget about RemoteException, UnicastRemoteObject, and the Remote interface altogether. We can expose existing code without changes. The only contract requirement needed with Spring is serialization. But Spring can’t avoid it. Serialization is in the nature of this distributed technology.

RMI registry handling is also much easier with Spring. Spring tries to see whether there is an RMI registry on given port. If not, an RMI registry is created on demand. Let’s explore Spring’s RMI constructs in an example. Listing 2-6 shows the service interface.

Listing 2-7 shows the implementation.

As you can see, no RMI-specific code is needed. This is a huge advantage over the pure Java RMI API, because we can expose any method via RMI without refactoring. The only requirement is serialization of all types in the method signature. The RmiServiceExporter bean is needed in the Spring context for RMI registry binding and registering, as shown in Listing 2-8.

barService bean is injected by Spring, so that it can be used for registering a service to the RMI register. Otherwise, the RMI server code is pretty straightforward.

Let’s take a look at the client side in Listing 2-9.

First we create org.springframework.remoting.rmi.RmiProxyFactoryBean. This factory class is used to create RMI proxy objects. It has a few mandatory properties:

  • The remote location of the RMI registry. Spring uses the URL notation of the RMI endpoint definition.
  • The service interface class definition.

Calling rmiProxyFactoryBean.afterPropertiesSet() applies the configured RMI settings and binds the factory to the remote RMI register. The last call is rmiProxyFactoryBean.getObject(), where we create a proxy object that will simulate the service bean. And that’s it. BarService is now a bean in our client Spring IoC container. Listing 2-10 shows how this bean can be used in client business logic.

In any Spring bean, we can inject an RMI proxy bean and use it with exactly the same API as we would call the server implementation directly. This is because Spring translates checked java.rmi.RemoteException into unchecked org.springframework.remoting.RemoteAccessException. So we can completely hide the fact that we are calling a remote method from the business logic.

When we run server example 0202-spring-rmi-java-config-service and subsequently client example 0202-spring-rmi-java-config-client, we can see the console output shown in Listing 2-11.

If you are using XML context for legacy reasons, Spring provides support for it also. In fact, it is similar to the Java configuration, without any special XML namespaces. Therefore, we skip the definition of the service interface and its implementation for XML examples. They are the same as in Listing 2-6 and Listing 2-7, only the packages are slightly different.

The server XML RMI configuration looks like Listing 2-12.

The beginning of the file contains a definition of the standard Spring XML namespace context and bean. First we perform a component scan of the package where the service implementation is located. Then we register the RMI service exporter bean. The XML configuration is using standard Spring bean registration syntax.

Notice the different port number than in Listing 2-8. This difference indicates that this configuration is located in a different project, but the service interface and its implementation are exactly the same. It is easier to handle integration tests when different projects within an integration test suite are using different ports.

As Listing 2-13 shows, the client XML configuration is interestingly less verbose than the Java configuration.

This example is the client counterpart for the XML server configuration in Listing 2-14. Therefore, port 10203 is used. You may notice that it is similar to the Java configuration. Notice that in the Java client configuration example in Listing 2-9, we had to create the proxy bean explicitly. In this case, Spring will create it automatically, so it’s enough to register only the factory bean.

As you can see, these examples document the API enhancements Spring provides for RMI. Although it’s easy to expose services via RMI and much easier with Spring, there are a few architectural risks when using RMI. First of all, every type that is part of the contract has to be serializable. This is restrictive, because both sides need to have the same version of each return type or parameter. So changes to the contract need to be aligned on both sides. This creates a tight coupling between the RMI exporter and consumer.

Another problem is the inability to use various versions of the same API contract. Of course, the contract should be as stable as possible. But sometimes changes are required. Other approaches, such as SOAP or REST, allow extending the API without needing to change the client. RMI serialization is not suitable for such changes. Each change to types used in the RMI method signature needs to be reflected on the client.

Another significant downside of RMI is that it can communicate with only the JVM runtime. This is solved by the RMI over CORBA implementation (RMI-IIOP). CORBA is a standard defined by Object Management Group (OMG) and designed for communication between different platforms. RMI-IIOP with Spring is similar to pure RMI. The only difference is to add a JNDI prefix to Spring RMI classes. So, for example, instead of RmiServiceExporter, we would use JndiRmiServiceExporter for RMI-IIOP. But even when CORBA seemed a promising technology for implementing distributed systems, it didn’t gain widespread adoption because of various problems with the protocol or its implementations.

Image Note  Diving into CORBA and its problems is beyond the scope of this book. Wikipedia is good place to start investigation: https://en.wikipedia.org/wiki/Common_Object_Request_Broker_Architecture#Problems_and_criticism

All these weak points of RMI can lead to questions, including why to use RMI at all. But it does have one more advantage (alongside simplicity and better performance than SOAP and REST): it doesn’t require a web server to communicate. Pure RMI can perform communication between JVMs without using additional abstractions or libraries. This can be handy in Java-based embedded systems that need to communicate remotely.

Spring RMI Summary

It is important to remember that Spring provides many benefits compared to the pure Java RMI API. First, on the server side:

  • Decouples server business logic from the remoting protocol because we can expose business services without changes.
  • Service method doesn’t need to throw java.rmi.RemoteException.
  • Service interface doesn’t extend java.rmi.Remote.
  • Service implementation doesn’t extend java.rmi.server.UnicastRemoteObject.
  • Service constructor doesn’t need to throw java.rmi.RemoteException.
  • Binding to the RMI registry can be done automatically by Spring.

Second, on the client side:

  • Existing code doesn’t have to be changed when invoking a remote method, because the client doesn’t need to handle checked java.rmi.RemoteException when the service method is used. Spring translates it into unchecked RemoteAccessException.
  • Decouples client business logic from remoting mechanism, so remoting protocols can be easily replaced.

Spring HttpInvoker

HttpInvoker is a technology similar to RMI. It also uses standard Java serialization and deserialization. But it uses a different protocol to transfer serialized data. RMI uses JRMP, and PMI-IIOP uses CORBA. In contrast, as its name suggests, HttpInvoker uses HTTP. This is probably the most widely used protocol nowadays, because it fuels the whole Web. HTTP provides a significant advantage over other protocols, because it is most likely to be widely supported by network infrastructures. For example, RMI or CORBA can be blocked by firewall rules, but HTTP is enabled in most firewalls.

When a remote call is performed, it uses an HTTP POST request and serializes parameters and response values via standard Java serialization. So it has similar performance to RMI. But HttpInvoker is officially supported by the Spring team as the preferred remoting approach, because it doesn’t need to use any special remoting contracts (remote interfaces, remote exceptions, proxies). It also doesn’t rely on the mechanism of an RMI registry. That is why Spring documentation considers HttpInvoker lightweight compared to other remoting mechanisms.

HttpInvoker can be served in several ways:

  • From the servlet container (Java EE)
  • Via HttpRequestHandlerServlet (without Spring MVC support)
  • Via DispatcherServlet (with Spring MVC support)
  • Via simple Java HTTP server implementation (Java SE)

No special runtime is needed for the latter implementation. The standard Java runtime is enough. For the first two, on the other hand, a Java EE servlet container is needed.

Each HttpInvoker solution uses a similar approach for bean exposure. It wraps the target service bean and the service interface into an HttpInvokerServiceExporter/SimpleHttpInvokerServiceExporter. This bean is registered in the Spring container and exposed via the previously mentioned HTTP communication mechanisms. Let’s explore each approach with examples. Listing 2-14 contains an example service interface and implementation that will be exposed.

This service interface contract and service implementation are used across all HttpInvoker examples in this chapter.

HttpInvoker Served via HttpRequestHandlerServlet

Examples of HttpInvoker served via HttpRequestHandlerServlet can be found in code examples under projects 0204-http-invoker-handler-servlet-java-config and 0205-http-invoker-handler-servlet-xml-config. First we need to wrap our service interface into HttpInvokerServiceExporter and register it as a Spring bean, as shown in Listing 2-15.

This is a pretty straightforward Spring Java configuration that scans for Spring components in the current package (also in subpackages) and registers a service exporter bean. Notice that this bean has the name barExporter (which is the name of the bean method by default). The bean name is important, because it needs to match the servlet name configured in the servlet descriptor configuration. The target service barService instance is injected and configured via the HttpInvokerServiceExporter.setService method. As you can see, we need to specify the service interface also. XML configuration of the same bean looks like Listing 2-16.

The next step is configuring an HttpRequestHandlerServlet. The Java servlet configuration look likes Listing 2-17.

WebApplicationInitializer is Spring’s interface used for servlet configuration. It is used as an abstraction on top of the Servlet 3 Java API (javax.servlet.ServletContainerInitializer). Spring scans the implementation of WebApplicationInitializer and bootstraps it in SpringServletContainerInitializer (Spring’s implementation of javax.servlet.ServletContainerInitializer). So WebApplicationInitializer can be used as a replacement for the web.xml configuration in Spring applications, or alongside the web.xml configuration. Such flexibility enables a smooth migration path from legacy XML configurations.

The WebApplicationInitializer.onStartup method is invoked by Spring during the servlet container initialization phase. Spring injects the javax.servlet.ServletContext instance, which is designed for interfacing with the underlying servlet container. So we can register our servlets into it. First of all, we create a Spring root context and register our ServerConfiguration. Now two boilerplate lines follow in Listing 2-17. The context needs to be refreshed after registering the configuration, and ContextLoaderListener needs to be registered also.

Finally, we register a new instance of HttpRequestHandlerServlet with a name matching the service exporter bean’s name (barExporter) and map the servlet onto a URL (/ in this case). You will probably be more familiar with the web.xml style of configuration, as shown in Listing 2-18. It looks similar to WebAppInitializer in Listing 2-17.

In this case, we used the spring-config.xml file as a Spring configuration from the example project 0205-http-invoker-handler-servlet-xml-config. But all other aspects of XML configuration follow the same concepts as in WebApplicationInitializer. Consistency of APIs across various technologies and configuration types is the beauty of Spring.

HttpRequestHandlerServlet is a lightweight alternative to DispatcherServlet, the backbone of the Spring MVC framework. So it is handy if we need to avoid the Spring MVC framework. But if your application already uses DispatcherServlet or you want to avoid unnecessary boilerplate configuration, the following approach is the way to go.

HttpInvoker Served via DispatcherServlet

DispatcherServlet is the central class of Spring’s web support. It is extensively used by Spring MVC for building web applications and REST APIs. Its job is to route HTTP messages to controllers based on declarative URL mappings and map responses onto the appropriate views.

With DispatcherServlet, you can map the HttpInvokerServiceExporter bean directly to a URL. This is done by starting the bean name with a slash character, as shown in Listing 2-19.

Configuration of the exporter bean is similar to that in the “HttpInvoker Served via HttpRequestHandlerServlet” section. The only difference is the bean name, /BarService (notice the forward slash character at the beginning). A name with a slash character tells Spring that the bean should be served by DispatcherServlet from the URL endpoint defined by this name. No other configuration changes are needed if DispatcherServlet is already configured properly.

Configuring DispatcherServlet is covered in the Spring Web certification exam and is highlighted in Chapters 3 and 4. Therefore, I grabbed the chance and used a new approach to configure HttpInvoker via DispatcherServlet. As experienced Spring developers would recognize, Listing 2-19 uses an API that isn’t part of the Spring Core framework (the EnableAutoConfiguration annotation and SpringApplication class). These are part of the new Spring Boot framework. Spring Boot promotes convention over configuration principles for a highly configurable Spring. This significantly helps decrease the amount of boilerplate configuration. But at the same time, conventions can be replaced with custom Spring configurations. This allows for rapid and flexible development with Spring family frameworks.

The EnableAutoConfiguration annotation tells Spring Boot to look at the classpath and apply the most appropriate Spring configuration based on found dependencies. Listing 2-20 contains Maven dependencies for this example.

In this case, we have Tomcat on the classpath. So because we have a servlet container as a dependency, Spring Boot applies the most common servlet configuration with DispatcherServlet. This is handy for our example because it is, in fact, all the configuration needed if we are using Spring Boot JAR packaging.

If we need to use Spring Boot WAR packaging, one more class would be needed to cover servlet configuration, as shown in Listing 2-21.

SpringBootServletInitializer maps DispatcherServlet to “/” URL and uses ServerConfiguration class as main Spring context definition.

HttpInvoker Served via Simple Java HTTP Server

If the servlet container is overkill for our application, we could use a pure Java SE approach for exposing the service with HttpInvoker. The standard Java runtime contains a simple HTTP server implementation, which can be used by Spring to expose the service via HttpInvoker. The API details of this simple HTTP server are not relevant because we want to use Spring abstractions. Let’s take a look at the Spring configuration in Listing 2-22.

ServerCondiguration.barExplorer bean is similar to the servlet HttpInvoker examples. The method creates an instance of SimpleHttpInvokerServiceExplorer. The service exporter has mandatory fields: the service and service interface. The service implementation instance is injected from Spring and represents the exposed logic. The service interface is the signature of the exposed service.

The second bean in our configuration configures a simple HTTP service. The exposed service needs to be bound to a URL endpoint. The second mandatory setting we need to provide is the port number where the simple HTTP server will be listening.

HttpInvoker Service Accessed from Client

The previous examples showed that we have various options for exposing a service via HttpInvoker, differentiated by the implementation of the web server/servlet. On the other side of the communication channel, we have two options for consuming the HttpInvoker protocol:

  • Standard Java Development Kit (JDK) HTTP client—used by default
  • Apache HttpComponents as the client library

Apache HttpComponents provides a few features that are not handled by the standard Java SE HTTP component:

  • Basic HTTP authentication
  • HTTP connection pooling
  • HTTP state management

An example of Java configuration with a standard Java HTTP proxy is shown in Listing 2-23.

We need to register a bean of type HttpInvokerProxyFactoryBean into the Spring context. It is a factory bean, which indicates that it will be used for creating object(s). In this case, it will create a proxy bean, which will act as a local instance of the remote service. All requests to this bean will be serialized and sent through the wire to the server.

So the remote service will look like a local bean to the business logic. As the example highlights, the URL of the service location and service interface are needed to create an HttpInvoker tunnel. The server counterpart for this example is hosted on localhost on port 10204. Notice that no request executor instance is configured. This means we are using the standard Java HTTP client. Finally, we can use the BarService bean with standard autowiring, as shown in Listing 2-24.

Listing 2-24 has a form of the TestNG integration test, but it is obvious that the same autowiring can be applied in our business logic bean. Neither the test nor the potential business bean has an idea that barService is a remote service exposed via HttpInvoker. This demonstrates how easy it is to expose and consume services via HttpInvoker without any change to the existing Spring application.

Now let’s explore the second option of consuming HttpInvoker with the HttpComponents library. We would use this option if the features of the standard Java SE HTTP client are not enough for our use case. Basic HTTP authentication or HTTP connection pooling are common requirements for security or performance reasons.

To switch gears a little, we will use it in conjunction with XML Spring configuration, to show how to configure HttpInvoker via XML configuration as well. See Listing 2-25.

This client is configured against the server on localhost with port 10205. It uses only a standard bean namespace to construct the proxy factory bean. The only difference here, compared to the Java configuration in Listing 2-24, is the httpInvokerRequestExecutor field. We explicitly configured it to use the Apache HttpComponents client library.

Summary

This chapter discussed remoting support of the Spring framework. In this synchronous communication, the client is blocked until the remote service finishes its job.

Serialization is needed for all classes involved in the communication (service method parameters and return type). Whether this is standard Java serialization, CORBA, or Caucho’s XML binary serialization, it creates a significant constraint: the inability to version a server API. Each change to the client/server contract needs to be aligned on both sides. Therefore, these technologies should be avoided if we need to create a server API that has the following characteristics:

  • Publicly facing.
  • Not under our control.
  • Consumed by a large number of clients. If the server contract is changed, all clients need to be changed also.

Each Spring remoting abstraction uses a different combination of transport protocol and serialization mechanism:

  • RMI uses Java serialization and Java Remote Method Protocol.
  • RMI-IIOP uses Java serialization and Internet Inter-Orb Protocol, which makes communication CORBA compatible.
  • HttpInvoker uses Java serialization and HTTP.
  • Hessian uses Caucho’s binary XML serialization and HTTP.
  • Burlap uses Caucho’s textual XML serialization and HTTP.

The transport protocol and serialization mechanism can narrow architectural decisions when a remoting method needs to be chosen. For example, if communicating with non-Java applications is required, only Hessian or RMI-IIOP are suitable (Burlap should be avoided, because its Spring abstractions are deprecated since Spring 4). When we need to communicate via HTTP, only HttpInvoker and Hessian can fulfill this requirement.

Spring significantly simplifies transitions between remoting technologies, because it uses the same concepts for exposing and consuming services. While exposing a service, the notion of an exported service is used (RmiServiceExporter, HttpInvokerServiceExporter, HessianServiceExporter, BurlapServiceExporter). When consuming, a proxy factory bean mechanism penetrates all remoting abstractions (RmiProxyFactoryBean, HttpInvokerProxyFactoryBean, HessianProxyFactoryBean, BurlapProxyFactoryBean). All these abstractions have similar properties across different remoting technologies. Such API consistencies decrease the learning curve if we need to switch remoting technologies.

When using RMI, there are numerous benefits to using Spring abstractions instead of standard Java APIs.

Benefits on the server are as follows:

  • No need to throw checked exceptions
  • No need to implement or extend invasive RMI types
  • Allows for exposing business services without changes
  • Handles creation of RMI registry if needed

Benefits on the client are as follows:

  • Translating of checked exceptions into runtime
  • Allows for consuming remote services without changing client code

While each remoting approach provides an easy way to implement distributed applications, they are rarely used in enterprise applications nowadays, because they create tight coupling between applications. So a certain remoting technology is not required or slightly lower performance is suitable, SOAP or especially RESTful web services likely will be considered for new projects.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
52.15.129.253