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

为什么写这篇文章?

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

  • 启动时的错误 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)
//package com.fufu.rmi_service;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping;
import org.springframework.web.servlet.support.AbstractDispatcherServletInitializer;
import java.util.Properties;

@Configuration
public class DispatcherConfig extends AbstractDispatcherServletInitializer {
  @Override
  protected WebApplicationContext createServletApplicationContext() {
      return null;
  }

  @Override
  protected String[] getServletMappings() {
      return new String[]{"/","*.service"};
  }

  @Override
  protected WebApplicationContext createRootApplicationContext() {
      return null;
  }

  @Bean
  public HandlerMapping hessianMapping(){
      SimpleUrlHandlerMapping mapping=new SimpleUrlHandlerMapping();
      Properties mappings=new Properties();
      mappings.setProperty("/whichever.service","hessianServiceExporter");//service的路径和第二个配置类中的方法名
      mapping.setMappings(mappings);
      return mapping;
  }
}
复制代码

2.HessianConfig

//package com.fufu.rmi_service;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.remoting.caucho.HessianServiceExporter;

@Configuration
public class HessianConfig {

    @Bean
    public HessianServiceExporter hessianServiceExporter(WhicheverService service){
        HessianServiceExporter exporter=new HessianServiceExporter();
        exporter.setService(service);
        exporter.setServiceInterface(service.getClass());
        return exporter;
    }
}
复制代码
  1. WhicheverService (服务接口)
public interface WhicheverService {
    String echo(String args);
}
复制代码
  1. WhicheverServiceImp (服务实现)
import org.springframework.stereotype.Component;

@Component
public class WhicheverServiceImp implements WhicheverService {
    @Override
    public String echo(String args) {
        return args;
    }
}
复制代码

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

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

WhicheverServiceImp.java

import org.springframework.stereotype.Component;
import java.io.Serializable;

@Component
public class WhicheverServiceImp implements WhicheverService,Serializable {
    @Override
    public String echo(String args) {
        return args;
    }
}

复制代码

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

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

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

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

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

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

//package com.fufu.rmi_service;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.remoting.caucho.HessianServiceExporter;

@Configuration
public class HessianConfig {
    @Bean
    public HessianServiceExporter hessianServiceExporter(WhicheverService service){
        HessianServiceExporter exporter=new HessianServiceExporter();
        exporter.setService(service);
        exporter.setServiceInterface(WhicheverService.class);//  已经更正
        return exporter;
    }
}

复制代码
OKay,这次我们‘成功’的启动了服务,为什么加引号呢?因为目前只是编译器向我们表明 我们的代码编译期正确 ,但我们此时并不知道出错,于是我们开发客户端,或者称之为调用端似乎更加贴切 RMI 的 I 代表的 invoke。

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

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

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

//package com.fufu.rmi_client;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.remoting.caucho.HessianProxyFactoryBean;

@Configuration
public class HessianConfig {

    @Bean
    public HessianProxyFactoryBean service(){
        HessianProxyFactoryBean proxy=new HessianProxyFactoryBean();
        proxy.setServiceUrl("http://localhost:8080/whichever.service");
        proxy.setServiceInterface(WhicheverService.class);
        return proxy;
    }
}
复制代码

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

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

//package com.fufu.rmi_client;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@SpringBootTest
@RunWith(SpringJUnit4ClassRunner.class)
public class HessianTest {
    @Autowired
    WhicheverService service;

    @Test
    public void test(){
        System.out.println(service.echo("gg"));
    }
}
复制代码

我们希望看到控制台输出“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 修改为

//package com.fufu.rmi_service;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.remoting.caucho.HessianServiceExporter;

@Configuration
public class HessianConfig {

    @Bean(name = "/whichever.service") //通过name指定bean的名字
    public HessianServiceExporter hessianServiceExporter(WhicheverService service){
        HessianServiceExporter exporter=new HessianServiceExporter();
        exporter.setService(service);
        exporter.setServiceInterface(WhicheverService.class);
        return exporter;
    }
}

复制代码

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

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

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

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

【最终最简洁的实现】

服务端的类

  • WhicheverService.java
//package com.fufu.rmi_service;
public interface WhicheverService {
    String echo(String args);
}

复制代码
  • WhicheverServiceImp.java
//package com.fufu.rmi_service;
import org.springframework.stereotype.Component;

@Component
public class WhicheverServiceImp implements WhicheverService{
    @Override
    public String echo(String args) {
        return args;
    }
}
复制代码
  • RmiServiceApplication.java
//package com.fufu.rmi_service;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.remoting.caucho.HessianServiceExporter;

@SpringBootApplication
public class RmiServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(RmiServiceApplication.class, args);
    }

    @Bean(name = "/whichever.service")
    public HessianServiceExporter hessianServiceExporter(WhicheverService service){
        HessianServiceExporter exporter=new HessianServiceExporter();
        exporter.setService(service);
        exporter.setServiceInterface(WhicheverService.class);
        return exporter;
    }
}

复制代码

调用端的类

  • WhicheverService.java
//package com.fufu.rmi_client;
public interface WhicheverService {
    String echo(String args);
}
复制代码
  • RmiClientApplication.java
//package com.fufu.rmi_client;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.remoting.caucho.HessianProxyFactoryBean;

@SpringBootApplication
public class RmiClientApplication {
    public static void main(String[] args) {
        SpringApplication.run(RmiClientApplication.class, args);
    }

    @Bean
    public HessianProxyFactoryBean service(){
        HessianProxyFactoryBean proxy=new HessianProxyFactoryBean();
        proxy.setServiceUrl("http://localhost:8080/whichever.service");
        proxy.setServiceInterface(WhicheverService.class);
        return proxy;
    }
}
复制代码
  • HessianTest.java
//package com.fufu.rmi_client;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@SpringBootTest
@RunWith(SpringJUnit4ClassRunner.class)
public class HessianTest {
    @Autowired
    WhicheverService service;

    @Test
    public void test(){
        System.out.println(service.echo("gg"));
    }
}
复制代码

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

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

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

END

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值