RMI 与 Spring
RMI 是 Java 平台实现远程调用的一种方式,于 JDK1.1 引入 Java 平台。“Spring 简化了 RMI 模型,它提供了一个代理工厂 Bean ,能让我们把 RMI 服务像本地 JavaBean 一样装配到我们的 Spring 应用中。”这里再多说一句, RMI 是 RPC 在 Java 平台的一种实现方式。
RMI 使用的注意事项
RMI 发布服务和调用服务需要指定端口,这意味着需要穿越防火墙,如果需要访问外网的话。
RMI 的服务端涉及到两个端口,即注册端口和服务端口,注册端口相当于服务的接线员,服务端口是客户端真正要找的业务人员。注册端口默认为1099,服务端口是随机分配的,所以在有防火墙的情况下需要自己指定。
RMI 是基于 Java 的,那么服务端和客户端都必须是 Java 开发的。
如果客户端配置为缓存服务端服务(即客户端 cacheStub=true),那么在服务器端重启的时候,客户端调用会报错,解决办法有两个:一个是客户端设置无缓存(即客户端 cacheStub=false),再一个方法是允许缓存调用失败后从新发起配置调用(refreshStubOnConnectFailure=true)。所以到这里,可以理解为 RMI 远程调用需要通过注册端口建立通道,然后通过服务端口访问业务。
如果需要在远程调用中传输对象,必须注意 Java 对象的序列化问题( 传送门)。
RMI-Spring 测试概况
我们使用三个项目来完成整个测试。
- api :定义接口和实体信息,内含抽象方法。
- server:实现api中的接口,并且对外提供 RMI 远程服务。启动方式为 main 函数启动。
- consumer:一个web项目,通过配置将远程服务注入到本地接口实现远程调用。
- master:Maven 聚合父项目,便于clean 、install。
api 主要代码
- 实体类 Model.java
import java.io.Serializable;
public class Model implements Serializable{
private static final long serialVersionUID = 1L;
private String userName;
private int age;
//get\set 方法略
}
- 接口 ApiService.java
public interface ApiService {
String getName(String name);//输入什么,返回什么
Model getModel();//返回实体类-测试远程传递对象
}
server 主要代码
- 接口实现类 ApiServiceImpl.java
public class ApiServiceImpl implements ApiService{
@Override
public String getName(String name) {
System.out.println("服务端被调用");
return name;
}
@Override
public Model getModel() {
Model m=new Model();
m.setUserName("jecket");
m.setAge(20);
return m;
}
}
- Spring Bean 注册与远程服务配置
可以使用注解配置
- Java 配置类
@Configuration
public class RpcBean {
/**
* 注册为远程服务
* @return
*/
@Bean
public RmiServiceExporter rmiExporter(){
RmiServiceExporter rmi=new RmiServiceExporter();
rmi.setService(getApiService());//接口实现 Spring Bean
rmi.setServiceName("ApiService");//远程服务名字
rmi.setServiceInterface(ApiService.class);//接口
rmi.setRegistryPort(8080);//远程服务注册端口
rmi.setServicePort(8082);//远程服务业务访问端口
return rmi;
}
/**
* 注册为Spring Bean
* @return
*/
@Bean
public ApiService getApiService(){
return new ApiServiceImpl();
}
}
- Spring 配置扫描信息
<context:component-scan base-package="com.bestcxx.stu.rpc.rmi.bean"/><!--RpcBean.java 所在包路劲 -->
也可以使用xml的配置方式
- Spring 配置文件内容
<bean id="apiService" class="com.bestcxx.stu.rpc.rmi.service.ApiServiceImpl" /> <!--Spring bean-->
<bean id="serviceExporter" class="org.springframework.remoting.rmi.RmiServiceExporter"> <!--RMI 远程服务配置-->
<property name="service" ref="apiService" />
<property name="serviceName" value="apiService" />
<property name="serviceInterface" value="com.bestcxx.stu.rpc.rmi.api.ApiService" />
<property name="registryPort" value="8080" /><!-- 注册端口 -->
<property name="servicePort" value="8080"/><!-- 通讯端口,不指定就随机 -->
</bean>
- 启动 Server
public static void main(String[] args) {
ApplicationContext ctx =
new ClassPathXmlApplicationContext("classpath:spring/applicationContext-bean.xml");
System.out.println("服务端启动...");
}
consumer 主要代码
- 为远程服务设置代理服务,注册为本地Bean
可以使用注解配置
rmi://127.0.0.1:8080/ApiService 中,127.0.0.1是远程服务地址,8080是远程服务注册端口,ApiService 是远程服务名称
@Configuration
public class RpcBean {
@Bean
public RmiProxyFactoryBean apiService(){
RmiProxyFactoryBean rmiProxy=new RmiProxyFactoryBean();
//远程服务地址,直接指定了具体的host、ip、服务名称
rmiProxy.setServiceUrl("rmi://192.168.149.1:8080/ApiService");
//rmiProxy.setServiceUrl("rmi://127.0.0.1:8080/ApiService");
//定义远程服务接口,服务端是该接口对应远程服务的实现
rmiProxy.setServiceInterface(ApiService.class);
//定义是否在首次配置远程服务后缓存该配置
rmiProxy.setCacheStub(true);
//如果远程调用缓存配置报错,设置为true,允许重新调用
rmiProxy.setRefreshStubOnConnectFailure(false);
//是否在客户端启动的时候检测服务端服务可用性-测试发现该属性没有发挥作用
rmiProxy.setLookupStubOnStartup(false);
return rmiProxy;
}
}
- Spring 配置扫描信息
<context:component-scan base-package="com.bestcxx.stu.rpc.rmi.bean"/><!--RpcBean.java 所在包路劲 -->
也可以使用xml配置
<bean id="apiService" class="org.springframework.remoting.rmi.RmiProxyFactoryBean">
<property name="serviceUrl" value="rmi://192.168.149.1:8080/ApiService" />
<property name="serviceUrl" value="rmi://localhost:8080/ApiService" />
<property name="refreshStubOnConnectFailure" value="true" />
<property name="lookupStubOnStartup" value="false" />
<property name="serviceInterface" value="com.bestcxx.stu.rpc.rmi.api.ApiService" />
</bean>
- controller 层调用
@RestController
public class HomeController {
@Autowired
private ApiService apiService;
/**
* Controller 访问案例,根据id访问数据库
* @return
*/
@RequestMapping("/home")
public Map<String,Object> home(){
Map<String,Object> map=new HashMap<String,Object>();
String name=apiService.getName("jecket");//调用 String 类型返参方法
Model m=apiService.getModel();//调用对象类型返参方法
map.put("result", "success");
map.put("name", name);
map.put("model", m);
return map;
}
}
结果展示
服务端在本地启动,客户端不知道其他网络环境访问
- 服务器端:
服务端启动...
服务端被调用
- 浏览器访问客户端
{"result":"success","name":"jecket","model":{"userName":"jecket","age":20}}