RPC,即 Remote Procedure Call(远程过程调用),说得通俗一点就是:调用远程计算机上的服务,就像调用本地服务一样
。RPC 可基于 HTTP 或 TCP 协议,具有跨平台的特性。本文主要说明了作者如何借用SpringMVC框架来实现RPC。
废话不多说,先把原理说清楚。要实现RPC框架,要解决两个问题:(1)数据传输;(2)序列化。首先,借助SpringMVC框架基于Http协议的数据交互,可以解决客户端和服务器端的数据传输问题。其实,SpringMVC框架很好的集成了Json和实体对象互转,利用这个功能就能很好的解决序列化问题。我设计的原理就是通过仿照Http的Request和Response交互原理,通过自定义一个RpcRequest和RpcResponse来实现RPC框架的数据传输和序列化。
RPC框架的两个最核心问题解决了,剩下的就是客户端如何调用服务端的方法,熟悉Java反射的同学,现在就应该知道如何做啦把。
我的框架的设计思路如下:(1)建一个RpcProcessor工程,专门负责处理服务器端数据传输、序列化和过程调用;(2)建立一个RpcProxy工程,专门负责客户端和服务器端数据传输、序列化和客户端接口调用转换;(3)服务器端工程引用RpcProcessor,开发接口和实现类即可;(4)客户端工程引用RpcProxy工程和服务器端工程的接口,直接调用服务器端接口即可。
(一)RpcProcessor工程
1.基于Spring的Component编写RPC接口注解
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Component
public @interface RpcService {
Class value();
String version() default "";
}
2.编写RpcRequest封装RPC请求,RpcResponse封装RPC的响应
public class RpcRequest implements Serializable {
private static final long serialVersionUID = -1131913296667492127L;
private String sid;
private String serviceName;
private String serviceVersion;
private String methodName;
private Class[] paramTypes;
private Object[] params;
public String getSid() {
return sid;
}
public void setSid(String sid) {
this.sid = sid;
}
public String getServiceName() {
return serviceName;
}
public void setServiceName(String serviceName) {
this.serviceName = serviceName;
}
public String getServiceVersion() {
return serviceVersion;
}
public void setServiceVersion(String serviceVersion) {
this.serviceVersion = serviceVersion;
}
public String getMethodName() {
return methodName;
}
public void setMethodName(String methodName) {
this.methodName = methodName;
}
public Class[] getParamTypes() {
return paramTypes;
}
public void setParamTypes(Class[] paramTypes) {
this.paramTypes = paramTypes;
}
public Object[] getParams() {
return params;
}
public void setParams(Object[] params) {
this.params = params;
}
}
public class RpcResponse implements Serializable {
private static final long serialVersionUID = -701120938312574409L;
private String sid;
private Class resultType;
private Object result;
public String getSid() {
return sid;
}
public void setSid(long sid) {
this.sid = sid;
}
public Class getResultType() {
return resultType;
}
public void setResultType(Class resultType) {
this.resultType = resultType;
}
public Object getResult() {
return result;
}
public void setResult(Object result) {
this.result = result;
}
}
3.利用Spring的Ioc原理,实现对RpcService注解的扫描
@Component
@Lazy(true)
public class RpcAnnotationScannerConfigurer implements BeanDefinitionRegistryPostProcessor {
private Map rpcServiceBeanMap = new HashMap<>();
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory arg0) throws BeansException {
// TODO Auto-generated method stub
Map serviceBeanMap = arg0.getBeansWithAnnotation(RpcService.class);
if (MapUtils.isNotEmpty(serviceBeanMap))
{
for(Object serviceBean : serviceBeanMap.values())
{
RpcService rpcService = serviceBean.getClass().getAnnotation(RpcService.class);
//拿到注解的名称
String serviceName = rpcService.value().getName();
String serviceVersion = rpcService.version();
if (!StringUtils.isEmpty(serviceVersion))
{
serviceName += "-" + serviceVersion;
}
rpcServiceBeanMap.put(serviceName, serviceBean);
}
}
}
public Map getRpcServiceBeanMap() {
return rpcServiceBeanMap;
}
public void setRpcServiceBeanMap(Map rpcServiceBeanMap) {
this.rpcServiceBeanMap = rpcServiceBeanMap;
}
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry arg0) throws BeansException {
// TODO Auto-generated method stub
}
}
4.使用RpcHandle实现Rpc请求的处理,这里使用到了反射的技术。在此不表啦,不懂得同学自己去查看cglib动态代理。
@Componet
public class RpcHandle
{
@Autowired
private RpcAnnotationScannerConfigurer rpcConfigurer;
private Object Handle(RpcRequest request) throws Exception{
String serviceName = request.getServiceName();
String serviceVersion = request.getServiceVersion();
if (!StringUtils.isEmpty(serviceVersion))
{
serviceName += "-"+serviceVersion;
}
Map rpcBeanMap = rpcConfigurer.getRpcServiceBeanMap();
Object serviceBean = rpcBeanMap.get(serviceName);
if (null == serviceBean)
{
throw new RuntimeException(String.format("can not find service bean by key:%s",serviceName));
}
//获取反射调用所需要的参数
Class serviceClass = serviceBean.getClass();
String methodName = request.getMethodName();
Class[] parameterTypes = request.getParamTypes();
Object[] parameters = request.getParams();
FastClass serviceFastClass = FastClass.create(serviceClass);
FastMethod serviceFastMethod = serviceFastClass.getMethod(methodName,parameterTypes);
return serviceFastMethod.invoke(serviceBean, parameters);
}
}
@RestController
@RequestMapping(value="/rpc")
public class RpcController {
@Autowired
private RPCHandler rpcHandler;
@RequestMapping(value="/invoke",method= RequestMethod.POST)
public ResponseEntity invokeRpc(@RequestBody RpcRequest request)
{
RpcResponse rpcResponse = new RpcResponse();
rpcResponse.setSid(request.getSid());
//获取对象,调用具体的对象
try {
Object object = rpcHandler.Handle(request);
rpcResponse.setResult(object);
} catch (Exception e) {
// TODO Auto-generated catch block
rpcResponse.setException(e);
}
return new ResponseEntity(rpcResponse, HttpStatus.OK);
}
}
二、建一个RpcServer工程来编写服务接口和服务实现,RpcServer需要把RpcProxy工程引入。
1.编写服务接口
public interface HelloService {
String greeting();
String HelloWorld(String name);
}
2.编写服务实现
@RpcService(value= HelloService.class)
public class HelloServiceImpl implements HelloService {
@Override
public String greeting() {
// TODO Auto-generated method stub
return "Hi";
}
@Override
public String HelloWorld(String name) {
// TODO Auto-generated method stub
StringBuilder sBuilder = new StringBuilder("Hello world ");
sBuilder.append(name);
return sBuilder.toString();
}
}
3.在application.properties配置服务的端口和服务路径
server.port=8080
server.context-path=/rpcserver
server.session.timeout=30
三、建立RpcProxy工程,负责和服务器端数据传输、序列化。
1.编写RpcRequest封装RPC请求,RpcResponse封装RPC的响应,和RpcProxy的RpcRequest和RpcResponse代码相同,这边就不表啦。
2.编写RpcInvoker类,采用SpringMVC的RestTemplate实现和RpcProcessor的通讯和序列化
@Component
public class RpcInvoker {
private RestTemplate restTemplate;
@Autowired
private Environment environment;
@SuppressWarnings("unchecked")
public T Create(final Class interfaceClass, final String serviceVersion){
return (T) Proxy.newProxyInstance(
interfaceClass.getClassLoader(),
new Class[]{interfaceClass},
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// TODO Auto-generated method stub
//创建RPC请求
RpcRequest request = new RpcRequest();
request.setSid(UUID.randomUUID().toString());
request.setServiceName(method.getDeclaringClass().getName());
request.setServiceVersion(null);
request.setMethodName(method.getName());
request.setParamTypes(method.getParameterTypes());
request.setParams(args);
RpcResponse response = invokeProcessHandler(request);
if (response == null)
{
throw new RuntimeException("Response is null");
}
return response.getResult();
}
});
}
private RpcResponse invokeProcessHandler(RpcRequest request) throws Exception
{
restTemplate = new RestTemplate();
ResponseEntity responseEntity = restTemplate.postForEntity(environment.getProperty("rpcserver"), request, RpcResponse.class);
RpcResponse response = responseEntity.getBody();
return response;
}
}
四、建立RpcClient工程,引入RpcProxy和服务器端的接口。在RpcClient中可以像调用本地接口一样实现远程调用啦。
1.HelloController类实现远程调用。
@Controller
@RequestMapping("/client")
public class HelloController {
@Autowired
private RpcInvoker rpcInvoker;
@RequestMapping(value="/hello/greeting",method=RequestMethod.GET)
public @ResponseBody String greeting(){
HelloService helloService = rpcInvoker.Create(HelloService.class, null);
String result = helloService.greeting();
return result;
}
@RequestMapping(value="/hello",method=RequestMethod.GET)
public @ResponseBody String greeting1(){
return "hello";
}
@RequestMapping(value="/hello/helloworld/{name}", method=RequestMethod.GET)
public @ResponseBody String HelloWorld(@PathVariable("name") String name)
{
HelloService helloService = rpcInvoker.Create(HelloService.class, null);
String result = helloService.HelloWorld(name);
return result;
}
}
2.在客户端工程的application.properties中配置Rpc服务的访问地址
rpcserver = http://localhost:8080/rpcserver/rpc/invoke