【转】Spring+Netty+Protostuff+ZooKeeper实现轻量级RPC服务(二)

来源:互联网 发布:matlab破解软件 编辑:程序博客网 时间:2024/05/29 02:47

接上文【转】Spring+Netty+Protostuff+ZooKeeper实现轻量级RPC服务 (二)

整体文件结构

这里写图片描述
其中(Maven 的多模块构建):

  • SpringMVC_RPC_Client
  • SpringMVC_RPC_Common
  • SpringMVC_RPC_Server
  • SpringMVC_RPC_Service 服务接口工程
  • SpringMVC_RPC_Service_Impl

服务端设计配置

服务接口工程SpringMVC_RPC_Service :

主要定义服务接口类和服务涉及到的实体类

SpringMVC_RPC_Service工程目录结构图

![这里写图片描述](http://img.blog.csdn.net/20170517142555653?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvaHVuZGFuXzUyMDUyMA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)SpringMVC_RPC_Service工程 pom.xml文件:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">    <modelVersion>4.0.0</modelVersion>    <parent>        <groupId>com.zhihua</groupId>        <artifactId>SpringMVC_RPC_Parent</artifactId>        <version>0.0.1-SNAPSHOT</version>    </parent>    <artifactId>SpringMVC_RPC_Service</artifactId>    <packaging>jar</packaging>    <dependencies>        <dependency>            <groupId>com.alibaba</groupId>            <artifactId>fastjson</artifactId>            <version>1.1.41</version>        </dependency>        <dependency>            <groupId>com.zhihua</groupId>            <artifactId>SpringMVC_RPC_Common</artifactId>            <version>0.0.1-SNAPSHOT</version>        </dependency>    </dependencies></project>
SpringMVC_RPC_Service工程服务接口IHelloService
package com.zhihua.service;import java.util.List;import com.zhihua.entity.User;public interface IHelloService {    public String hello(String name);    public User getUser(String name);    public List<User> getUsers(int size);    public User updateUser(User user);}
SpringMVC_RPC_Service工程User类
package com.zhihua.entity;import java.util.Date;import com.alibaba.fastjson.JSON;public class User {    private String name;    private Date birthday;    private boolean sex;    public User(String name,Date birthday,boolean sex){        this.name = name;        this.birthday = birthday;        this.sex = sex;    }    public String getName() {        return name;    }    public void setName(String name) {        this.name = name;    }    public Date getBirthday() {        return birthday;    }    public void setBirthday(Date birthday) {        this.birthday = birthday;    }    public boolean isSex() {        return sex;    }    public void setSex(boolean sex) {        this.sex = sex;    }    public String toString(){        return JSON.toJSONString(this);    }}

工程依赖引用的SpringMVC_RPC_Common工程:

主要放置服务端和客户端共用的组件,而且这些组件可以被其他服务包共用,所以要抽取出来

SpringMVC_RPC_Common工程目录结构图

![这里写图片描述](http://img.blog.csdn.net/20170517143223662?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvaHVuZGFuXzUyMDUyMA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)SpringMVC_RPC_Common工程pom.xml文件
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">    <modelVersion>4.0.0</modelVersion>    <parent>        <groupId>com.zhihua</groupId>        <artifactId>SpringMVC_RPC_Parent</artifactId>        <version>0.0.1-SNAPSHOT</version>    </parent>    <artifactId>SpringMVC_RPC_Common</artifactId>    <properties>        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>    </properties>    <dependencies>        <!-- Netty -->        <dependency>            <groupId>io.netty</groupId>            <artifactId>netty-all</artifactId>            <version>4.0.24.Final</version>        </dependency>        <!-- Protostuff -->        <dependency>            <groupId>com.dyuproject.protostuff</groupId>            <artifactId>protostuff-core</artifactId>            <version>1.0.8</version>        </dependency>        <dependency>            <groupId>com.dyuproject.protostuff</groupId>            <artifactId>protostuff-runtime</artifactId>            <version>1.0.8</version>        </dependency>        <!-- Objenesis -->        <dependency>            <groupId>org.objenesis</groupId>            <artifactId>objenesis</artifactId>            <version>2.1</version>        </dependency>        <!-- Spring -->        <dependency>            <groupId>org.springframework</groupId>            <artifactId>spring-context</artifactId>            <version>4.0.1.RELEASE</version>        </dependency>    </dependencies></project>
SpringMVC_RPC_Common工程RpcService注解
package com.zhihua.annotation;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;import org.springframework.stereotype.Component;@Target({ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Component // 表明可以被 Spring 扫描public @interface RpcService {    Class<?> value();}
SpringMVC_RPC_Common工程Constant接口
package com.zhihua.common;/** * Constant接口 * <请替换成功能描述> <br> * <请替换成详细描述> * @author caizh * @since [1.0.0] * @version [1.0.0,2017年3月24日] */public interface Constant {    int ZK_SESSION_TIMEOUT = 5000;    //在创建数据节点前,先用zkCli.sh客户端连接上服务端,查看目前存在的数据节点,    //把下面的/zookeeper/quota改为你自己的,/zookeeper/quota是我自己Zookeeper的节点    String ZK_REGISTRY_PATH = "/zookeeper_quota";    String ZK_DATA_PATH = ZK_REGISTRY_PATH + "/data";}
SpringMVC_RPC_Common工程RpcDecoder解码类
package com.zhihua.common;import java.util.List;import io.netty.buffer.ByteBuf;import io.netty.channel.ChannelHandlerContext;import io.netty.handler.codec.ByteToMessageDecoder;/** * RpcDecoder解码类 * <请替换成功能描述> <br> * <请替换成详细描述> * @author caizh * @since [1.0.0] * @version [1.0.0,2017年3月24日] */public class RpcDecoder extends ByteToMessageDecoder{    private Class<?> genericClass;    public RpcDecoder(Class<?> genericClass) {        this.genericClass = genericClass;    }    @Override    public void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {        if (in.readableBytes() < 4) {            return;        }        in.markReaderIndex();        int dataLength = in.readInt();        if (dataLength < 0) {            ctx.close();        }        if (in.readableBytes() < dataLength) {            in.resetReaderIndex();            return;        }        byte[] data = new byte[dataLength];        in.readBytes(data);        Object obj = SerializationUtil.deserialize(data, genericClass);        out.add(obj);    }}
SpringMVC_RPC_Common工程RpcEncoder解码类
package com.zhihua.common;import io.netty.buffer.ByteBuf;import io.netty.channel.ChannelHandlerContext;import io.netty.handler.codec.MessageToByteEncoder;/** * RpcEncoder编码类 * <请替换成功能描述> <br> * <请替换成详细描述> * @author caizh * @since [1.0.0] * @version [1.0.0,2017年3月24日] */@SuppressWarnings("rawtypes")public class RpcEncoder extends MessageToByteEncoder{    private Class<?> genericClass;    public RpcEncoder(Class<?> genericClass) {        this.genericClass = genericClass;    }    @Override    public void encode(ChannelHandlerContext ctx, Object in, ByteBuf out) throws Exception {        if (genericClass.isInstance(in)) {            byte[] data = SerializationUtil.serialize(in);            out.writeInt(data.length);            out.writeBytes(data);        }    }}
SpringMVC_RPC_Common工程RpcRequest请求类
package com.zhihua.common;/** * RpcRequest请求类 * <请替换成功能描述> <br> * <请替换成详细描述> * @author caizh * @since [1.0.0] * @version [1.0.0,2017年3月24日] */public class RpcRequest {    private String requestId;    private String className;    private String methodName;    private Class<?>[] parameterTypes;    private Object[] parameters;    public String getRequestId() {        return requestId;    }    public void setRequestId(String requestId) {        this.requestId = requestId;    }    public String getClassName() {        return className;    }    public void setClassName(String className) {        this.className = className;    }    public String getMethodName() {        return methodName;    }    public void setMethodName(String methodName) {        this.methodName = methodName;    }    public Class<?>[] getParameterTypes() {        return parameterTypes;    }    public void setParameterTypes(Class<?>[] parameterTypes) {        this.parameterTypes = parameterTypes;    }    public Object[] getParameters() {        return parameters;    }    public void setParameters(Object[] parameters) {        this.parameters = parameters;    }}
SpringMVC_RPC_Common工程RpcResponse响应类
package com.zhihua.common;/** * RpcResponse响应类 * <请替换成功能描述> <br> * <请替换成详细描述> * @author caizh * @since [1.0.0] * @version [1.0.0,2017年3月24日] */public class RpcResponse {    private String requestId;    private Throwable error;    private Object result;    public String getRequestId() {        return requestId;    }    public void setRequestId(String requestId) {        this.requestId = requestId;    }    public Throwable getError() {        return error;    }    public void setError(Throwable error) {        this.error = error;    }    public Object getResult() {        return result;    }    public void setResult(Object result) {        this.result = result;    }}
SpringMVC_RPC_Common工程SerializationUtil序列化反序列化类
package com.zhihua.common;import java.util.Map;import java.util.concurrent.ConcurrentHashMap;import org.objenesis.Objenesis;import org.objenesis.ObjenesisStd;import com.dyuproject.protostuff.LinkedBuffer;import com.dyuproject.protostuff.ProtostuffIOUtil;import com.dyuproject.protostuff.Schema;import com.dyuproject.protostuff.runtime.RuntimeSchema;public class SerializationUtil {    private static Map<Class<?>, Schema<?>> cachedSchema = new ConcurrentHashMap<>();    private static Objenesis objenesis = new ObjenesisStd(true);    private SerializationUtil() {    }    @SuppressWarnings("unchecked")    private static <T> Schema<T> getSchema(Class<T> cls) {        Schema<T> schema = (Schema<T>) cachedSchema.get(cls);        if (schema == null) {            schema = RuntimeSchema.createFrom(cls);            if (schema != null) {                cachedSchema.put(cls, schema);            }        }        return schema;    }    @SuppressWarnings("unchecked")    public static <T> byte[] serialize(T obj) {        Class<T> cls = (Class<T>) obj.getClass();        LinkedBuffer buffer = LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE);        try {            Schema<T> schema = getSchema(cls);            return ProtostuffIOUtil.toByteArray(obj, schema, buffer);        } catch (Exception e) {            throw new IllegalStateException(e.getMessage(), e);        } finally {            buffer.clear();        }    }    public static <T> T deserialize(byte[] data, Class<T> cls) {        try {            T message = (T) objenesis.newInstance(cls);            Schema<T> schema = getSchema(cls);            ProtostuffIOUtil.mergeFrom(data, message, schema);            return message;        } catch (Exception e) {            throw new IllegalStateException(e.getMessage(), e);        }    }}

服务接口实现工程SpringMVC_RPC_Service_Impl :

主要放置SpringMVC_RPC_Service接口的实现类,这个工程会依赖公共RPC服务工程SpringMVC_RPC_Server

SpringMVC_RPC_Service_Impl 工程目录结构图

![这里写图片描述](http://img.blog.csdn.net/20170517144409596?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvaHVuZGFuXzUyMDUyMA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)SpringMVC_RPC_Service_Impl 服务实现工程pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">  <modelVersion>4.0.0</modelVersion>  <parent>    <groupId>com.zhihua</groupId>    <artifactId>SpringMVC_RPC_Parent</artifactId>    <version>0.0.1-SNAPSHOT</version>  </parent>  <artifactId>SpringMVC_RPC_Service_Impl</artifactId>  <packaging>war</packaging>  <dependencies>        <dependency>            <groupId>com.zhihua</groupId>            <artifactId>SpringMVC_RPC_Server</artifactId>            <version>0.0.1-SNAPSHOT</version>        </dependency>        <dependency>            <groupId>org.springframework</groupId>            <artifactId>spring-webmvc</artifactId>            <version>4.0.1.RELEASE</version>        </dependency>        <!-- Servlet核心包 -->        <dependency>            <groupId>javax.servlet</groupId>            <artifactId>javax.servlet-api</artifactId>            <version>3.0.1</version>            <scope>provided</scope>        </dependency>        <dependency>            <groupId>com.zhihua</groupId>            <artifactId>SpringMVC_RPC_Service</artifactId>            <version>0.0.1-SNAPSHOT</version>        </dependency>    </dependencies>    <build>        <defaultGoal>compile</defaultGoal>        <plugins>            <plugin>                <groupId>org.apache.maven.plugins</groupId>                <artifactId>maven-source-plugin</artifactId>                <version>2.2.1</version>                <executions>                    <execution>                        <id>attach-sources</id>                        <goals>                            <goal>jar</goal>                        </goals>                    </execution>                </executions>            </plugin>            <plugin>                <groupId>org.apache.tomcat.maven</groupId>                <artifactId>tomcat8-maven-plugin</artifactId>                <version>2.2</version>                <configuration>                    <port>8000</port>                    <path>/</path>                </configuration>            </plugin>        </plugins>    </build></project>
SpringMVC_RPC_Service_Impl工程HelloServiceImpl服务实现类
package com.zhihua.service.Impl;import java.util.ArrayList;import java.util.Calendar;import java.util.Date;import java.util.List;import com.zhihua.annotation.RpcService;import com.zhihua.entity.User;import com.zhihua.service.IHelloService;/** * 指定远程接口 使用RpcService注解定义在服务接口的实现类上 * 需要对该实现类指定远程接口,因为实现类可能会实现多个接口,一定要告诉框架哪个才是远程接口。 * <请替换成功能描述> <br> * <请替换成详细描述> * @author caizh * @since [1.0.0] * @version [1.0.0,2017年3月24日] */@RpcService(IHelloService.class)public class HelloServiceImpl implements IHelloService{    @Override    public String hello(String name) {        String result = "hello" + name;        System.out.println(result);        return "hello" + name;    }    @Override    public User getUser(String name) {        User user = new User(name,new Date(),true);        return user;    }    @Override    public List<User> getUsers(int size) {        List<User> list = new ArrayList<User>();        User user = null;        String name = "foo";        Date birthday = new Date();        Calendar cal = Calendar.getInstance();        cal.setTime(birthday);        for(int i = 0; i < size; i++){            cal.add(Calendar.DAY_OF_MONTH, 1);            user = new User(name, cal.getTime(), i%2==0 ? true : false);            list.add(user);        }        return list;    }    @Override    public User updateUser(User user) {        user.setName(user.getName()+"--update");        return user;    }}
SpringMVC_RPC_Service_Impl工程 RpcBootstrap 启动服务器并发布服务

为了加载 applicationContext.xml 配置文件来发布服务,只需编写一个引导程序即可:

package com.zhihua.bootstrap;import org.springframework.context.support.ClassPathXmlApplicationContext;/** * 运行RpcBootstrap类的main方法即可启动服务端 * <请替换成功能描述> <br> * <请替换成详细描述> * @author caizh * @since [1.0.0] * @version [1.0.0,2017年5月16日] */public class RpcBootstrap {    @SuppressWarnings("resource")    public static void main(String[] args) {        try {            System.out.println("服务器正在启动!");            if(new ClassPathXmlApplicationContext("applicationContext.xml")!=null){                System.out.println("服务器启动成功!");            }        } catch (Exception e) {            System.out.println("服务器启动失败!");            System.out.println(e.getMessage());        }              }}
SpringMVC_RPC_Service_Impl工程配置文件config.properties
# ZooKeeper 服务器registry.address=127.0.0.1:2181# RPC 服务器server.address=127.0.0.1:8000#以上配置表明:连接本地的 ZooKeeper 服务器,并在 8000 端口上发布 RPC 服务。
SpringMVC_RPC_Service_Impl工程 applicationContext.xml 配置文件
<?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"    xmlns:tx="http://www.springframework.org/schema/tx"    xmlns:aop="http://www.springframework.org/schema/aop"    xsi:schemaLocation="http://www.springframework.org/schema/beans     http://www.springframework.org/schema/beans/spring-beans.xsd        http://www.springframework.org/schema/tx         http://www.springframework.org/schema/tx/spring-tx-4.0.xsd        http://www.springframework.org/schema/context         http://www.springframework.org/schema/context/spring-context-4.0.xsd        http://www.springframework.org/schema/aop        http://www.springframework.org/schema/aop/spring-aop-4.0.xsd">    <!-- 自动扫描web包 ,将带有注解的类 纳入spring容器管理 -->    <context:component-scan base-package="com.zhihua.*"/>    <context:property-placeholder location="classpath:config.properties"/>      <context:annotation-config/>               <!-- 配置服务注册组件 -->    <bean id="serviceRegistry" class="com.zhihua.server.ServiceRegistry">        <constructor-arg name="registryAddress" value="${registry.address}"/>    </bean>    <!-- 配置RPC服务器 -->    <bean id="rpcServer" class="com.zhihua.server.RpcServer">        <constructor-arg name="serverAddress" value="${server.address}"/>        <constructor-arg name="serviceRegistry" ref="serviceRegistry"/>    </bean></beans>

服务的注册与发现工程 SpringMVC_RPC_Server :

SpringMVC_RPC_Server 主要是连接Zookeeper实现服务的注册与发现

SpringMVC_RPC_Server 工程的目录结构图

![这里写图片描述](http://img.blog.csdn.net/20170517145312959?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvaHVuZGFuXzUyMDUyMA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)SpringMVC_RPC_Server工程pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">    <modelVersion>4.0.0</modelVersion>    <parent>        <groupId>com.zhihua</groupId>        <artifactId>SpringMVC_RPC_Parent</artifactId>        <version>0.0.1-SNAPSHOT</version>    </parent>    <artifactId>SpringMVC_RPC_Server</artifactId>    <packaging>jar</packaging>    <properties>        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>    </properties>    <dependencies>        <dependency>            <groupId>com.zhihua</groupId>            <artifactId>SpringMVC_RPC_Common</artifactId>            <version>0.0.1-SNAPSHOT</version>        </dependency>        <!-- ZooKeeper -->        <dependency>            <groupId>org.apache.zookeeper</groupId>            <artifactId>zookeeper</artifactId>            <version>3.4.6</version>        </dependency>        <!-- Apache Commons Collections -->        <dependency>            <groupId>org.apache.commons</groupId>            <artifactId>commons-collections4</artifactId>            <version>4.0</version>        </dependency>    </dependencies></project>
SpringMVC_RPC_Server工程RpcServer服务启动类
package com.zhihua.server;import java.util.HashMap;import java.util.Map;import org.apache.commons.collections4.MapUtils;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.beans.BeansException;import org.springframework.beans.factory.InitializingBean;import org.springframework.context.ApplicationContext;import org.springframework.context.ApplicationContextAware;import com.zhihua.annotation.RpcService;import com.zhihua.common.RpcDecoder;import com.zhihua.common.RpcEncoder;import com.zhihua.common.RpcRequest;import com.zhihua.common.RpcResponse;import io.netty.bootstrap.ServerBootstrap;import io.netty.channel.ChannelFuture;import io.netty.channel.ChannelInitializer;import io.netty.channel.ChannelOption;import io.netty.channel.EventLoopGroup;import io.netty.channel.nio.NioEventLoopGroup;import io.netty.channel.socket.SocketChannel;import io.netty.channel.socket.nio.NioServerSocketChannel;/** * RpcServer服务启动类 * <请替换成功能描述> <br> * <请替换成详细描述> * @author caizh * @since [1.0.0] * @version [1.0.0,2017年3月24日] */public class RpcServer implements ApplicationContextAware,InitializingBean{    private static final Logger LOGGER = LoggerFactory.getLogger(RpcServer.class);    private String serverAddress;    private ServiceRegistry serviceRegistry;    private Map<String, Object> handlerMap = new HashMap<>(); // 存放接口名与服务对象之间的映射关系    public RpcServer(String serverAddress) {        this.serverAddress = serverAddress;    }    public RpcServer(String serverAddress, ServiceRegistry serviceRegistry) {        this.serverAddress = serverAddress;        this.serviceRegistry = serviceRegistry;    }    @Override    public void setApplicationContext(ApplicationContext ctx) throws BeansException {        Map<String, Object> serviceBeanMap = ctx.getBeansWithAnnotation(RpcService.class); // 获取所有带有 RpcService 注解的 Spring Bean        if (MapUtils.isNotEmpty(serviceBeanMap)) {            for (Object serviceBean : serviceBeanMap.values()) {                String interfaceName = serviceBean.getClass().getAnnotation(RpcService.class).value().getName();                handlerMap.put(interfaceName, serviceBean);            }        }    }    @Override    public void afterPropertiesSet() throws Exception {        EventLoopGroup bossGroup = new NioEventLoopGroup();        EventLoopGroup workerGroup = new NioEventLoopGroup();        try {            ServerBootstrap bootstrap = new ServerBootstrap();            bootstrap.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class)            .childHandler(new ChannelInitializer<SocketChannel>() {                @Override                public void initChannel(SocketChannel channel) throws Exception {                    channel.pipeline()                    .addLast(new RpcDecoder(RpcRequest.class)) // 将 RPC 请求进行解码(为了处理请求)                    .addLast(new RpcEncoder(RpcResponse.class)) // 将 RPC 响应进行编码(为了返回响应)                    .addLast(new RpcHandler(handlerMap)); // 处理 RPC 请求                }            })            .option(ChannelOption.SO_BACKLOG, 128)            .childOption(ChannelOption.SO_KEEPALIVE, true);            String[] array = serverAddress.split(":");            String host = array[0];            int port = Integer.parseInt(array[1]);            ChannelFuture future = bootstrap.bind(host, port).sync();            LOGGER.debug("server started on port {}", port);            if (serviceRegistry != null) {                serviceRegistry.register(serverAddress); // 注册服务地址            }            future.channel().closeFuture().sync();        } finally {            workerGroup.shutdownGracefully();            bossGroup.shutdownGracefully();        }    }}
SpringMVC_RPC_Server工程ServiceRegistry服务注册类
package com.zhihua.server;import java.io.IOException;import java.util.concurrent.CountDownLatch;import org.apache.zookeeper.CreateMode;import org.apache.zookeeper.KeeperException;import org.apache.zookeeper.WatchedEvent;import org.apache.zookeeper.Watcher;import org.apache.zookeeper.ZooDefs;import org.apache.zookeeper.ZooKeeper;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import com.zhihua.common.Constant;/** * 服务注册类 * <请替换成功能描述> <br> * <请替换成详细描述> * @author caizh * @since [1.0.0] * @version [1.0.0,2017年3月27日] */public class ServiceRegistry {    private static final Logger LOGGER = LoggerFactory.getLogger(ServiceRegistry.class);    private CountDownLatch latch = new CountDownLatch(1);    private String registryAddress;    public ServiceRegistry(String registryAddress) {        this.registryAddress = registryAddress;    }    public void register(String data) {        if (data != null) {            ZooKeeper zk = connectServer();            if (zk != null) {                createNode(zk, data);            }        }    }    private ZooKeeper connectServer() {        ZooKeeper zk = null;        try {            zk = new ZooKeeper(registryAddress, Constant.ZK_SESSION_TIMEOUT, new Watcher() {                @Override                public void process(WatchedEvent event) {                    if (event.getState() == Event.KeeperState.SyncConnected) {                        latch.countDown();                    }                }            });            latch.await();        } catch (IOException | InterruptedException e) {            LOGGER.error("", e);        }        return zk;    }    private void createNode(ZooKeeper zk, String data) {        try {            byte[] bytes = data.getBytes();            String path = zk.create(Constant.ZK_DATA_PATH, bytes, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);            LOGGER.debug("create zookeeper node ({} => {})", path, data);        } catch (KeeperException | InterruptedException e) {            LOGGER.error("", e);        }    }}
SpringMVC_RPC_Server工程RpcHandler请求统一处理类
package com.zhihua.server;import java.util.Map;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.cglib.reflect.FastClass;import org.springframework.cglib.reflect.FastMethod;import com.zhihua.common.RpcRequest;import com.zhihua.common.RpcResponse;import io.netty.channel.ChannelFutureListener;import io.netty.channel.ChannelHandlerContext;import io.netty.channel.SimpleChannelInboundHandler;/** * RpcHandler请求统一处理类 * <请替换成功能描述> <br> * <请替换成详细描述> * @author caizh * @since [1.0.0] * @version [1.0.0,2017年3月24日] */public class RpcHandler extends SimpleChannelInboundHandler<RpcRequest> {    /**     * 为了避免使用Java反射带来的性能的问题,我们可以使用CGLib提供的反射API,如上面用到的FastClass与FastMethod     */    private static final Logger LOGGER = LoggerFactory.getLogger(RpcHandler.class);    private final Map<String, Object> handlerMap;    public RpcHandler(Map<String, Object> handlerMap) {        this.handlerMap = handlerMap;    }    @Override    public void channelRead0(final ChannelHandlerContext ctx, RpcRequest request) throws Exception {        RpcResponse response = new RpcResponse();        response.setRequestId(request.getRequestId());        try {            Object result = handle(request);            response.setResult(result);        } catch (Throwable t) {            response.setError(t);        }        ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);    }    private Object handle(RpcRequest request) throws Throwable {        String className = request.getClassName();        Object serviceBean = handlerMap.get(className);        Class<?> serviceClass = serviceBean.getClass();        String methodName = request.getMethodName();        Class<?>[] parameterTypes = request.getParameterTypes();        Object[] parameters = request.getParameters();        /*Method method = serviceClass.getMethod(methodName, parameterTypes);      method.setAccessible(true);      return method.invoke(serviceBean, parameters);*/        FastClass serviceFastClass = FastClass.create(serviceClass);        FastMethod serviceFastMethod = serviceFastClass.getMethod(methodName, parameterTypes);        return serviceFastMethod.invoke(serviceBean, parameters);    }    @Override    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {        LOGGER.error("server caught exception", cause);        ctx.close();    }}

至此,服务端代码编写完毕,接下来开始编写客户端代码

客户端工程SpringMVC_RPC_Client:

主要是springMVC实现服务端服务接口的调用测试

SpringMVC_RPC_Client工程目录结构图

![这里写图片描述](http://img.blog.csdn.net/20170517152218783?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvaHVuZGFuXzUyMDUyMA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)SpringMVC_RPC_Client工程的pom.xml文件
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">    <modelVersion>4.0.0</modelVersion>    <parent>        <groupId>com.zhihua</groupId>        <artifactId>SpringMVC_RPC_Parent</artifactId>        <version>0.0.1-SNAPSHOT</version>    </parent>    <artifactId>SpringMVC_RPC_Client</artifactId>    <packaging>war</packaging>    <dependencies>        <dependency>            <groupId>junit</groupId>            <artifactId>junit</artifactId>            <version>3.8.1</version>            <scope>test</scope>        </dependency>        <dependency>            <groupId>com.zhihua</groupId>            <artifactId>SpringMVC_RPC_Common</artifactId>            <version>0.0.1-SNAPSHOT</version>        </dependency>        <dependency>            <groupId>com.zhihua</groupId>            <artifactId>SpringMVC_RPC_Service</artifactId>            <version>0.0.1-SNAPSHOT</version>        </dependency>        <dependency>            <groupId>javax.servlet</groupId>            <artifactId>javax.servlet-api</artifactId>            <version>3.1.0</version>            <scope>provided</scope>        </dependency>        <!-- Spring -->        <dependency>            <groupId>org.springframework</groupId>            <artifactId>spring-context</artifactId>            <version>4.0.1.RELEASE</version>        </dependency>        <dependency>            <groupId>org.springframework</groupId>            <artifactId>spring-webmvc</artifactId>            <version>4.0.1.RELEASE</version>        </dependency>        <dependency>            <groupId>org.springframework</groupId>            <artifactId>spring-test</artifactId>            <version>4.0.1.RELEASE</version>            <scope>test</scope>        </dependency>        <!-- ZooKeeper -->        <dependency>            <groupId>org.apache.zookeeper</groupId>            <artifactId>zookeeper</artifactId>            <version>3.4.6</version>        </dependency>        <!-- CGLib -->        <dependency>            <groupId>cglib</groupId>            <artifactId>cglib</artifactId>            <version>3.1</version>        </dependency>    </dependencies></project>
SpringMVC_RPC_Client工程HelloController类
package com.zhihua.controller;import java.util.Date;import java.util.List;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestMapping;import com.zhihua.client.RpcProxy;import com.zhihua.entity.User;import com.zhihua.service.IHelloService;@Controllerpublic class HelloController {    @Autowired    private RpcProxy rpcProxy;    @RequestMapping("/hello")    public void hello(String name){        IHelloService service = rpcProxy.create(IHelloService.class);        String result = service.hello(name);        System.out.println(result);    }    @RequestMapping("/getUser")    public void getUser(String name){        IHelloService service = rpcProxy.create(IHelloService.class);        System.out.println(service.getUser(name).toString());    }    @RequestMapping("/getUsers")    public void getUsers(int size){        IHelloService service = rpcProxy.create(IHelloService.class);        List<User> list = service.getUsers(size);        for(User user : list){            System.out.println(user.toString());        }    }    @RequestMapping("/updateUser")    public void updateUser(String name){        User user = new User(name, new Date(), true);        IHelloService service = rpcProxy.create(IHelloService.class);        user = service.updateUser(user);        System.out.println(user.toString());    }    @RequestMapping("/test")    public void test(){        System.out.println("测试路径");    }}
SpringMVC_RPC_Client工程代理类RpcProxy
package com.zhihua.client;import java.lang.reflect.Method;import java.util.UUID;import com.zhihua.common.RpcRequest;import com.zhihua.common.RpcResponse;import net.sf.cglib.proxy.InvocationHandler;import net.sf.cglib.proxy.Proxy;public class RpcProxy {    private String serverAddress;    private ServiceDiscovery serviceDiscovery;    public RpcProxy(String serverAddress) {        this.serverAddress = serverAddress;    }    public RpcProxy(ServiceDiscovery serviceDiscovery) {        this.serviceDiscovery = serviceDiscovery;    }    @SuppressWarnings("unchecked")    public <T> T create(Class<?> interfaceClass) {        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(); // 创建并初始化 RPC 请求                        request.setRequestId(UUID.randomUUID().toString());                        request.setClassName(method.getDeclaringClass().getName());                        request.setMethodName(method.getName());                        request.setParameterTypes(method.getParameterTypes());                        request.setParameters(args);                        if (serviceDiscovery != null) {                            serverAddress = serviceDiscovery.discover(); // 发现服务                        }                        String[] array = serverAddress.split(":");                        String host = array[0];                        int port = Integer.parseInt(array[1]);                        RpcClient client = new RpcClient(host, port); // 初始化 RPC 客户端                        RpcResponse response = client.send(request); // 通过 RPC 客户端发送 RPC 请求并获取 RPC 响应                        if (response.getError() != null) {                            throw response.getError();                        } else {                            return response.getResult();                        }                    }                }                );    }}
SpringMVC_RPC_Client工程客户端类RpcClient
package com.zhihua.client;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import com.zhihua.common.RpcDecoder;import com.zhihua.common.RpcEncoder;import com.zhihua.common.RpcRequest;import com.zhihua.common.RpcResponse;import io.netty.bootstrap.Bootstrap;import io.netty.channel.ChannelFuture;import io.netty.channel.ChannelHandlerContext;import io.netty.channel.ChannelInitializer;import io.netty.channel.ChannelOption;import io.netty.channel.EventLoopGroup;import io.netty.channel.SimpleChannelInboundHandler;import io.netty.channel.nio.NioEventLoopGroup;import io.netty.channel.socket.SocketChannel;import io.netty.channel.socket.nio.NioSocketChannel;public class RpcClient extends SimpleChannelInboundHandler<RpcResponse>{    private static final Logger LOGGER = LoggerFactory.getLogger(RpcClient.class);    private String host;    private int port;    private RpcResponse response;    private final Object obj = new Object();    public RpcClient(String host, int port) {        this.host = host;        this.port = port;    }    @Override    public void channelRead0(ChannelHandlerContext ctx, RpcResponse response) throws Exception {        this.response = response;        synchronized (obj) {            obj.notifyAll(); // 收到响应,唤醒线程        }    }    @Override    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {        LOGGER.error("client caught exception", cause);        ctx.close();    }    public RpcResponse send(RpcRequest request) throws Exception {        EventLoopGroup group = new NioEventLoopGroup();        try {            Bootstrap bootstrap = new Bootstrap();            bootstrap.group(group).channel(NioSocketChannel.class)            .handler(new ChannelInitializer<SocketChannel>() {                @Override                public void initChannel(SocketChannel channel) throws Exception {                    channel.pipeline()                    .addLast(new RpcEncoder(RpcRequest.class)) // 将 RPC 请求进行编码(为了发送请求)                    .addLast(new RpcDecoder(RpcResponse.class)) // 将 RPC 响应进行解码(为了处理响应)                    .addLast(RpcClient.this); // 使用 RpcClient 发送 RPC 请求                }            })            .option(ChannelOption.SO_KEEPALIVE, true);            ChannelFuture future = bootstrap.connect(host, port).sync();            future.channel().writeAndFlush(request).sync();            synchronized (obj) {                obj.wait(); // 未收到响应,使线程等待            }            if (response != null) {                future.channel().closeFuture().sync();            }            return response;        } finally {            group.shutdownGracefully();        }    }}
SpringMVC_RPC_Client工程服务发现类ServiceDiscovery
package com.zhihua.client;import java.io.IOException;import java.util.ArrayList;import java.util.List;import java.util.concurrent.CountDownLatch;import org.apache.zookeeper.KeeperException;import org.apache.zookeeper.WatchedEvent;import org.apache.zookeeper.Watcher;import org.apache.zookeeper.ZooKeeper;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import com.zhihua.common.Constant;import io.netty.util.internal.ThreadLocalRandom;public class ServiceDiscovery {    private static final Logger LOGGER = LoggerFactory.getLogger(ServiceDiscovery.class);    private CountDownLatch latch = new CountDownLatch(1);    private volatile List<String> dataList = new ArrayList<>();    private String registryAddress;    public ServiceDiscovery(String registryAddress) {        this.registryAddress = registryAddress;        ZooKeeper zk = connectServer();        if (zk != null) {            watchNode(zk);        }    }    public String discover() {        String data = null;        int size = dataList.size();        if (size > 0) {            if (size == 1) {                data = dataList.get(0);                LOGGER.debug("using only data: {}", data);            } else {                data = dataList.get(ThreadLocalRandom.current().nextInt(size));                LOGGER.debug("using random data: {}", data);            }        }        return data;    }    private ZooKeeper connectServer() {        ZooKeeper zk = null;        try {            zk = new ZooKeeper(registryAddress, Constant.ZK_SESSION_TIMEOUT, new Watcher() {                @Override                public void process(WatchedEvent event) {                    if (event.getState() == Event.KeeperState.SyncConnected) {                        latch.countDown();                    }                }            });            latch.await();        } catch (IOException | InterruptedException e) {            LOGGER.error("", e);        }        return zk;    }    private void watchNode(final ZooKeeper zk) {        try {            List<String> nodeList = zk.getChildren(Constant.ZK_REGISTRY_PATH, new Watcher() {                @Override                public void process(WatchedEvent event) {                    if (event.getType() == Event.EventType.NodeChildrenChanged) {                        watchNode(zk);                    }                }            });            List<String> dataList = new ArrayList<>();            for (String node : nodeList) {                byte[] bytes = zk.getData(Constant.ZK_REGISTRY_PATH + "/" + node, false, null);                dataList.add(new String(bytes));            }            LOGGER.debug("node data: {}", dataList);            this.dataList = dataList;        } catch (KeeperException | InterruptedException e) {            LOGGER.error("", e);        }    }}
SpringMVC_RPC_Client工程配置文件config.properties
# ZooKeeper 服务器registry.address=127.0.0.1:2181
SpringMVC_RPC_Client工程 applicationContext.xml 配置文件
<?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"    xmlns:tx="http://www.springframework.org/schema/tx"    xmlns:aop="http://www.springframework.org/schema/aop"    xsi:schemaLocation="http://www.springframework.org/schema/beans     http://www.springframework.org/schema/beans/spring-beans.xsd        http://www.springframework.org/schema/tx         http://www.springframework.org/schema/tx/spring-tx-4.0.xsd        http://www.springframework.org/schema/context         http://www.springframework.org/schema/context/spring-context-4.0.xsd        http://www.springframework.org/schema/aop        http://www.springframework.org/schema/aop/spring-aop-4.0.xsd">    <!-- 自动扫描web包 ,将带有注解的类 纳入spring容器管理 -->    <context:component-scan base-package="com.zhihua.*"/>    <context:property-placeholder location="classpath:config.properties"/>      <context:annotation-config/>    <!-- 完成请求和注解POJO的映射 -->    <bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter" />     <!-- 配置服务发现组件 -->    <bean id="serviceDiscovery" class="com.zhihua.client.ServiceDiscovery">        <constructor-arg name="registryAddress" value="${registry.address}"/>    </bean>    <!-- 配置 RPC代理 -->    <bean id="rpcProxy" class="com.zhihua.client.RpcProxy">        <constructor-arg name="serviceDiscovery" ref="serviceDiscovery"/>    </bean></beans>

至此,客户端代码编写完毕,接下来就是测试了

注意:

  • 因为该例都是在本地上测试的,所以在测试的时候,一定要确认本地的 ZooKeeper 是打开的,否则实例是运行不成功的
  • 在运行SpringMVC_RPC_Client工程前,要先把其他工程打包,先执行SpringMVC_RPC_Service_Impl工程中的RpcBootstrap 的main方法,启动服务,然后在tomcat中部署SpringMVC_RPC_Client
  • 说一下我在执行测试过程中碰到的问题:在执行的时候,一直报错,找不到 IHelloService这个接口,可我已经在pom文件中引用了SpringMVC_RPC_Service工程依赖,引用本身也没有问题,百度也没出结果,最后我直接SpringMVC_RPC_Client添加了之前已经打包的SpringMVC_RPC_Service,就不再报错,测试通过

测试结果:

http://localhost:8090/SpringMVC_RPC_Client/hello?name=aaaaa

helloaaaaa

http://localhost:8090/SpringMVC_RPC_Client/getUsers?size=5

{“birthday”:1495094682365,”name”:”foo”,”sex”:true}{“birthday”:1495181082365,”name”:”foo”,”sex”:false}{“birthday”:1495267482365,”name”:”foo”,”sex”:true}{“birthday”:1495353882365,”name”:”foo”,”sex”:false}{“birthday”:1495440282365,”name”:”foo”,”sex”:true}

阅读全文
0 0
原创粉丝点击