案例篇:利用ProtoBuf文件,一鍵生成Java代碼

1.背景

在我們?nèi)粘5拈_發(fā)中,前后端之間的接口聯(lián)調(diào)很麻煩,經(jīng)常出現(xiàn)后端加了字段,前端還不知道,所謂接口文檔,經(jīng)常和代碼是不同步的。

好在現(xiàn)在有了grpc,它可以定義好ProtoBuf接口文件后,自動生成代碼。原理就是RPC,在客戶端生成一個遠程服務的代理,可以像訪問訪問本地方法一樣,訪問遠程方法。

但是,在客戶端使用grpc的實踐中,我們發(fā)現(xiàn)了grpc有幾個弊端:

  1. 生成的實體類太大,很占內(nèi)存,所以,我們最好只是用grpc的實體類來傳輸,但是在業(yè)務層,還是使用自己寫的實體類更方便一點
  2. 自動生成的同步方法和異步方法,都不夠好用,使用起來還是有一點點麻煩的
  3. grpc生成的類都是final類,不便修改和繼承
  4. grpc生成的類沒有實現(xiàn)Parcelable和Serializable接口,沒法通過Intent傳遞,這在不同頁面之間傳遞數(shù)據(jù)比較麻煩

為了解決grpc的弊端,我們最好在業(yè)務層自己封裝一套實體類,grpc實體類只負責傳輸層,但是這又帶來了一個新的問題:兩套實體類之間互相轉(zhuǎn)換的問題。

在我們的應用中,實體類的數(shù)量已經(jīng)有接近800個,服務也有100多個。因為歷史遺留問題,現(xiàn)在這些grpc的類還是廣泛存在于業(yè)務層,遷移的話,非常麻煩。

經(jīng)過一段時間的研究,我終于想到了一個方案:利用ProtoBuf接口文件,一鍵生成Java代碼。

實現(xiàn)過程中,遇到了幾個問題:

  1. 如何解析ProtoBuf文件?
    因為我主要是玩Java的,所以還是想用Java代碼來解析,但是在網(wǎng)上找了半天,好像都沒有直接用Java解析ProtoBuf的方案。好在有一個替代方案,先將proto文件編譯成desc文件,然后再來解析desc文件。
    編譯的話,就用Java調(diào)用一個cmd指令就好了
    解析的話,就有不少坑了:
    第1坑:List<int>里的int需要額外處理
    第2坑:map字段需要額外處理
    第3坑:message嵌套message需要額外處理
    第4坑:類名重復問題,因為我之前都是直接生成到一個包里面的,這就難免重復了,所以protobuf里面定義的package字段也要用上,
    其它的小坑就略過不提了~~~

  2. 如何生成Java代碼?
    這肯定要用到模板引擎來做代碼自動生成了,這里我使用的是FreeMaker。因為之前沒玩過它,在寫ftl模板文件時,經(jīng)常運行失敗。后來才知道這個坑:FreeMaker的數(shù)據(jù)模型必須有g(shù)et方法,哪怕你的字段是public的也必須要有。
    了解了FreeMaker的語法之后,再來寫的話,就方便多了,后來而遇到了一個坑:Class里面嵌套Class的問題。這種內(nèi)部類該怎么生成?這就需要用到FreeMaker的宏來做遞歸了。

2. 案例

ProtoBuf定義

文件名:user.proto,這里僅是一個示例,其實這個定義文件也不規(guī)范,更好的定義規(guī)范,還需要實踐探索,和自己的項目結(jié)合

syntax = "proto3";
package crm.usercenter;

message UserMessage {
    string name = 1;
    int32 id = 2;
    string message = 3;
    MessageType type=4;
}

message FindUserMessageByIdReq {
    int32 id = 1;
}

message Result {
    string msg = 4;
    int32 code = 5;
}

enum MessageType {
    SYSTEM = 0;
    CUSTOMER = 1;
    OTHER = 2;
}

service UserPublic {
    rpc FindUserMessageById (FindUserMessageByIdReq) returns (UserMessage);
    rpc AddUserMessage (UserMessage) returns (Result);
}

生成的實體類

這里就只貼一個message了,其它類似

package dest.bean.crm.usercenter;

import dest.bean.crm.usercenter.MessageType;

public class UserMessage {
    public String name;
    public int id;
    public String message;
    public MessageType type;

}

生成的枚舉類

package dest.bean.crm.usercenter;

public enum MessageType {
    SYSTEM,
    CUSTOMER,
    OTHER,
}

生成的接口類

package dest.bean.crm.usercenter;

import dest.bean.crm.usercenter.FindUserMessageByIdReq;
import dest.bean.crm.usercenter.UserMessage;
import dest.bean.crm.usercenter.Result;

public interface UserPublic {

    UserMessage findUserMessageById(FindUserMessageByIdReq findUserMessageByIdReq);

    Result addUserMessage(UserMessage userMessage);

}

3. 異步封裝

上面只是最基本的封裝而已,其實用處不是很大,存在問題:

  1. 實體類自動生成,雖然減輕了一點工作量,不用自己再寫那么多字段了,但是和Grpc生產(chǎn)的實體類之間的轉(zhuǎn)換還是問題
  2. 生成的接口是同步的,但是我們在Android端用的話,肯定是要異步的接口的

下面,好戲開始上演了~~~

生成的異步接口

這里的異步接口,涉及到了我自己封裝的網(wǎng)絡層框架的幾個接口了,
ResourceObserver是基于觀察者模式實現(xiàn)的,類似于一個Rx的Disposable,用于取消網(wǎng)絡請求,需要注冊到ResourceSubject上。我們最好是將BaseActivity和BaseFragment都實現(xiàn)ResourceSubject接口,然后在退出Activity就可以自動取消網(wǎng)絡請求了。

然后Consumer是我自己封裝的函數(shù)式接口,之所以不用Rx和Java8的,主要是這種接口定義本來很簡單,沒必要過度依賴第三方,萬一哪一天,我們不想用Rx了的話,用到這個接口的地方都得改,很蛋疼的。類似的我還封裝了Mapper、Provider等幾個函數(shù)式接口。

package dest.bean.crm.usercenter;

import com.ezbuy.functions.Consumer;
import com.ezbuy.web.ResourceObserver;

import dest.bean.crm.usercenter.FindUserMessageByIdReq;
import dest.bean.crm.usercenter.UserMessage;
import dest.bean.crm.usercenter.Result;

public interface UserPublicWebService {

    ResourceObserver findUserMessageById(FindUserMessageByIdReq findUserMessageByIdReq, Consumer<UserMessage> consumer);

    ResourceObserver addUserMessage(UserMessage userMessage, Consumer<Result> consumer);

}

生成異步接口的實現(xiàn)類

這里的異步接口的實現(xiàn)類是Rx的方式,返回的觀察者是RxResourceObserver。但是實際上,我們完全可以將這里改成任何自己想要的方式。比如我們應用里面有GRPC和RPC兩種方式,我們完全可以定義另外兩種ResourceObserver。

package dest.bean.crm.usercenter;

import com.ezbuy.functions.Consumer;
import com.ezbuy.functions.Mapper;
import com.ezbuy.web.ResourceObserver;
import com.ezbuy.web.helper.RxExecuter;
import com.ezbuy.web.observer.RxResourceObserver;
import io.reactivex.disposables.Disposable;

public class UserPublicWebServiceImpl implements UserPublicWebService {

    @Override
    public ResourceObserver findUserMessageById(FindUserMessageByIdReq req, Consumer<UserMessage> consumer) {
        Disposable disposable = RxExecuter.execute(req, consumer, new Mapper<FindUserMessageByIdReq, UserMessage>() {
            @Override
            public UserMessage map(FindUserMessageByIdReq req) {
                UserOuterClass.UserMessage resp = UserPublicGrpc.newBlockingStub(null).findUserMessageById(new FindUserMessageByIdReqMapper().toGrpc(req));
                return new UserMessageMapper().fromGrpc(resp);
            }
        });
        return new RxResourceObserver(disposable);
    }
    
    @Override
    public ResourceObserver addUserMessage(UserMessage req, Consumer<Result> consumer) {
        Disposable disposable = RxExecuter.execute(req, consumer, new Mapper<UserMessage, Result>() {
            @Override
            public Result map(UserMessage req) {
                UserOuterClass.Result resp = UserPublicGrpc.newBlockingStub(null).addUserMessage(new UserMessageMapper().toGrpc(req));
                return new ResultMapper().fromGrpc(resp);
            }
        });
        return new RxResourceObserver(disposable);
    }
    
}

這是返回的RxResourceObserver,這里為了強行和Observer的官方接口保證一致,把cancel()命名為update()其實不好,以后再改回來吧。

package com.ezbuy.web.observer;

import com.ezbuy.web.ResourceObserver;
import com.ezbuy.web.ResourceSubject;
import io.reactivex.disposables.Disposable;

public class RxResourceObserver implements ResourceObserver {

    private Disposable disposable;

    public RxResourceObserver(Disposable disposable) {
        this.disposable = disposable;
    }

    @Override
    public void update() {
        System.out.println("請求反注冊:" + disposable);
        if (isRelease()) {
            System.out.println("請求已取消,無須反注冊");
        } else {
            System.out.println("請求未取消,執(zhí)行反注冊");
            disposable.dispose();
        }

    }

    @Override
    public void register(ResourceSubject manager) {
        System.out.println("請求注冊:" + disposable);
        manager.attach(this);
    }

    @Override
    public boolean isRelease() {
        return disposable.isDisposed();
    }
}

這里是我們定義的其它的ResourceObserver,只以GRPC為例子吧,RPC的類似

package com.ezbuy.web.observer;

import com.daigou.model.grpc.GrpcRequest;
import com.ezbuy.web.ResourceSubject;

public class GrpcRequestObserver implements ResourceObserver {

    private GrpcRequest request;

    public GrpcRequestObserver(GrpcRequest request) {
        this.request = request;
    }

    @Override
    public void update() {
        request.cancel();
    }

    @Override
    public void register(ResourceSubject subject) {
        subject.attach(this);
    }

    @Override
    public boolean isRelease() {
        return request.isCanceled();
    }

}

生成的異步接口的測試類

為了方便測試接口通沒通,我這里會自動根據(jù)protobuf生成測試類,每個接口方法都有

package dest.bean.crm.usercenter;

import com.ezbuy.functions.Consumer;
import com.ezbuy.web.ResourceSubject;
import com.ezbuy.web.ServiceFactory;
import com.ezbuy.web.observer.ResourceSubjectImpl;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import java.util.concurrent.TimeUnit;

public class UserPublicWebServiceTest {

    @Before
    public void setUp() throws Exception {
        //注冊服務,建議放在Application,UserPublicWebServiceImpl的實例會在需要時才初始化,并有弱引用的緩存,防止內(nèi)存泄露
        ServiceFactory.registerService(UserPublicWebService.class, UserPublicWebServiceImpl.class);
    }

    @After
    public void tearDown() throws Exception {
        //反注冊服務
        ServiceFactory.unRegisterService(UserPublicWebService.class);
    }

    @Test
    public void testFindUserMessageById() throws Exception {
        System.out.println("開始執(zhí)行:testFindUserMessageById");
        long time = System.currentTimeMillis();
        //模擬Activity,我們最好在BaseActivity和BaseFragment實現(xiàn)此接口
        ResourceSubject activity = new ResourceSubjectImpl();
        //模擬網(wǎng)絡請求
        FindUserMessageByIdReq req = new FindUserMessageByIdReq();
        ServiceFactory.getService(UserPublicWebService.class).findUserMessageById(req, new Consumer<UserMessage>() {
            @Override
            public void consume(UserMessage resp) {
                System.out.println("請求成功:" + resp);
            }
        }).register(activity);
        //其實不register也可以,但是那就不會自動取消了,回來時如果Activity退出了,Consumer的回調(diào)里面可能拋出空指針異常
        //模擬3秒之后,退出Activity
        TimeUnit.SECONDS.sleep(3);
        System.out.println("退出Activity");
        activity.notifyAllObservers();
        System.out.println("結(jié)束執(zhí)行:testFindUserMessageById" + ",耗時:" + (System.currentTimeMillis() - time));
    }

    @Test
    public void testAddUserMessage() throws Exception {
        System.out.println("開始執(zhí)行:testAddUserMessage");
        long time = System.currentTimeMillis();
        //模擬Activity,我們最好在BaseActivity和BaseFragment實現(xiàn)此接口
        ResourceSubject activity = new ResourceSubjectImpl();
        //模擬網(wǎng)絡請求
        UserMessage req = new UserMessage();
        ServiceFactory.getService(UserPublicWebService.class).addUserMessage(req, new Consumer<Result>() {
            @Override
            public void consume(Result resp) {
                System.out.println("請求成功:" + resp);
            }
        }).register(activity);
        //其實不register也可以,但是那就不會自動取消了,回來時如果Activity退出了,Consumer的回調(diào)里面可能拋出空指針異常
        //模擬3秒之后,退出Activity
        TimeUnit.SECONDS.sleep(3);
        System.out.println("退出Activity");
        activity.notifyAllObservers();
        System.out.println("結(jié)束執(zhí)行:testAddUserMessage" + ",耗時:" + (System.currentTimeMillis() - time));
    }

}

testFindUserMessageById()方法為例,我們可以看看輸出,基本是和預期一致的。

registerService:dest.bean.crm.usercenter.UserPublicWebService
開始執(zhí)行:testFindUserMessageById
請求注冊:io.reactivex.internal.operators.observable.ObservableMap$MapObserver@52af6cff
請求成功:dest.bean.crm.usercenter.UserMessage@37670c36
退出Activity
請求反注冊:DISPOSED
請求已取消,無須反注冊
結(jié)束執(zhí)行:testFindUserMessageById,耗時:3365
unRegisterService:dest.bean.crm.usercenter.UserPublicWebService

test里面的ServiceFactory.getService(UserPublicWebService.class)是根據(jù)工廠模式,自己寫的一個簡單的服務工廠類,我們只需要將每一個接口類和實現(xiàn)類一一注冊到這個工廠類就好了,使用它的好處就是,我可以很方便切換一個服務的具體實現(xiàn)方式,只需要改一下注冊類就好了,具體使用的地方都不用動。

package com.ezbuy.web;


import com.ezbuy.web.error.ErrorConsumer;
import com.ezbuy.web.error.ErrorConsumerImpl;
import com.ezbuy.web.impl.CartServiceRxImpl;

import java.lang.ref.WeakReference;
import java.util.HashMap;
import java.util.Map;

/**
 * 服務管理類
 *
 * @author Yutianran
 */
public class ServiceFactory {

    //服務類 -> 實現(xiàn)類
    private static Map<Class<?>, Class<?>> router = new HashMap<>();

    //服務類 -> 實現(xiàn)對象的弱引用,資源不足時,GC會主動回收對象
    private static Map<Class<?>, WeakReference<Object>> implMap = new HashMap<>();

    //在這里靜態(tài)注冊所有服務
    static {
        router.put(CartService.class, CartServiceRxImpl.class);
        router.put(ErrorConsumer.class, ErrorConsumerImpl.class);
    }

    //在這里動態(tài)注冊服務,不直接注冊實現(xiàn)類的對象,是為了懶加載,在需要時再創(chuàng)建
    public static void registerService(Class<?> service, Class<?> impl) {
        System.out.println("registerService:"+service.getName());
        router.put(service, impl);
    }

    //反注冊服務
    public static void unRegisterService(Class<?> service) {
        System.out.println("unRegisterService:"+service.getName());
        router.remove(service);
    }

    public static <T> T getService(Class<T> serverClass) {
        WeakReference<Object> reference = implMap.get(serverClass);
        Object obj = null;
        //緩存沒有被清除掉
        if (reference != null) {
            obj = reference.get();
            if(obj!=null){
                return (T) obj;
            }
        }
        //緩存被清除掉了,或者是還沒有創(chuàng)建緩存
        try {
            if (obj == null) {
                Class<?> implClass = router.get(serverClass);
                obj = implClass.newInstance();
                implMap.put(serverClass, new WeakReference<>(obj));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return (T) obj;
    }

}

4. 兼容GRPC

上面其實故意漏了一個,就是具體是怎么將request變成response的呢,其實就在是異步接口實現(xiàn)類的map操作里面做的。

Disposable disposable = RxExecuter.execute(req, consumer, new Mapper<FindUserMessageByIdReq, UserMessage>() {
    @Override
    public UserMessage map(FindUserMessageByIdReq req) {
        UserOuterClass.UserMessage resp = UserPublicGrpc.newBlockingStub(null).findUserMessageById(new FindUserMessageByIdReqMapper().toGrpc(req));
        return new UserMessageMapper().fromGrpc(resp);
    }
});

這里涉及到了一個RxExecuter.execute,這個只是封裝了Rx的subscribeOn和observeOn這些操作的工具類而已,等下會貼,現(xiàn)在我們的重點是:

UserOuterClass.UserMessage resp = UserPublicGrpc.newBlockingStub(null).findUserMessageById(new FindUserMessageByIdReqMapper().toGrpc(req));

這個才是test的時候?qū)崿F(xiàn)request -> response的核心。

涉及到了兩個類:UserOuterClass數(shù)據(jù)類、UserPublicGrpc請求執(zhí)行類

生成的GRPC數(shù)據(jù)類-模擬

其實,這是只是模擬的GRPC數(shù)據(jù)類,真正的GRPC數(shù)據(jù)類是由protobuf-gradle-plugin插件自動生成的,但是因為我這個電腦沒安裝,就只好先用我自己生成的類來模擬了。

package dest.bean.crm.usercenter;

public class UserOuterClass {

    public static class UserMessage {
        public String name;

        public String getName() {
            return name;
        }
        public int id;

        public int getId() {
            return id;
        }
        public String message;

        public String getMessage() {
            return message;
        }
        public MessageType type;

        public MessageType getType() {
            return type;
        }

        private UserMessage(Builder builder) {
            name = builder.name;
            id = builder.id;
            message = builder.message;
            type = builder.type;
        }

        public static Builder newBuilder() {
            return new Builder();
        }

        public static final class Builder {
            private String name;
            private int id;
            private String message;
            private MessageType type;

            public Builder() {
            }

            public Builder setName(String name) {
                this.name = name;
                return this;
            }
            public Builder setId(int id) {
                this.id = id;
                return this;
            }
            public Builder setMessage(String message) {
                this.message = message;
                return this;
            }
            public Builder setType(MessageType type) {
                this.type = type;
                return this;
            }

            public UserMessage build() {
                return new UserMessage(this);
            }
        }
    }
    public static class FindUserMessageByIdReq {
        public int id;

        public int getId() {
            return id;
        }

        private FindUserMessageByIdReq(Builder builder) {
            id = builder.id;
        }

        public static Builder newBuilder() {
            return new Builder();
        }

        public static final class Builder {
            private int id;

            public Builder() {
            }

            public Builder setId(int id) {
                this.id = id;
                return this;
            }

            public FindUserMessageByIdReq build() {
                return new FindUserMessageByIdReq(this);
            }
        }
    }
    public static class Result {
        public String msg;

        public String getMsg() {
            return msg;
        }
        public int code;

        public int getCode() {
            return code;
        }

        private Result(Builder builder) {
            msg = builder.msg;
            code = builder.code;
        }

        public static Builder newBuilder() {
            return new Builder();
        }

        public static final class Builder {
            private String msg;
            private int code;

            public Builder() {
            }

            public Builder setMsg(String msg) {
                this.msg = msg;
                return this;
            }
            public Builder setCode(int code) {
                this.code = code;
                return this;
            }

            public Result build() {
                return new Result(this);
            }
        }
    }
}

這個模擬類和真實的GRPC類一樣,都是用建造者模式來創(chuàng)建對象的。怎么說呢,建造者模式,有好用的地方,也有不好用的地方,好用在于控制對象的創(chuàng)建過程,不好用在于有的時候,只有一兩個字段的時候,其實直接new可能更方便,而且,grpc的建造者模式,修改數(shù)據(jù)真的有點蛋疼。

生成的GRPC請求執(zhí)行類-模擬

這個類同樣也只是模擬類,真正的類是由protobuf-gradle-plugin插件自動生成。grpc的插件其實也就主要為我們自動生成了這兩個類而已。

package dest.bean.crm.usercenter;

public class UserPublicGrpc {

    public static UserPublicGrpc newBlockingStub(Object channel) {
        return new UserPublicGrpc();
    }

    public UserOuterClass.UserMessage findUserMessageById(UserOuterClass.FindUserMessageByIdReq findUserMessageByIdReq) {
        return UserOuterClass.UserMessage.newBuilder().build();
    }

    public UserOuterClass.Result addUserMessage(UserOuterClass.UserMessage userMessage) {
        return UserOuterClass.Result.newBuilder().build();
    }

}

生成的GRPC數(shù)據(jù)轉(zhuǎn)換類

這個類不是模擬類,grpc不會給我們生成這個類,這個類很重要,功能也很簡單,就是實現(xiàn)GRPC數(shù)據(jù)類和自定義實體類之間的互相轉(zhuǎn)換。

有了這個類,我們就真的可以實現(xiàn),在業(yè)務層用自定義實體類,在傳輸層用GRPC數(shù)據(jù)類了。額,其實這里還有幾個坑,還沒有填,比如:

  1. 現(xiàn)在grpc和自定義的枚舉類是同一個類,這是有問題,還需要優(yōu)化
  2. 涉及到字段的類型也是自定義類的時候,這種簡單的轉(zhuǎn)換方式就有問題了,需要將字段的值也用它的類的Mapper轉(zhuǎn)換以便才行,這樣的話,我們自動生成的機制就更復雜了,后續(xù)優(yōu)化?。。?/li>
package dest.bean.crm.usercenter;

import com.ezbuy.web.GrpcMapper;

public class UserMessageMapper implements GrpcMapper<UserOuterClass.UserMessage, UserMessage> {

    @Override
    public UserOuterClass.UserMessage toGrpc(UserMessage entity) {
        return UserOuterClass.UserMessage.newBuilder()
                .setName(entity.name)
                .setId(entity.id)
                .setMessage(entity.message)
                .setType(entity.type)
                .build();
    }

    @Override
    public UserMessage fromGrpc(UserOuterClass.UserMessage grpc) {
        UserMessage entity = new UserMessage();
        entity.name=grpc.getName();
        entity.id=grpc.getId();
        entity.message=grpc.getMessage();
        entity.type=grpc.getType();
        return entity;
    }
}

這個自動生成的類,實現(xiàn)了我定義的一個接口:GrpcMapper,其實這個接口的作用主要就是一個規(guī)范,保證所有的數(shù)據(jù)轉(zhuǎn)換的方法都是toGrpcfromGrpc,保證統(tǒng)一。同時也方便后期拓展。

package com.ezbuy.web;

public interface GrpcMapper<Grpc,Entity> {

    Grpc toGrpc(Entity entity);

    Entity fromGrpc(Grpc grpc);
}

之前提到的RxExecuter輔助類,我這里屏蔽掉了AndroidSchedulers.main(),是為了方便在測試時能跑,后續(xù)得想個辦法測試和正式時,自動決定要不要切換回主線程

package com.ezbuy.web.helper;

import com.ezbuy.functions.Consumer;
import com.ezbuy.functions.Mapper;
import com.ezbuy.web.ServiceFactory;
import com.ezbuy.web.error.BaseConsumer;
import com.ezbuy.web.error.ErrorConsumer;
import io.reactivex.Observable;
import io.reactivex.annotations.NonNull;
import io.reactivex.disposables.Disposable;
import io.reactivex.functions.Function;
import io.reactivex.schedulers.Schedulers;

/**
 * yutianran 2018/12/1 下午12:15
 */
public class RxExecuter {

    public static <Request, Response> Disposable execute(Request request, final Consumer<Response> consumer, Mapper<Request, Response> mapper) {
        return Observable.just(request)
                .subscribeOn(Schedulers.io())
                .map(new Function<Request, Response>() {
                    @Override
                    public Response apply(@NonNull Request request) throws Exception {
                        return mapper.map(request);
                    }
                })
                //.observeOn(AndroidSchedulers.main())
                .subscribe(new io.reactivex.functions.Consumer<Response>() {
                    @Override
                    public void accept(Response response) throws Exception {
                        //這里將rx的Consumer接口,轉(zhuǎn)換成我們自己的Consumer接口,是因為我們的Consumer接口可以兼容RpcRequest和GrpcRequest的請求方式
                        if (consumer != null) {
                            consumer.consume(response);
                        }
                    }
                }, new io.reactivex.functions.Consumer<Throwable>() {
                    @Override
                    public void accept(Throwable throwable) throws Exception {
                        //如果有處理onError的BaseConsumer,就用BaseConsumer的onError處理
                        if (consumer != null && consumer instanceof BaseConsumer) {
                            System.out.println("自定義錯誤處理:" + throwable.getMessage());
                            BaseConsumer baseConsumer = (BaseConsumer) consumer;
                            baseConsumer.onError(throwable);
                            return;
                        }
                        //否則,就用注冊在公共服務里面的ErrorConsumer的實現(xiàn)類處理
                        System.out.println("公共錯誤處理:" + throwable.getMessage());
                        ServiceFactory.getService(ErrorConsumer.class).onError(throwable);
                    }
                });
    }
}

總結(jié)

好了,總結(jié)一下,我們利用ProtoBuf文件,一共生成了幾種文件

  1. 基本的解析類:Message、Enum、Service
  2. 異步方式的類:WebService、WebServiceImpl、WebServiceTest
  3. 兼容GRPC的類:GrpcMapper、GRPC數(shù)據(jù)模擬類、GRPC請求模擬類

從上面我們可以看到,代碼自動生成還是很有用的,尤其是配合我們自己定義的框架和規(guī)范來的時候,就更強大的。雖然我們有泛型和反射,可以實現(xiàn)一定的動態(tài)性,但是,在實際編寫代碼的過程中,還是有很多的模式代碼的,想要學會偷懶的話,一定得能從日常的開發(fā)中,發(fā)現(xiàn)模式,然后利用模式來簡化日常的開發(fā)。泛型是一種運行時的模式,代碼自動生成則是一種運行前的模式。

好了,案例篇介紹完畢,敬請期待下一篇:原理篇:利用ProtoBuf文件,一鍵生成Java代碼,看看我們究竟是如何解析ProtoBuf文件,又是如何生成Java代碼的。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內(nèi)容