前言
在java生态圈谈到Rpc,很多人可能就会想到Dubbo、Motan、Grpc等框架。但是你知道吗?作为Java编程全家桶的Spring已经内置了多种RPC的实现方式,可以直接使用。存在即合理,有些场景下其实并不需要Dubbo,Grpc等重量级的RPC组件,那么Spring的轻量封装就可以派上用场了。下面就来探索下Spring中的RPC的实现方式以及如何使用的。
文中代码地址:https://gitee.com/kailing/spring-rpc
什么是Rpc?
Rpc(Remote Procedure Call): 封装了内部实现的远程调用过程就是rpc,rpc主要为了简化远程服务调用,通俗的讲就是调用远程服务(跨主机,跨进程)就像调用本地方法一样。Spring Cloud体系中的Fegin 技术也可以认为是采用http协议传输数据的一种Rpc技术。
Spring中的Rpc
Spring中内置了六种不同数据传输方式的原生的Rpc实现,分别是WebService、Jms、Rmi、Http、Hessian(http)、Amqp。熟悉Rpc的知道,在Java中,主要是通过生成服务接口的代理来实现Rpc服务的调用,Dubbo、Motan这样,Spring的实现也是这样。在Rpc服务调用中,有两个角色,分别是服务的提供者和调用者(消费者)。一方面服务调用者通过代理,在服务调用时会传输服务定义的接口名+方法参数给到提供者。另一方面服务提供者拿到接口信息找到本地服务生成调用结果返回给调用者。所以下面所述六种Rpc实现都会有一个公共的服务接口定义,以及各自的代理实现配置。
定义服务接口
/**
* @WebService 注解只用于ws 提供的RPC服务
*/
@WebService
public interface AccountService {
Account getAccount(String name);
class Account implements Serializable {
private String name;
public String getName(){
return name;
}
public void setName(String name) {
this.name = name;
}
}
}
公共的api,在Rpc的提供者和消费者中都会使用到,提供者中会实现这个接口提供服务,消费者会通过代理,生成这个接口的代理实现 ,然后通过底层封装发送具体的消息。和使用dubbo和motan类似
调用服务代码
@SpringBootApplication
public class WsConsumerApplication {
@Autowired
private AccountService accountService;
@PostConstruct
public void callRpcService(){
System.out.println("RPC远程访问开始!");
System.err.println(accountService.getAccount("kl").getName());
System.out.println("RPC远程访问结束!");
}
public static void main(String[] args) {
SpringApplication.run(WsConsumerApplication.class, args);
}
}
每个Rpc实现都一样,都是通过注入AccountService 接口的代理实现来调用服务。不过每个Rpc的代理的配置方式会略有不同,主要体现在不同的传输技术会用到不同的配置。总的来说,连接url(http://127.0.0.1、tcp://172.0.0.1、rmi://127.0.0.1),端口、代理接口信息等都是共同需要的。
WebService的Rpc实现
服务提供者
服务实现
@WebService(serviceName="AccountService",endpointInterface = "com.spring.rpc.api.AccountService")
@Service
public class AccountServiceImpl extends SpringBeanAutowiringSupport implements AccountService {
Logger logger = LoggerFactory.getLogger(getClass());
@Override
@WebMethod
public Account getAccount(String name) {
logger.info("{} 请求获取账号!", name);
Account account = new Account();
account.setName(name + "的账号");
return account;
}
}
和其他服务实现不一样,WebService定义服务时,需要使用@WebService和@WebMethod注解标记
服务暴露
@Configuration
public class WsConfig {
private String ipList = "127.0.0.1";
private String userName = "admin";
private String passWord = "sasa";
@Bean
public SimpleHttpServerJaxWsServiceExporter rmiServiceExporter(Authenticator authenticator) {
SimpleHttpServerJaxWsServiceExporter exporter = new SimpleHttpServerJaxWsServiceExporter();
exporter.setHostname("127.0.0.1");
exporter.setPort(8083);
exporter.setAuthenticator(authenticator);
return exporter;
}
@Bean
public Authenticator authenticator(){
Authenticator authenticator = new Authenticator();
authenticator.setIpList(ipList);
authenticator.setUserName(userName);
authenticator.setPassWord(passWord);
return authenticator;
}
}
完成如上代码,其实我们已经构建了一个完整的WebService服务,而且还加上了用户、密码和ip白名单等接口权限认证,访问:http://127.0.0.1:8083/AccountServiceImpl?WSDL 就可以看到服务的定义,如下:
服务消费者
@Configuration
public class WsConfig {
@Bean("accountService")
public JaxWsPortProxyFactoryBean accountService()throws Exception{
JaxWsPortProxyFactoryBean factoryBean = new JaxWsPortProxyFactoryBean();
factoryBean.setServiceName("AccountService");
factoryBean.setPortName("AccountServiceImplPort");
factoryBean.setNamespaceUri("http://provider.ws.rpc.spring.com/");
URL wsdlDocumentUrl = new URL("http://127.0.0.1:8083/AccountServiceImpl?WSDL");
factoryBean.setWsdlDocumentUrl(wsdlDocumentUrl);
factoryBean.setServiceInterface(AccountService.class);
factoryBean.setUsername("admin");
factoryBean.setPassword("sasa");
return factoryBean;
}
}
通过声明JaxWsPortProxyFactoryBean来获得AccountService.class的代理实例。当注入服务调用方法时,实际上是触发了一次WebService的远程调用
Http的Rpc实现
服务提供者
服务实现
@Service
public class AccountServiceImpl implements AccountService {
Logger logger = LoggerFactory.getLogger(getClass());
@Override
public Account getAccount(String name) {
logger.info("{} 请求获取账号!", name);
Account account = new Account();
account.setName(name + "的账号");
return account;
}
}
服务暴露
@Configuration
public class HttpConfig {
@Bean("/AccountService")
public HttpInvokerServiceExporter rmiServiceExporter(AccountServiceImpl accountService){
HttpInvokerServiceExporter exporter = new HttpInvokerServiceExporter();
exporter.setService(accountService);
exporter.setServiceInterface(AccountService.class);
return exporter;
}
@Bean
public ServletRegistrationBean servletRegistrationBean(DispatcherServlet dispatcherServlet) {
ServletRegistrationBean servlet = new ServletRegistrationBean();
servlet.setServlet(dispatcherServlet);
servlet.setName("remoting");
servlet.setLoadOnStartup(1);
servlet.addUrlMappings("/remoting/*");
return servlet;
}
}
服务消费者
@Configuration
public class HttpConfig {
@Bean("accountService")
public HttpInvokerProxyFactoryBean accountService(){
HttpInvokerProxyFactoryBean factoryBean = new HttpInvokerProxyFactoryBean();
factoryBean.setHttpInvokerRequestExecutor(new HttpComponentsHttpInvokerRequestExecutor());
factoryBean.setServiceUrl("http://127.0.0.1:8081/remoting/AccountService");
factoryBean.setServiceInterface(AccountService.class);
return factoryBean;
}
}
可以看到,在配置Http实现的Rpc服务消费者时,和WebService是类似的,定义一个FactoryBean就ok了。其实其他的四种Rpc实现也都大同小异。后面就不一一列举了
文末结语
博文起草构思的时候本来打算将Spring中内置六种Rpc实现都详细描述下,后面看着看着就觉得使用起来真的很类似。只不过像Amqp和Jms以及WebService等实现需要有这方面技术经验的人才能看的明白。但单就Rpc使用和实现来说基本差不多,所以后面就没有一一列出占用篇幅。但是上面提到的WebService、Jms、Rmi、Http、Hessian、Amqp这六种实现在上面的git仓库中都有详细的实例程序。感兴趣的不妨下载下来跑一跑,看下每个实现的代理工厂类都是如何实现的,非常有助于你真正理解Rpc的调用过程,以及实现自己的Rpc轮子。
作者简介:
陈凯玲,2016年5月加入凯京科技。现任凯京科技研发中心架构&运维部架构组经理。独立博客KL博客(http://www.kailing.pub)博主。
欢迎加入凯京开源技术QQ群:613025121,和我们一起交流互联网应用的技术架构落地实践