文档章节

Spring Http Invoker使用简介

摆渡者
 摆渡者
发布于 2015/08/20 20:01
字数 1941
阅读 3827
收藏 16

一、Spring HTTP Invoker简介

Spring HTTP invoker 是 spring 框架中的一个远程调用模型,执行基于 HTTP 的远程调用(意味着可以通过防火墙),并使用 java 的序列化机制在网络间传递对象。这需要在远端和本地都使用Spring才行。客户端可以很轻松的像调用本地对象一样调用远程服务器上的对象,这有点类似于 ‍webservice ‍,但又不同于 ‍webservice ‍,区别如下:

WebService Http Invoker
跨平台,跨语言 只支持 java 语言
支持 SOAP ,提供 wsdl 不支持
结构庞大,依赖特定的 webservice 实现,如 xfire 结构简单,只依赖于 spring 框架本身

说明:

1. 服务器端:通过 HTTP invoker 服务将服务接口的某个实现类提供为远程服务

2. 客户端:通过 HTTP invoker 代理向服务器端发送请求,远程调用服务接口的方法

3. 服务器端与客户端通信的数据均需要序列化‍

二、配置服务器端和客户端的步骤

配置服务器端

  1. ‍添‍加 springJAR 文件‍
  2. 创建相应的DTO(如果需要用到的话)
  3. 创建服务接口
  4. 创建服务接口的具体实现类
  5. 公开服务

配置客户端

  1. 添加 springJAR 文件
  2. 创建相应的DTO(如果需要用到的话)
  3. 创建服务接口
  4. 访问服务

三、实例讲解

配置服务器端

先来个项目结构图:

1). 添加 springJAR 文件,这就不用说了,直接照着图片添加相应的类库。

2). 创建服务接口和相应的DTO(Data Transmission Object)

这里我们需要调用远端的服务来查询一个User对象,因此需要DTO啦。下面这个User类就是用于在网络中传输的POJO类,也就是DTO啦,因此需要实现Serializable接口:

package com.abc.invoke.bean;

import java.io.Serializable;

public class User implements Serializable {
    private static final long serialVersionUID = -6970967506712260305L;
    private String name;
    private int age;
    private String email;

    //Getter and Setter

    @Override
    public String toString() {
        return "User [name=" + name + ", age=" + age + ", email=" + email + "]";
    }
}

3). UserService是一个接口,里面定义了服务的方法,这里面的方法将会被客户端调用:

package com.abc.invoke.server.service;

import com.abc.invoke.bean.User;

public interface UserService {
    public User getUserbyName(String name);
}

4). 创建服务接口的具体实现类。这里的UserServiceImpl是实现了UserService方法:

package com.abc.invoke.server.service.impl;

import com.abc.invoke.bean.User;
import com.abc.invoke.server.service.UserService;

public class UserServiceImpl implements UserService {
    public User getUserbyName(String name) {
        User u = new User();
        u.setName(name);
        u.setEmail("abc@abc.com");
        u.setAge(20);
        return u;
    }
}

这里面我没有写DAO等层面的东西,因为那些不是这篇文章要讲述的内容,因而我只是简单的将传给服务端的参数封装到对象里的一个字段就返回了。

5). 公开服务

下面是web.xml文件的内容:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns="http://java.sun.com/xml/ns/javaee" 
	xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 
	http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
	<display-name>SpringInvoke</display-name>
	<servlet>
		<servlet-name>service</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<init-param>
			<param-name>contextConfigLocation</param-name>
			<param-value>classpath:service-servlet.xml</param-value>
		</init-param>
		<load-on-startup>1</load-on-startup>
	</servlet>
	<servlet-mapping>
		<servlet-name>service</servlet-name>
		<url-pattern>/service/*</url-pattern>
	</servlet-mapping>
	
	<!-- 其实下面这个welcome-file-list没啥用,我留着只是为了在起好Tomcat后不会报一个404而已 -->
	<welcome-file-list>
	        <welcome-file>index.html</welcome-file>
	</welcome-file-list>
</web-app>

这里我们使用/service作为service的前缀,那么客户端请求调用时需要加上这个前缀,比如:

http://{host}:{port}/InvokeServer/service/{serviceName}

里面用到的service-servlet文件:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    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/context
    http://www.springframework.org/schema/context/spring-context-3.0.xsd">

    <!-- 这个Bean映射了当URL是/userService时,处理器为userServiceInvoker -->
    <bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
        <property name="mappings">
            <props>
                <prop key="/userService">userServiceInvoker</prop>
            </props>
        </property>
    </bean>

    <!-- Announce that this interface is a HTTP invoker service. -->
    <bean id="userServiceInvoker" 
        class="org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter">
        <property name="service" ref="userServiceImpl" />
        <property name="serviceInterface" value="com.abc.invoke.server.service.UserService" />
    </bean>
    <bean id="userServiceImpl" class="com.abc.invoke.server.service.impl.UserServiceImpl" />
</beans>

注意:

  1. <prop key="/userService">userServiceInvoker</prop>中的/userService是请求的服务的URL中的一部分,就是说这样的URL会被userServiceInvoker处理

  2. 这里将com.abc.invoke.server.service.UserService映射给了com.abc.invoke.server.service.impl.UserServiceImpl类了。

到此为止,服务器算是配置好了,接下来开始配置客户端。

配置客户端

先来看看项目结构图:

1). 添加 springJAR 文件,这也不用说了,直接照着图片添加相应的类库。

2). 创建服务接口和相应的DTO。

特别注意:这个类和Server端声明的DTO要一样,包名和字段名都要一样才行。因为客户端发起请求查询User,服务端处理后先将User序列化后在返回给客户端,而客户端拿到这个User后需要将其反序列化。如果包名或者字段名不同,则会被认为是不同的对象,会反序列化失败,调用也就出错了。我之前就是将User类的包名写得不一样(User类的包名在服务端为com.abc.invoke.server.bean,而在客户端则为com.abc.invoke.client.bean),报了以下错误:

Exception in thread "main" org.springframework.remoting.RemoteAccessException: 
	Could not deserialize result from HTTP invoker remote service [http://localhost:8080/InvokeServer/service/userService]; 
	nested exception is java.lang.ClassNotFoundException: com.abc.invoke.server.bean.User
	at org.springframework.remoting.httpinvoker.HttpInvokerClientInterceptor.convertHttpInvokerAccessException(HttpInvokerClientInterceptor.java:208)
	at org.springframework.remoting.httpinvoker.HttpInvokerClientInterceptor.invoke(HttpInvokerClientInterceptor.java:145)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
	at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:202)
	at com.sun.proxy.$Proxy0.getUserbyName(Unknown Source)
	at com.abc.invoke.client.Test.main(Test.java:14)

很明显可以看出,Could not deserialize result from HTTP invoker remote service......,就是因为Server端与Client端的DTO的包名不同导致反序列化失败。

3). 创建服务接口

这也没啥好说的,接口和Server端定义的一样就行,不一样肯定报错。可以直接将DTO和接口定义的类拷贝到客户端即可。这个接口将会被看做是客户端和服务端通信的“契约”。

4). 访问服务

来看看application-context.xml:

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	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/context
         http://www.springframework.org/schema/context/spring-context-3.0.xsd">
    
    <!-- 客户端使用 HttpInvokerProxyFactoryBean 代理客户端向服务器端发送请求,请求接口为 UserService 的服务 -->
    <bean id="userService" class="org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean" >
        <property name="serviceUrl" value="http://localhost:8080/InvokeServer/service/userService"/>
        <property name="serviceInterface" value="com.abc.invoke.client.service.UserService" />
    </bean>
    
</beans>

这里使用了org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean这个类来作为一个service的代理类。注意到serviceUrl属性为http://localhost:8080/InvokeServer/service/userService 当然,我在本机启动的服务端并在本机通过main函数调用service,我在另一台机器上运行Test类的main函数,调用结果正确)。这个localhost:8080应改为实际的IP地址和端口。),这个URL的地址以/service开始,因此会被Server端拦截下来,而URL中的 /userService则为service路径,该路径与在Server端中service-servlet.xml中声明的

<prop key="/userService">userServiceInvoker</prop>

路径一致,因此这个调用会被userServiceInvoker处理。

最后再写一个简单的测试类Test.java:

package com.abc.invoke.client;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.abc.invoke.bean.User;
import com.abc.invoke.client.service.UserService;

public class Test {
   public static void main(String[] args) {
       ApplicationContext ac = new ClassPathXmlApplicationContext(
                       "classpath:application-context.xml");
       UserService service = (UserService)ac.getBean("userService");
       User u = service.getUserbyName("Alvis");
       System.out.println(u);
   }
}

这个类也很简单,就是从Spring的Context中取出了定义的userService这个Bean(这其实就是服务端service的一个代理类),然后直接调用该service的方法获得结果并打印。

到此为止,客户端配置完成。

四、启动服务并测试

直接在项目InvokeServer上启动Tomcat,可以看到路径/userService的处理者是userServiceInvoker:

下面是远程调用的执行结果:

从结果中可以看到,我代码里写的名字叫Alvis,用客户端调用服务端的service后,返回的对象中名字是客户端设置的名字,测试成功。

这里是项目源代码,供需要的朋友参考。

 

参考页面:http://hanqunfeng.iteye.com/blog/868210

© 著作权归作者所有

共有 人打赏支持
摆渡者
粉丝 333
博文 171
码字总数 205876
作品 0
浦东
程序员
私信 提问
加载中

评论(6)

喵喵和步步
你好 已经私信你了
m
mxzou
你们能跑起来么,跑起来没得结果的呀
摆渡者
摆渡者

引用来自“gx咖咖咖咖咖”的评论

好文!解决了我的很多疑惑
嗯嗯,一起学习
gx咖咖咖咖咖
gx咖咖咖咖咖
好文!解决了我的很多疑惑
摆渡者
摆渡者

引用来自“刘万祥”的评论

描述的很详细,提供了一个新的思路.79
嗯嗯,写详细点是因为就怕哪天我自己都忘了
刘万祥
刘万祥
描述的很详细,提供了一个新的思路.79
使用Spring HTTP invoker进行远程调用

6.4 使用Spring HTTP invoker进行远程调用 Spring 开发小组意识到在RMI服务和基于 HTTP的服务(如Hessian和Burlap)之间的空白。一方面,RMI使用Java标准的对象序列化,但很难穿越防火墙;另...

红薯
2010/08/15
5.1K
1
Spring HTTP Invoker 学习小记

Spring HTTP Invoker是spring框架中的一个远程调用模型,执行基于HTTP的远程调用,也就是说,可以通过防火墙,并使用java的序列化机制在网络间传递对象。客户端可以很轻松的像调用本地对象一...

felixlv
2013/04/05
0
5
疯狂Spring Cloud连载(14)Spring Cloud整合Feign

本文节选自《疯狂Spring Cloud微服务架构实战》 京东购买地址:https://item.jd.com/12256011.html 当当网购买地址:http://product.dangdang.com/25201393.html Spring Cloud教学视频:htt...

杨大仙的程序空间
2017/10/29
0
2
dubbo源码理解(1)启动初始化与bean加载

今天看了一些博文,都是关于dubbo源码解析方面的。觉得有必要记一下。 问题1:spring 如何注入dubbo 的?或者说怎么集成dubbo 的,或者说 dubbo启动时怎么启动spring的? 1、首先想要实现 在...

明瞐
10/13
0
0
网秦招聘java开发工程师

公司简介 北京网秦天下科技有限公司是美国纽交所上市企业(NYSE:NQ),创立于2005年,是全球领先的移动互联网云服务提供商。 网址:http://www.netqin.com 招聘信息 公司安全事业部,安全A...

璐~
2011/07/14
511
10

没有更多内容

加载失败,请刷新页面

加载更多

iOS补位动画、沙漏效果、移动UITableViewCell、模拟贪吃蛇、拖拽进度等源码

iOS精选源码 JHAlertView - 一款黑白配色的HUD之沙漏效果 继承UIButton的自定义按钮SPButton 用递归算法实现iOS补位动画 iOS 长按移动UITableViewCell JHLikeButton - 有趣的点赞动画 兼容X...

Android爱开源
7分钟前
0
0
08.Beetl自定义方法以及直接访问java类方法---《Beetl视频课程》

本期视频实现了发布评论时间自定义显示; 内容简介:使用了自定义方法以及直接访问java方法实现了发布时间自定义显示 一起学beetl目录:https://my.oschina.net/u/1590490?tab=newest&catal...

Gavin-King
8分钟前
0
0
上币至iamToken

https://github.com/consenlabs/token-profile 点击Fork按钮,插入到自己的github项目中 cd /Users/shijun/Desktop/blockChain/iamToken git clone https://github.com/yellmi1983/token-pro......

八戒八戒八戒
11分钟前
0
0
spark——sparkCore源码解析之RangePartitioner

HashPartitioner分区可能导致每个分区中数据量的不均匀。而RangePartitioner分区则尽量保证每个分区中数据量的均匀,将一定范围内的数映射到某一个分区内。分区与分区之间数据是有序的,但分...

freeli
11分钟前
1
0
常用的ES6语法

什么是ES6?   ECMAScript 6(以下简称ES6)是JavaScript语言的下一代标准,已经在2015年6月正式发布了。Mozilla公司将在这个标准的基础上,推出JavaScript 2.0。   ECMAScript和JavaScr...

peakedness丶
15分钟前
1
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部