(三)远程服务:RMI 服务实现远程调用

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}}
github 地址

https://github.com/Bestcxy/RPC/tree/master/rmi/master

猜你喜欢

转载自blog.csdn.net/bestcxx/article/details/79488165