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有幾個弊端:
- 生成的實體類太大,很占內(nèi)存,所以,我們最好只是用grpc的實體類來傳輸,但是在業(yè)務層,還是使用自己寫的實體類更方便一點
- 自動生成的同步方法和異步方法,都不夠好用,使用起來還是有一點點麻煩的
- grpc生成的類都是final類,不便修改和繼承
- 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)過程中,遇到了幾個問題:
如何解析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字段也要用上,
其它的小坑就略過不提了~~~如何生成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. 異步封裝
上面只是最基本的封裝而已,其實用處不是很大,存在問題:
- 實體類自動生成,雖然減輕了一點工作量,不用自己再寫那么多字段了,但是和Grpc生產(chǎn)的實體類之間的轉(zhuǎn)換還是問題
- 生成的接口是同步的,但是我們在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ù)類了。額,其實這里還有幾個坑,還沒有填,比如:
- 現(xiàn)在grpc和自定義的枚舉類是同一個類,這是有問題,還需要優(yōu)化
- 涉及到字段的類型也是自定義類的時候,這種簡單的轉(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)換的方法都是toGrpc和fromGrpc,保證統(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文件,一共生成了幾種文件
- 基本的解析類:Message、Enum、Service
- 異步方式的類:WebService、WebServiceImpl、WebServiceTest
- 兼容GRPC的類:GrpcMapper、GRPC數(shù)據(jù)模擬類、GRPC請求模擬類
從上面我們可以看到,代碼自動生成還是很有用的,尤其是配合我們自己定義的框架和規(guī)范來的時候,就更強大的。雖然我們有泛型和反射,可以實現(xiàn)一定的動態(tài)性,但是,在實際編寫代碼的過程中,還是有很多的模式代碼的,想要學會偷懶的話,一定得能從日常的開發(fā)中,發(fā)現(xiàn)模式,然后利用模式來簡化日常的開發(fā)。泛型是一種運行時的模式,代碼自動生成則是一種運行前的模式。
好了,案例篇介紹完畢,敬請期待下一篇:原理篇:利用ProtoBuf文件,一鍵生成Java代碼,看看我們究竟是如何解析ProtoBuf文件,又是如何生成Java代碼的。