为什么写这篇文章?
因为我遇到了两个问题,并且用了大量的时间解决
- 启动时的错误 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部分也进行了展示
- 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;
}
}
复制代码
- WhicheverService (服务接口)
public interface WhicheverService {
String echo(String args);
}
复制代码
- 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这个类
//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中去
//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 发现
这是为什么呢?
回到书里 我看到这样一句
突然发现似乎少了些将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" 这里的路径可以是任意以“/”开头的
然后启动服务端的状态下再启动调用端的测试类
最后让我们一起看看最简洁的配置,我会将现有的配置进一步精简
【最终最简洁的实现】
服务端的类
- 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配置中加入如下方法