1.简介
RMI技术是java最初远程调用的技术,在JDK1.1中被引入到Java平台中,它提供了一种强大的方式实现Java程序之间的交互。Spring简化了RMI模型,它提供了一个代理工厂bean,可以将RMI服务像本地JavaBean那样装配到spring应用中,另外spring提供了一个远程导出类RMIServiceExporter,用来简化spring管理的bean转化成为RMI服务的工作。
RMI是一种实现远程服务交互的好方法,但它存在着某些限制:。
- 在互联网上运行,很难穿越防火墙,RMI服务涉及两个端口,即注册端口和服务端口,RMI服务注册端口默认是
1099
,而服务端口是随机分配的,用于传送数据,所以在有防火墙的情况下,RMI客户端要穿越防火墙与RMI服务端进行通讯,需要让随机分配的端口固定下来。 - RMI是基于Java的,这意味着客户端和服务端必须都是使用Java开发。因为RMI使用了Java的序列化机制,所以通过网络传输的对象类型必须是要保证在调用两端的java运行时是完全相同的版本。
- 客户端配置为缓存服务端服务(
cacheStub=true
),如果服务端重新启动时,客户端调用将报错,解决方式:1.客户端设置为无缓存(cacheStub =false
)。2.缓存调用失败后重新发起调用(refreshStubOnConnectFailure=true
)
1.1 重要的类
- RMIServiceExporter:此类可以将任意Spring管理的bean发布为RMI服务,在服务端使用可以简化服务的发布。
- RMIProxyFactoryBean:Spring的一个工厂bean,该bean可以为RMI服务创建代理,将此类(RMI服务代理)声明为Spring管理的bean,依赖注入到接口bean中,可以实现客户端调用RMI远程服务。
2.介绍
客户端和服务端都需要接口和实体类,为了减少重复类,将实体类和接口定义到一个单独项目里面,在客户端和服务端进行jar包引入。项目介绍如下:
——common:实体类和接口。
——RMIServer:服务端,实现common中的接口,提供RMI远程服务。
——RMIConsumer:客户端,将远程服务RMI的代理注入,进行服务的调用。
2.1 common
- 实体类
Account
为了远程传输实体类,实体类要实现Serializable接口。
public class Account implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Account [name=" + name + "]";
}
}
- 接口
AccoutService
public interface AccountService {
public void insertAccount(Account account);
public List<Account> getAccounts(String name);
}
2.2 RMIServer
- 接口实现类
AccountServiceImpl
@Service
public class AccountServiceImpl implements AccountService{
public void insertAccount(Account account) {
return;
}
public List<Account> getAccounts(String name) {
List<Account> accounts = new ArrayList<>();
Account account = new Account();
account.setName("DreamTech1113");
accounts.add(account);
return accounts;
}
}
- SpringBoot启动项目类
Application
@SpringBootApplication
//xml配置方式,需要将applicationContext.xml配置加载
@ImportResource("classpath:applicationContext.xml")
//java类配置方式,需要配置@ComponentScan,
//@ComponentScan(basePackageClasses=RMIConfiguration.class)
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
——xml配置方式。
- spring配置文件
applicationContext.xml
<!-- 接口实现类 -->
<bean id="accountService" class="com.spring.service.AccountServiceImpl">
</bean>
<!--RmiServerExporter暴露服务端的接口-->
<bean class="org.springframework.remoting.rmi.RmiServiceExporter">
<!-- does not necessarily have to be the same name as the bean to be exported -->
<property name="serviceName" value="AccountService"/>
<property name="service" ref="accountService"/>
<property name="serviceInterface" value="com.spring.service.AccountService"/>
<!-- defaults to 1099 -->
<property name="registryPort" value="1199"/>
<!--服务访问端口,不指定的情况下会随机分配-->
<!--<property name="servicePort" value="1200"/>-->
</bean>
——Java类配置方式
- Java类配置方式进行配置
使用java类配置的时候,需要注解@Configuration
表明这是配置类,另外需要注意的是springBoot项目启动类上注解@SpringBootApplication
是个复合注解,包含了注解@ComponentScan
,默认扫描的是和启动类Application
同一个包下的注解。为了扫描配置类RMIConiguration
的注解,在类Application
上加入注解@ComponentScan(basePackages="com.spring")
(当然如果和SpringBoot项目启动类在同一个目录下,不用额外@ComponentScan注解)。
@Configuration
public class RMIConfiguration {
@Bean
public RmiServiceExporter rmiExporter(AccountService accountService) {
RmiServiceExporter rmiExporter = new RmiServiceExporter();
//RMI远程服务的名称
rmiExporter.setServiceName("AccountService");
//通过@Bean注解将AccountService的实现AccountSeviceImpl注入到rmiExporter()方法中
//acountService实际就是服务的实现
rmiExporter.setService(accountService);
//接口,AccountService是接口,定义在Common项目中
rmiExporter.setServiceInterface(AccountService.class);
//远程服务注册端口
rmiExporter.setRegistryPort(1199);
//远程服务访问端口,不指定会随机分配
//rmiExporter.setServicePort(1200);
return rmiExporter;
}
}
- pom.xml文件配置
服务端项目RMIServer中pom.xml依赖主要是Common的jar包(定义了实体和接口)以及其他SpringBoot项目配置。
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.0.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--实体和接口项目依赖-->
<dependency>
<groupId>com.spring</groupId>
<artifactId>common</artifactId>
<version>1.0.0</version>
</dependency>
</dependencies>
<properties>
<java.version>1.8</java.version>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
2.3 RMIConsumer
- 调用服务端的类RMIController
@RestController
public class RMIController {
private AccountService accountService;
//远程服务代理注入到本地
@Autowired
public void setAccountService(AccountService accountService) {
this.accountService = accountService;
}
@RequestMapping("/home")
public List<Account> getAccount() {
List<Account> accounts = accountService.getAccounts("name");
return accounts;
}
@RequestMapping("/helloworld")
public String getWorld() {
return "hello";
}
}
- SpringBoot项目启动类Application
@SpringBootApplication
//加载spring配置文件
@ImportResource("classpath:applicationContext.xml")
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
——xml配置方式
- spring配置文件
applicationContext.xml
<bean class="com.spring.controller.RMIController">
<property name="accountService" ref="accountService"/>
</bean>
<!-- 远程服务代理 -->
<bean id="accountService" class="org.springframework.remoting.rmi.RmiProxyFactoryBean">
<property name="serviceUrl" value="rmi://localhost:1199/AccountService"/>
<property name="serviceInterface" value="com.spring.service.AccountService"/>
</bean>
——Java类配置方式
- Java类配置方式进行配置
@Configuration
public class RMIClientConfiguration {
@Bean
public RmiProxyFactoryBean rmiProxy() {
RmiProxyFactoryBean rmiProxy=new RmiProxyFactoryBean();
//远程服务的接口,服务端对此接口进行了实现。
rmiProxy.setServiceInterface(AccountService.class);
//调用远程服务的地址,localhost(127.0.0.1)远程服务ip地址,1199是远程服务注册端口
//AccountService是远程服务名称
rmiProxy.setServiceUrl("rmi://localhost:1199/AccountService");
return rmiProxy;
}
}
同RMIServer一样,需要在启动类Application
上加注解@ComponentScan(basePackages="com.spring")
其中配置类RMIClientConfiguration
位于包com.spring下面。
- pom.xml
同服务端项目RMIServer中的pom.xml配置。
3.测试调用
3.1 Controller层调用
在客户端写了controller层,可以通过访问页面的方式,页面访问RMIConsumer---->RMIServer方式返回远程调用结果。需要注意的是服务端RMIServer项目启动的tomcat端口是8080,客户端需要修改tomcat端口号(我这里修改为8889),在RMIConsumer项目/src/main/resources目录下创建一个文件名application.properties
,内容如下:
server.port=8889
页面访问:http://localhost:8889/home
,返回结果是:
[{“name”:“DreamTech1113”}]
3.2 单元测试
Spring配置文件方式(xml配置访问
)可以通过单元测试方式来测试。
public class TestRMIInvoker {
@Test
public void testDemo() {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
AccountService accountService =(AccountService) context.getBean("accountService");
List<Account> accounts = accountService.getAccounts("name");
System.out.println(accounts);
}
}
4.参考
1.Spring官方文档
2.《Spring实战(Spring IN ACTION)》