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.
very decriptive and thorough maitee...
ReplyDeleteNice one, will try to implement in our project.
ReplyDeleteThanks for Sharing your experience with code sample. Spring Remoting in integration solutions using Apache Camel makes it more apealing.
ReplyDeleteThanks,
Pavan