Implementation of RPC function based on JMS

original
2017/06/13 22:25
Reading number 812

preface
The point-to-point messaging mode of JMS allows JMS clients to send and receive messages synchronously and asynchronously through the virtual channel queue; The point-to-point messaging mode supports both asynchronous "send and discard" messaging and synchronous request/response messaging; The point-to-point messaging mode supports load balancing, which allows multiple receivers to listen to the same queue and use it to distribute load; Therefore, it is completely possible to implement a set of RPC frameworks in the point-to-point messaging mode of JMS.

get ready
jdk:jdk1.7.0_80
jms:apache-activemq-5.10.0
serialize:protostuff

Overall structure
The whole implementation includes: rpc jms common, rpc-jms-client,rpc-jms-server;
And test module: rpc jms test api, rpc-jms-test-client,rpc-jms-test-server
As a whole, it is shown in the following figure:

Rpc jms common: common module
Rpc jms client: support library for client
Rpc jms server: support library for server

Client implementation
The core idea of the client is dynamic proxy. The generated proxy class encapsulates the interface name, version information, method name, parameter type, parameter value and other information into RpcRequest, and then uses protostuff to serialize it. The serialized binary data is put into BytesMessage and sent to the message queue; Then wait for the message to return, get binary data from BytesMessage, and use protostuff to deserialize; Finally, the result or exception is displayed to the user, and the specific code is as follows:

 public class RpcClient { private static final Logger LOGGER = LoggerFactory.getLogger(RpcClient.class); private QueueConnection qConnection; private QueueSession qSession; private Queue requestQ; private Queue responseQ; public RpcClient(String rpcFactory, String rpcRequest, String rpcResponse) { try { Context ctx = new InitialContext(); QueueConnectionFactory factory = (QueueConnectionFactory) ctx.lookup(rpcFactory); qConnection = factory.createQueueConnection(); qSession = qConnection.createQueueSession(false,  Session.AUTO_ACKNOWLEDGE); requestQ = (Queue) ctx.lookup(rpcRequest); responseQ = (Queue) ctx.lookup(rpcResponse); qConnection.start(); } catch (Exception e) { LOGGER.error("init rpcproxy error", e); } } public <T> T create(final Class<?>  interfaceClass) { return create(interfaceClass, ""); } @SuppressWarnings("unchecked") public <T> T create(final Class<?>  interfaceClass, final String serviceVersion) { return (T) Proxy.newProxyInstance(interfaceClass.getClassLoader(), new Class<?>[] { interfaceClass }, new InvocationHandler() { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { RpcRequest request = new RpcRequest(); request.setRequestId(UUID.randomUUID().toString()); request.setInterfaceName(method.getDeclaringClass().getName()); request.setServiceVersion(serviceVersion); request.setMethodName(method.getName()); request.setParameterTypes(method.getParameterTypes()); request.setParameters(args); BytesMessage requestMessage = qSession.createBytesMessage(); requestMessage.writeBytes(SerializationUtil.serialize(request)); requestMessage.setJMSReplyTo(responseQ); QueueSender qsender = qSession.createSender(requestQ); qsender.send(requestMessage); String filter = "JMSCorrelationID = '" + requestMessage.getJMSMessageID() + "'"; QueueReceiver qReceiver = qSession.createReceiver(responseQ, filter); BytesMessage responseMessage = (BytesMessage) qReceiver.receive(30000); byte messByte[] = new byte[(int) responseMessage.getBodyLength()]; responseMessage.readBytes(messByte); RpcResponse rpcResponse = SerializationUtil.deserialize(messByte, RpcResponse.class); if (rpcResponse.hasException()) { throw rpcResponse.getException(); } else { return rpcResponse.getResult(); } } }); } }

RpcClient has established a connection with jms when it is created. The configuration information of relevant jms is explained in the testing section.

The RpcRequest code of encapsulation request class is as follows:

 public class RpcRequest { private String requestId; private String interfaceName; private String serviceVersion; private String methodName; private Class<?>[] parameterTypes; private Object[] parameters; //Omit get and set methods }

The RpcResponse code of encapsulation response class is as follows:

 public class RpcResponse{ private String requestId; private Exception exception; private Object result; //Omit get and set methods }

Server implementation
The server first loads all class objects that need to provide external services; Then listen to the message queue, obtain binary data from BytesMessage, and deserialize it to the RpcRequest object through protostuff; Finally, the service class is called through reflection, and the obtained results are encapsulated into RpcResponse, and then serialized binary is written into BytesMessage and sent to the client. The specific code is as follows:

 public class RpcServer implements ApplicationContextAware, InitializingBean { private static final Logger LOGGER = LoggerFactory.getLogger(RpcServer.class); private QueueConnection qConnection; private QueueSession qSession; private Queue requestQ; private String rpcFactory; private String rpcRequest; /** *Store the mapping relationship between service name and service object */ private Map<String, Object> serviceMap = new HashMap<String, Object>(); public RpcServer(String rpcFactory, String rpcRequest) { this.rpcFactory = rpcFactory; this.rpcRequest = rpcRequest; } @Override public void afterPropertiesSet() throws Exception { try { Context ctx = new InitialContext(); QueueConnectionFactory factory = (QueueConnectionFactory) ctx.lookup(rpcFactory); qConnection = factory.createQueueConnection(); qSession = qConnection.createQueueSession(false,  Session.AUTO_ACKNOWLEDGE); requestQ = (Queue) ctx.lookup(rpcRequest); qConnection.start(); QueueReceiver receiver = qSession.createReceiver(requestQ); receiver.setMessageListener(new RpcMessageListener(qSession, serviceMap)); LOGGER.info("ready receiver message"); } catch (Exception e) { if (qConnection !=  null) { try { qConnection.close(); } catch (JMSException e1) { } } LOGGER.error("server start error", e); } } @Override public void setApplicationContext(ApplicationContext ctx) throws BeansException { Map<String, Object> serviceBeanMap = ctx.getBeansWithAnnotation(RpcService.class); if (serviceBeanMap !=  null && serviceBeanMap.size() > 0) { for (Object serviceBean : serviceBeanMap.values()) { RpcService rpcService = serviceBean.getClass().getAnnotation(RpcService.class); String serviceName = rpcService.value().getName(); String serviceVersion = rpcService.version(); if (serviceVersion !=  null && !serviceVersion.equals("")) { serviceName += "-" + serviceVersion; } serviceMap.put(serviceName, serviceBean); } } } }

Message listener class RpcMessageListener:

 public class RpcMessageListener implements MessageListener { private static final Logger LOGGER = LoggerFactory.getLogger(RpcMessageListener.class); private QueueSession qSession; /** *Store the mapping relationship between service name and service object */ private Map<String, Object> serviceMap = new HashMap<String, Object>(); public RpcMessageListener(QueueSession qSession, Map<String, Object> serviceMap) { this.qSession = qSession; this.serviceMap = serviceMap; } @Override public void onMessage(Message message) { try { LOGGER.info("receiver message : " + message.getJMSMessageID()); RpcResponse response = new RpcResponse(); BytesMessage responeByte = qSession.createBytesMessage(); responeByte.setJMSCorrelationID(message.getJMSMessageID()); QueueSender sender = qSession.createSender((Queue) message.getJMSReplyTo()); try { BytesMessage byteMessage = (BytesMessage) message; byte messByte[] = new byte[(int) byteMessage.getBodyLength()]; byteMessage.readBytes(messByte); RpcRequest rpcRequest = SerializationUtil.deserialize(messByte, RpcRequest.class); response.setRequestId(rpcRequest.getRequestId()); String serviceName = rpcRequest.getInterfaceName(); String serviceVersion = rpcRequest.getServiceVersion(); if (serviceVersion !=  null && !serviceVersion.equals("")) { serviceName += "-" + serviceVersion; } Object serviceBean = serviceMap.get(serviceName); if (serviceBean == null) { throw new RuntimeException(String.format("can not find service bean by key: %s", serviceName)); } Class<?> serviceClass = serviceBean.getClass(); String methodName = rpcRequest.getMethodName(); Class<?>[] parameterTypes = rpcRequest.getParameterTypes(); Object[] parameters = rpcRequest.getParameters(); Method method = serviceClass.getMethod(methodName, parameterTypes); method.setAccessible(true); Object result = method.invoke(serviceBean, parameters); response.setResult(result); } catch (Exception e) { response.setException(e); LOGGER.error("onMessage error", e); } responeByte.writeBytes(SerializationUtil.serialize(response)); sender.send(responeByte); } catch (Exception e) { LOGGER.error("send message error", e); } } }

test
1. rpc jms test api: interface module, used jointly by rpc jms test client and rpc jms test server
IHelloService class:

 public interface IHelloService { String hello(String name); }

2. rpc jms test server: server side test module, depending on rpc jms server
The jms related information is configured in jndi. properties, as shown below:

 java.naming.factory.initial=org.apache.activemq.jndi.ActiveMQInitialContextFactory java.naming.provider.url= tcp://localhost:61616 java.naming.security.principal=system java.naming.security.credentials=manager connectionFactoryNames=RPCFactory queue.RPCRequest=jms.RPCRequest queue.RPCResponse=jms.RPCResponse

The spring-server.xml configuration file that the server mainly depends on is mainly used to instantiate RpcServer

 <? xml version="1.0" encoding="UTF-8"?> <beans xmlns=" http://www.springframework.org/schema/beans " xmlns:xsi=" http://www.w3.org/2001/XMLSchema-instance " xmlns:context=" http://www.springframework.org/schema/context " xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd "> <context:component-scan base-package="zh.rpc.jms.test.server.impl" /> <context:property-placeholder location="classpath:jms.properties" /> <bean id="rpcServer" class="zh.rpc.jms.server.RpcServer"> <constructor-arg name="rpcFactory" value="${rpc.rpc_factory}" /> <constructor-arg name="rpcRequest" value="${rpc.rpc_request}" /> </bean> </beans>

Specific implementation class of IHelloService:

 @RpcService(IHelloService.class) public class HelloServiceImpl implements IHelloService { @Override public String hello(String name) { return "REQ+" + name; } }

3. rpc jms test client: client test module, depending on rpc jms client
The client also needs to connect to the message queue, so the configuration file jndi. properties is also provided;
The spring-client.xml that the client mainly depends on:

 <? xml version="1.0" encoding="UTF-8"?> <beans xmlns=" http://www.springframework.org/schema/beans " xmlns:xsi=" http://www.w3.org/2001/XMLSchema-instance " xmlns:context=" http://www.springframework.org/schema/context " xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd "> <context:property-placeholder location="classpath:jms.properties" /> <bean id="rpcProxy" class="zh.rpc.jms.client.RpcClient"> <constructor-arg name="rpcFactory" value="${rpc.rpc_factory}" /> <constructor-arg name="rpcRequest" value="${rpc.rpc_request}" /> <constructor-arg name="rpcResponse" value="${rpc.rpc_response}" /> </bean> </beans>

Client test class:

 public class ClientTest { private static ApplicationContext context; public static void main(String[] args) throws Exception { context = new ClassPathXmlApplicationContext("spring-client.xml"); RpcClient rpcProxy = context.getBean(RpcClient.class); IHelloService helloService = rpcProxy.create(IHelloService.class); String result = helloService.hello("World"); System.out.println(result); System.exit(0); } }

4. Test
First start the prepared activemq and run bin win64 activemq.bat;
Then start the server ServerTest, ready receiver message
Finally, run the ClientTest and send the message verification results. The results are as follows:

 REQ+World

Only some important codes are listed above. For more details, please refer to: https://github.com/ksfzhaohui/rpc-jms.git

Expand to read the full text
Loading
Click to lead the topic 📣 Post and join the discussion 🔥
zero comment
zero Collection
zero fabulous
 Back to top
Top