利用SpringMVC实现基于Http和Json的轻量级RPC框架

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);		
	}
}


5.建立RpcController负责实现和客户端RpcProxy的数据传输,序列化和过程调用

@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

大功告成。本文通过 SpringMVC和cglib动态代理技术 实现了一个轻量级 RPC 框架,使用 Spring 提供依赖注入与参数配置、Json格式序列化,让让服务端与客户端的开发完全分离,为实现大规模分布式应用提供了基础支持。













你可能感兴趣的:(开发相关)