面试题:DUBBO源码使用了哪些设计模式
0 文章概述
DUBBO作為RPC領域優秀開源的框架在業界十分流行,本文我們閱讀其源碼并對其使用到的設計模式進行分析。需要說明的是本文所說的設計模式更加廣義,不僅包括標準意義上23種設計模式,還有一些常見經過檢驗的代碼模式例如雙重檢查鎖模式、多線程保護性暫停模式等等。
1 模板方法
模板方法模式定義一個操作中的算法骨架,一般使用抽象類定義算法骨架。抽象類同時定義一些抽象方法,這些抽象方法延遲到子類實現,這樣子類不僅遵守了算法骨架約定,也實現了自己的算法。既保證了規約也兼顧靈活性。這就是用抽象構建框架,用實現擴展細節。
DUBBO源碼中有一個非常重要的核心概念Invoker,我們可以理解為執行器或者說一個可執行對象,能夠根據方法的名稱、參數得到相應執行結果,這個特性體現了代理模式我們后面章節再說,本章節我們先分析其中的模板方法模式。
public?abstract?class?AbstractInvoker<T>?implements?Invoker<T>?{@Overridepublic?Result?invoke(Invocation?inv)?throws?RpcException?{RpcInvocation?invocation?=?(RpcInvocation)?inv;invocation.setInvoker(this);if?(attachment?!=?null?&&?attachment.size()?>?0)?{invocation.addAttachmentsIfAbsent(attachment);}Map<String,?String>?contextAttachments?=?RpcContext.getContext().getAttachments();if?(contextAttachments?!=?null?&&?contextAttachments.size()?!=?0)?{invocation.addAttachments(contextAttachments);}if?(getUrl().getMethodParameter(invocation.getMethodName(),?Constants.ASYNC_KEY,?false))?{invocation.setAttachment(Constants.ASYNC_KEY,?Boolean.TRUE.toString());}RpcUtils.attachInvocationIdIfAsync(getUrl(),?invocation);try?{return?doInvoke(invocation);}?catch?(InvocationTargetException?e)?{Throwable?te?=?e.getTargetException();if?(te?==?null)?{return?new?RpcResult(e);}?else?{if?(te?instanceof?RpcException)?{((RpcException)?te).setCode(RpcException.BIZ_EXCEPTION);}return?new?RpcResult(te);}}?catch?(RpcException?e)?{if?(e.isBiz())?{return?new?RpcResult(e);}?else?{throw?e;}}?catch?(Throwable?e)?{return?new?RpcResult(e);}}protected?abstract?Result?doInvoke(Invocation?invocation)?throws?Throwable; }AbstractInvoker作為抽象父類定義了invoke方法這個方法骨架,并且定義了doInvoke抽象方法供子類擴展,例如子類InjvmInvoker、DubboInvoker各自實現了doInvoke方法。
InjvmInvoker是本地引用,調用時直接從本地暴露生產者容器獲取生產者Exporter對象即可。
class?InjvmInvoker<T>?extends?AbstractInvoker<T>?{@Overridepublic?Result?doInvoke(Invocation?invocation)?throws?Throwable?{Exporter<?>?exporter?=?InjvmProtocol.getExporter(exporterMap,?getUrl());if?(exporter?==?null)?{throw?new?RpcException("Service?["?+?key?+?"]?not?found.");}RpcContext.getContext().setRemoteAddress(Constants.LOCALHOST_VALUE,?0);return?exporter.getInvoker().invoke(invocation);} }DubboInvoker相對復雜一些,需要考慮同步異步調用方式,配置優先級,遠程通信等等。
public?class?DubboInvoker<T>?extends?AbstractInvoker<T>?{@Overrideprotected?Result?doInvoke(final?Invocation?invocation)?throws?Throwable?{RpcInvocation?inv?=?(RpcInvocation)?invocation;final?String?methodName?=?RpcUtils.getMethodName(invocation);inv.setAttachment(Constants.PATH_KEY,?getUrl().getPath());inv.setAttachment(Constants.VERSION_KEY,?version);ExchangeClient?currentClient;if?(clients.length?==?1)?{currentClient?=?clients[0];}?else?{currentClient?=?clients[index.getAndIncrement()?%?clients.length];}try?{boolean?isAsync?=?RpcUtils.isAsync(getUrl(),?invocation);boolean?isAsyncFuture?=?RpcUtils.isReturnTypeFuture(inv);boolean?isOneway?=?RpcUtils.isOneway(getUrl(),?invocation);//?超時時間方法級別配置優先級最高int?timeout?=?getUrl().getMethodParameter(methodName,?Constants.TIMEOUT_KEY,?Constants.DEFAULT_TIMEOUT);if?(isOneway)?{boolean?isSent?=?getUrl().getMethodParameter(methodName,?Constants.SENT_KEY,?false);currentClient.send(inv,?isSent);RpcContext.getContext().setFuture(null);return?new?RpcResult();}?else?if?(isAsync)?{ResponseFuture?future?=?currentClient.request(inv,?timeout);FutureAdapter<Object>?futureAdapter?=?new?FutureAdapter<>(future);RpcContext.getContext().setFuture(futureAdapter);Result?result;if?(isAsyncFuture)?{result?=?new?AsyncRpcResult(futureAdapter,?futureAdapter.getResultFuture(),?false);}?else?{result?=?new?SimpleAsyncRpcResult(futureAdapter,?futureAdapter.getResultFuture(),?false);}return?result;}?else?{RpcContext.getContext().setFuture(null);return?(Result)?currentClient.request(inv,?timeout).get();}}?catch?(TimeoutException?e)?{throw?new?RpcException(RpcException.TIMEOUT_EXCEPTION,?"Invoke?remote?method?timeout.?method:?"?+?invocation.getMethodName()?+?",?provider:?"?+?getUrl()?+?",?cause:?"?+?e.getMessage(),?e);}?catch?(RemotingException?e)?{throw?new?RpcException(RpcException.NETWORK_EXCEPTION,?"Failed?to?invoke?remote?method:?"?+?invocation.getMethodName()?+?",?provider:?"?+?getUrl()?+?",?cause:?"?+?e.getMessage(),?e);}} }2 動態代理
代理模式核心是為一個目標對象提供一個代理,以控制對這個對象的訪問,我們可以通過代理對象訪問目標對象,這樣可以增強目標對象功能。
代理模式分為靜態代理與動態代理,動態代理又分為JDK代理和Cglib代理,JDK代理只能代理實現類接口的目標對象,但是Cglib沒有這種要求。
2.1 JDK動態代理
動態代理本質是通過生成字節碼的方式將代理對象織入目標對象,本文以JDK動態代理為例。
第一步定義業務方法,即被代理的目標對象:
public?interface?StudentJDKService?{public?void?addStudent(String?name);public?void?updateStudent(String?name); }public?class?StudentJDKServiceImpl?implements?StudentJDKService?{@Overridepublic?void?addStudent(String?name)?{System.out.println("add?student="?+?name);}@Overridepublic?void?updateStudent(String?name)?{System.out.println("update?student="?+?name);} }第二步定義一個事務代理對象:
public?class?TransactionInvocationHandler?implements?InvocationHandler?{private?Object?target;public?TransactionInvocationHandler(Object?target)?{this.target?=?target;}@Overridepublic?Object?invoke(Object?proxy,?Method?method,?Object[]?args)?throws?Throwable?{System.out.println("------前置通知------");Object?rs?=?method.invoke(target,?args);System.out.println("------后置通知------");return?rs;} }第三步定義代理工廠:
public?class?ProxyFactory?{public?Object?getProxy(Object?target,?InvocationHandler?handler)?{ClassLoader?loader?=?this.getClass().getClassLoader();Class<?>[]?interfaces?=?target.getClass().getInterfaces();Object?proxy?=?Proxy.newProxyInstance(loader,?interfaces,?handler);return?proxy;} }第四步進行測試:
public?class?ZTest?{public?static?void?main(String[]?args)?throws?Exception?{testSimple();}public?static?void?testSimple()?{StudentJDKService?target?=?new?StudentJDKServiceImpl();TransactionInvocationHandler?handler?=?new?TransactionInvocationHandler(target);ProxyFactory?proxyFactory?=?new?ProxyFactory();Object?proxy?=?proxyFactory.getProxy(target,?handler);StudentJDKService?studentService?=?(StudentJDKService)?proxy;studentService.addStudent("JAVA前線");} }ProxyGenerator.generateProxyClass是生成字節碼文件核心方法,我們看一看動態字節碼到底如何定義:
public?class?ZTest?{public?static?void?main(String[]?args)?throws?Exception?{createProxyClassFile();}public?static?void?createProxyClassFile()?{String?name?=?"Student$Proxy";byte[]?data?=?ProxyGenerator.generateProxyClass(name,?new?Class[]?{?StudentJDKService.class?});FileOutputStream?out?=?null;try?{String?fileName?=?"c:/test/"?+?name?+?".class";File?file?=?new?File(fileName);out?=?new?FileOutputStream(file);out.write(data);}?catch?(Exception?e)?{System.out.println(e.getMessage());}?finally?{if?(null?!=?out)?{try?{out.close();}?catch?(IOException?e)?{e.printStackTrace();}}}} }最終生成字節碼文件如下,我們看到代理對象被織入了目標對象:
import?com.xpz.dubbo.simple.jdk.StudentJDKService; import?java.lang.reflect.InvocationHandler; import?java.lang.reflect.Method; import?java.lang.reflect.Proxy; import?java.lang.reflect.UndeclaredThrowableException;public?final?class?Student$Proxy?extends?Proxy?implements?StudentJDKService?{private?static?Method?m1;private?static?Method?m2;private?static?Method?m4;private?static?Method?m3;private?static?Method?m0;public?Student$Proxy(InvocationHandler?paramInvocationHandler)?{super(paramInvocationHandler);}public?final?boolean?equals(Object?paramObject)?{try?{return?((Boolean)this.h.invoke(this,?m1,?new?Object[]?{?paramObject?})).booleanValue();}?catch?(Error?|?RuntimeException?error)?{throw?null;}?catch?(Throwable?throwable)?{throw?new?UndeclaredThrowableException(throwable);}}public?final?String?toString()?{try?{return?(String)this.h.invoke(this,?m2,?null);}?catch?(Error?|?RuntimeException?error)?{throw?null;}?catch?(Throwable?throwable)?{throw?new?UndeclaredThrowableException(throwable);}}public?final?void?updateStudent(String?paramString)?{try?{this.h.invoke(this,?m4,?new?Object[]?{?paramString?});return;}?catch?(Error?|?RuntimeException?error)?{throw?null;}?catch?(Throwable?throwable)?{throw?new?UndeclaredThrowableException(throwable);}}public?final?void?addStudent(String?paramString)?{try?{this.h.invoke(this,?m3,?new?Object[]?{?paramString?});return;}?catch?(Error?|?RuntimeException?error)?{throw?null;}?catch?(Throwable?throwable)?{throw?new?UndeclaredThrowableException(throwable);}}public?final?int?hashCode()?{try?{return?((Integer)this.h.invoke(this,?m0,?null)).intValue();}?catch?(Error?|?RuntimeException?error)?{throw?null;}?catch?(Throwable?throwable)?{throw?new?UndeclaredThrowableException(throwable);}}static?{try?{m1?=?Class.forName("java.lang.Object").getMethod("equals",?new?Class[]?{?Class.forName("java.lang.Object")?});m2?=?Class.forName("java.lang.Object").getMethod("toString",?new?Class[0]);m4?=?Class.forName("com.xpz.dubbo.simple.jdk.StudentJDKService").getMethod("updateStudent",?new?Class[]?{?Class.forName("java.lang.String")?});m3?=?Class.forName("com.xpz.dubbo.simple.jdk.StudentJDKService").getMethod("addStudent",?new?Class[]?{?Class.forName("java.lang.String")?});m0?=?Class.forName("java.lang.Object").getMethod("hashCode",?new?Class[0]);return;}?catch?(NoSuchMethodException?noSuchMethodException)?{throw?new?NoSuchMethodError(noSuchMethodException.getMessage());}?catch?(ClassNotFoundException?classNotFoundException)?{throw?new?NoClassDefFoundError(classNotFoundException.getMessage());}} }2.2 DUBBO源碼應用
那么在DUBBO源碼中動態代理是如何體現的呢?我們知道消費者在消費方法時實際上執行的代理方法,這是消費者在refer時生成的代理方法。
代理工廠AbstractProxyFactory有兩個子類:
JdkProxyFactory JavassistProxyFactory通過下面源碼我們可以分析得到,DUBBO通過InvokerInvocationHandler對象代理了invoker對象:
public?class?JdkProxyFactory?extends?AbstractProxyFactory?{@Overridepublic?<T>?T?getProxy(Invoker<T>?invoker,?Class<?>[]?interfaces)?{return?(T)?Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),?interfaces,?new?InvokerInvocationHandler(invoker));} }public?class?JavassistProxyFactory?extends?AbstractProxyFactory?{@Overridepublic?<T>?T?getProxy(Invoker<T>?invoker,?Class<?>[]?interfaces)?{return?(T)?Proxy.getProxy(interfaces).newInstance(new?InvokerInvocationHandler(invoker));} }InvokerInvocationHandler將參數信息封裝至RpcInvocation進行傳遞:
public?class?InvokerInvocationHandler?implements?InvocationHandler?{private?final?Invoker<?>?invoker;public?InvokerInvocationHandler(Invoker<?>?handler)?{this.invoker?=?handler;}@Overridepublic?Object?invoke(Object?proxy,?Method?method,?Object[]?args)?throws?Throwable?{String?methodName?=?method.getName();Class<?>[]?parameterTypes?=?method.getParameterTypes();if?(method.getDeclaringClass()?==?Object.class)?{return?method.invoke(invoker,?args);}if?("toString".equals(methodName)?&&?parameterTypes.length?==?0)?{return?invoker.toString();}if?("hashCode".equals(methodName)?&&?parameterTypes.length?==?0)?{return?invoker.hashCode();}if?("equals".equals(methodName)?&&?parameterTypes.length?==?1)?{return?invoker.equals(args[0]);}//?RpcInvocation?[methodName=sayHello,?parameterTypes=[class?java.lang.String],?arguments=[JAVA前線],?attachments={}]RpcInvocation?rpcInvocation?=?createInvocation(method,?args);return?invoker.invoke(rpcInvocation).recreate();}private?RpcInvocation?createInvocation(Method?method,?Object[]?args)?{RpcInvocation?invocation?=?new?RpcInvocation(method,?args);if?(RpcUtils.hasFutureReturnType(method))?{invocation.setAttachment(Constants.FUTURE_RETURNTYPE_KEY,?"true");invocation.setAttachment(Constants.ASYNC_KEY,?"true");}return?invocation;} }3 策略模式
在1995年出版的《設計模式:可復用面向對象軟件的基礎》給出了策略模式定義:
Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from clients that use it
定義一系列算法,封裝每一個算法,并使它們可以互換。策略模式可以使算法的變化獨立于使用它們的客戶端代碼。
在設計模式原則中有一條開閉原則:對擴展開放,對修改關閉,我認為這是設計模式中最重要設計原則原因如下:
(1) 當需求變化時應該通過擴展而不是通過修改已有代碼來實現變化,這樣就保證代碼的穩定性,避免牽一發而動全身
(2) 擴展也不是隨意擴展,因為事先定義了算法,擴展也是根據算法擴展,體現了用抽象構建框架,用實現擴展細節
(3) 標準意義的二十三種設計模式說到底最終都是在遵循開閉原則
3.1 策略模式實例
假設我們現在需要解析一段文本,這段文本有可能是HTML也有可能是TEXT,如果不使用策略模式應該怎么寫呢?
public?enum?DocTypeEnum?{HTML(1,?"HTML"),TEXT(2,?"TEXT");private?int?value;private?String?description;private?DocTypeEnum(int?value,?String?description)?{this.value?=?value;this.description?=?description;}public?int?value()?{??return?value;??}???? }public?class?ParserManager?{public?void?parse(Integer?docType,?String?content)?{//?文本類型是HTMLif(docType?==?DocTypeEnum.HTML.getValue())?{//?解析邏輯}//?文本類型是TEXTelse?if(docType?==?DocTypeEnum.TEXT.getValue())?{//?解析邏輯}} }這種寫法功能上沒有問題,但是當本文類型越來越多時,那么parse方法將會越來越冗余和復雜,if else代碼塊也會越來越多,所以我們要使用策略模式。
第一步定義業務類型和業務實體:
public?enum?DocTypeEnum?{HTML(1,?"HTML"),TEXT(2,?"TEXT");private?int?value;private?String?description;private?DocTypeEnum(int?value,?String?description)?{this.value?=?value;this.description?=?description;}public?int?value()?{return?value;} }public?class?BaseModel?{//?公共字段 }public?class?HtmlContentModel?extends?BaseModel?{//?HTML自定義字段 }public?class?TextContentModel?extends?BaseModel?{//?TEXT自定義字段 }第二步定義策略:
public?interface?Strategy<T?extends?BaseModel>?{public?T?parse(String?sourceContent); }@Service public?class?HtmlStrategy?implements?Strategy?{@Overridepublic?HtmlContentModel?parse(String?sourceContent)?{return?new?HtmlContentModel("html");} }@Service public?class?TextStrategy?implements?Strategy?{@Overridepublic?TextContentModel?parse(String?sourceContent)?{return?new?TextContentModel("text");} }第三步定義策略工廠:
@Service public?class?StrategyFactory?implements?InitializingBean?{private?Map<Integer,Strategy>?strategyMap?=?new?HashMap<>();??@Resourceprivate?Strategy<HtmlContentModel>?htmlStrategy?;@Resourceprivate?Strategy<TextContentModel>?textStrategy?;@Overridepublic?void?afterPropertiesSet()?throws?Exception??{strategyMap.put(RechargeTypeEnum.HTML.value(),?htmlStrategy);???strategyMap.put(RechargeTypeEnum.TEXT.value(),textStrategy);}public?Strategy?getStrategy(int?type)?{return?strategyMap.get(type);} }?第四步定義策略執行器:
@Service public?class?StrategyExecutor<T?extends?BaseModel>?{@Resourceprivate?StrategyFactory<T>?strategyFactory;public?T?parse(String?sourceContent,?Integer?type)?{Strategy?strategy?=?StrategyFactory.getStrategy(type);return?strategy.parse(sourceContent);} }第五步執行測試用例:
public?class?Test?{@Resourceprivate?StrategyExecutor??executor;@Testpublic?void?test()?{//?解析HTMLHtmlContentModel?content1?=?(HtmlContentModel)?executor.parse("<a>測試內容</a>",??DocTypeEnum.HTML.value());System.out.println(content1);//?解析TEXTTextContentModel?content2?=?(TextContentModel)executor.calRecharge("測試內容",??DocTypeEnum.TEXT.value());System.out.println(content2);} }如果新增文本類型我們再擴展新策略即可。我們再回顧策略模式定義會有更深的體會:定義一系列算法,封裝每一個算法,并使它們可以互換。策略模式可以使算法的變化獨立于使用它們的客戶端代碼。
3.2 DUBBO源碼應用
在上述實例中我們將策略存儲在map容器,我們思考一下還有沒有其它地方可以存儲策略?答案是配置文件。下面就要介紹SPI機制,我認為這個機制在廣義上實現了策略模式。
SPI(Service Provider Interface)是一種服務發現機制,本質是將接口實現類的全限定名配置在文件中,并由服務加載器讀取配置文件加載實現類,這樣可以在運行時動態為接口替換實現類,我們通過SPI機制可以為程序提供拓展功能。
3.2.1 JDK SPI
我們首先分析JDK自身SPI機制,定義一個數據驅動接口并提供兩個驅動實現,最后通過serviceLoader加載驅動。
(1) 新建DataBaseDriver工程并定義接口
public?interface?DataBaseDriver?{String?connect(String?hostIp); }(2) 打包這個工程為JAR
<dependency><groupId>com.javafont.spi</groupId><artifactId>DataBaseDriver</artifactId><version>1.0.0-SNAPSHOT</version> </dependency>(3) 新建MySQLDriver工程引用上述依賴并實現DataBaseDriver接口
import?com.javafont.database.driver.DataBaseDriver;public?class?MySQLDataBaseDriver?implements?DataBaseDriver?{@Overridepublic?String?connect(String?hostIp)?{return?"MySQL?DataBase?Driver?connect";} }(4) 在MySQLDriver項目新建文件
src/main/resources/META-INF/services/com.javafont.database.driver.DataBaseDriver(5) 在上述文件新增如下內容
com.javafont.database.mysql.driver.MySQLDataBaseDriver(6) 按照上述相同步驟創建工程OracleDriver
(7) 打包上述兩個項目
<dependency><groupId>com.javafont.spi</groupId><artifactId>MySQLDriver</artifactId><version>1.0.0-SNAPSHOT</version> </dependency><dependency><groupId>com.javafont.spi</groupId><artifactId>OracleDriver</artifactId><version>1.0.0-SNAPSHOT</version> </dependency>(8) 新建測試項目引入上述MySQLDriver、OracleDriver
public?class?DataBaseConnector?{public?static?void?main(String[]?args)?{ServiceLoader<DataBaseDriver>?serviceLoader?=?ServiceLoader.load(DataBaseDriver.class);Iterator<DataBaseDriver>?iterator?=?serviceLoader.iterator();while?(iterator.hasNext())?{DataBaseDriver?driver?=?iterator.next();System.out.println(driver.connect("localhost"));}} }//?輸出結果 //?MySQL?DataBase?Driver?connect //?Oracle?DataBase?Driver?connect我們并沒有指定使用哪個驅動連接數據庫,而是通過ServiceLoader方式加載所有實現了DataBaseDriver接口的實現類。假設我們只需要使用MySQL數據庫驅動那么直接引入相應依賴即可。
3.2.2 DUBBO SPI
我們發現JDK SPI機制還是有一些不完善之處:例如通過ServiceLoader會加載所有實現了某個接口的實現類,但是不能通過一個key去指定獲取哪一個實現類,但是DUBBO自己實現的SPI機制解決了這個問題。
例如Protocol接口有如下實現類:
org.apache.dubbo.rpc.protocol.injvm.InjvmProtocol org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol我們現在將這些類配置信息在配置文件,配置文件在如下目錄:
META-INF/services/ META-INF/dubbo/ META-INF/dubbo/internal/配置方式和JDK SPI方式配置不一樣,每個實現類都有key與之對應:
dubbo=org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol injvm=org.apache.dubbo.rpc.protocol.injvm.InjvmProtocol使用時通過擴展點方式加載實現類:
public?class?ReferenceConfig<T>?extends?AbstractReferenceConfig?{private?static?final?Protocol?refprotocol?=?ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();private?T?createProxy(Map<String,?String>?map)?{if?(isJvmRefer)?{URL?url?=?new?URL(Constants.LOCAL_PROTOCOL,?Constants.LOCALHOST_VALUE,?0,?interfaceClass.getName()).addParameters(map);invoker?=?refprotocol.refer(interfaceClass,?url);if?(logger.isInfoEnabled())?{logger.info("Using?injvm?service?"?+?interfaceClass.getName());}}} }getAdaptiveExtension()是加載自適應擴展點,javassist會為自適應擴展點生成動態代碼:
public?class?Protocol$Adaptive?implements?org.apache.dubbo.rpc.Protocol?{public?org.apache.dubbo.rpc.Invoker?refer(java.lang.Class?arg0,?org.apache.dubbo.common.URL?arg1)?throws?org.apache.dubbo.rpc.RpcException?{if?(arg1?==?null)throw?new?IllegalArgumentException("url?==?null");org.apache.dubbo.common.URL?url?=?arg1;String?extName?=?(url.getProtocol()?==?null???"dubbo"?:?url.getProtocol());if?(extName?==?null)throw?new?IllegalStateException("Fail?to?get?extension(org.apache.dubbo.rpc.Protocol)?name?from?url("?+?url.toString()?+?")?use?keys([protocol])");org.apache.dubbo.rpc.Protocol?extension?=?(org.apache.dubbo.rpc.Protocol)?ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName);return?extension.refer(arg0,?arg1);} }extension對象就是根據url中protocol屬性等于injvm最終加載InjvmProtocol對象,動態獲取到了我們制定的業務對象,所以我認為SPI體現了策略模式。
4 裝飾器模式
裝飾器模式可以動態將責任附加到對象上,在不改變原始類接口情況下,對原始類功能進行增強,并且支持多個裝飾器的嵌套使用。實現裝飾器模式需要以下組件:
(1) Component(抽象構件)
核心業務抽象:可以使用接口或者抽象類
(2) ConcreteComponent(具體構件)
實現核心業務:最終執行的業務代碼
(3) Decorator(抽象裝飾器)
抽象裝飾器類:實現Component并且組合一個Component對象
(4) ConcreteDecorator(具體裝飾器)
具體裝飾內容:裝飾核心業務代碼
4.1 裝飾器實例
有一名足球運動員要去踢球,我們用球鞋和球襪為他裝飾一番,這樣可以使其戰力值增加,我們使用裝飾器模式實現這個實例。
(1) Component
/***?抽象構件(可以用接口替代)*/ public?abstract?class?Component?{/***?踢足球(業務核心方法)*/public?abstract?void?playFootBall(); }(2) ConcreteComponent
/***?具體構件*/ public?class?ConcreteComponent?extends?Component?{@Overridepublic?void?playFootBall()?{System.out.println("球員踢球");} }(3) Decorator
/***?抽象裝飾器*/ public?abstract?class?Decorator?extends?Component?{private?Component?component?=?null;public?Decorator(Component?component)?{this.component?=?component;}@Overridepublic?void?playFootBall()?{this.component.playFootBall();} }(4) ConcreteDecorator
/***?球襪裝飾器*/ public?class?ConcreteDecoratorA?extends?Decorator?{public?ConcreteDecoratorA(Component?component)?{super(component);}/***?定義球襪裝飾邏輯*/private?void?decorateMethod()?{System.out.println("換上球襪戰力值增加");}/***?重寫父類方法*/@Overridepublic?void?playFootBall()?{this.decorateMethod();super.playFootBall();} }/***?球鞋裝飾器*/ public?class?ConcreteDecoratorB?extends?Decorator?{public?ConcreteDecoratorB(Component?component)?{super(component);}/***?定義球鞋裝飾邏輯*/private?void?decorateMethod()?{System.out.println("換上球鞋戰力值增加");}/***?重寫父類方法*/@Overridepublic?void?playFootBall()?{this.decorateMethod();super.playFootBall();} }(5) 運行測試
public?class?TestDecoratorDemo?{public?static?void?main(String[]?args)?{Component?component?=?new?ConcreteComponent();component?=?new?ConcreteDecoratorA(component);component?=?new?ConcreteDecoratorB(component);component.playFootBall();} }//?換上球鞋戰力值增加 //?換上球襪戰力值增加 //?球員踢球4.2 DUBBO源碼應用
DUBBO是通過SPI機制實現裝飾器模式,我們以Protocol接口進行分析,首先分析裝飾器類,抽象裝飾器核心要點是實現了Component并且組合一個Component對象。
public?class?ProtocolFilterWrapper?implements?Protocol?{private?final?Protocol?protocol;public?ProtocolFilterWrapper(Protocol?protocol)?{if?(protocol?==?null)?{throw?new?IllegalArgumentException("protocol?==?null");}this.protocol?=?protocol;} }public?class?ProtocolListenerWrapper?implements?Protocol?{private?final?Protocol?protocol;public?ProtocolListenerWrapper(Protocol?protocol)?{if?(protocol?==?null)?{throw?new?IllegalArgumentException("protocol?==?null");}this.protocol?=?protocol;} }在配置文件中配置裝飾器:
filter=org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper listener=org.apache.dubbo.rpc.protocol.ProtocolListenerWrapper通過SPI機制加載擴展點時會使用裝飾器裝飾具體構件:
public?class?ReferenceConfig<T>?extends?AbstractReferenceConfig?{private?static?final?Protocol?refprotocol?=?ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();private?T?createProxy(Map<String,?String>?map)?{if?(isJvmRefer)?{URL?url?=?new?URL(Constants.LOCAL_PROTOCOL,?Constants.LOCALHOST_VALUE,?0,?interfaceClass.getName()).addParameters(map);invoker?=?refprotocol.refer(interfaceClass,?url);if?(logger.isInfoEnabled())?{logger.info("Using?injvm?service?"?+?interfaceClass.getName());}}} }最終生成refprotocol為如下對象:
ProtocolFilterWrapper(ProtocolListenerWrapper(InjvmProtocol))5 責任鏈模式
責任鏈模式將請求發送和接收解耦,讓多個接收對象都有機會處理這個請求。這些接收對象串成一條鏈路并沿著這條鏈路傳遞這個請求,直到鏈路上某個接收對象能夠處理它。我們介紹責任鏈模式兩種應用場景和四種代碼實現方式,最后介紹了DUBBO如何應用責任鏈構建過濾器鏈路。
5.1 應用場景:命中立即中斷
實現一個關鍵詞過濾功能。系統設置三個關鍵詞過濾器,輸入內容命中任何一個過濾器規則就返回校驗不通過,鏈路立即中斷無需繼續進行。
(1) 實現方式一
public?interface?ContentFilter?{public?boolean?filter(String?content); }public?class?AaaContentFilter?implements?ContentFilter?{private?final?static?String?KEY_CONTENT?=?"aaa";@Overridepublic?boolean?filter(String?content)?{boolean?isValid?=?Boolean.FALSE;if?(StringUtils.isEmpty(content))?{return?isValid;}isValid?=?!content.contains(KEY_CONTENT);return?isValid;} }public?class?BbbContentFilter?implements?ContentFilter?{private?final?static?String?KEY_CONTENT?=?"bbb";@Overridepublic?boolean?filter(String?content)?{boolean?isValid?=?Boolean.FALSE;if?(StringUtils.isEmpty(content))?{return?isValid;}isValid?=?!content.contains(KEY_CONTENT);return?isValid;} }public?class?CccContentFilter?implements?ContentFilter?{private?final?static?String?KEY_CONTENT?=?"ccc";@Overridepublic?boolean?filter(String?content)?{boolean?isValid?=?Boolean.FALSE;if?(StringUtils.isEmpty(content))?{return?isValid;}isValid?=?!content.contains(KEY_CONTENT);return?isValid;} }具體過濾器已經完成,我們下面構造過濾器責任鏈路:
@Service public?class?ContentFilterChain?{private?List<ContentFilter>?filters?=?new?ArrayList<ContentFilter>();@PostConstructpublic?void?init()?{ContentFilter?aaaContentFilter?=?new?AaaContentFilter();ContentFilter?bbbContentFilter?=?new?BbbContentFilter();ContentFilter?cccContentFilter?=?new?CccContentFilter();filters.add(aaaContentFilter);filters.add(bbbContentFilter);filters.add(cccContentFilter);}public?void?addFilter(ContentFilter?filter)?{filters.add(filter);}public?boolean?filter(String?content)?{if?(CollectionUtils.isEmpty(filters))?{throw?new?RuntimeException("ContentFilterChain?is?empty");}for?(ContentFilter?filter?:?filters)?{boolean?isValid?=?filter.filter(content);if?(!isValid)?{System.out.println("校驗不通過");return?isValid;}}return?Boolean.TRUE;} }public?class?Test?{public?static?void?main(String[]?args)?throws?Exception?{ClassPathXmlApplicationContext?context?=?new?ClassPathXmlApplicationContext(new?String[]?{?"classpath*:META-INF/chain/spring-core.xml"?});ContentFilterChain?chain?=?(ContentFilterChain)?context.getBean("contentFilterChain");System.out.println(context);boolean?result1?=?chain.filter("ccc");boolean?result2?=?chain.filter("ddd");System.out.println("校驗結果1="?+?result1);System.out.println("校驗結果2="?+?result2);} }(2) 實現方式二
public?abstract?class?FilterHandler?{/**?下一個節點?**/protected?FilterHandler?successor?=?null;public?void?setSuccessor(FilterHandler?successor)?{this.successor?=?successor;}public?final?boolean?filter(String?content)?{/**?執行自身方法?**/boolean?isValid?=?doFilter(content);if?(!isValid)?{System.out.println("校驗不通過");return?isValid;}/**?執行下一個節點鏈路?**/if?(successor?!=?null?&&?this?!=?successor)?{isValid?=?successor.filter(content);}return?isValid;}/**?每個節點過濾方法?**/protected?abstract?boolean?doFilter(String?content); }public?class?AaaContentFilterHandler?extends?FilterHandler?{private?final?static?String?KEY_CONTENT?=?"aaa";@Overrideprotected?boolean?doFilter(String?content)?{boolean?isValid?=?Boolean.FALSE;if?(StringUtils.isEmpty(content))?{return?isValid;}isValid?=?!content.contains(KEY_CONTENT);return?isValid;} }//?省略其它過濾器代碼具體過濾器已經完成,我們下面構造過濾器責任鏈路:
@Service public?class?FilterHandlerChain?{private?FilterHandler?head?=?null;private?FilterHandler?tail?=?null;@PostConstructpublic?void?init()?{FilterHandler?aaaHandler?=?new?AaaContentFilterHandler();FilterHandler?bbbHandler?=?new?BbbContentFilterHandler();FilterHandler?cccHandler?=?new?CccContentFilterHandler();addHandler(aaaHandler);addHandler(bbbHandler);addHandler(cccHandler);}public?void?addHandler(FilterHandler?handler)?{if?(head?==?null)?{head?=?tail?=?handler;}/**?設置當前tail繼任者?**/tail.setSuccessor(handler);/**?指針重新指向tail?**/tail?=?handler;}public?boolean?filter(String?content)?{if?(null?==?head)?{throw?new?RuntimeException("FilterHandlerChain?is?empty");}/**?head發起調用?**/return?head.filter(content);} }public?class?Test?{public?static?void?main(String[]?args)?throws?Exception?{ClassPathXmlApplicationContext?context?=?new?ClassPathXmlApplicationContext(new?String[]?{?"classpath*:META-INF/chain/spring-core.xml"?});FilterHandlerChain?chain?=?(FilterHandlerChain)?context.getBean("filterHandlerChain");System.out.println(context);boolean?result1?=?chain.filter("ccc");boolean?result2?=?chain.filter("ddd");System.out.println("校驗結果1="?+?result1);System.out.println("校驗結果2="?+?result2);} }5.2 應用場景:全鏈路執行
我們實現一個考題生成功能。在線考試系統根據不同年級生成不同考題。系統設置三個考題生成器,每個生成器都會執行,根據學生年級決定是否生成考題,無需生成則執行下一個生成器。
(1) 實現方式一
public?interface?QuestionGenerator?{public?Question?generateQuestion(String?gradeInfo); }public?class?AaaQuestionGenerator?implements?QuestionGenerator?{@Overridepublic?Question?generateQuestion(String?gradeInfo)?{if?(!gradeInfo.equals("一年級"))?{return?null;}Question?question?=?new?Question();question.setId("aaa");question.setScore(10);return?question;} }//?省略其它生成器代碼具體生成器已經編寫完成,我們下面構造生成器責任鏈路:
@Service public?class?QuestionChain?{private?List<QuestionGenerator>?generators?=?new?ArrayList<QuestionGenerator>();@PostConstructpublic?void?init()?{QuestionGenerator?aaaQuestionGenerator?=?new?AaaQuestionGenerator();QuestionGenerator?bbbQuestionGenerator?=?new?BbbQuestionGenerator();QuestionGenerator?cccQuestionGenerator?=?new?CccQuestionGenerator();generators.add(aaaQuestionGenerator);generators.add(bbbQuestionGenerator);generators.add(cccQuestionGenerator);}public?List<Question>?generate(String?gradeInfo)?{if?(CollectionUtils.isEmpty(generators))?{throw?new?RuntimeException("QuestionChain?is?empty");}List<Question>?questions?=?new?ArrayList<Question>();for?(QuestionGenerator?generator?:?generators)?{Question?question?=?generator.generateQuestion(gradeInfo);if?(null?==?question)?{continue;}questions.add(question);}return?questions;} }public?class?Test?{public?static?void?main(String[]?args)?{ClassPathXmlApplicationContext?context?=?new?ClassPathXmlApplicationContext(new?String[]?{?"classpath*:META-INF/chain/spring-core.xml"?});System.out.println(context);QuestionChain?chain?=?(QuestionChain)?context.getBean("questionChain");List<Question>?questions?=?chain.generate("一年級");System.out.println(questions);} }(2) 實現方式二
public?abstract?class?GenerateHandler?{/**?下一個節點?**/protected?GenerateHandler?successor?=?null;public?void?setSuccessor(GenerateHandler?successor)?{this.successor?=?successor;}public?final?List<Question>?generate(String?gradeInfo)?{List<Question>?result?=?new?ArrayList<Question>();/**?執行自身方法?**/Question?question?=?doGenerate(gradeInfo);if?(null?!=?question)?{result.add(question);}/**?執行下一個節點鏈路?**/if?(successor?!=?null?&&?this?!=?successor)?{List<Question>?successorQuestions?=?successor.generate(gradeInfo);if?(null?!=?successorQuestions)?{result.addAll(successorQuestions);}}return?result;}/**?每個節點生成方法?**/protected?abstract?Question?doGenerate(String?gradeInfo); }public?class?AaaGenerateHandler?extends?GenerateHandler?{@Overrideprotected?Question?doGenerate(String?gradeInfo)?{if?(!gradeInfo.equals("一年級"))?{return?null;}Question?question?=?new?Question();question.setId("aaa");question.setScore(10);return?question;} }//?省略其它生成器代碼具體生成器已經完成,我們下面構造生成器責任鏈路:
@Service public?class?GenerateChain?{private?GenerateHandler?head?=?null;private?GenerateHandler?tail?=?null;@PostConstructpublic?void?init()?{GenerateHandler?aaaHandler?=?new?AaaGenerateHandler();GenerateHandler?bbbHandler?=?new?BbbGenerateHandler();GenerateHandler?cccHandler?=?new?CccGenerateHandler();addHandler(aaaHandler);addHandler(bbbHandler);addHandler(cccHandler);}public?void?addHandler(GenerateHandler?handler)?{if?(head?==?null)?{head?=?tail?=?handler;}/**?設置當前tail繼任者?**/tail.setSuccessor(handler);/**?指針重新指向tail?**/tail?=?handler;}public?List<Question>?generate(String?gradeInfo)?{if?(null?==?head)?{throw?new?RuntimeException("GenerateChain?is?empty");}/**?head發起調用?**/return?head.generate(gradeInfo);} }public?class?Test?{public?static?void?main(String[]?args)?{ClassPathXmlApplicationContext?context?=?new?ClassPathXmlApplicationContext(new?String[]?{?"classpath*:META-INF/chain/spring-core.xml"?});GenerateChain?chain?=?(GenerateChain)?context.getBean("generateChain");System.out.println(context);List<Question>?result?=?chain.generate("一年級");System.out.println(result);} }5.3 DUBBO源碼應用
生產者和消費者最終執行對象都是過濾器鏈路最后一個節點,整個鏈路包含多個過濾器進行業務處理。我們看看生產者和消費者最終生成的過濾器鏈路。
生產者過濾器鏈路 EchoFilter?>?ClassloaderFilter?>?GenericFilter?>?ContextFilter?>?TraceFilter?>?TimeoutFilter?>?MonitorFilter?>?ExceptionFilter?>?AbstractProxyInvoker消費者過濾器鏈路 ConsumerContextFilter?>?FutureFilter?>?MonitorFilter?>?DubboInvokerProtocolFilterWrapper作為鏈路生成核心通過匿名類方式構建過濾器鏈路,我們以消費者構建過濾器鏈路為例:
public?class?ProtocolFilterWrapper?implements?Protocol?{private?static?<T>?Invoker<T>?buildInvokerChain(final?Invoker<T>?invoker,?String?key,?String?group)?{//?invoker?=?DubboInvokerInvoker<T>?last?=?invoker;//?查詢符合條件過濾器列表List<Filter>?filters?=?ExtensionLoader.getExtensionLoader(Filter.class).getActivateExtension(invoker.getUrl(),?key,?group);if?(!filters.isEmpty())?{for?(int?i?=?filters.size()?-?1;?i?>=?0;?i--)?{final?Filter?filter?=?filters.get(i);final?Invoker<T>?next?=?last;//?構造一個簡化Invokerlast?=?new?Invoker<T>()?{@Overridepublic?Class<T>?getInterface()?{return?invoker.getInterface();}@Overridepublic?URL?getUrl()?{return?invoker.getUrl();}@Overridepublic?boolean?isAvailable()?{return?invoker.isAvailable();}@Overridepublic?Result?invoke(Invocation?invocation)?throws?RpcException?{//?構造過濾器鏈路Result?result?=?filter.invoke(next,?invocation);if?(result?instanceof?AsyncRpcResult)?{AsyncRpcResult?asyncResult?=?(AsyncRpcResult)?result;asyncResult.thenApplyWithContext(r?->?filter.onResponse(r,?invoker,?invocation));return?asyncResult;}?else?{return?filter.onResponse(result,?invoker,?invocation);}}@Overridepublic?void?destroy()?{invoker.destroy();}@Overridepublic?String?toString()?{return?invoker.toString();}};}}return?last;}@Overridepublic?<T>?Invoker<T>?refer(Class<T>?type,?URL?url)?throws?RpcException?{//?RegistryProtocol不構造過濾器鏈路if?(Constants.REGISTRY_PROTOCOL.equals(url.getProtocol()))?{return?protocol.refer(type,?url);}Invoker<T>?invoker?=?protocol.refer(type,?url);return?buildInvokerChain(invoker,?Constants.REFERENCE_FILTER_KEY,?Constants.CONSUMER);} }6 保護性暫停模式
在多線程編程實踐中我們肯定會面臨線程間數據交互的問題。在處理這類問題時需要使用一些設計模式,從而保證程序的正確性和健壯性。
保護性暫停設計模式就是解決多線程間數據交互問題的一種模式。本文先從基礎案例介紹保護性暫停基本概念和實踐,再由淺入深,最終分析DUBBO源碼中保護性暫停設計模式使用場景。
6.1 保護性暫停實例
我們設想這樣一種場景:線程A生產數據,線程B讀取數據這個數據。
但是有一種情況:線程B準備讀取數據時,此時線程A還沒有生產出數據。
在這種情況下線程B不能一直空轉,也不能立即退出,線程B要等到生產數據完成并拿到數據之后才退出。
那么在數據沒有生產出這段時間,線程B需要執行一種等待機制,這樣可以達到對系統保護目的,這就是保護性暫停。
保護性暫停有多種實現方式,本文我們用synchronized/wait/notify的方式實現。
class?Resource?{private?MyData?data;private?Object?lock?=?new?Object();public?MyData?getData(int?timeOut)?{synchronized?(lock)?{//?運行時長long?timePassed?=?0;//?開始時間long?begin?=?System.currentTimeMillis();//?如果結果為空while?(data?==?null)?{try?{//?如果運行時長大于超時時間退出循環if?(timePassed?>?timeOut)?{break;}//?如果運行時長小于超時時間表示虛假喚醒?->?只需再等待時間差值long?waitTime?=?timeOut?-?timePassed;//?等待時間差值lock.wait(waitTime);//?結果不為空直接返回if?(data?!=?null)?{break;}//?被喚醒后計算運行時長timePassed?=?System.currentTimeMillis()?-?begin;}?catch?(InterruptedException?e)?{e.printStackTrace();}}if?(data?==?null)?{throw?new?RuntimeException("超時未獲取到結果");}return?data;}}public?void?sendData(MyData?data)?{synchronized?(lock)?{this.data?=?data;lock.notifyAll();}} }/***?保護性暫停實例*/ public?class?ProtectDesignTest?{public?static?void?main(String[]?args)?{Resource?resource?=?new?Resource();new?Thread(()?->?{try?{MyData?data?=?new?MyData("hello");System.out.println(Thread.currentThread().getName()?+?"生產數據="?+?data);//?模擬發送耗時TimeUnit.SECONDS.sleep(3);resource.sendData(data);}?catch?(InterruptedException?e)?{e.printStackTrace();}},?"t1").start();new?Thread(()?->?{MyData?data?=?resource.getData(1000);System.out.println(Thread.currentThread().getName()?+?"接收到數據="?+?data);},?"t2").start();} }6.2 加一個編號
現在再來設想一個場景:現在有三個生產數據的線程1、2、3,三個獲取數據的線程4、5、6,我們希望每個獲取數據線程都只拿到其中一個生產線程的數據,不能多拿也不能少拿。
這里引入一個Futures模型,這個模型為每個資源進行編號并存儲在容器中,例如線程1生產的數據被拿走則從容器中刪除,一直到容器為空結束。
@Getter @Setter public?class?MyNewData?implements?Serializable?{private?static?final?long?serialVersionUID?=?1L;private?static?final?AtomicLong?ID?=?new?AtomicLong(0);private?Long?id;private?String?message;public?MyNewData(String?message)?{this.id?=?newId();this.message?=?message;}/***?自增到最大值會回到最小值(負值可以作為識別ID)*/private?static?long?newId()?{return?ID.getAndIncrement();}public?Long?getId()?{return?this.id;} }class?MyResource?{private?MyNewData?data;private?Object?lock?=?new?Object();public?MyNewData?getData(int?timeOut)?{synchronized?(lock)?{long?timePassed?=?0;long?begin?=?System.currentTimeMillis();while?(data?==?null)?{try?{if?(timePassed?>?timeOut)?{break;}long?waitTime?=?timeOut?-?timePassed;lock.wait(waitTime);if?(data?!=?null)?{break;}timePassed?=?System.currentTimeMillis()?-?begin;}?catch?(InterruptedException?e)?{e.printStackTrace();}}if?(data?==?null)?{throw?new?RuntimeException("超時未獲取到結果");}return?data;}}public?void?sendData(MyNewData?data)?{synchronized?(lock)?{this.data?=?data;lock.notifyAll();}} }class?MyFutures?{private?static?final?Map<Long,?MyResource>?FUTURES?=?new?ConcurrentHashMap<>();public?static?MyResource?newResource(MyNewData?data)?{final?MyResource?future?=?new?MyResource();FUTURES.put(data.getId(),?future);return?future;}public?static?MyResource?getResource(Long?id)?{return?FUTURES.remove(id);}public?static?Set<Long>?getIds()?{return?FUTURES.keySet();} }/***?保護性暫停實例*/ public?class?ProtectDesignTest?{public?static?void?main(String[]?args)?throws?Exception?{for?(int?i?=?0;?i?<?3;?i++)?{final?int?index?=?i;new?Thread(()?->?{try?{MyNewData?data?=?new?MyNewData("hello_"?+?index);MyResource?resource?=?MyFutures.newResource(data);//?模擬發送耗時TimeUnit.SECONDS.sleep(1);resource.sendData(data);System.out.println("生產數據data="?+?data);}?catch?(InterruptedException?e)?{e.printStackTrace();}}).start();}TimeUnit.SECONDS.sleep(1);for?(Long?i?:?MyFutures.getIds())?{final?long?index?=?i;new?Thread(()?->?{MyResource?resource?=?MyFutures.getResource(index);int?timeOut?=?3000;System.out.println("接收數據data="?+?resource.getData(timeOut));}).start();}} }6.3 DUBBO源碼應用
我們順著這一個鏈路跟蹤代碼:消費者發送請求 > 提供者接收請求并執行,并且將運行結果發送給消費者 > 消費者接收結果。
(1) 消費者發送請求
消費者發送的數據包含請求ID,并且將關系維護進FUTURES容器
final?class?HeaderExchangeChannel?implements?ExchangeChannel?{@Overridepublic?ResponseFuture?request(Object?request,?int?timeout)?throws?RemotingException?{if?(closed)?{throw?new?RemotingException(this.getLocalAddress(),?null,?"Failed?to?send?request?"?+?request?+?",?cause:?The?channel?"?+?this?+?"?is?closed!");}Request?req?=?new?Request();req.setVersion(Version.getProtocolVersion());req.setTwoWay(true);req.setData(request);DefaultFuture?future?=?DefaultFuture.newFuture(channel,?req,?timeout);try?{channel.send(req);}?catch?(RemotingException?e)?{future.cancel();throw?e;}return?future;} }class?DefaultFuture?implements?ResponseFuture?{//?FUTURES容器private?static?final?Map<Long,?DefaultFuture>?FUTURES?=?new?ConcurrentHashMap<>();private?DefaultFuture(Channel?channel,?Request?request,?int?timeout)?{this.channel?=?channel;this.request?=?request;//?請求IDthis.id?=?request.getId();this.timeout?=?timeout?>?0???timeout?:?channel.getUrl().getPositiveParameter(Constants.TIMEOUT_KEY,?Constants.DEFAULT_TIMEOUT);FUTURES.put(id,?this);CHANNELS.put(id,?channel);} }(2) 提供者接收請求并執行,并且將運行結果發送給消費者
public?class?HeaderExchangeHandler?implements?ChannelHandlerDelegate?{void?handleRequest(final?ExchangeChannel?channel,?Request?req)?throws?RemotingException?{//?response與請求ID對應Response?res?=?new?Response(req.getId(),?req.getVersion());if?(req.isBroken())?{Object?data?=?req.getData();String?msg;if?(data?==?null)?{msg?=?null;}?else?if?(data?instanceof?Throwable)?{msg?=?StringUtils.toString((Throwable)?data);}?else?{msg?=?data.toString();}res.setErrorMessage("Fail?to?decode?request?due?to:?"?+?msg);res.setStatus(Response.BAD_REQUEST);channel.send(res);return;}//?message?=?RpcInvocation包含方法名、參數名、參數值等Object?msg?=?req.getData();try?{//?DubboProtocol.reply執行實際業務方法CompletableFuture<Object>?future?=?handler.reply(channel,?msg);//?如果請求已經完成則發送結果if?(future.isDone())?{res.setStatus(Response.OK);res.setResult(future.get());channel.send(res);return;}}?catch?(Throwable?e)?{res.setStatus(Response.SERVICE_ERROR);res.setErrorMessage(StringUtils.toString(e));channel.send(res);}} }(3) 消費者接收結果
以下DUBBO源碼很好體現了保護性暫停這個設計模式,說明參看注釋
class?DefaultFuture?implements?ResponseFuture?{private?final?Lock?lock?=?new?ReentrantLock();private?final?Condition?done?=?lock.newCondition();public?static?void?received(Channel?channel,?Response?response)?{try?{//?取出對應的請求對象DefaultFuture?future?=?FUTURES.remove(response.getId());if?(future?!=?null)?{future.doReceived(response);}?else?{logger.warn("The?timeout?response?finally?returned?at?"+?(new?SimpleDateFormat("yyyy-MM-dd?HH:mm:ss.SSS").format(new?Date()))+?",?response?"?+?response+?(channel?==?null???""?:?",?channel:?"?+?channel.getLocalAddress()+?"?->?"?+?channel.getRemoteAddress()));}}?finally?{CHANNELS.remove(response.getId());}}@Overridepublic?Object?get(int?timeout)?throws?RemotingException?{if?(timeout?<=?0)?{timeout?=?Constants.DEFAULT_TIMEOUT;}if?(!isDone())?{long?start?=?System.currentTimeMillis();lock.lock();try?{while?(!isDone())?{//?放棄鎖并使當前線程阻塞,直到發出信號中斷它或者達到超時時間done.await(timeout,?TimeUnit.MILLISECONDS);//?阻塞結束后再判斷是否完成if?(isDone())?{break;}//?阻塞結束后判斷是否超時if(System.currentTimeMillis()?-?start?>?timeout)?{break;}}}?catch?(InterruptedException?e)?{throw?new?RuntimeException(e);}?finally?{lock.unlock();}//?response對象仍然為空則拋出超時異常if?(!isDone())?{throw?new?TimeoutException(sent?>?0,?channel,?getTimeoutMessage(false));}}return?returnFromResponse();}private?void?doReceived(Response?res)?{lock.lock();try?{//?接收到服務器響應賦值responseresponse?=?res;if?(done?!=?null)?{//?喚醒get方法中處于等待的代碼塊done.signal();}}?finally?{lock.unlock();}if?(callback?!=?null)?{invokeCallback(callback);}} }7 雙重檢查鎖模式
單例設計模式可以保證在整個應用某個類只能存在一個對象實例,并且這個類只提供一個取得其對象實例方法,通常這個對象創建和銷毀比較消耗資源,例如數據庫連接對象等等。我們分析一個雙重檢查鎖實現的單例模式實例。
public?class?MyDCLConnection?{private?static?volatile?MyDCLConnection?myConnection?=?null;private?MyDCLConnection()?{System.out.println(Thread.currentThread().getName()?+?"?->?init?connection");}public?static?MyDCLConnection?getConnection()?{if?(null?==?myConnection)?{synchronized?(MyDCLConnection.class)?{if?(null?==?myConnection)?{myConnection?=?new?MyDCLConnection();}}}return?myConnection;} }在DUBBO服務本地暴露時使用了雙重檢查鎖模式判斷exporter是否已經存在避免重復創建:
public?class?RegistryProtocol?implements?Protocol?{private?<T>?ExporterChangeableWrapper<T>?doLocalExport(final?Invoker<T>?originInvoker,?URL?providerUrl)?{String?key?=?getCacheKey(originInvoker);ExporterChangeableWrapper<T>?exporter?=?(ExporterChangeableWrapper<T>)?bounds.get(key);if?(exporter?==?null)?{synchronized?(bounds)?{exporter?=?(ExporterChangeableWrapper<T>)?bounds.get(key);if?(exporter?==?null)?{final?Invoker<?>?invokerDelegete?=?new?InvokerDelegate<T>(originInvoker,?providerUrl);final?Exporter<T>?strongExporter?=?(Exporter<T>)?protocol.export(invokerDelegete);exporter?=?new?ExporterChangeableWrapper<T>(strongExporter,?originInvoker);bounds.put(key,?exporter);}}}return?exporter;} }8 文章總結
本文我們結合DUBBO源碼分析了模板方法模式、動態代理模式、策略模式、裝飾器模式、責任鏈模式、保護性暫停模式、雙重檢查鎖模式,我認為在閱讀源碼時要學習其中優秀的設計模式和代碼實例,這樣有助于提高代碼水平,希望本文對大家有所幫助。
有道無術,術可成;有術無道,止于術
歡迎大家關注Java之道公眾號
好文章,我在看??
新人創作打卡挑戰賽發博客就能抽獎!定制產品紅包拿不停!總結
以上是生活随笔為你收集整理的面试题:DUBBO源码使用了哪些设计模式的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: WPF编程,TextBlock中的文字修
- 下一篇: 打印数字