一、Hessian
hessian是一种高效简洁的远程调用框架,它采用的是二进制Binary-RPC协议,基于Http协议进行传输,具有轻量、传输量小、平台无关的特点,Hessian通常通过Web应用来提供服务,通过接口暴露,特别适合于目前网络带宽比较小的手机网络应用项目。采用Binary RPC协议,相比webservice而言更简单、快捷,可与spring集成,配置简单,使用HessianServiceExporter提供bean服务。Hessian是一个支持跨语言传输的二进制序列化协议,相对于Java默认的序列化机制来说, Hessian 具有更好的性能和易用性,而且支持多种不同的语言,实际上Dubbo采用的就是 Hessian 序列化来实现,只不过Dubbo对Hessian进行了重构,性能更高。
通过Hessian本身提供的API来发起请求和接收请求,Hessian通过其自定义的串行化机制将请求信息进行序列化,产生二进制流,在通过其私有的串行化机制来将请求信息进行反序列化,复杂对象序列化速度仅次于RMI,简单对象序列化优于RMI。另外还能跨语言使用,精简高效,可以透过防火墙。但是缺乏安全机制,传输没有加密处理,事务处理欠缺。一般都是与WEB服务器结合,借助WEB服务器的成熟功能,对于异常处理等一些不足方面都可以由成熟的WEB服务器保证。
Hessian处理过程示意图:客户端——>序列化写到输出流——>远程方法(服务器端)——>序列化写到输出流 ——>客户端读取输入流——>输出结果
简单来说:客户端拥有与服务器相同(兼容)的接口,客户端通过Hession请求就可以调用服务器的方法
二、Hessian与Servlet结合
通过服务端与客户端两个WEB工程项目通信,实现客户端发送请求上传文件。需要导入hessian.jar包。
1、服务端
创建一个web服务端工程。
1.接口类
通过后面的配置需要把接口暴露出去。
public interface FileUploadService {
public void upload(String filename, InputStream data);
}
2.接口实现类
public class FileUploadServiceImpl implements FileUploadService {
public void upload(String filename, InputStream data) {
BufferedInputStream bis = null;
BufferedOutputStream bos = null;
try {
bis = new BufferedInputStream(data);
bos = new BufferedOutputStream(new FileOutputStream("E:/" + filename));
byte[] buffer = new byte[8192];
int r = bis.read(buffer, 0, buffer.length);
while (r > 0) {
bos.write(buffer, 0, r);
r = bis.read(buffer, 0, buffer.length);
}
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
if (bos != null) {
try {
bos.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
if (bis != null) {
try {
bis.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
}
3.配置xml
在web.xml配置servlet,HessianServlet是Hessian内的,并不要我们自己实现。home-api或者api-class来配置接口,而home-class或者service-class配置该接口的实现类。然后配置URL映射拦截请求。
<servlet>
<servlet-name>FileUploadService</servlet-name>
<servlet-class>com.caucho.hessian.server.HessianServlet</servlet-class>
<init-param>
<param-name>home-class</param-name>
<param-value>service.FileUploadServiceImpl</param-value>
</init-param>
<init-param>
<param-name>home-api</param-name>
<param-value>service.FileUploadService</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>FileUploadService</servlet-name>
<url-pattern>/FileUploadService</url-pattern>
</servlet-mapping>
2、客户端
需要把服务端接口放到客户端,也就是上面的FileUploadService接口,因为客户端通过hessian获取该接口的代理对象,接口调用方法来实现真正的方法调用。
在这里通过java应用来模拟发送http请求。
public class FileUploaderClient {
public static void main(String[] args) throws Exception {
HessianProxyFactory factory = new HessianProxyFactory();
FileUploadService uploader = (FileUploadService) factory.create(FileUploadService.class,
"http://localhost:8080/HessianServer/FileUploadService");
InputStream data = new BufferedInputStream(new FileInputStream("E:/text.txt"));
uploader.upload("text.txt", data);
}
}
3、实现过程
客户端用Hessian通过HessianProxyFactory的create方法来获取该接口实例,create方法返回的是一个动态代理对象,当调用接口方法的时候运行的是代理对象的invoke方法,使用HessianProxy对象作为代理的Handler,handler的invoke方法,在进行一些方法名和参数的确认之后,创建HttpURLConnection对象,调用sendRequest方法,将方法名和参数用HessianOutput对象(设置序列化的方式)的call方法,写入到服务端。
服务端通过HessianServlet来接收请求,当第一次请求来时会调用init方法初始化,根据web.xml配置的service接口创建HessianSkeleton,在service方法中调用invoke方法具体去调用HessianSkeleton中的invoke方法,在方法内创建序列化流,从HessianInput对象中获取到Method信息,获取到真正的service对象,根据反射机制,调用service对象的invoke方法,获取到返回值,最后调用HessianOutput对象将结果写回到调用方,在这里面通过序列化获取发来的方法和参数实现真正的方法调用,最后把结果序列化流返回出去。
Hessian协议用于集成Hessian的服务,Hessian底层采用Http通讯,采用Servlet暴露服务。适用场景:传入传出参数数据包较大,提供者比消费者个数多,提供者压力较大,可传文件。因此比较高效的做法是带上传下载文件的服务使用hessian协议,去普通的服务使用dubbo
三、Hessian与Spring结合
跟spring结合需要加入spring包,spring-beans、spring-context、spring-core、spring-expression4个spring依赖包,spring内部底层用了日志,所以还得加上commons-logging,还要spring-aop因为hessian底层用了aop代理对象。另外需要用springmvc作为拦截请求,所以得加上spring-webmvc,而与hessian结合的jar包,在spring中已经拥有,导入spring-web。不要去添加spring-remoting来完成hessian和spring的结合,从2.5开始,spring-remoting的内容被拆分到spring-webmvc或spring-web中,所以要匹配上spring-webmvc那部分需要用spring-web中的,自己额外导入的会引起不匹配接收不到请求报错。
1、服务端
1.实体类
定义user实体类并实现Serializable接口用于序列化传输
public class User implements Serializable{
private static final long serialVersionUID = 1692800630322115854L;
private String name;
public User() {
}
public User(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
2.创建服务层接口
Hessian的这个远程过程调用,完全使用动态代理来实现的,采用面向接口编程,因此,Hessian服务建议通过接口暴露。
public interface IService {
public User getUser();
public String setUser(User user);
}
3.服务层实现
public class ServiceImpl implements IService {
private User vo = new User();
@Override
public User getUser() {
return vo;
}
@Override
public String setUser(User user) {
vo.setName(user.getName());
return "添加成功";
}
}
4.配置springmvc.xml
HessianServiceExporter是一个Spring MVC控制器,它接收Hessian请求(HTTP协议的请求),并将这些请求转换成对被导出POJO的方法调用。既然是HTTP请求,那我们就必须配置Spring 的DispatcherServlet ,并配置HandlerMapping,将相应的URL映射给HessianServiceExporter
<beans>
<bean id="ServiceImpl" class="service.ServiceImpl" />
<bean name="/user"
class="org.springframework.remoting.caucho.HessianServiceExporter">
<property name="service" ref="ServiceImpl" />
<property name="serviceInterface" value="service.IService" />
</bean>
</beans>
5.配置web.xml
Hessian通过Servlet提供远程服务。需要将匹配某个模式的请求映射到Hessian服务。Spring的DispatcherServlet可以完成该功能,DispatcherServlet可将匹配模式的请求转发到Hessian服务。
<servlet>
<servlet-name>hessianServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>hessianServlet</servlet-name>
<url-pattern>/hessian/*</url-pattern>
</servlet-mapping>
2、客户端
首先需要把model层的user和service层的接口拷贝一份过来。
1.配置客户端的springmvc.xml
<bean id="user"
class="org.springframework.remoting.caucho.HessianProxyFactoryBean">
<property name="serviceUrl">
<value>http://localhost:8080/HessianServer/hessian/user</value>
</property>
<property name="serviceInterface">
<value>service.IService</value>
</property>
</bean>
2.配置客户端的web.xml
Spring的DispatcherServlet用来接收发送到前端发来的请求
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
3.创建控制层
通过@Autowired依赖注入把service的代理对象注入进去,接收前端发来的数据并返回到前端。
@Controller
public class HessianController {
@Autowired
@Qualifier("user")
private IService service;
@RequestMapping("/user.do")
public String add(User user,Model m) {
service.setUser(user);
m.addAttribute(service.getUser());
return "success.jsp";
}
}
4.创建jsp页面
index.jsp用于表单提交上传数据到控制层
<form action="user.do">
<input type="text" name="name">
<input type="submit" value="提交">
</form>
success.jsp用于提交数据成功后展示数据界面
<body>
${user.name}
</body>
3、运行
同时运行两个项目,在index.jsp中填写数据"张三",并提交就能在success.jsp看到
在这里可以看到完成了控制层和服务层的分离,另外我没有引入数据库,你可以自行加入数据库保存数据。
4、实现过程
客户端配置的HessianProxyFactoryBean实例化的时候会调用父类的afterPropertiesSet方法来创建hessianProxy代理对象,而该类通过afterPropertiesSet方法来创建AopProxy代理对象serviceProxy,HessianProxyFactoryBean实现了FactoryBean接口,而接口的方法getObject()正是我们在调用context.getBean(“x”)的时候被调用,所以获取的bean其实是serviceProxy,而默认使用的是JDK动态代理。当在客户端调用服务层方法时就会触发serviceProxy的invoke方法,JdkDynamicAopProxy实现了InvocationHandler接口,在invoke方法内会把方法名参数封装到了ReflectiveMethodInvocation中,再调用它的proceed方法,然后又调用了HessianProxyFactoryBean中的invoke()方法,而这里就执行了hessianProxy对象的方法调用,hessianProxy对象在初始化HessianProxyFactoryBean的时候就初始化好了,也就是我们配置的service和url,后面的实现跟和servlet结合发送请求一样,也就是这个代理对象调用invoke与服务端通信,从而实现真正的方法调用。
而服务端DispatcherServlet只是启动一个映射的作用,真正的处理在HessianServiceExporter类中,容器初始化的时候会调用InitializingBean的afterPropertiesSet()方法,所以HessianServiceExporter在实例化的同时会调用prepare方法来检查配置文件的正确性和调用getProxyForService方法创建ProxyFactory,通过ProxyFactory来实例化HessianSkeleton对象,又进一步的将Inputstream封装入HessianInput中,将Outputstream封装入Hessian2Output中,接下来把HessianInput和Hessian2Output传入HessianSkeleton中,当请求来时先到DispatcherServlet,然后根据路径获取handler,并调用HessianServiceExporter类的handleRequest方法,里面调用invoke方法交给HessianSkeleton来处理,而后面接收请求跟前面于servlet一样通过HessianSkeleton来实现。