当前位置:   article > 正文

Spring boot 全注解 无xml 利用 Hessian 进行 RMI (远程方法调用)

class 'org.springframework.remoting.caucho.hessianserviceexporter' not found

为什么写这篇文章?

因为我遇到了两个问题,并且用了大量的时间解决

  • 启动时的错误 java.lang.ClassNotFoundException: com.caucho.hessian.io.SerializerFactory
  • Caused by: java.lang.IllegalArgumentException: 'serviceInterface' must be an interface
  • Caused by: java.io.FileNotFoundException: localhost:8080/whichever.service
  • xx
  • xx

如果你也遇到了以上的问题,那么这篇文章或许对你有一些帮助

现在你有两种选择,一种是直接到最终实现,另一种是和我一起经历一遍这些错误

【过坑】

按照书上或者博客上的步骤,我在服务端创建了下面几个类,为了方便,所有的类都放在和Application类同级中

其中前两个类都是配置类,后两个一个是服务接口,一个是实现该接口的服务。下面我将列出这几个类,为了方便看客,我将import部分也进行了展示
  1. DispatcherConfig (为了配置mappping)
  1. //package com.fufu.rmi_service;
  2. import org.springframework.context.annotation.Bean;
  3. import org.springframework.context.annotation.Configuration;
  4. import org.springframework.web.context.WebApplicationContext;
  5. import org.springframework.web.servlet.HandlerMapping;
  6. import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping;
  7. import org.springframework.web.servlet.support.AbstractDispatcherServletInitializer;
  8. import java.util.Properties;
  9. @Configuration
  10. public class DispatcherConfig extends AbstractDispatcherServletInitializer {
  11. @Override
  12. protected WebApplicationContext createServletApplicationContext() {
  13. return null;
  14. }
  15. @Override
  16. protected String[] getServletMappings() {
  17. return new String[]{"/","*.service"};
  18. }
  19. @Override
  20. protected WebApplicationContext createRootApplicationContext() {
  21. return null;
  22. }
  23. @Bean
  24. public HandlerMapping hessianMapping(){
  25. SimpleUrlHandlerMapping mapping=new SimpleUrlHandlerMapping();
  26. Properties mappings=new Properties();
  27. mappings.setProperty("/whichever.service","hessianServiceExporter");//service的路径和第二个配置类中的方法名
  28. mapping.setMappings(mappings);
  29. return mapping;
  30. }
  31. }
  32. 复制代码

2.HessianConfig

  1. //package com.fufu.rmi_service;
  2. import org.springframework.context.annotation.Bean;
  3. import org.springframework.context.annotation.Configuration;
  4. import org.springframework.remoting.caucho.HessianServiceExporter;
  5. @Configuration
  6. public class HessianConfig {
  7. @Bean
  8. public HessianServiceExporter hessianServiceExporter(WhicheverService service){
  9. HessianServiceExporter exporter=new HessianServiceExporter();
  10. exporter.setService(service);
  11. exporter.setServiceInterface(service.getClass());
  12. return exporter;
  13. }
  14. }
  15. 复制代码
  1. WhicheverService (服务接口)
  1. public interface WhicheverService {
  2. String echo(String args);
  3. }
  4. 复制代码
  1. WhicheverServiceImp (服务实现)
  1. import org.springframework.stereotype.Component;
  2. @Component
  3. public class WhicheverServiceImp implements WhicheverService {
  4. @Override
  5. public String echo(String args) {
  6. return args;
  7. }
  8. }
  9. 复制代码

一、 java.lang.ClassNotFoundException: com.caucho.hessian.io.SerializerFactory

这个问题的产生,是因为Hessian的过程中是二进制通信,对象都会被序列化,想当然的我们就把觉得有关联的类实现Serializable 由于我们把 WhicheverServiceImp再实现一个Serializable

WhicheverServiceImp.java

  1. import org.springframework.stereotype.Component;
  2. import java.io.Serializable;
  3. @Component
  4. public class WhicheverServiceImp implements WhicheverService,Serializable {
  5. @Override
  6. public String echo(String args) {
  7. return args;
  8. }
  9. }
  10. 复制代码

然后自以为解决了这个错误地重启服务,发现错误并没有变 还是这个错误

经查资料发现,Hessian有自己的序列化接口,并非是java.io包内的,所以我就怀疑是不是少了包的依赖,发现果然如此,于是在pom.xml中添加如下依赖

  1. <dependency>
  2. <groupId>com.caucho</groupId>
  3. <artifactId>hessian</artifactId>
  4. <version>4.0.38</version>
  5. </dependency>
  6. 复制代码
这个问题被修复了,然后就出现了新的错误

二、Caused by: java.lang.IllegalArgumentException: 'serviceInterface' must be an interface

跟随提示我们进入HessianConfig.java这个类

发现这一行出错,我们将 service.getClass() 直接写成 WhicheverService.class 最终代码

  1. //package com.fufu.rmi_service;
  2. import org.springframework.context.annotation.Bean;
  3. import org.springframework.context.annotation.Configuration;
  4. import org.springframework.remoting.caucho.HessianServiceExporter;
  5. @Configuration
  6. public class HessianConfig {
  7. @Bean
  8. public HessianServiceExporter hessianServiceExporter(WhicheverService service){
  9. HessianServiceExporter exporter=new HessianServiceExporter();
  10. exporter.setService(service);
  11. exporter.setServiceInterface(WhicheverService.class);// 已经更正
  12. return exporter;
  13. }
  14. }
  15. 复制代码
OKay,这次我们‘成功’的启动了服务,为什么加引号呢?因为目前只是编译器向我们表明 我们的代码编译期正确 ,但我们此时并不知道出错,于是我们开发客户端,或者称之为调用端似乎更加贴切 RMI 的 I 代表的 invoke。

首先按照书上或者书上的步骤,写出如下的类

我的书——Spring实战,甚至没告诉我要把服务接口Copy一份到调用端。当然,别忘了把Maven依赖加入pom.xml中去

调用端非常简单,一个 HessianConfig.java 配置类就够了

  1. //package com.fufu.rmi_client;
  2. import org.springframework.context.annotation.Bean;
  3. import org.springframework.context.annotation.Configuration;
  4. import org.springframework.remoting.caucho.HessianProxyFactoryBean;
  5. @Configuration
  6. public class HessianConfig {
  7. @Bean
  8. public HessianProxyFactoryBean service(){
  9. HessianProxyFactoryBean proxy=new HessianProxyFactoryBean();
  10. proxy.setServiceUrl("http://localhost:8080/whichever.service");
  11. proxy.setServiceInterface(WhicheverService.class);
  12. return proxy;
  13. }
  14. }
  15. 复制代码

setServiceUrl方法配置了调用的方法,端口默认是8080,whichever.service对应于我们刚刚在服务端配置的mapping路径

然后写个测试类调用一下方法

  1. //package com.fufu.rmi_client;
  2. import org.junit.Test;
  3. import org.junit.runner.RunWith;
  4. import org.springframework.beans.factory.annotation.Autowired;
  5. import org.springframework.boot.test.context.SpringBootTest;
  6. import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
  7. @SpringBootTest
  8. @RunWith(SpringJUnit4ClassRunner.class)
  9. public class HessianTest {
  10. @Autowired
  11. WhicheverService service;
  12. @Test
  13. public void test(){
  14. System.out.println(service.echo("gg"));
  15. }
  16. }
  17. 复制代码

我们希望看到控制台输出“gg”,“gg”似乎给我们一种不好的感觉,于是我们在启动服务端的状态下再启动客户端。果然,错误出现了

三、Caused by: java.io.FileNotFoundException: localhost:8080/whichever.service

我们试着点开错误提示中的url 发现

竟然是404 也就是说这个路径并不存在,也就是说我们配置的mapping路径无效了。但是启动服务器的时候有这样一条Info级别的日志被输出在控制台上

这是为什么呢?

回到书里 我看到这样一句

突然发现似乎少了些将mapping关联到整个应用,随后我去搜索了相关的配置,发现太过于复杂了,这不符合 Spring框架的理念 于是我回想起一个内容就是:Spring可以把资源或者函数映射成url,而在RMI的过程中,服务端就是将Bean代理出去而已,这时我把配置Hessian的Bean映射成一个url不就行了么~于是我将 DispatcherConfig.java 废弃,并将HessianConfig 修改为

  1. //package com.fufu.rmi_service;
  2. import org.springframework.context.annotation.Bean;
  3. import org.springframework.context.annotation.Configuration;
  4. import org.springframework.remoting.caucho.HessianServiceExporter;
  5. @Configuration
  6. public class HessianConfig {
  7. @Bean(name = "/whichever.service") //通过name指定bean的名字
  8. public HessianServiceExporter hessianServiceExporter(WhicheverService service){
  9. HessianServiceExporter exporter=new HessianServiceExporter();
  10. exporter.setService(service);
  11. exporter.setServiceInterface(WhicheverService.class);
  12. return exporter;
  13. }
  14. }
  15. 复制代码

这里我通过@Bean的注解name属性将Hessian的配置方法的Bean映射成"/whichever.service" 这里的路径可以是任意以“/”开头的

然后启动服务端的状态下再启动调用端的测试类

终于看到了想看到的"gg",尽管这次并没有gg。

最后让我们一起看看最简洁的配置,我会将现有的配置进一步精简

【最终最简洁的实现】

服务端的类

  • WhicheverService.java
  1. //package com.fufu.rmi_service;
  2. public interface WhicheverService {
  3. String echo(String args);
  4. }
  5. 复制代码
  • WhicheverServiceImp.java
  1. //package com.fufu.rmi_service;
  2. import org.springframework.stereotype.Component;
  3. @Component
  4. public class WhicheverServiceImp implements WhicheverService{
  5. @Override
  6. public String echo(String args) {
  7. return args;
  8. }
  9. }
  10. 复制代码
  • RmiServiceApplication.java
  1. //package com.fufu.rmi_service;
  2. import org.springframework.boot.SpringApplication;
  3. import org.springframework.boot.autoconfigure.SpringBootApplication;
  4. import org.springframework.context.annotation.Bean;
  5. import org.springframework.remoting.caucho.HessianServiceExporter;
  6. @SpringBootApplication
  7. public class RmiServiceApplication {
  8. public static void main(String[] args) {
  9. SpringApplication.run(RmiServiceApplication.class, args);
  10. }
  11. @Bean(name = "/whichever.service")
  12. public HessianServiceExporter hessianServiceExporter(WhicheverService service){
  13. HessianServiceExporter exporter=new HessianServiceExporter();
  14. exporter.setService(service);
  15. exporter.setServiceInterface(WhicheverService.class);
  16. return exporter;
  17. }
  18. }
  19. 复制代码

调用端的类

  • WhicheverService.java
  1. //package com.fufu.rmi_client;
  2. public interface WhicheverService {
  3. String echo(String args);
  4. }
  5. 复制代码
  • RmiClientApplication.java
  1. //package com.fufu.rmi_client;
  2. import org.springframework.boot.SpringApplication;
  3. import org.springframework.boot.autoconfigure.SpringBootApplication;
  4. import org.springframework.context.annotation.Bean;
  5. import org.springframework.remoting.caucho.HessianProxyFactoryBean;
  6. @SpringBootApplication
  7. public class RmiClientApplication {
  8. public static void main(String[] args) {
  9. SpringApplication.run(RmiClientApplication.class, args);
  10. }
  11. @Bean
  12. public HessianProxyFactoryBean service(){
  13. HessianProxyFactoryBean proxy=new HessianProxyFactoryBean();
  14. proxy.setServiceUrl("http://localhost:8080/whichever.service");
  15. proxy.setServiceInterface(WhicheverService.class);
  16. return proxy;
  17. }
  18. }
  19. 复制代码
  • HessianTest.java
  1. //package com.fufu.rmi_client;
  2. import org.junit.Test;
  3. import org.junit.runner.RunWith;
  4. import org.springframework.beans.factory.annotation.Autowired;
  5. import org.springframework.boot.test.context.SpringBootTest;
  6. import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
  7. @SpringBootTest
  8. @RunWith(SpringJUnit4ClassRunner.class)
  9. public class HessianTest {
  10. @Autowired
  11. WhicheverService service;
  12. @Test
  13. public void test(){
  14. System.out.println(service.echo("gg"));
  15. }
  16. }
  17. 复制代码

总结:Bean的name指定成url就可以通过url定位Bean

当项目中用到了Spring Security时,客户端会有403错误

这个需要在服务端的Security配置中加入如下方法

END

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/知新_RL/article/detail/507627
推荐阅读
相关标签
  

闽ICP备14008679号