├── README.md ├── pom.xml └── src ├── main ├── java │ └── com │ │ └── linkedkeeper │ │ └── easyrpc │ │ ├── client │ │ ├── AsyncRPCCallback.java │ │ ├── ConnectManager.java │ │ ├── RpcClient.java │ │ ├── RpcClientHandler.java │ │ ├── RpcClientInitializer.java │ │ ├── RpcFuture.java │ │ └── proxy │ │ │ ├── IAsyncObjectProxy.java │ │ │ └── ObjectProxy.java │ │ ├── codec │ │ ├── RpcDecoder.java │ │ ├── RpcEncoder.java │ │ ├── RpcRequest.java │ │ ├── RpcResponse.java │ │ └── Serialization.java │ │ ├── config │ │ ├── api │ │ │ ├── AbstractConfig.java │ │ │ ├── AbstractIdConfig.java │ │ │ ├── AbstractInterfaceConfig.java │ │ │ ├── ConsumerConfig.java │ │ │ ├── ProviderConfig.java │ │ │ └── ServerConfig.java │ │ └── spring │ │ │ ├── ConsumerBean.java │ │ │ ├── ConsumerFactoryBean.java │ │ │ ├── ProviderBean.java │ │ │ ├── ServerBean.java │ │ │ └── schema │ │ │ ├── EasyRpcBeanDefinitionParser.java │ │ │ └── EasyRpcNamespaceHandler.java │ │ └── server │ │ ├── RpcHandler.java │ │ └── RpcServer.java └── resources │ └── META-INF │ ├── easyrpc.xsd │ ├── spring.handlers │ └── spring.schemas └── test ├── java └── com │ └── linkedkeeper │ └── easyrpc │ └── test │ ├── client │ ├── HelloService.java │ ├── HelloServiceTest.java │ └── Person.java │ └── server │ ├── HelloServiceImpl.java │ └── RpcBootstrap.java └── resources ├── log4j.properties ├── spring-client.xml └── spring-server.xml /README.md: -------------------------------------------------------------------------------- 1 | # easy-rpc 2 | RPC,即 Remote Procedure Call(远程过程调用),调用远程计算机上的服务,就像调用本地服务一样。RPC 可以很好的解耦系统,如 WebService 就是一种基于 Http 协议的 RPC。 3 | EasyRpc 框架使用的一些技术所解决的问题: 4 | * 通信:使用Netty作为通信框架。 5 | * Spring:使用Spring配置服务,加载Bean。 6 | * 动态代理:客户端使用代理模式透明化服务调用。 7 | * 消息编解码:使用Protostuff序列化和反序列化消息。 8 | 9 | ## 服务端发布服务 10 | 一个服务接口 11 | ```java 12 | public interface HelloService { 13 | 14 | String hello(String name); 15 | 16 | String hello(Person person); 17 | } 18 | ``` 19 | 一个服务实现 20 | ```java 21 | public class HelloServiceImpl implements HelloService { 22 | 23 | public String hello(String name) { 24 | return "Hello! " + name; 25 | } 26 | 27 | public String hello(Person person) { 28 | return "Hello! " + person.getFirstName() + " " + person.getLastName(); 29 | } 30 | } 31 | ``` 32 | spring-server.xml 配置文件 33 | ```xml 34 | 35 | 45 | 46 | 48 | 49 | 52 | 53 | 54 | 55 | 56 | ``` 57 | 58 | ## 客户端调用服务 59 | Junit Test 60 | ```java 61 | @RunWith(SpringJUnit4ClassRunner.class) 62 | @ContextConfiguration(locations = "classpath:spring-client.xml") 63 | public class HelloServiceTest { 64 | 65 | @Autowired 66 | private RpcClient rpcClient = null; 67 | 68 | @Test 69 | public void helloTest1() { 70 | HelloService helloService = rpcClient.create(HelloService.class); 71 | String result = helloService.hello("World"); 72 | System.out.println(result); 73 | Assert.assertEquals("Hello! World", result); 74 | } 75 | 76 | @Test 77 | public void helloTest2() { 78 | HelloService helloService = rpcClient.create(HelloService.class); 79 | Person person = new Person("Yong", "Huang"); 80 | String result = helloService.hello(person); 81 | System.out.println(result.toString()); 82 | Assert.assertEquals("Hello! Yong Huang", result); 83 | } 84 | 85 | @Test 86 | public void helloFutureTest1() throws ExecutionException, InterruptedException { 87 | IAsyncObjectProxy helloService = rpcClient.createAsync(HelloService.class); 88 | RpcFuture result = helloService.call("hello", "World"); 89 | Assert.assertEquals("Hello! World", result.get()); 90 | } 91 | 92 | @Test 93 | public void helloFutureTest2() throws ExecutionException, InterruptedException { 94 | IAsyncObjectProxy helloService = rpcClient.createAsync(HelloService.class); 95 | Person person = new Person("Yong", "Huang"); 96 | RpcFuture result = helloService.call("hello", person); 97 | Assert.assertEquals("Hello! Yong Huang", result.get()); 98 | } 99 | 100 | @After 101 | public void setTear() { 102 | if (rpcClient != null) { 103 | rpcClient.stop(); 104 | } 105 | } 106 | } 107 | ``` 108 | spring-client.xml 配置文件 109 | ```xml 110 | 111 | 118 | 119 | 122 | 123 | 124 | ``` -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | com.linkedkeeper 6 | easy-rpc 7 | 1.1-SNAPSHOT 8 | jar 9 | 10 | easy-rpc 11 | http://www.linkedkeeper.com 12 | 13 | 14 | UTF-8 15 | 16 | 17 | 18 | 19 | 20 | junit 21 | junit 22 | 4.11 23 | test 24 | 25 | 26 | 27 | org.slf4j 28 | slf4j-log4j12 29 | 1.7.7 30 | 31 | 32 | 33 | org.springframework 34 | spring-context 35 | 3.2.12.RELEASE 36 | 37 | 38 | org.springframework 39 | spring-test 40 | 3.2.12.RELEASE 41 | test 42 | 43 | 44 | 45 | io.netty 46 | netty-all 47 | 4.0.24.Final 48 | 49 | 50 | 51 | com.dyuproject.protostuff 52 | protostuff-core 53 | 1.0.8 54 | 55 | 56 | com.dyuproject.protostuff 57 | protostuff-runtime 58 | 1.0.8 59 | 60 | 61 | 62 | org.apache.commons 63 | commons-collections4 64 | 4.0 65 | 66 | 67 | org.apache.commons 68 | commons-lang3 69 | 3.4 70 | 71 | 72 | 73 | org.objenesis 74 | objenesis 75 | 2.1 76 | 77 | 78 | 79 | cglib 80 | cglib 81 | 3.1 82 | 83 | 84 | com.fasterxml.jackson.core 85 | jackson-databind 86 | 2.7.2 87 | 88 | 89 | com.fasterxml.jackson.core 90 | jackson-core 91 | 2.7.2 92 | 93 | 94 | 95 | 96 | 97 | 98 | org.apache.maven.plugins 99 | maven-compiler-plugin 100 | 3.1 101 | 102 | 1.7 103 | 1.7 104 | 105 | 106 | 107 | org.apache.maven.plugins 108 | maven-source-plugin 109 | 2.2.1 110 | 111 | 112 | attach-sources 113 | 114 | jar 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | -------------------------------------------------------------------------------- /src/main/java/com/linkedkeeper/easyrpc/client/AsyncRPCCallback.java: -------------------------------------------------------------------------------- 1 | package com.linkedkeeper.easyrpc.client; 2 | 3 | public interface AsyncRPCCallback { 4 | 5 | void success(Object result); 6 | 7 | void fail(Exception e); 8 | 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/linkedkeeper/easyrpc/client/ConnectManager.java: -------------------------------------------------------------------------------- 1 | package com.linkedkeeper.easyrpc.client; 2 | 3 | import io.netty.bootstrap.Bootstrap; 4 | import io.netty.channel.ChannelFuture; 5 | import io.netty.channel.ChannelFutureListener; 6 | import io.netty.channel.ChannelOption; 7 | import io.netty.channel.EventLoopGroup; 8 | import io.netty.channel.nio.NioEventLoopGroup; 9 | import io.netty.channel.socket.nio.NioSocketChannel; 10 | import io.netty.util.concurrent.GenericFutureListener; 11 | import org.apache.commons.collections4.CollectionUtils; 12 | import org.slf4j.Logger; 13 | import org.slf4j.LoggerFactory; 14 | 15 | import java.net.InetSocketAddress; 16 | import java.net.SocketAddress; 17 | import java.util.Arrays; 18 | import java.util.HashSet; 19 | import java.util.List; 20 | import java.util.Map; 21 | import java.util.concurrent.*; 22 | import java.util.concurrent.atomic.AtomicInteger; 23 | import java.util.concurrent.locks.Condition; 24 | import java.util.concurrent.locks.ReentrantLock; 25 | 26 | public class ConnectManager { 27 | 28 | private static final Logger LOGGER = LoggerFactory.getLogger(ConnectManager.class); 29 | private static volatile ConnectManager connectManager = new ConnectManager(); 30 | 31 | private ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(16, 16, 600L, TimeUnit.SECONDS, new ArrayBlockingQueue(65536)); 32 | private EventLoopGroup eventLoopGroup = new NioEventLoopGroup(4); 33 | 34 | private CopyOnWriteArrayList connectedHandlers = new CopyOnWriteArrayList(); 35 | private Map connectedServerNodes = new ConcurrentHashMap(); 36 | 37 | private ReentrantLock lock = new ReentrantLock(); 38 | private Condition connected = lock.newCondition(); 39 | protected long connectTimeoutMills = 6000; 40 | private AtomicInteger roundRobin = new AtomicInteger(0); 41 | private volatile boolean isRunning = true; 42 | 43 | private ConnectManager() { 44 | } 45 | 46 | public static ConnectManager getInstance() { 47 | return connectManager; 48 | } 49 | 50 | public void updateConnectedServer(List allServerAddress) { 51 | if (CollectionUtils.isNotEmpty(allServerAddress)) { 52 | //update local serverNodes cache 53 | HashSet newAllServerNodeSet = new HashSet(); 54 | for (int i = 0; i < allServerAddress.size(); ++i) { 55 | String[] array = allServerAddress.get(i).split(":"); 56 | if (array.length == 2) { // Should check IP and port 57 | String host = array[0]; 58 | int port = Integer.parseInt(array[1]); 59 | final InetSocketAddress remotePeer = new InetSocketAddress(host, port); 60 | newAllServerNodeSet.add(remotePeer); 61 | } 62 | } 63 | // Add new server node 64 | for (final InetSocketAddress serverNodeAddress : newAllServerNodeSet) { 65 | if (!connectedServerNodes.keySet().contains(serverNodeAddress)) { 66 | connectServerNode(serverNodeAddress); 67 | } 68 | } 69 | // Close and remove invalid server nodes 70 | for (int i = 0; i < connectedHandlers.size(); ++i) { 71 | RpcClientHandler connectedServerHandler = connectedHandlers.get(i); 72 | SocketAddress remotePeer = connectedServerHandler.getRemotePeer(); 73 | if (!newAllServerNodeSet.contains(remotePeer)) { 74 | LOGGER.info("Remove invalid server node " + remotePeer); 75 | RpcClientHandler handler = connectedServerNodes.get(remotePeer); 76 | handler.close(); 77 | connectedServerNodes.remove(remotePeer); 78 | connectedHandlers.remove(connectedServerHandler); 79 | } 80 | } 81 | } else { // No available server node ( All server nodes are down ) 82 | LOGGER.error("No available server node. All server nodes are down !!!"); 83 | clearConnectedServer(); 84 | } 85 | } 86 | 87 | public void clearConnectedServer() { 88 | for (final RpcClientHandler connectedServerHandler : connectedHandlers) { 89 | SocketAddress remotePeer = connectedServerHandler.getRemotePeer(); 90 | RpcClientHandler handler = connectedServerNodes.get(remotePeer); 91 | handler.close(); 92 | connectedServerNodes.remove(connectedServerHandler); 93 | } 94 | connectedHandlers.clear(); 95 | } 96 | 97 | public void reconnect(final RpcClientHandler handler, final SocketAddress remotePeer) { 98 | if (handler != null) { 99 | connectedHandlers.remove(handler); 100 | connectedServerNodes.remove(handler.getRemotePeer()); 101 | } 102 | connectServerNode((InetSocketAddress) remotePeer); 103 | } 104 | 105 | private void connectServerNode(final InetSocketAddress remotePeer) { 106 | threadPoolExecutor.submit(new Runnable() { 107 | public void run() { 108 | Bootstrap b = new Bootstrap(); 109 | b.group(eventLoopGroup) 110 | .channel(NioSocketChannel.class) 111 | .option(ChannelOption.TCP_NODELAY, true) 112 | .handler(new RpcClientInitializer()); 113 | connect(b, remotePeer); 114 | } 115 | }); 116 | } 117 | 118 | private void connect(final Bootstrap b, final InetSocketAddress remotePeer) { 119 | final ChannelFuture connectFuture = b.connect(remotePeer); 120 | connectFuture.channel().closeFuture().addListener(new ChannelFutureListener() { 121 | public void operationComplete(ChannelFuture future) throws Exception { 122 | LOGGER.info("connectFuture.channel close operationComplete. remote peer = " + remotePeer); 123 | future.channel().eventLoop().schedule(new Runnable() { 124 | public void run() { 125 | LOGGER.warn("Attempting to reconnect."); 126 | clearConnectedServer(); 127 | connect(b, remotePeer); 128 | } 129 | }, 3, TimeUnit.SECONDS); 130 | } 131 | }); 132 | connectFuture.addListener(new ChannelFutureListener() { 133 | public void operationComplete(final ChannelFuture future) throws Exception { 134 | if (future.isSuccess()) { 135 | LOGGER.info("Successfully connect to remote server. remote peer = " + remotePeer); 136 | RpcClientHandler handler = future.channel().pipeline().get(RpcClientHandler.class); 137 | addHandler(handler); 138 | } else { 139 | LOGGER.error("Failed to connect.", future.cause()); 140 | } 141 | } 142 | }); 143 | } 144 | 145 | private void addHandler(RpcClientHandler handler) { 146 | connectedHandlers.add(handler); 147 | InetSocketAddress remoteAddress = (InetSocketAddress) handler.getChannel().remoteAddress(); 148 | connectedServerNodes.put(remoteAddress, handler); 149 | signalAvailableHandler(); 150 | } 151 | 152 | private void signalAvailableHandler() { 153 | lock.lock(); 154 | try { 155 | connected.signalAll(); 156 | } finally { 157 | lock.unlock(); 158 | } 159 | } 160 | 161 | private boolean waitingForHandler() throws InterruptedException { 162 | lock.lock(); 163 | try { 164 | return connected.await(this.connectTimeoutMills, TimeUnit.MILLISECONDS); 165 | } finally { 166 | lock.unlock(); 167 | } 168 | } 169 | 170 | public void connect(final String serverAddress) { 171 | List allServerAddress = Arrays.asList(serverAddress.split(",")); 172 | updateConnectedServer(allServerAddress); 173 | } 174 | 175 | public RpcClientHandler chooseHandler() { 176 | CopyOnWriteArrayList handlers = (CopyOnWriteArrayList) this.connectedHandlers.clone(); 177 | int size = handlers.size(); 178 | while (isRunning && size <= 0) { 179 | try { 180 | boolean available = waitingForHandler(); 181 | if (available) { 182 | handlers = (CopyOnWriteArrayList) this.connectedHandlers.clone(); 183 | size = handlers.size(); 184 | } 185 | } catch (InterruptedException e) { 186 | LOGGER.error("Waiting for available node is interrupted! ", e); 187 | throw new RuntimeException("Can't connect any servers!", e); 188 | } 189 | } 190 | int index = (roundRobin.getAndAdd(1) + size) % size; 191 | return handlers.get(index); 192 | } 193 | 194 | public void stop() { 195 | isRunning = false; 196 | for (int i = 0; i < connectedHandlers.size(); ++i) { 197 | RpcClientHandler connectedServerHandler = connectedHandlers.get(i); 198 | connectedServerHandler.close(); 199 | } 200 | signalAvailableHandler(); 201 | threadPoolExecutor.shutdown(); 202 | eventLoopGroup.shutdownGracefully(); 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /src/main/java/com/linkedkeeper/easyrpc/client/RpcClient.java: -------------------------------------------------------------------------------- 1 | package com.linkedkeeper.easyrpc.client; 2 | 3 | import com.linkedkeeper.easyrpc.client.proxy.IAsyncObjectProxy; 4 | import com.linkedkeeper.easyrpc.client.proxy.ObjectProxy; 5 | 6 | import java.lang.reflect.Proxy; 7 | import java.util.Map; 8 | import java.util.concurrent.ConcurrentHashMap; 9 | 10 | public class RpcClient { 11 | 12 | private final Map proxyInstances = new ConcurrentHashMap(); 13 | 14 | private String serverAddress; 15 | private long timeout; 16 | 17 | public void initClient(String serverAddress, long timeout) { 18 | this.serverAddress = serverAddress; 19 | this.timeout = timeout; 20 | connect(); 21 | } 22 | 23 | private void connect() { 24 | ConnectManager.getInstance().connect(this.serverAddress); 25 | } 26 | 27 | public T create(Class interfaceClass) { 28 | if (proxyInstances.containsKey(interfaceClass)) { 29 | return (T) proxyInstances.get(interfaceClass); 30 | } else { 31 | Object proxy = Proxy.newProxyInstance( 32 | interfaceClass.getClassLoader(), 33 | new Class[]{interfaceClass}, 34 | new ObjectProxy(interfaceClass, timeout) 35 | ); 36 | proxyInstances.put(interfaceClass, proxy); 37 | return (T) proxy; 38 | } 39 | } 40 | 41 | public IAsyncObjectProxy createAsync(Class interfaceClass) { 42 | return new ObjectProxy(interfaceClass, timeout); 43 | } 44 | 45 | public void stop() { 46 | ConnectManager.getInstance().stop(); 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/com/linkedkeeper/easyrpc/client/RpcClientHandler.java: -------------------------------------------------------------------------------- 1 | package com.linkedkeeper.easyrpc.client; 2 | 3 | import com.linkedkeeper.easyrpc.codec.RpcRequest; 4 | import com.linkedkeeper.easyrpc.codec.RpcResponse; 5 | import io.netty.buffer.Unpooled; 6 | import io.netty.channel.Channel; 7 | import io.netty.channel.ChannelFutureListener; 8 | import io.netty.channel.ChannelHandlerContext; 9 | import io.netty.channel.SimpleChannelInboundHandler; 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | 13 | import java.net.SocketAddress; 14 | import java.util.Map; 15 | import java.util.concurrent.ConcurrentHashMap; 16 | 17 | public class RpcClientHandler extends SimpleChannelInboundHandler { 18 | 19 | private static final Logger LOGGER = LoggerFactory.getLogger(RpcClientHandler.class); 20 | 21 | private Map pendingRpc = new ConcurrentHashMap(); 22 | 23 | private volatile Channel channel; 24 | private SocketAddress remotePeer; 25 | 26 | public Channel getChannel() { 27 | return channel; 28 | } 29 | 30 | public SocketAddress getRemotePeer() { 31 | return remotePeer; 32 | } 33 | 34 | public void channelActive(ChannelHandlerContext ctx) throws Exception { 35 | super.channelActive(ctx); 36 | this.remotePeer = this.channel.remoteAddress(); 37 | } 38 | 39 | public void channelRegistered(ChannelHandlerContext ctx) throws Exception { 40 | super.channelRegistered(ctx); 41 | this.channel = ctx.channel(); 42 | } 43 | 44 | protected void channelRead0(ChannelHandlerContext ctx, RpcResponse response) throws Exception { 45 | String requestId = response.getRequestId(); 46 | RpcFuture rpcFuture = pendingRpc.get(requestId); 47 | if (rpcFuture != null) { 48 | pendingRpc.remove(requestId); 49 | rpcFuture.done(response); 50 | } 51 | } 52 | 53 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 54 | LOGGER.error("client caught exception", cause); 55 | ctx.close(); 56 | } 57 | 58 | public void close() { 59 | channel.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE); 60 | } 61 | 62 | public RpcFuture sendRequest(RpcRequest request) { 63 | RpcFuture rpcFuture = new RpcFuture(request); 64 | pendingRpc.put(request.getRequestId(), rpcFuture); 65 | channel.writeAndFlush(request); 66 | 67 | return rpcFuture; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/com/linkedkeeper/easyrpc/client/RpcClientInitializer.java: -------------------------------------------------------------------------------- 1 | package com.linkedkeeper.easyrpc.client; 2 | 3 | import com.linkedkeeper.easyrpc.codec.RpcDecoder; 4 | import com.linkedkeeper.easyrpc.codec.RpcEncoder; 5 | import com.linkedkeeper.easyrpc.codec.RpcRequest; 6 | import com.linkedkeeper.easyrpc.codec.RpcResponse; 7 | import io.netty.channel.ChannelInitializer; 8 | import io.netty.channel.ChannelPipeline; 9 | import io.netty.channel.socket.SocketChannel; 10 | import io.netty.handler.codec.LengthFieldBasedFrameDecoder; 11 | 12 | public class RpcClientInitializer extends ChannelInitializer { 13 | 14 | @Override 15 | protected void initChannel(SocketChannel socketChannel) throws Exception { 16 | ChannelPipeline cp = socketChannel.pipeline(); 17 | cp.addLast(new RpcEncoder(RpcRequest.class)); 18 | cp.addLast(new LengthFieldBasedFrameDecoder(65536, 0, 4, 0, 0)); 19 | cp.addLast(new RpcDecoder(RpcResponse.class)); 20 | cp.addLast(new RpcClientHandler()); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/linkedkeeper/easyrpc/client/RpcFuture.java: -------------------------------------------------------------------------------- 1 | package com.linkedkeeper.easyrpc.client; 2 | 3 | import com.linkedkeeper.easyrpc.codec.RpcRequest; 4 | import com.linkedkeeper.easyrpc.codec.RpcResponse; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | import java.util.concurrent.*; 11 | import java.util.concurrent.locks.AbstractQueuedSynchronizer; 12 | import java.util.concurrent.locks.ReentrantLock; 13 | 14 | public class RpcFuture implements Future { 15 | 16 | private static final Logger LOGGER = LoggerFactory.getLogger(RpcFuture.class); 17 | 18 | private ThreadPoolExecutor executor = new ThreadPoolExecutor(16, 16, 600L, TimeUnit.SECONDS, new ArrayBlockingQueue(65536)); 19 | 20 | private Sync sync; 21 | private RpcRequest request; 22 | private RpcResponse response; 23 | private long startTime; 24 | 25 | private long responseTimeThreshold = 5000; 26 | 27 | private List pendingCallbacks = new ArrayList(); 28 | private ReentrantLock lock = new ReentrantLock(); 29 | 30 | 31 | public RpcFuture(RpcRequest request) { 32 | this.sync = new Sync(); 33 | this.request = request; 34 | this.startTime = System.currentTimeMillis(); 35 | } 36 | 37 | public boolean isDone() { 38 | return sync.isDone(); 39 | } 40 | 41 | public Object get() throws InterruptedException, ExecutionException { 42 | sync.acquire(-1); 43 | if (this.response != null) { 44 | return this.response.getResult(); 45 | } else { 46 | return null; 47 | } 48 | } 49 | 50 | public Object get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { 51 | boolean success = sync.tryAcquireNanos(-1, unit.toNanos(timeout)); 52 | if (success) { 53 | if (this.response != null) { 54 | return this.response.getResult(); 55 | } else { 56 | return null; 57 | } 58 | } else { 59 | throw new RuntimeException("Timeout exception. Request id: " + this.request.getRequestId() 60 | + ". Request class name: " + this.request.getClassName() 61 | + ". Request method: " + this.request.getMethodName()); 62 | } 63 | } 64 | 65 | public boolean isCancelled() { 66 | throw new UnsupportedOperationException(); 67 | } 68 | 69 | public boolean cancel(boolean mayInterruptIfRunning) { 70 | throw new UnsupportedOperationException(); 71 | } 72 | 73 | public void done(RpcResponse response) { 74 | this.response = response; 75 | sync.release(1); 76 | invokeCallbacks(); 77 | // Threshold 78 | long responseTime = System.currentTimeMillis() - startTime; 79 | if (responseTime > this.responseTimeThreshold) { 80 | LOGGER.warn("Service response time is too slow. Request id = " + response.getRequestId() + ". Response Time = " + responseTime + "ms"); 81 | } 82 | } 83 | 84 | private void invokeCallbacks() { 85 | lock.lock(); 86 | try { 87 | for (final AsyncRPCCallback callback : pendingCallbacks) { 88 | runCallback(callback); 89 | } 90 | } finally { 91 | lock.unlock(); 92 | } 93 | } 94 | 95 | public RpcFuture addCallback(AsyncRPCCallback callback) { 96 | lock.lock(); 97 | try { 98 | if (isDone()) { 99 | runCallback(callback); 100 | } else { 101 | this.pendingCallbacks.add(callback); 102 | } 103 | } finally { 104 | lock.unlock(); 105 | } 106 | return this; 107 | } 108 | 109 | public void runCallback(final AsyncRPCCallback callback) { 110 | final RpcResponse response = this.response; 111 | executor.submit(new Runnable() { 112 | public void run() { 113 | if (!response.isError()) { 114 | callback.success(response.getResult()); 115 | } else { 116 | callback.fail(new RuntimeException("Response error", new Throwable(response.getError()))); 117 | } 118 | } 119 | }); 120 | } 121 | 122 | class Sync extends AbstractQueuedSynchronizer { 123 | 124 | private final long serialVersionUID = 1L; 125 | 126 | // future status 127 | private final int done = 1; 128 | private final int pending = 0; 129 | 130 | protected boolean tryAcquire(int acquires) { 131 | return getState() == done ? true : false; 132 | } 133 | 134 | protected boolean tryRelease(int releases) { 135 | if (getState() == pending) { 136 | if (compareAndSetState(pending, done)) { 137 | return true; 138 | } 139 | } 140 | return false; 141 | } 142 | 143 | public boolean isDone() { 144 | getState(); 145 | return getState() == done; 146 | } 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /src/main/java/com/linkedkeeper/easyrpc/client/proxy/IAsyncObjectProxy.java: -------------------------------------------------------------------------------- 1 | package com.linkedkeeper.easyrpc.client.proxy; 2 | 3 | import com.linkedkeeper.easyrpc.client.RpcFuture; 4 | 5 | public interface IAsyncObjectProxy { 6 | 7 | RpcFuture call(String funcName, Object... args); 8 | 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/linkedkeeper/easyrpc/client/proxy/ObjectProxy.java: -------------------------------------------------------------------------------- 1 | package com.linkedkeeper.easyrpc.client.proxy; 2 | 3 | import com.linkedkeeper.easyrpc.client.ConnectManager; 4 | import com.linkedkeeper.easyrpc.client.RpcClientHandler; 5 | import com.linkedkeeper.easyrpc.client.RpcFuture; 6 | import com.linkedkeeper.easyrpc.codec.RpcRequest; 7 | 8 | import java.lang.reflect.InvocationHandler; 9 | import java.lang.reflect.Method; 10 | import java.util.UUID; 11 | import java.util.concurrent.TimeUnit; 12 | 13 | public class ObjectProxy implements InvocationHandler, IAsyncObjectProxy { 14 | 15 | private Class clazz; 16 | private long timeout; 17 | 18 | public ObjectProxy(Class clazz, long timeout) { 19 | this.clazz = clazz; 20 | this.timeout = timeout; 21 | } 22 | 23 | public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 24 | if (Object.class == method.getDeclaringClass()) { 25 | String name = method.getName(); 26 | if ("equals".equals(name)) { 27 | return proxy == args[0]; 28 | } else if ("hashCode".equals(name)) { 29 | return System.identityHashCode(proxy); 30 | } else if ("toString".equals(name)) { 31 | return proxy.getClass().getName() + "@" + 32 | Integer.toHexString(System.identityHashCode(proxy)) + 33 | ", with InvocationHandler " + this; 34 | } else { 35 | throw new IllegalStateException(String.valueOf(method)); 36 | } 37 | } 38 | 39 | RpcRequest request = new RpcRequest(); 40 | request.setRequestId(UUID.randomUUID().toString()); 41 | request.setClassName(method.getDeclaringClass().getName()); 42 | request.setMethodName(method.getName()); 43 | request.setParameterTypes(method.getParameterTypes()); 44 | request.setParameters(args); 45 | 46 | RpcClientHandler handler = ConnectManager.getInstance().chooseHandler(); 47 | RpcFuture rpcFuture = handler.sendRequest(request); 48 | return rpcFuture.get(timeout, TimeUnit.SECONDS); 49 | } 50 | 51 | public RpcFuture call(String funcName, Object... args) { 52 | RpcClientHandler handler = ConnectManager.getInstance().chooseHandler(); 53 | RpcRequest request = createRequest(this.clazz.getName(), funcName, args); 54 | RpcFuture rpcFuture = handler.sendRequest(request); 55 | return rpcFuture; 56 | } 57 | 58 | private RpcRequest createRequest(String className, String methodName, Object[] args) { 59 | RpcRequest request = new RpcRequest(); 60 | request.setRequestId(UUID.randomUUID().toString()); 61 | request.setClassName(className); 62 | request.setMethodName(methodName); 63 | request.setParameters(args); 64 | 65 | Class[] parameterTypes = new Class[args.length]; 66 | // Get the right class type 67 | for (int i = 0; i < args.length; i++) { 68 | parameterTypes[i] = getClassType(args[i]); 69 | } 70 | request.setParameterTypes(parameterTypes); 71 | 72 | return request; 73 | } 74 | 75 | private Class getClassType(Object obj) { 76 | Class classType = obj.getClass(); 77 | String typeName = classType.getName(); 78 | if (typeName.equals("java.lang.Integer")) { 79 | return Integer.TYPE; 80 | } else if (typeName.equals("java.lang.Long")) { 81 | return Long.TYPE; 82 | } else if (typeName.equals("java.lang.Float")) { 83 | return Float.TYPE; 84 | } else if (typeName.equals("java.lang.Double")) { 85 | return Double.TYPE; 86 | } else if (typeName.equals("java.lang.Character")) { 87 | return Character.TYPE; 88 | } else if (typeName.equals("java.lang.Boolean")) { 89 | return Boolean.TYPE; 90 | } else if (typeName.equals("java.lang.Short")) { 91 | return Short.TYPE; 92 | } else if (typeName.equals("java.lang.Byte")) { 93 | return Byte.TYPE; 94 | } 95 | return classType; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/main/java/com/linkedkeeper/easyrpc/codec/RpcDecoder.java: -------------------------------------------------------------------------------- 1 | package com.linkedkeeper.easyrpc.codec; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.netty.channel.ChannelHandlerContext; 5 | import io.netty.handler.codec.ByteToMessageDecoder; 6 | 7 | import java.util.List; 8 | 9 | public class RpcDecoder extends ByteToMessageDecoder { 10 | 11 | private Class genericClass; 12 | 13 | public RpcDecoder(Class genericClass) { 14 | this.genericClass = genericClass; 15 | } 16 | 17 | protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception { 18 | if (in.readableBytes() < 4) { 19 | return; 20 | } 21 | in.markReaderIndex(); 22 | int dataLength = in.readInt(); 23 | if (in.readableBytes() < dataLength) { 24 | in.resetReaderIndex(); 25 | return; 26 | } 27 | byte[] data = new byte[dataLength]; 28 | in.readBytes(data); 29 | 30 | Object obj = Serialization.deserialize(data, genericClass); 31 | out.add(obj); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/linkedkeeper/easyrpc/codec/RpcEncoder.java: -------------------------------------------------------------------------------- 1 | package com.linkedkeeper.easyrpc.codec; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.netty.channel.ChannelHandlerContext; 5 | import io.netty.handler.codec.MessageToByteEncoder; 6 | 7 | public class RpcEncoder extends MessageToByteEncoder { 8 | 9 | private Class genericClass; 10 | 11 | public RpcEncoder(Class genericClass) { 12 | this.genericClass = genericClass; 13 | } 14 | 15 | protected void encode(ChannelHandlerContext ctx, Object in, ByteBuf out) throws Exception { 16 | if (genericClass.isInstance(in)) { 17 | byte[] data = Serialization.serialize(in); 18 | out.writeInt(data.length); 19 | out.writeBytes(data); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/linkedkeeper/easyrpc/codec/RpcRequest.java: -------------------------------------------------------------------------------- 1 | package com.linkedkeeper.easyrpc.codec; 2 | 3 | import java.io.Serializable; 4 | 5 | public class RpcRequest implements Serializable { 6 | 7 | private String requestId; 8 | private String className; 9 | private String methodName; 10 | private Class[] parameterTypes; 11 | private Object[] parameters; 12 | 13 | public String getRequestId() { 14 | return requestId; 15 | } 16 | 17 | public void setRequestId(String requestId) { 18 | this.requestId = requestId; 19 | } 20 | 21 | public String getClassName() { 22 | return className; 23 | } 24 | 25 | public void setClassName(String className) { 26 | this.className = className; 27 | } 28 | 29 | public String getMethodName() { 30 | return methodName; 31 | } 32 | 33 | public void setMethodName(String methodName) { 34 | this.methodName = methodName; 35 | } 36 | 37 | public Class[] getParameterTypes() { 38 | return parameterTypes; 39 | } 40 | 41 | public void setParameterTypes(Class[] parameterTypes) { 42 | this.parameterTypes = parameterTypes; 43 | } 44 | 45 | public Object[] getParameters() { 46 | return parameters; 47 | } 48 | 49 | public void setParameters(Object[] parameters) { 50 | this.parameters = parameters; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/com/linkedkeeper/easyrpc/codec/RpcResponse.java: -------------------------------------------------------------------------------- 1 | package com.linkedkeeper.easyrpc.codec; 2 | 3 | import java.io.Serializable; 4 | 5 | public class RpcResponse implements Serializable { 6 | 7 | private String requestId; 8 | private String error; 9 | private Object result; 10 | 11 | public String getRequestId() { 12 | return requestId; 13 | } 14 | 15 | public void setRequestId(String requestId) { 16 | this.requestId = requestId; 17 | } 18 | 19 | public boolean isError() { 20 | return error != null; 21 | } 22 | 23 | public String getError() { 24 | return error; 25 | } 26 | 27 | public void setError(String error) { 28 | this.error = error; 29 | } 30 | 31 | public Object getResult() { 32 | return result; 33 | } 34 | 35 | public void setResult(Object result) { 36 | this.result = result; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/linkedkeeper/easyrpc/codec/Serialization.java: -------------------------------------------------------------------------------- 1 | package com.linkedkeeper.easyrpc.codec; 2 | 3 | import com.dyuproject.protostuff.LinkedBuffer; 4 | import com.dyuproject.protostuff.ProtostuffIOUtil; 5 | import com.dyuproject.protostuff.Schema; 6 | import com.dyuproject.protostuff.runtime.RuntimeSchema; 7 | import org.objenesis.Objenesis; 8 | import org.objenesis.ObjenesisStd; 9 | 10 | import java.util.Map; 11 | import java.util.concurrent.ConcurrentHashMap; 12 | 13 | public class Serialization { 14 | 15 | private static Map, Schema> cachedSchema = new ConcurrentHashMap, Schema>(); 16 | 17 | private static Objenesis objenesis = new ObjenesisStd(true); 18 | 19 | public Serialization() { 20 | } 21 | 22 | private static Schema getSchema(Class cls) { 23 | Schema schema = (Schema) cachedSchema.get(cls); 24 | if (schema == null) { 25 | schema = RuntimeSchema.createFrom(cls); 26 | if (schema != null) { 27 | cachedSchema.put(cls, schema); 28 | } 29 | } 30 | return schema; 31 | } 32 | 33 | /** 34 | * 序列化(对象->字节数组) 35 | * 36 | * @param obj 37 | * @param 38 | * @return 39 | */ 40 | public static byte[] serialize(T obj) { 41 | Class cls = (Class) obj.getClass(); 42 | LinkedBuffer buffer = LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE); 43 | try { 44 | Schema schema = getSchema(cls); 45 | return ProtostuffIOUtil.toByteArray(obj, schema, buffer); 46 | } catch (Exception e) { 47 | throw new IllegalStateException(e.getMessage(), e); 48 | } finally { 49 | buffer.clear(); 50 | } 51 | } 52 | 53 | /** 54 | * 反序列化(字节数组->对象) 55 | * 56 | * @param data 57 | * @param cls 58 | * @param 59 | * @return 60 | */ 61 | public static T deserialize(byte[] data, Class cls) { 62 | try { 63 | T message = objenesis.newInstance(cls); 64 | Schema schema = getSchema(cls); 65 | ProtostuffIOUtil.mergeFrom(data, message, schema); 66 | return message; 67 | } catch (Exception e) { 68 | throw new IllegalStateException(e.getMessage(), e); 69 | } 70 | } 71 | } -------------------------------------------------------------------------------- /src/main/java/com/linkedkeeper/easyrpc/config/api/AbstractConfig.java: -------------------------------------------------------------------------------- 1 | package com.linkedkeeper.easyrpc.config.api; 2 | 3 | import java.io.Serializable; 4 | import java.util.regex.Pattern; 5 | 6 | public abstract class AbstractConfig implements Serializable { 7 | 8 | /** 9 | * 可用的字符串为:英文大小写,数字,横杆-,下划线_,点. 冒号: 10 | * !@#$*,;有特殊含义 11 | */ 12 | protected Pattern NORMAL_COLON = Pattern.compile("^[a-zA-Z0-9\\-\\_\\.:]+$"); 13 | 14 | 15 | /** 16 | * 匹配正常字符串 17 | * 18 | * @param configValue 配置项 19 | * @return 是否匹配,否表示有其他字符 20 | */ 21 | protected boolean matchValue(Pattern pattern, String configValue) { 22 | return pattern.matcher(configValue).find(); 23 | } 24 | 25 | /** 26 | * 检查字符串是否是正常值(含冒号),不是则抛出异常 27 | * 28 | * @param configKey 配置项 29 | * @param configValue 配置值 30 | */ 31 | protected void checkNormalWithColon(String configKey, String configValue) throws Exception { 32 | checkPattern(configKey, configValue, NORMAL_COLON, "only allow a-zA-Z0-9 '-' '_' '.' ':'"); 33 | } 34 | 35 | /** 36 | * 根据正则表达式检查字符串是否是正常值(含冒号),不是则抛出异常 37 | * 38 | * @param configKey 配置项 39 | * @param configValue 配置值 40 | * @param pattern 正则表达式 41 | * @param message 消息 42 | */ 43 | protected void checkPattern(String configKey, String configValue, Pattern pattern, String message) throws Exception { 44 | if (configValue != null && !matchValue(pattern, configValue)) { 45 | throw new Exception("check pattern error."); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/com/linkedkeeper/easyrpc/config/api/AbstractIdConfig.java: -------------------------------------------------------------------------------- 1 | package com.linkedkeeper.easyrpc.config.api; 2 | 3 | import java.util.concurrent.atomic.AtomicInteger; 4 | 5 | public abstract class AbstractIdConfig extends AbstractConfig { 6 | 7 | /** 8 | * Id生成器 9 | */ 10 | private AtomicInteger ID_GENERATOR = new AtomicInteger(0); 11 | 12 | /** 13 | * Spring的BeanId 14 | */ 15 | protected String id = null; 16 | 17 | /** 18 | * Gets id. 19 | * 20 | * @return the id 21 | */ 22 | private String getId() { 23 | if (id == null) { 24 | id = "easyrpc-cfg-gen-" + ID_GENERATOR.getAndIncrement(); 25 | } 26 | return id; 27 | } 28 | 29 | /** 30 | * Sets id. 31 | * 32 | * @param id the id 33 | */ 34 | private void setId(String id) { 35 | this.id = id; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/linkedkeeper/easyrpc/config/api/AbstractInterfaceConfig.java: -------------------------------------------------------------------------------- 1 | package com.linkedkeeper.easyrpc.config.api; 2 | 3 | public abstract class AbstractInterfaceConfig extends AbstractIdConfig { 4 | 5 | /** 6 | * 不管普通调用和泛化调用,都是设置实际的接口类名称 7 | */ 8 | protected String interfaceClass = null; 9 | 10 | /** 11 | * 服务别名= "group::version" 12 | */ 13 | protected String alias = ""; 14 | 15 | /** 16 | * 代理接口类,和T对应,主要针对泛化调用 17 | */ 18 | protected volatile transient Class proxyClass = null; 19 | 20 | /** 21 | * Gets interface. 22 | * 23 | * @return the interface 24 | */ 25 | public String getInterface() { 26 | return interfaceClass; 27 | } 28 | 29 | /** 30 | * Sets interface. 31 | * 32 | * @param interfaceClass the interface 33 | */ 34 | public void setInterface(String interfaceClass) { 35 | this.interfaceClass = interfaceClass; 36 | } 37 | 38 | /** 39 | * Gets alias. 40 | * 41 | * @return the alias 42 | */ 43 | public String getAlias() { 44 | return alias; 45 | } 46 | 47 | /** 48 | * Sets alias. 49 | * 50 | * @param alias the alias 51 | */ 52 | public void setAlias(String alias) throws Exception { 53 | checkNormalWithColon("alias", alias); 54 | this.alias = alias; 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/com/linkedkeeper/easyrpc/config/api/ConsumerConfig.java: -------------------------------------------------------------------------------- 1 | package com.linkedkeeper.easyrpc.config.api; 2 | 3 | import com.linkedkeeper.easyrpc.client.RpcClient; 4 | import org.apache.commons.lang3.StringUtils; 5 | 6 | public class ConsumerConfig extends AbstractInterfaceConfig { 7 | 8 | /** 9 | * 直连调用地址 10 | */ 11 | protected String url; 12 | 13 | /** 14 | * 连接超时时间 15 | */ 16 | protected int connectTimeout; 17 | 18 | private volatile transient T proxyIns = null; 19 | 20 | protected T refer() { 21 | if (proxyIns != null) 22 | return proxyIns; 23 | try { 24 | proxyIns = (T) getProxyClass().newInstance(); 25 | initConnections(); 26 | } catch (Exception e) { 27 | throw new RuntimeException("Build consumer proxy error!", e); 28 | } 29 | return proxyIns; 30 | } 31 | 32 | private void initConnections() { 33 | RpcClient client = (RpcClient) proxyIns; 34 | client.initClient(url, connectTimeout); 35 | } 36 | 37 | protected Class getProxyClass() { 38 | if (proxyClass != null) { 39 | return proxyClass; 40 | } 41 | try { 42 | if (StringUtils.isNotBlank(interfaceClass)) { 43 | this.proxyClass = Class.forName(interfaceClass); 44 | } else { 45 | throw new Exception("consumer.interfaceId, null, interfaceId must be not null"); 46 | } 47 | } catch (Exception e) { 48 | throw new IllegalStateException(e.getMessage(), e); 49 | } 50 | return proxyClass; 51 | } 52 | 53 | /** 54 | * Gets url. 55 | * 56 | * @return the url 57 | */ 58 | public String getUrl() { 59 | return url; 60 | } 61 | 62 | /** 63 | * Sets url. 64 | * 65 | * @param url the url 66 | */ 67 | public void setUrl(String url) { 68 | this.url = url; 69 | } 70 | 71 | /** 72 | * Gets connect timeout. 73 | * 74 | * @return the connect timeout 75 | */ 76 | public int getTimeout() { 77 | return connectTimeout; 78 | } 79 | 80 | /** 81 | * Sets connect timeout. 82 | * 83 | * @param connectTimeout the connect timeout 84 | */ 85 | public void setTimeout(int connectTimeout) { 86 | this.connectTimeout = connectTimeout; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/main/java/com/linkedkeeper/easyrpc/config/api/ProviderConfig.java: -------------------------------------------------------------------------------- 1 | package com.linkedkeeper.easyrpc.config.api; 2 | 3 | import com.linkedkeeper.easyrpc.server.RpcServer; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | 7 | import java.util.List; 8 | 9 | public class ProviderConfig extends AbstractInterfaceConfig { 10 | 11 | /** 12 | * slf4j logger for this class 13 | */ 14 | private Logger logger = LoggerFactory.getLogger(ProviderConfig.class); 15 | 16 | /** 17 | * 接口实现类引用 18 | */ 19 | protected transient Object ref; 20 | 21 | /** 22 | * 配置的协议列表 23 | */ 24 | private List serverConfigs = null; 25 | 26 | /** 27 | * 是否已发布 28 | */ 29 | protected volatile boolean exported = false; 30 | 31 | /** 32 | * 发布服务 33 | * 34 | * @throws Exception the init error exception 35 | */ 36 | protected void export() throws Exception { 37 | if (!exported) { 38 | for (ServerConfig serverConfig : serverConfigs) { 39 | try { 40 | serverConfig.start(); 41 | // 注册接口 42 | RpcServer server = serverConfig.getServer(); 43 | server.registerProcessor(this); 44 | } catch (Exception e) { 45 | logger.error("Catch exception server.", e); 46 | } 47 | } 48 | exported = true; 49 | } 50 | } 51 | 52 | /** 53 | * Gets ref. 54 | * 55 | * @return the ref 56 | */ 57 | public Object getRef() { 58 | return ref; 59 | } 60 | 61 | /** 62 | * Sets ref. 63 | * 64 | * @param ref the ref 65 | */ 66 | public void setRef(Object ref) { 67 | this.ref = ref; 68 | } 69 | 70 | /** 71 | * Gets serverConfigs. 72 | * 73 | * @return the serverConfigs 74 | */ 75 | public List getServerConfigs() { 76 | return serverConfigs; 77 | } 78 | 79 | /** 80 | * Sets serverConfigs. 81 | * 82 | * @param serverConfigs the serverConfigs 83 | */ 84 | public void setServerConfigs(List serverConfigs) { 85 | this.serverConfigs = serverConfigs; 86 | } 87 | 88 | } 89 | -------------------------------------------------------------------------------- /src/main/java/com/linkedkeeper/easyrpc/config/api/ServerConfig.java: -------------------------------------------------------------------------------- 1 | package com.linkedkeeper.easyrpc.config.api; 2 | 3 | import com.linkedkeeper.easyrpc.server.RpcServer; 4 | 5 | public class ServerConfig extends AbstractInterfaceConfig { 6 | 7 | /** 8 | * 绑定的地址。是某个网卡,还是全部地址 9 | */ 10 | private final String host = "127.0.0.1"; 11 | 12 | /** 13 | * 监听端口 14 | */ 15 | protected int port; 16 | 17 | /** 18 | * 服务端对象 19 | */ 20 | private volatile transient RpcServer server = null; 21 | 22 | public void start() throws Exception { 23 | if (server == null) { 24 | server = new RpcServer(host + ":" + port); 25 | } 26 | } 27 | 28 | /** 29 | * Gets server. 30 | * 31 | * @return the server 32 | */ 33 | public RpcServer getServer() { 34 | return server; 35 | } 36 | 37 | public int getPort() { 38 | return port; 39 | } 40 | 41 | public void setPort(int port) { 42 | this.port = port; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/com/linkedkeeper/easyrpc/config/spring/ConsumerBean.java: -------------------------------------------------------------------------------- 1 | package com.linkedkeeper.easyrpc.config.spring; 2 | 3 | import com.linkedkeeper.easyrpc.client.RpcClient; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import org.springframework.beans.BeansException; 7 | import org.springframework.beans.factory.BeanNameAware; 8 | import org.springframework.beans.factory.DisposableBean; 9 | import org.springframework.beans.factory.InitializingBean; 10 | import org.springframework.context.ApplicationContext; 11 | import org.springframework.context.ApplicationContextAware; 12 | 13 | public class ConsumerBean extends ConsumerFactoryBean implements InitializingBean, DisposableBean, ApplicationContextAware, BeanNameAware { 14 | 15 | /** 16 | * slf4j logger for this class 17 | */ 18 | private Logger logger = LoggerFactory.getLogger(ConsumerBean.class); 19 | 20 | protected transient ApplicationContext applicationContext = null; 21 | private transient String beanName = null; 22 | 23 | /** 24 | * @param name 25 | * @see org.springframework.beans.factory.BeanNameAware#setBeanName(java.lang.String) 26 | */ 27 | @Override 28 | public void setBeanName(String name) { 29 | this.beanName = name; 30 | } 31 | 32 | /** 33 | * @see org.springframework.context.ApplicationContextAware#setApplicationContext(org.springframework.context.ApplicationContext) 34 | *

35 | * 不支持Spring3.2以下版本, 无法通过addApplicationListener启动export 36 | */ 37 | @Override 38 | public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { 39 | this.applicationContext = applicationContext; 40 | } 41 | 42 | @Override 43 | public void afterPropertiesSet() throws Exception { 44 | } 45 | 46 | @Override 47 | public void destroy() throws Exception { 48 | logger.info("easy rpc destroy consumer with beanName {}", beanName); 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/com/linkedkeeper/easyrpc/config/spring/ConsumerFactoryBean.java: -------------------------------------------------------------------------------- 1 | package com.linkedkeeper.easyrpc.config.spring; 2 | 3 | import com.linkedkeeper.easyrpc.config.api.ConsumerConfig; 4 | import org.springframework.beans.factory.FactoryBean; 5 | 6 | public class ConsumerFactoryBean extends ConsumerConfig implements FactoryBean { 7 | 8 | private transient T bean = null; 9 | private transient Class objectType = null; 10 | 11 | @Override 12 | public T getObject() throws Exception { 13 | bean = refer(); 14 | return bean; 15 | } 16 | 17 | @Override 18 | public Class getObjectType() { 19 | try { 20 | objectType = getProxyClass(); 21 | } catch (Exception e) { 22 | objectType = null; 23 | } 24 | return objectType; 25 | } 26 | 27 | @Override 28 | public boolean isSingleton() { 29 | return true; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/linkedkeeper/easyrpc/config/spring/ProviderBean.java: -------------------------------------------------------------------------------- 1 | package com.linkedkeeper.easyrpc.config.spring; 2 | 3 | import com.linkedkeeper.easyrpc.config.api.ProviderConfig; 4 | import com.linkedkeeper.easyrpc.config.api.ServerConfig; 5 | import com.linkedkeeper.easyrpc.server.RpcServer; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | import org.springframework.beans.BeansException; 9 | import org.springframework.beans.factory.BeanNameAware; 10 | import org.springframework.beans.factory.DisposableBean; 11 | import org.springframework.beans.factory.InitializingBean; 12 | import org.springframework.context.ApplicationContext; 13 | import org.springframework.context.ApplicationContextAware; 14 | import org.springframework.util.CollectionUtils; 15 | 16 | import java.util.ArrayList; 17 | import java.util.List; 18 | import java.util.Map; 19 | 20 | public class ProviderBean extends ProviderConfig implements InitializingBean, DisposableBean, ApplicationContextAware, BeanNameAware { 21 | 22 | /** 23 | * slf4j logger for this class 24 | */ 25 | private Logger logger = LoggerFactory.getLogger(ProviderBean.class); 26 | 27 | protected transient ApplicationContext applicationContext = null; 28 | private transient String beanName = null; 29 | 30 | /** 31 | * @param name 32 | * @see org.springframework.beans.factory.BeanNameAware#setBeanName(java.lang.String) 33 | */ 34 | @Override 35 | public void setBeanName(String name) { 36 | this.beanName = name; 37 | } 38 | 39 | /** 40 | * @see org.springframework.context.ApplicationContextAware#setApplicationContext(org.springframework.context.ApplicationContext) 41 | *

42 | * 不支持Spring3.2以下版本, 无法通过addApplicationListener启动export 43 | */ 44 | @Override 45 | public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { 46 | this.applicationContext = applicationContext; 47 | } 48 | 49 | /** 50 | * Using implements InitializingBean 51 | */ 52 | @Override 53 | public void afterPropertiesSet() throws Exception { 54 | propertiesInit(); 55 | export(); 56 | } 57 | 58 | /* 59 | * 组装相应的ServiceConfig 60 | */ 61 | private void propertiesInit() { 62 | if (applicationContext != null) { 63 | if (getServerConfigs() == null) { 64 | Map protocolMaps = applicationContext.getBeansOfType(ServerConfig.class, false, false); 65 | if (!CollectionUtils.isEmpty(protocolMaps)) { 66 | List protocolLists = new ArrayList(protocolMaps.values()); 67 | setServerConfigs(protocolLists); 68 | } 69 | } 70 | } 71 | } 72 | 73 | @Override 74 | public void destroy() throws Exception { 75 | logger.info("easy rpc destroy provider with beanName {}", beanName); 76 | // todo unexport 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/com/linkedkeeper/easyrpc/config/spring/ServerBean.java: -------------------------------------------------------------------------------- 1 | package com.linkedkeeper.easyrpc.config.spring; 2 | 3 | import com.linkedkeeper.easyrpc.config.api.ServerConfig; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import org.springframework.beans.factory.BeanNameAware; 7 | import org.springframework.beans.factory.DisposableBean; 8 | import org.springframework.beans.factory.InitializingBean; 9 | 10 | public class ServerBean extends ServerConfig implements InitializingBean, DisposableBean, BeanNameAware { 11 | 12 | /** 13 | * slf4j logger for this class 14 | */ 15 | private Logger logger = LoggerFactory.getLogger(ServerBean.class); 16 | 17 | private transient String beanName = null; 18 | 19 | /** 20 | * @param name 21 | * @see org.springframework.beans.factory.BeanNameAware#setBeanName(java.lang.String) 22 | */ 23 | @Override 24 | public void setBeanName(String name) { 25 | this.beanName = name; 26 | } 27 | 28 | @Override 29 | public void afterPropertiesSet() throws Exception { 30 | } 31 | 32 | @Override 33 | public void destroy() throws Exception { 34 | logger.info("Stop server with beanName {}", beanName); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/linkedkeeper/easyrpc/config/spring/schema/EasyRpcBeanDefinitionParser.java: -------------------------------------------------------------------------------- 1 | package com.linkedkeeper.easyrpc.config.spring.schema; 2 | 3 | import org.apache.commons.lang3.StringUtils; 4 | import org.springframework.beans.factory.config.BeanDefinition; 5 | import org.springframework.beans.factory.config.BeanReference; 6 | import org.springframework.beans.factory.config.RuntimeBeanReference; 7 | import org.springframework.beans.factory.support.ManagedList; 8 | import org.springframework.beans.factory.support.RootBeanDefinition; 9 | import org.springframework.beans.factory.xml.BeanDefinitionParser; 10 | import org.springframework.beans.factory.xml.ParserContext; 11 | import org.w3c.dom.Element; 12 | 13 | import java.lang.reflect.Method; 14 | import java.lang.reflect.Modifier; 15 | 16 | public class EasyRpcBeanDefinitionParser implements BeanDefinitionParser { 17 | 18 | private final Class beanClass; 19 | private final boolean required; 20 | 21 | public EasyRpcBeanDefinitionParser(Class beanClass, boolean required) { 22 | this.beanClass = beanClass; 23 | this.required = required; 24 | } 25 | 26 | /** 27 | * Method Name parse 28 | * 29 | * @param element 30 | * @param parserContext 31 | * @return Return Type BeanDefinition 32 | */ 33 | @Override 34 | public BeanDefinition parse(Element element, ParserContext parserContext) { 35 | return parse(element, parserContext, this.beanClass, this.required); 36 | } 37 | 38 | 39 | private BeanDefinition parse(Element element, ParserContext parserContext, Class beanClass, boolean required) { 40 | RootBeanDefinition beanDefinition = new RootBeanDefinition(); 41 | beanDefinition.setBeanClass(beanClass); 42 | beanDefinition.setLazyInit(false); 43 | 44 | String id = element.getAttribute("id"); 45 | if (StringUtils.isBlank(id) && required) { 46 | throw new IllegalStateException("This bean do not set spring bean id " + id); 47 | } 48 | 49 | // id肯定是必须的所以此处去掉对id是否为空的判断 50 | if (required) { 51 | if (parserContext.getRegistry().containsBeanDefinition(id)) { 52 | throw new IllegalStateException("Duplicate spring bean id " + id); 53 | } 54 | parserContext.getRegistry().registerBeanDefinition(id, beanDefinition); 55 | } 56 | 57 | // set各个属性 58 | for (Method setter : beanClass.getMethods()) { 59 | if (isProperty(setter, beanClass)) { 60 | String name = setter.getName(); 61 | String property = name.substring(3, 4).toLowerCase() + name.substring(4); 62 | String value = element.getAttribute(property).trim(); 63 | Object reference = value; 64 | //根据property名称来进行区别处理 65 | switch (property) { 66 | case "protocol": 67 | case "interface": 68 | if (StringUtils.isNotBlank(value)) { 69 | beanDefinition.getPropertyValues().addPropertyValue(property, reference); 70 | } 71 | break; 72 | case "ref": 73 | if (StringUtils.isNotBlank(value)) { 74 | BeanDefinition refBean = parserContext.getRegistry().getBeanDefinition(value); 75 | if (!refBean.isSingleton()) { 76 | throw new IllegalStateException("The exported service ref " + value + " must be singleton! Please set the " + value + " bean scope to singleton, eg: "); 77 | } 78 | reference = new RuntimeBeanReference(value); 79 | } else { 80 | // 保持ref的null值 81 | reference = null; 82 | } 83 | beanDefinition.getPropertyValues().addPropertyValue(property, reference); 84 | break; 85 | case "server": 86 | parseMultiRef(property, value, beanDefinition); 87 | break; 88 | default: 89 | // 默认非空字符串只是绑定值到属性 90 | if (StringUtils.isNotBlank(value)) { 91 | beanDefinition.getPropertyValues().addPropertyValue(property, reference); 92 | } 93 | break; 94 | } 95 | } 96 | } 97 | return beanDefinition; 98 | } 99 | 100 | private void parseMultiRef(String property, String value, RootBeanDefinition beanDefinition) { 101 | String[] values = value.split("\\s*[,]+\\s*"); 102 | ManagedList list = null; 103 | for (String v : values) { 104 | if (StringUtils.isNotBlank(v)) { 105 | if (list == null) { 106 | list = new ManagedList(); 107 | } 108 | list.add(new RuntimeBeanReference(v)); 109 | } 110 | } 111 | beanDefinition.getPropertyValues().addPropertyValue(property, list); 112 | } 113 | 114 | /** 115 | * 判断是否有相应get\set方法的property 116 | */ 117 | protected boolean isProperty(Method method, Class beanClass) { 118 | String methodName = method.getName(); 119 | boolean flag = methodName.length() > 3 && methodName.startsWith("set") && Modifier.isPublic(method.getModifiers()) && method.getParameterTypes().length == 1; 120 | Method getter = null; 121 | if (flag) { 122 | Class type = method.getParameterTypes()[0]; 123 | try { 124 | getter = beanClass.getMethod("get" + methodName.substring(3), new Class[0]); 125 | } catch (NoSuchMethodException e) { 126 | try { 127 | getter = beanClass.getMethod("is" + methodName.substring(3), new Class[0]); 128 | } catch (NoSuchMethodException e2) { 129 | } 130 | } 131 | flag = getter != null && Modifier.isPublic(getter.getModifiers()) && type.equals(getter.getReturnType()); 132 | } 133 | return flag; 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/main/java/com/linkedkeeper/easyrpc/config/spring/schema/EasyRpcNamespaceHandler.java: -------------------------------------------------------------------------------- 1 | package com.linkedkeeper.easyrpc.config.spring.schema; 2 | 3 | import com.linkedkeeper.easyrpc.config.spring.ConsumerBean; 4 | import com.linkedkeeper.easyrpc.config.spring.ProviderBean; 5 | import com.linkedkeeper.easyrpc.config.spring.ServerBean; 6 | import org.springframework.beans.factory.xml.NamespaceHandlerSupport; 7 | 8 | /** 9 | * Title: 继承NamespaceHandlerSupport,将xml的标签绑定到解析器 10 | *

11 | * Description: 在META-INF下增加spring.handlers和spring.schemas 12 | */ 13 | public class EasyRpcNamespaceHandler extends NamespaceHandlerSupport { 14 | @Override 15 | public void init() { 16 | registerBeanDefinitionParser("provider", new EasyRpcBeanDefinitionParser(ProviderBean.class, true)); 17 | registerBeanDefinitionParser("consumer", new EasyRpcBeanDefinitionParser(ConsumerBean.class, true)); 18 | registerBeanDefinitionParser("server", new EasyRpcBeanDefinitionParser(ServerBean.class, true)); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/linkedkeeper/easyrpc/server/RpcHandler.java: -------------------------------------------------------------------------------- 1 | package com.linkedkeeper.easyrpc.server; 2 | 3 | import com.linkedkeeper.easyrpc.codec.RpcRequest; 4 | import com.linkedkeeper.easyrpc.codec.RpcResponse; 5 | import io.netty.channel.ChannelFuture; 6 | import io.netty.channel.ChannelFutureListener; 7 | import io.netty.channel.ChannelHandlerContext; 8 | import io.netty.channel.SimpleChannelInboundHandler; 9 | import net.sf.cglib.reflect.FastClass; 10 | import net.sf.cglib.reflect.FastMethod; 11 | import org.slf4j.Logger; 12 | import org.slf4j.LoggerFactory; 13 | 14 | import java.lang.reflect.Method; 15 | import java.util.Map; 16 | import java.util.concurrent.ArrayBlockingQueue; 17 | import java.util.concurrent.ThreadPoolExecutor; 18 | import java.util.concurrent.TimeUnit; 19 | 20 | public class RpcHandler extends SimpleChannelInboundHandler { 21 | 22 | private static final Logger LOGGER = LoggerFactory.getLogger(RpcHandler.class); 23 | 24 | private ThreadPoolExecutor executor = new ThreadPoolExecutor(16, 16, 600L, TimeUnit.SECONDS, new ArrayBlockingQueue(65536)); 25 | 26 | private final Map handlerMap; 27 | 28 | public RpcHandler(Map handlerMap) { 29 | this.handlerMap = handlerMap; 30 | } 31 | 32 | protected void channelRead0(final ChannelHandlerContext ctx, final RpcRequest request) throws Exception { 33 | executor.submit(new Runnable() { 34 | public void run() { 35 | LOGGER.debug("Receive request " + request.getRequestId()); 36 | RpcResponse response = new RpcResponse(); 37 | response.setRequestId(request.getRequestId()); 38 | try { 39 | Object result = handle(request); 40 | response.setResult(result); 41 | } catch (Throwable t) { 42 | response.setError(t.toString()); 43 | LOGGER.error("RPC Server handle request error", t); 44 | } 45 | ctx.writeAndFlush(response).addListener(new ChannelFutureListener() { 46 | public void operationComplete(ChannelFuture future) throws Exception { 47 | LOGGER.debug("Send response for request " + request.getRequestId()); 48 | } 49 | }); 50 | } 51 | }); 52 | } 53 | 54 | private Object handle(RpcRequest request) throws Throwable { 55 | String className = request.getClassName(); 56 | Object serviceBean = handlerMap.get(className); 57 | 58 | Class serviceClass = serviceBean.getClass(); 59 | String methodName = request.getMethodName(); 60 | Class[] parameterTypes = request.getParameterTypes(); 61 | Object[] parameters = request.getParameters(); 62 | 63 | // JDK reflect 64 | Method method = serviceClass.getMethod(methodName, parameterTypes); 65 | method.setAccessible(true); 66 | return method.invoke(serviceBean, parameters); 67 | 68 | // Cglib reflect 69 | /**FastClass serviceFastClass = FastClass.create(serviceClass); 70 | FastMethod serviceFastMethod = serviceFastClass.getMethod(methodName, parameterTypes); 71 | return serviceFastMethod.invoke(serviceBean, parameters);**/ 72 | } 73 | 74 | public void exceptiionCaught(ChannelHandlerContext ctx, Throwable cause) { 75 | LOGGER.error("server caught exception", cause); 76 | ctx.close(); 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /src/main/java/com/linkedkeeper/easyrpc/server/RpcServer.java: -------------------------------------------------------------------------------- 1 | package com.linkedkeeper.easyrpc.server; 2 | 3 | import com.linkedkeeper.easyrpc.codec.RpcDecoder; 4 | import com.linkedkeeper.easyrpc.codec.RpcEncoder; 5 | import com.linkedkeeper.easyrpc.codec.RpcRequest; 6 | import com.linkedkeeper.easyrpc.codec.RpcResponse; 7 | import com.linkedkeeper.easyrpc.config.api.ProviderConfig; 8 | import io.netty.bootstrap.ServerBootstrap; 9 | import io.netty.channel.*; 10 | import io.netty.channel.nio.NioEventLoopGroup; 11 | import io.netty.channel.socket.SocketChannel; 12 | import io.netty.channel.socket.nio.NioServerSocketChannel; 13 | import io.netty.handler.codec.LengthFieldBasedFrameDecoder; 14 | import org.slf4j.Logger; 15 | import org.slf4j.LoggerFactory; 16 | import org.springframework.beans.factory.DisposableBean; 17 | import org.springframework.beans.factory.InitializingBean; 18 | 19 | import java.util.HashMap; 20 | import java.util.Map; 21 | import java.util.concurrent.TimeUnit; 22 | 23 | public class RpcServer { 24 | 25 | private static final Logger LOGGER = LoggerFactory.getLogger(RpcServer.class); 26 | 27 | private String serverAddress; 28 | public volatile Map handleMap = new HashMap(); 29 | 30 | EventLoopGroup bossGroup = new NioEventLoopGroup(); 31 | EventLoopGroup workerGroup = new NioEventLoopGroup(); 32 | 33 | public RpcServer(String serverAddress) throws Exception { 34 | this.serverAddress = serverAddress; 35 | this.startServer(); 36 | } 37 | 38 | public void registerProcessor(ProviderConfig providerConfig) { 39 | handleMap.put(providerConfig.getInterface(), providerConfig.getRef()); 40 | } 41 | 42 | private void startServer() throws Exception { 43 | ServerBootstrap bootstrap = new ServerBootstrap(); 44 | bootstrap.group(bossGroup, workerGroup) 45 | .channel(NioServerSocketChannel.class) 46 | .childHandler(new ChannelInitializer() { 47 | public void initChannel(SocketChannel ch) throws Exception { 48 | ch.pipeline() 49 | .addLast(new LengthFieldBasedFrameDecoder(65536, 0, 4, 0, 0)) 50 | .addLast(new RpcDecoder(RpcRequest.class)) 51 | .addLast(new RpcEncoder(RpcResponse.class)) 52 | .addLast(new RpcHandler(handleMap)); 53 | } 54 | }) 55 | .option(ChannelOption.SO_BACKLOG, 128) 56 | .childOption(ChannelOption.SO_KEEPALIVE, true); 57 | 58 | String[] array = serverAddress.split(":"); 59 | String host = array[0]; 60 | int port = Integer.parseInt(array[1]); 61 | 62 | ChannelFuture future = bootstrap.bind(host, port).sync(); 63 | ChannelFuture channelFuture = future.addListener(new ChannelFutureListener() { 64 | public void operationComplete(ChannelFuture future) throws Exception { 65 | if (future.isSuccess()) { 66 | LOGGER.info("Server have success bind to " + serverAddress); 67 | } else { 68 | LOGGER.error("Server fail bind to " + serverAddress); 69 | throw new Exception("Server start fail !", future.cause()); 70 | } 71 | } 72 | }); 73 | 74 | try { 75 | channelFuture.await(5000, TimeUnit.MILLISECONDS); 76 | if (channelFuture.isSuccess()) { 77 | LOGGER.info("start easy rpc server success."); 78 | } 79 | 80 | } catch (InterruptedException e) { 81 | LOGGER.error("start easy rpc occur InterruptedException!", e); 82 | } 83 | } 84 | 85 | public void destroy() throws Exception { 86 | workerGroup.shutdownGracefully(); 87 | bossGroup.shutdownGracefully(); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/easyrpc.xsd: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/spring.handlers: -------------------------------------------------------------------------------- 1 | http\://www.linkedkeeper.com/schema/easyrpc=com.linkedkeeper.easyrpc.config.spring.schema.EasyRpcNamespaceHandler -------------------------------------------------------------------------------- /src/main/resources/META-INF/spring.schemas: -------------------------------------------------------------------------------- 1 | http\://www.linkedkeeper.com/schema/easyrpc/easyrpc.xsd=META-INF/easyrpc.xsd -------------------------------------------------------------------------------- /src/test/java/com/linkedkeeper/easyrpc/test/client/HelloService.java: -------------------------------------------------------------------------------- 1 | package com.linkedkeeper.easyrpc.test.client; 2 | 3 | public interface HelloService { 4 | 5 | String hello(String name); 6 | 7 | String hello(Person person); 8 | 9 | } 10 | -------------------------------------------------------------------------------- /src/test/java/com/linkedkeeper/easyrpc/test/client/HelloServiceTest.java: -------------------------------------------------------------------------------- 1 | package com.linkedkeeper.easyrpc.test.client; 2 | 3 | import com.linkedkeeper.easyrpc.client.RpcClient; 4 | import com.linkedkeeper.easyrpc.client.RpcFuture; 5 | import com.linkedkeeper.easyrpc.client.proxy.IAsyncObjectProxy; 6 | import org.junit.After; 7 | import org.junit.Assert; 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.test.context.ContextConfiguration; 12 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 13 | 14 | import java.util.concurrent.ExecutionException; 15 | 16 | @RunWith(SpringJUnit4ClassRunner.class) 17 | @ContextConfiguration(locations = "classpath:spring-client.xml") 18 | public class HelloServiceTest { 19 | 20 | @Autowired 21 | private RpcClient rpcClient = null; 22 | 23 | @Test 24 | public void helloTest1() { 25 | HelloService helloService = rpcClient.create(HelloService.class); 26 | String result = helloService.hello("World"); 27 | System.out.println(result); 28 | Assert.assertEquals("Hello! World", result); 29 | } 30 | 31 | @Test 32 | public void helloTest2() { 33 | HelloService helloService = rpcClient.create(HelloService.class); 34 | Person person = new Person("Yong", "Huang"); 35 | String result = helloService.hello(person); 36 | System.out.println(result.toString()); 37 | Assert.assertEquals("Hello! Yong Huang", result); 38 | } 39 | 40 | @Test 41 | public void helloFutureTest1() throws ExecutionException, InterruptedException { 42 | IAsyncObjectProxy helloService = rpcClient.createAsync(HelloService.class); 43 | RpcFuture result = helloService.call("hello", "World"); 44 | Assert.assertEquals("Hello! World", result.get()); 45 | } 46 | 47 | @Test 48 | public void helloFutureTest2() throws ExecutionException, InterruptedException { 49 | IAsyncObjectProxy helloService = rpcClient.createAsync(HelloService.class); 50 | Person person = new Person("Yong", "Huang"); 51 | RpcFuture result = helloService.call("hello", person); 52 | Assert.assertEquals("Hello! Yong Huang", result.get()); 53 | } 54 | 55 | @After 56 | public void setTear() { 57 | if (rpcClient != null) { 58 | rpcClient.stop(); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/test/java/com/linkedkeeper/easyrpc/test/client/Person.java: -------------------------------------------------------------------------------- 1 | package com.linkedkeeper.easyrpc.test.client; 2 | 3 | public class Person { 4 | 5 | private String firstName; 6 | private String lastName; 7 | 8 | public Person() { 9 | } 10 | 11 | public Person(String firstName, String lastName) { 12 | this.firstName = firstName; 13 | this.lastName = lastName; 14 | } 15 | 16 | public String getFirstName() { 17 | return firstName; 18 | } 19 | 20 | public void setFirstName(String firstName) { 21 | this.firstName = firstName; 22 | } 23 | 24 | public String getLastName() { 25 | return lastName; 26 | } 27 | 28 | public void setLastName(String lastName) { 29 | this.lastName = lastName; 30 | } 31 | 32 | @Override 33 | public String toString() { 34 | return firstName + " " + lastName; 35 | } 36 | 37 | @Override 38 | public int hashCode() { 39 | return this.firstName.hashCode() ^ this.lastName.hashCode(); 40 | } 41 | 42 | @Override 43 | public boolean equals(Object obj) { 44 | if (!(obj instanceof Person)) return false; 45 | Person p = (Person) obj; 46 | return this.firstName.equals(p.firstName) && this.lastName.equals(p.lastName); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/test/java/com/linkedkeeper/easyrpc/test/server/HelloServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.linkedkeeper.easyrpc.test.server; 2 | 3 | import com.linkedkeeper.easyrpc.test.client.HelloService; 4 | import com.linkedkeeper.easyrpc.test.client.Person; 5 | 6 | public class HelloServiceImpl implements HelloService { 7 | 8 | public String hello(String name) { 9 | return "Hello! " + name; 10 | } 11 | 12 | public String hello(Person person) { 13 | return "Hello! " + person.getFirstName() + " " + person.getLastName(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/test/java/com/linkedkeeper/easyrpc/test/server/RpcBootstrap.java: -------------------------------------------------------------------------------- 1 | package com.linkedkeeper.easyrpc.test.server; 2 | 3 | import org.springframework.context.support.ClassPathXmlApplicationContext; 4 | 5 | public class RpcBootstrap { 6 | 7 | public static void main(String[] args) { 8 | new ClassPathXmlApplicationContext("spring-server.xml"); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/test/resources/log4j.properties: -------------------------------------------------------------------------------- 1 | log4j.rootLogger=Info,console,file 2 | 3 | log4j.appender.console=org.apache.log4j.ConsoleAppender 4 | log4j.appender.console.target=System.out 5 | log4j.appender.console.layout=org.apache.log4j.PatternLayout 6 | log4j.appender.console.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n 7 | 8 | log4j.appender.file = org.apache.log4j.DailyRollingFileAppender 9 | log4j.appender.file.File = /Users/sauronzhang/Desktop/logs/easyrpc.log 10 | log4j.appender.file.Append = true 11 | log4j.appender.file.Threshold = Error 12 | log4j.appender.file.layout = org.apache.log4j.PatternLayout 13 | log4j.appender.file.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss} %5p %c{1}:%L - %m%n -------------------------------------------------------------------------------- /src/test/resources/spring-client.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 13 | 14 | -------------------------------------------------------------------------------- /src/test/resources/spring-server.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 12 | 14 | 15 | 16 | 17 | --------------------------------------------------------------------------------