基於netty、zookeeper手寫RPC框架之三——接入Spring

在接入前,先自定義一組傳輸對象,而不是原來傳輸一個Invocation,返回則是一個String,這裏需要用到netty的編解碼器,當然這裏可以用netty已經實現好的對象編解碼、第三方的Probuff編解碼器,而如果想要實現自己的傳輸協議,就要繼承netty的編解碼,他可以用來自定義解析對象以及封裝對象,這裏對對象處理採用本來是採用gson,但是有很多bug,便用了另一種進行序列化,這樣自定義協議的話,可以做很多其他的處理,比如協議的魔數、格式校驗等等,自定義協議的處理

定一個序列化接口

/**
 * @author: lele
 * @date: 2019/11/19 下午5:15
 * 對象-字節轉換接口
 */
public interface RpcSerializer {
    /**
     * 序列化
     * @param target
     * @return
     */
    byte[] serialize(Object target);

    /**
     * 反序列化
     * @param target
     * @param clazz
     * @param <T>
     * @return
     * @throws Exception
     */
    <T> T deserialize(byte[] target,Class<T> clazz) throws Exception;
}

具體的序列化實現類,需要引入protostuff-stuff,protostuff-runtime依賴

public class ProtobufSerializer  {
    private static Map<Class, Schema> schemaMap = new HashMap<Class, Schema>();

    // objenesis是一個小型Java類庫用來實例化一個特定class的對象。
    private static Objenesis objenesis = new ObjenesisStd(true);

    // 存儲模式對象映射
    private static Schema getSchema(Class cls) {
        Schema schema = schemaMap.get(cls);
        if (null == schema) {
            schema = RuntimeSchema.createFrom(cls);
            if (null != schema) {
                schemaMap.put(cls, schema);
            }
        }
        return schema;
    }

    public byte[] serialize(Object target) {
        LinkedBuffer buffer = LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE);
        Class cls = target.getClass();

        try {
            Schema schema = getSchema(cls);
            byte[] bytes = ProtobufIOUtil.toByteArray(target, schema, buffer);
            return bytes;
        } catch (Exception e) {
            throw new IllegalStateException(e);
        } finally {
            buffer.clear();
        }
    }

    public <T> T deserialize(byte[] target, Class<T> clazz) throws Exception {
        try {
            T instance = objenesis.newInstance(clazz);
            Schema schema = getSchema(clazz);
            ProtobufIOUtil.mergeFrom(target, instance, schema);
            return instance;
        } catch (Exception e) {
            throw new IllegalStateException(e);
        }
    }

//單例
    private static class Holder {
        private static final ProtobufSerializer j = new ProtobufSerializer();
    }

    public static ProtobufSerializer getInstance() {
        return ProtobufSerializer.Holder.j;
    }
}

然後定義請求/返回的對象,這裏定義的requestID用於標記該次請求,作用往後再談

/**
 * @author: lele
 * @date: 2019/11/15 下午7:01
 * 封裝調用方所想調用的遠程方法信息
 */
@Data
@AllArgsConstructor
public class RpcRequest  {

    private String requestId;

    private String interfaceName;

    private String methodName;

    private Object[] params;
    //防止重載
    private Class[] paramsTypes;
}



/**
 * @author: lele
 * @date: 2019/11/19 下午5:12
 */
@Data
public class RpcResponse  {
    private String requestId;

    private Object result;

    private String error;
}

定義處理對象的編解碼器

/**
 * @author: lele
 * @date: 2019/11/19 下午5:16
 * 把字節轉爲實體類,如byte->request供後續處理
 */
public class RpcDecoder extends ByteToMessageDecoder {
    //處理的對象
    private Class<?> target;

    private ProtobufSerializer serializer=ProtobufSerializer.getInstance();

    public RpcDecoder(Class<?> target) {
        this.target = target;
    }

    /**
     * byte轉實體
     */
    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf byteBuf, List<Object> list) throws Exception {
        System.out.println("收到字節");
        //如果小於一個int的長度,不作處理
        if (byteBuf.readableBytes() < 4) {
            return;
        }
        //獲取數據長度
        int dataLength = byteBuf.readInt();
        //寫入byte數組
        byte[] data = new byte[dataLength];
        byteBuf.readBytes(data);
        //解碼轉成對象
        Object res = serializer.deserialize(data, target);
        //給後面的handler處理
        list.add(res);

    }
}



/**
 * @author: lele
 * @date: 2019/11/19 下午5:16
 * 把實體變爲字節,可用於req->byte、response->byte
 */
public class RpcEncoder extends MessageToByteEncoder {
    //處理的對象
    private Class<?> entity;
    ProtobufSerializer serializer=ProtobufSerializer.getInstance();

    public RpcEncoder(Class<?> entity) {
        this.entity = entity;
    }
    @Override
    protected void encode(ChannelHandlerContext channelHandlerContext, Object o, ByteBuf byteBuf) throws Exception {
        System.out.println("轉成字節");
        if (entity.equals(o.getClass())) {
            byte[] data=serializer.serialize(o);
            //寫入消息長度,這裏還可以寫入版本號、魔數等協議信息
            byteBuf.writeInt(data.length);
            //寫入消息主體
            byteBuf.writeBytes(data);
        }
    }
}

這樣,傳輸對象及其序列化、相應的編解碼器就寫好了,把原來自帶的對象編解碼器替換掉即可

而以前那種{interfaceName:{url:具體實現類}的zk存儲方式不適合具體的業務處理,以接口爲粒度不方便管理,而且現有的設計是每請求一次,就會新建一個client,不能複用,造成資源浪費,然後client如果保持和以接口獲取的服務進行鏈接,當請求的接口時,client就無法複用,比如下圖,client可以與serverA保持鏈接後,訪問interfaceC,interfaceA都是可以的,如果調用interfaceB,就不能保持請求

但如果以client和對應服務來寫,則不會出現上述情況,也可以保持鏈接以複用

最後設計爲/服務名/url的格式,這次更改的結果是,相應的獲取可用地址、註冊服務的格式也要進行更改。

接下來就是接入spring了,有了上面的基礎,這時候我們可以仿feign的功能,把某一個包下帶有某個註解的接口注入spring中,同時爲這些接口生成代理,當執行這些接口的方法時,進行動態代理訪問遠端接口

定義作用在要代理的接口上,這裏的name爲服務名,zk則在註冊服務改爲——/服務名/ip,服務端通過傳來的接口名通過反射獲取類,或者通過給spring託管獲取其class(下面server端就是這樣處理)

/**
 * @author: lele
 * @date: 2019/11/20 下午2:44
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)

//用於接口上,name爲服務名,zk則在註冊服務改爲 服務名/ip,服務端通過傳來的接口名通過反射獲取類,或者通過給spring託管獲取其class
public @interface RpcStudyClient {

    String name();
}

流程爲:自定義一個可以動態註冊bean的類,然後通過獲取所要掃描的包,添加掃描條件,符合掃描條件之後,再複寫判斷是否爲接口,是的話對其進行註冊,註冊時通過對象工廠來定製化生成bean,這裏是加入了動態代理攔截其方法進行遠程調用


/**
 * @author: lele
 * @date: 2019/11/20 下午3:06
 * 第一個接口獲取註冊bean能力,第二個接口獲取類加載器,仿feignregister寫法
 */

public class RpcStudyClientRegisty implements ImportBeanDefinitionRegistrar, BeanClassLoaderAware {


    private ClassLoader classLoader;

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {


        if (DemoApplication.mode == 1) {
            //獲取指定路徑中註解bean定義掃描器
            ClassPathScanningCandidateComponentProvider scanner = getScanner();
            //獲取掃描的包,通過enable那個註解的屬性
            Set<String> basePackages = getBasePackages(importingClassMetadata);
            //添加過濾規則,屬於rpcstudyclient的加入,excludeFilter則是排除
            scanner.addIncludeFilter(new AnnotationTypeFilter(RpcStudyClient.class));

            Set<BeanDefinition> candidateBeans = new HashSet<>();
            //獲取符合條件的bean
            for (String basePackage : basePackages) {
                Set<BeanDefinition> candidateComponents = scanner.findCandidateComponents(basePackage);
                candidateBeans.addAll(candidateComponents);
            }
            //spring中用BeanDefintion來表示bean,這裏判斷bean類型是否合適,合適就註冊
            for (BeanDefinition candidateBean : candidateBeans) {
                //如果bean還沒有註冊
                if (!registry.containsBeanDefinition(candidateBean.getBeanClassName())) {
                    //判讀是否含有註解
                    if (candidateBean instanceof AnnotatedBeanDefinition) {
                        //存儲該類信息的bean,methodMetadata(方法),AnnotationMetadata(裏面也包括methodMetadata,可以獲取註解,類信息等等)
                        AnnotatedBeanDefinition annotatedBeanDefinition = (AnnotatedBeanDefinition) candidateBean;
                        //獲取bean的類信息
                        AnnotationMetadata annotationMetadata = annotatedBeanDefinition.getMetadata();
                        //判斷其否爲接口
                        Assert.isTrue(annotationMetadata.isInterface(), "@RpcStudeyClient註解只能用在接口上");
                        Map<String, Object> attributes = annotationMetadata.getAnnotationAttributes(RpcStudyClient.class.getCanonicalName());

                        this.registerRpcClient(registry, annotationMetadata, attributes);
                    }
                }
            }
        }

    }

    //註冊bean
    private void registerRpcClient(BeanDefinitionRegistry registry,
                                   AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {

        //獲取bean類名
        String className = annotationMetadata.getClassName();
        //使用自定義的對象工廠定製化生成bean
        BeanDefinitionBuilder definition = BeanDefinitionBuilder
                .genericBeanDefinition(RpcStudyClientFactoryBean.class);
        //設置根據類型的注入方式
        definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);

        definition.addPropertyValue("type", className);
        String name = attributes.get("name") == null ? "" : (String) (attributes.get("name"));

        String alias = name + "RpcStudyClient";
        //獲取bean基類
        AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
        //防止其他有實現,設置此實現爲首要
        beanDefinition.setPrimary(true);
        BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
                new String[]{alias});
        //註冊bean
        BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
    }


    //複寫bean掃描的判斷
    protected ClassPathScanningCandidateComponentProvider getScanner() {
        return new ClassPathScanningCandidateComponentProvider(false
        ) {
            @Override
            protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
                //存放註解相關信息,具備了class、註解的信息
                AnnotationMetadata metadata = beanDefinition.getMetadata();
                //是否是獨立能創建對象的,比如class、內部類、靜態內部類
                if (metadata.isIndependent()) {
                    //用於過濾註解爲@RpcClient的註解
                    if (metadata.isInterface() &&
                            metadata.getInterfaceNames().length == 1 &&
                            Annotation.class.getName().equals(metadata.getInterfaceNames()[0])) {
                        try {
                            Class<?> target = ClassUtils.forName(metadata.getClassName(),
                                    RpcStudyClientRegisty.this.classLoader);
                            return !target.isAnnotation();
                        } catch (Exception ex) {
                            this.logger.error(
                                    "Could not load target class: " + beanDefinition.getMetadata().getClassName(), ex);
                        }
                    }
                    return true;
                }
                return false;

            }
        };
    }

    @Override
    public void setBeanClassLoader(ClassLoader classLoader) {
        this.classLoader = classLoader;
    }

    //獲取需要掃描的包位置
    protected Set<String> getBasePackages(AnnotationMetadata importingClassMetadata) {
        Map<String, Object> attributes = importingClassMetadata
                .getAnnotationAttributes(EnableRpcStudyClient.class.getCanonicalName());
        String[] scanPackages = (String[]) attributes.get("basePackages");
        Set<String> basePackages = new HashSet<>();

        if (scanPackages.length > 0) {
            //掃描指定包
            for (String pkg : scanPackages) {
                if (StringUtils.hasText(pkg)) {
                    basePackages.add(pkg);
                }
            }
        } else {
            //掃描主入口所在的包
            basePackages.add(
                    ClassUtils.getPackageName(importingClassMetadata.getClassName()));
        }
        return basePackages;
    }

}

處理接口的FactoryBean,爲每個接口生成代理

/**
 * @author: lele
 * @date: 2019/11/20 下午3:08
 * bean工廠類,這裏爲接口代理其方法
 */

@Data
@EqualsAndHashCode(callSuper = false)
public class RpcStudyClientFactoryBean implements FactoryBean<Object> {
    private Class<?> type;
    @Override
    public Object getObject() throws Exception {
        return ProxyFactory.getProxy(this.type);
    }

    @Override
    public Class<?> getObjectType() {
        return this.type;
    }
}

把上面的註冊配置類注入到該註解

/**
 * @author: lele
 * @date: 2019/11/20 下午2:42
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import(RpcStudyClientRegisty.class)
public @interface EnableRpcStudyClient {
    //掃描的包,如果爲空,根據啓動類所在的包名掃描
    String[] basePackages() default {};
}

需要掃描的接口上添加RpcStudyClient註解

@RpcStudyClient(name="user")
public interface HelloService {
    String sayHello(String userName);
    String qq();
}

啓動類上添加@EnableRpcStudyClient註解指定要掃描的包,這樣就完成了client端的處理

然後到server端註冊具體的服務實現類,定義一個spring會掃描有該註解的類

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Component
public @interface RpcStudyService {
    // 用來指定實現的接口
    Class<?> value();
}

服務註冊類,核心是通過getBeanWithAnnonation來獲取相應的bean,然後存儲在map裏面,並且在初始化後註冊本實例的ip地址,netty的handler則通過這個map直接獲取實例來執行相應的方法

@Component
@Data
public class RpcStudyRegister implements InitializingBean,ApplicationContextAware {
    public static Map<String,Object> serviceMap;
    @Value("${spring.application.name}")
    private String name;

    @Value("${rpcstudy.port}")
    private Integer port;



    @Override
    public void afterPropertiesSet() throws Exception {
        if(DemoApplication.mode==0){
            String hostAddress = InetAddress.getLocalHost().getHostName();
            URL url=new URL(hostAddress,port);
            Protocol server= ProtocolFactory.netty();
            //註冊服務
            ZkRegister.register(name,url);
            server.start(url);
        }

    }

     @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        //把有RpcStudyService註解的bean添加到map裏面,key爲該註解的接口
        if(DemoApplication.mode==0){
            Map<String, Object> beans = applicationContext.getBeansWithAnnotation(RpcStudyService.class);
            if (beans != null && beans.size() > 0) {
                serviceMap = new HashMap<>(beans.size());
                for (Object o : beans.values()) {
                    RpcStudyService rpcService = o.getClass().getAnnotation(RpcStudyService.class);
                    String interfaceName = rpcService.value().getName();
                    serviceMap.put(interfaceName, o);
                }
                serviceMap = Collections.unmodifiableMap(serviceMap);

            }
        }

    }
}

這樣當本地的netty獲取class時,可以通過該map從傳來的request的interface爲參數來獲取相應的實現類

這裏由於沒有做模塊劃分,而server和client也需要分開,所以在啓動類上面定義一個mode變量,mode爲0時爲client端,只啓動tomcat服務,mode爲1時是server端只啓動netty,可以看到上面的client/server的register類在相應模式下才做處理,啓動client(手動改mode=1)和server(手動改mode=0),訪問localhost:8080/t2,得到下面的結果

client:

server:

項目地址:https://github.com/97lele/rpcstudy/tree/withspring

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章