为什么写这篇文章?
因为我遇到了两个问题,并且用了大量的时间解决
- 启动时的错误 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配置中加入如下方法