Spring HTTP Invoker简介
Hessian和Burlap都是基于HTTP的,他们都解决了RMI所头疼的防火墙渗透问题。但当传递过来的RPC消息中包含序列化对象时,RMI就完胜Hessian和Burlap了。 因为Hessian和Burlap都是采用了私有的序列化机制,而RMI使用的是Java本身的序列化机制。如果数据模型非常复杂,那么Hessian/Burlap的序列化模型可能就无法胜任了。 Spring开发团队意识到RMI服务和基于HTTP的服务之前的空白,Spring的HttpInvoker应运而生。 Spring的HttpInvoker,它基于HTTP之上提供RPC,同时又使用了Java的对象序列化机制。
使用HTTP 协议进行remoting.由于传统的RMI方式存在协议可能被拦截等问题,spring增加了http方式的remoting,也就是说使用HTTP协议取代了JRMP协议。这种方式有优点为只要防火墙允许HTTP协议即可运行。其他方面依旧使用Java平台默认的序列化方法序处理对象。由于使用了HTTP协议,服务器端需要运行在web服务器中,同时核心类HttpInvokerServiceExporter和HttpInvokerProxyFactoryBean也被放在spring-web这个jar中.
Spring HTTP invoker 是 spring 框架中的一个远程调用模型,执行基于 HTTP 的远程调用(意味着可以通过防火墙),并使用 java 的序列化机制在网络间传递对象。这需要在远端和本地都使用Spring才行。客户端可以很轻松的像调用本地对象一样调用远程服务器上的对象,这有点类似于 webservice ,但又不同于 webservice ,区别如下:
WebService | Http Invoker |
跨平台,跨语言 | 只支持 java 语言 |
支持 SOAP ,提供 wsdl | 不支持 |
结构庞大,依赖特定的 webservice 实现,如 xfire等 | 结构简单,只依赖于 spring 框架本身 |
配置服务器端
创建实现Serializable接口的bean
package com.iframe.springmvc.bean;
import java.io.Serializable;
import java.util.Date;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import org.springframework.stereotype.Component;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
@Component
public class User implements Serializable{
/**
*
*/
private static final long serialVersionUID = 1L;
private int Cd;
private String username;
private String password;
private Date birthday;
private String address;
public User(){
}
public User(int cd, String username, String password, Date birthday, String address) {
super();
Cd = cd;
this.username = username;
this.password = password;
this.birthday = birthday;
this.address = address;
}
public int getCd() {
return Cd;
}
public void setCd(int cd) {
Cd = cd;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public String toString() {
return "User [Cd=" + Cd + ", username=" + username + ", password=" + password + ", birthday=" + birthday
+ ", address=" + address + "]";
}
}
创建接口及其实现类
UserService
package com.iframe.springmvc.service;
import java.util.Map;
import com.iframe.springmvc.bean.User;
public interface UserService {
//通过id查询用户信息
public User searchUserById(int id) throws Exception;
//检查用户是否存在
public int checkUser(User user) throws Exception;
//检查用户是否存在
public int checkUser2(int id,String password) throws Exception;
public int updateUser(Map<String,Object> map);
}
package com.iframe.springmvc.service.impl;
import java.util.Map;
import javax.annotation.Resource;
import org.springframework.stereotype.Service;
import com.iframe.springmvc.bean.User;
import com.iframe.springmvc.mapper.UserMapper;
import com.iframe.springmvc.service.UserService;
@Service("userService")
public class UserServiceImpl implements UserService {
private UserMapper userMapper;
public User searchUserById(int id) throws Exception {
return userMapper.findUserById(id);
}
public int checkUser(User user) throws Exception {
return userMapper.checkUser(user);
}
public int checkUser2(int id, String password) throws Exception {
return userMapper.checkUser2(id,password);
}
public int updateUser(Map<String, Object> map) {
return userMapper.updateUser(map);
}
public UserMapper getUserMapper() {
return userMapper;
}
@Resource(name="userMapper")
//@Resource(type=UserMapper.class)
public void setUserMapper(UserMapper userMapper) {
this.userMapper = userMapper;
}
}
UserMapper
使用mybatis
package com.iframe.springmvc.mapper;
import java.util.Map;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import com.iframe.springmvc.bean.User;
@Mapper
public interface UserMapper {
//通过id 查询用户信息
public User findUserById(int id) throws Exception;
//检查用户是否存在 方式一
public int checkUser(User user) throws Exception;
//检查用户是否存在 方式二 @Param注解的作用是给参数命名,参数命名后就能根据名字得到参数值
public int checkUser2(@Param("ids") int id,@Param("password") String password) throws Exception;
//向用户表中插入信息 ()
public int updateUser(Map<String,Object> map);
}
UserMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- namespace接口全名 -->
<mapper namespace="com.iframe.springmvc.mapper.UserMapper">
<!--
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true" />
-->
<select id="findUserById" parameterType="int" resultType="com.iframe.springmvc.bean.User">
SELECT
id as Cd,
username as username,
password as passwrod,
birthday as birthday,
address as address
FROM USER where id = #{cd}
</select>
<select id="checkUser" parameterType="com.iframe.springmvc.bean.User" resultType="int">
SELECT COUNT(*) FROM USER where id= #{cd} AND password=#{password}
</select>
<select id="checkUser2" parameterType="map" resultType="int">
SELECT COUNT(*) FROM USER where id= #{ids} AND password=#{password}
</select>
<update id="updateUser" parameterType="map">
update user set
username=#{username,jdbcType=VARCHAR}
where id=#{id,jdbcType=INTEGER}
</update>
</mapper>
公开服务 使用springmvc配置
dispatcher-server.xml
<!--httpInvoker 服务器端设置 使用springmvc URL映射配置 -->
<bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="mappings">
<props>
<prop key="/userService">userHttpInvokerService</prop>
</props>
</property>
</bean>
<!-- Announce that this interface is a HTTP invoker service. 宣布该接口是一个http invoker 服务-->
<bean name="userHttpInvokerService" class="org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter">
<property name="service" ref="userService"/>
<property name="serviceInterface" value="com.iframe.springmvc.service.UserService"/>
</bean>
org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean
org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter 原属于 spring-remoting 现整合到spring-web
配置客户端
访问服务配置 server-invoker.xml
<bean id="userServiceProxy" class="org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean">
<property name="serviceUrl">
<value>http://localhost:8080/HttpInvoke/userService</value>
</property>
<!-- Server端与Client端的DTO的包名不同导致反序列化失败。 接口名字和bean名字保持一致 -->
<!-- 如果包名或者字段名不同,则会被认为是不同的对象,会反序列化失败,调用也就出错了 -->
<property name="serviceInterface">
<value>com.iframe.springmvc.service.UserService</value>
</property>
</bean>
注意
1.客户端配置bean 接口 及其实现类并保证和服务器端同名同包。不然会因为客户端反序列化失败(报ClassCastException异常)。Bean对象需要实现Serializable接口。
2.如果serialVersionUID是指定生成,需要保持客户端和服务端的UID保持一致
原因 :
Java的序列化机制是通过判断类的serialVersionUID来验证版本一致性的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地相应实体类的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常,即是InvalidCastException
参考博文: