Thursday, February 7, 2013

Take Peek at spring’s Remote Procedure Call (RPC) over HTTP:


During one of the discussion with my colleagues, I came to know that one of the data service application was implemented using Spring remoting strategy since then was curious about the implementation.


In general when you speak about a data service application that is/will be solely responsible to do CRUD operations on database with complexity that varies from simple to complex database operations in-turn will maintain the state of the data(insert / Update) or do Search operations etc and In my so many years of experience , for this kind of application is typically implemented using IIOP-RMI technologies (EJB) to take full advantage of services a ejb container provides. Robust, Scalability, easy handling of transactions, lifecycle management, and state management etc . Or a typical SOA based spring application that will be exposed as cheap webservices using technologies JAX-RPC or JAX-WS. One of my favorite is Apache CXF (a JAX-WS implementation) seamlessly works with Spring . Using CXF The data services can be exposed a services for any kind of the client agnositic(java or non-java clients). But since then I came to know about RPC over http was implemented for data service, I have started to take look at the HTTPInvoker spring remoting option.



HttpInvoker is a Spring-specific remoting option that essentially enables Remote Procedure Calls (RPC) over HTTP. Spring provides a special remoting strategy which allows for Java serialization via HTTP, supporting any Java interface (just like the RMI invoker). The corresponding support classes are HttpInvokerProxyFactoryBean 
and HttpInvokerServiceExporter. Spring Http invokers use the standard Java serialization mechanism to expose services through HTTP. Spring uses either the standard facilities provided by J2SE to perform HTTP calls or Commons HttpClient. 

As per this startergy, an outbound representation of a method invocation is serialized using standard Java serialization and then passed within an HTTP POST request. After being invoked on the target system, the method's return value is then serialized and written to the HTTP response.


There are two main requirements for this implementation.


First, you must be using Spring on both sides since the marshalling to and from HTTP requests and responses is handled by the client-side invoker and server-side exporter.


·         On the server side it would be easy as configure a RemoteExporter, declaring the interface you want to expose and associating it to the real bean that will handle the invocations, typically implemented using HttpInvokerServiceExporter 

·         On the client it would be necessary configure the RemoteAcessor that basically needs the interface that is going be access on the server side, typically implemented using HttpInvokerProxyFactoryBean


Second, the Objects that you are passing must implement Serializable and be available on both the client and server.         

As usual I have implemented a sample application using Spring 3.2 with annotation based configuration (as well as XML) , JDBC template etc.


Server Side implementation:
DAO Layer:

  • Interface and implementation -  handle database calls.

Service Layer:

  • Interface and implementation -  that is a service interface exposed to clients .

         

Web Layer:

  • Web deployment configuration and Spring application context xml.


Domain Layer:

  • Typically a Value Object or Data transfer Object(DTO)


Spring Configuration:

  • Annotation based configuration (as well as XML)


Client Side implementation:

  • A Simple java pojo that gets handle to Service interface using spring application context and invokes the Service..

Server Side Code:

DAO Layer Code:


/**

 *

 */

package com.westsideauto.dataservices.dao;


import com.westsideauto.dataservices.domain.BasePrice;


/**

 * @author advaita

 *

 */

public interface WestSideAutoDataServicesDAO {

      

       public BasePrice addBasePrice(BasePrice basePrice);


}



/**

 *

 */

package com.westsideauto.dataservices.dao.impl;


import java.util.ArrayList;

import java.util.List;


import org.springframework.jdbc.core.JdbcTemplate;


import com.westsideauto.dataservices.dao.WestSideAutoDataServicesDAO;

import com.westsideauto.dataservices.domain.BasePrice;


/**

 * @author advaita

 *

 */

public class WestSideAutoDataServicesDAOImpl implements WestSideAutoDataServicesDAO {


       private JdbcTemplate jdbcTemplate;

      

       @Override

       public BasePrice addBasePrice(BasePrice basePrice) {

              final List batchArgs = new ArrayList();

             

              String sql = "INSERT INTO BASEPRICES ( MAKE , MODEL, PRICE) Values ('"+basePrice.getMake()+"', '"+basePrice.getModel()+"' , "+basePrice.getPrice()+")";

              jdbcTemplate.update(sql);

              return basePrice;

       }


       public JdbcTemplate getJdbcTemplate() {

              return jdbcTemplate;

       }


       public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {

              this.jdbcTemplate = jdbcTemplate;

       }

      

}


Service Layer Code:


package com.westsideauto.dataservices.service;

import com.westsideauto.dataservices.domain.BasePrice;

public interface WestSideAutoDataService {
      public BasePrice addBasePrice(BasePrice basePrice);
}

package com.westsideauto.dataservices.service.impl;

import com.westsideauto.dataservices.dao.WestSideAutoDataServicesDAO;
import com.westsideauto.dataservices.domain.BasePrice;
import com.westsideauto.dataservices.service.WestSideAutoDataService;

public class WestSideAutoDataServiceImpl implements WestSideAutoDataService {

      public WestSideAutoDataServicesDAO dao ;
     
      @Override
      public BasePrice addBasePrice(BasePrice basePrice) {
            return dao.addBasePrice(basePrice);
      }

      public WestSideAutoDataServicesDAO getDao() {
            return dao;
      }

      public void setDao(WestSideAutoDataServicesDAO dao) {
            this.dao = dao;
      }

}
Domain Layer:

/**

 *

 */

package com.westsideauto.dataservices.domain;


import java.io.Serializable;


/**

 * @author advaita

 *

 */

public class BasePrice implements Serializable {


      private static final long serialVersionUID = 8383262344692423883L;


      private String make;

      private String model;

      private long price;

     

     

      public String getMake() {

            return make;

      }

      public void setMake(String make) {

            this.make = make;

      }

      public String getModel() {

            return model;

      }

      public void setModel(String model) {

            this.model = model;

      }

      public long getPrice() {

            return price;

      }

      public void setPrice(long price) {

            this.price = price;

      }

     

      @Override

      public String toString() {

            return "BasePricesVO [make=" + make + ", model=" + model + ", price="

                        + price + "]";

      }

     

}







Spring Configuration:

package com.westsideauto.dataservices.spring.context;


import javax.annotation.Resource;

import javax.sql.DataSource;


import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

import org.springframework.jdbc.core.JdbcTemplate;


import com.westsideauto.dataservices.dao.WestSideAutoDataServicesDAO;

import com.westsideauto.dataservices.dao.impl.WestSideAutoDataServicesDAOImpl;

import com.westsideauto.dataservices.service.WestSideAutoDataService;

import com.westsideauto.dataservices.service.impl.WestSideAutoDataServiceImpl;


@Configuration

public class WestSideAutoDataServicesConfig {

     

      @Resource

      public DataSource westSideAutoDataSource;

     

      @Bean

      public WestSideAutoDataService westSideAutoDataService(){

            WestSideAutoDataServiceImpl service = new WestSideAutoDataServiceImpl();

            service.setDao(westSideAutoDataServicesDAO());

            return service;

      }

     

      @Bean

      public WestSideAutoDataServicesDAO westSideAutoDataServicesDAO(){

            WestSideAutoDataServicesDAOImpl westSideAutoDataServicesDAO = new WestSideAutoDataServicesDAOImpl();

            westSideAutoDataServicesDAO.setJdbcTemplate(getJdbcTemplate());

            return westSideAutoDataServicesDAO;

      }

     

      @Bean

      public JdbcTemplate getJdbcTemplate(){

            JdbcTemplate jdbcTemplate = new JdbcTemplate(westSideAutoDataSource);

      //    jdbcTemplate.setDataSource(dataSource)

            return jdbcTemplate;

      }

     

      /*@Bean

      public HttpInvokerServiceExporter getHttpInvokerServiceExporter(){

            HttpInvokerServiceExporter exporter = new HttpInvokerServiceExporter();

            exporter.setServiceInterface(WestSideAutoDataService.class);

            exporter.setService(getWestSideAutoDataServiceImpl());

            return exporter;

           

      }*/

}




XML based application context file

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

    xmlns:jee="http://www.springframework.org/schema/jee"

    xmlns:p="http://www.springframework.org/schema/p"

    xmlns:context="http://www.springframework.org/schema/context"

    xsi:schemaLocation="

        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/jee
        http://www.springframework.org/schema/jee/spring-jee-3.0.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-3.0.xsd">

       <context:annotation-config/>
       <context:spring-configured/>
     
    <jee:jndi-lookup id="westSideAutoDataSource"
        jndi-name="jdbc/westsideautoDS"

        resource-ref="false" />

     
      <bean id="westSideAutoConfig" class="com.westsideauto.dataservices.spring.context.WestSideAutoDataServicesConfig"></bean>
     
     
      <bean id="transactionManager" class="org.springframework.transaction.jta.WebSphereUowTransactionManager"/>
     
     
</beans>


Web Layer:


Web container configuration (Web.Xml)


xml version="1.0" encoding="UTF-8"?>
<web-app id="WebApp_ID" version="2.5"
      xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

      xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">

      <display-name>WestSideAutoDataServices_WEB</display-name>
      <welcome-file-list>
            <welcome-file>index.html</welcome-file>
            <welcome-file>index.htm</welcome-file>
            <welcome-file>index.jsp</welcome-file>
            <welcome-file>default.html</welcome-file>
            <welcome-file>default.htm</welcome-file>
            <welcome-file>default.jsp</welcome-file>
      </welcome-file-list>
      <listener>
            <display-name>ContextLoaderListener</display-name>
            <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
      </listener>
      <servlet>
            <description>WestSideAutoDataServiceServlet</description>
            <display-name>WestSideAutoDataServiceServlet</display-name>
            <servlet-name>westSideAutoDataServiceServlet</servlet-name>
            <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
            <load-on-startup>1</load-on-startup>
      </servlet>
      <servlet-mapping>
            <servlet-name>westSideAutoDataServiceServlet</servlet-name>
            <url-pattern>/westsideautodataservice</url-pattern>
      </servlet-mapping>
      <context-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:com/westsideauto/dataservices/spring/config/application-context.xml</param-value>
      </context-param>
</web-app>


Spring Web-Context XML


<beans xmlns="http://www.springframework.org/schema/beans"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

      xmlns:jee="http://www.springframework.org/schema/jee"

      xmlns:context="http://www.springframework.org/schema/context"

      xmlns:p="http://www.springframework.org/schema/p"

      xsi:schemaLocation="

        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/jee
        http://www.springframework.org/schema/jee/spring-jee-3.0.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-3.0.xsd">

      <context:component-scan base-package="com.westsideauto.dataservices.service"/>
       <context:annotation-config/>
       <context:spring-configured/>

       <bean id="httpWestSideAutoDataService"
         class="org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter"

         p:service-ref="westSideAutoDataService">

        
      <property name="serviceInterface">
         <value>com.westsideauto.dataservices.service.WestSideAutoDataService</value>
      </property>
   </bean>

   <bean id="urlMapping"
          class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">

      <property name="mappings">
         <props>
            <prop key="/westsideautodataservice">httpWestSideAutoDataService</prop>
         </props>
      </property>
   </bean>

</beans>

Client Side Code:



package com.westsideauto.dataservices.service.client;


import org.springframework.context.ApplicationContext;

import org.springframework.context.support.ClassPathXmlApplicationContext;


import com.westsideauto.dataservices.domain.BasePrice;

import com.westsideauto.dataservices.service.WestSideAutoDataService;


public class WestSideAutoDataServiceClient {


      /**

       * @param args

       */

      public static void main(String[] args) {

            ApplicationContext sContext = new ClassPathXmlApplicationContext("classpath:com/westsideauto/dataservices/service/client/config/httpclient-application-context.xml"); 

            WestSideAutoDataService httpClient = (WestSideAutoDataService)sContext.getBean("HttpUserService");

            BasePrice basePrice = new BasePrice();

            basePrice.setMake("TESTMake3");

            basePrice.setModel("TESTMo13l1");

            basePrice.setPrice(30000);

           

            httpClient.addBasePrice(basePrice);

      }


}


Client Side Spring application Context


<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

    xmlns:jee="http://www.springframework.org/schema/jee"

    xmlns:p="http://www.springframework.org/schema/p"

    xsi:schemaLocation="

        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/jee
        http://www.springframework.org/schema/jee/spring-jee-3.0.xsd">
     
      <bean id="HttpUserService" class="org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean">
       <property name="serviceUrl" value="http://localhost:9080/WestSideAutoDataServices_WEB/westsideautodataservice"/>
       <property name="serviceInterface" value="com.westsideauto.dataservices.service.WestSideAutoDataService"/>
       </bean>
       
</beans>



Summary:


The Http invokers are wonderfully easy to use, and quick to get up and running. However, they suffer from the same major caveat that affects RMI, which is that your object serialization has to be carefully managed. If one end of the connection is running a different version of the code, it must be serialization compatible with the other end. This is a general "problem" with java serialization, and not specific to Spring remoting.  HTTP invoker is the recommended protocol for Java-to-Java remoting and hence Http invoker is the preferred choice, with least overhead if it is Java-to-Java.HTTP Invoker is designed to site behind the Spring servlet infrastructure so you can't really use it without that in place. HTTP Invoker relies on the infrastructure provided by the servlet container for HTTP communication.

However, it falls a long way short of EJB-style remoting, which as well as being more efficient (HTTP remoting is not very performant), adds facilities such as transactions and security. Both of these can be provided by Spring, but it means additional wiring and configuration.

As far as deciding between HTTPInvoker and proper Web Services, the former is highly proprietary (both ends must be Spring), and tightly couples the client with the server (they have to be serialization-compatible). Proper web-services are standards-compliant and client-agnostic (if done properly).

Resource:

Spring Source documentation and various Web sites from google.






3 comments:

  1. very decriptive and thorough maitee...

    ReplyDelete
  2. Nice one, will try to implement in our project.

    ReplyDelete
  3. Thanks for Sharing your experience with code sample. Spring Remoting in integration solutions using Apache Camel makes it more apealing.

    Thanks,
    Pavan

    ReplyDelete