创意电子
标题: 长文详解:DUBBO源码利用了哪些筹划模式,面试不慌了 [打印本页]
作者: JAVA前线 时间: 2021-9-4 16:50
标题: 长文详解:DUBBO源码利用了哪些筹划模式,面试不慌了
欢迎各人关注今日头条号「JAVA火线」查察更多精彩分享文章,重要包罗源码分析、实际应用、架构思维、职场分享、产物思考
0 文章概述
DUBBO作为RPC领域优秀开源的框架在业界非常流行,本文我们阅读其源码并对其使用到的计划模式进行分析。需要说明的是本文所说的计划模式更加广义,不仅包罗标准意义上23种计划模式,还有一些常见经过检验的代码模式例如双重查抄锁模式、多线程保护性暂停模式等等。
1 模板方法
模板方法模式界说一个操纵中的算法骨架,一样寻常使用抽象类界说算法骨架。抽象类同时界说一些抽象方法,这些抽象方法延迟到子类实现,这样子类不仅遵守了算法骨架约定,也实现了自己的算法。既包管了规约也兼顾灵活性。这就是用抽象构建框架,用实现扩展细节。
DUBBO源码中有一个非常重要的核心概念Invoker,我们可以理解为执行器或者说一个可执行对象,能够根据方法的名称、参数得到相应执行效果,这个特性体现了代理模式我们后面章节再说,本章节我们先分析其中的模板方法模式。
public abstract class AbstractInvoker implements Invoker { @Override public Result invoke(Invocation inv) throws RpcException { RpcInvocation invocation = (RpcInvocation) inv; invocation.setInvoker(this); if (attachment != null && attachment.size() > 0) { invocation.addAttachmentsIfAbsent(attachment); } Map 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 extends AbstractInvoker { @Override public 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 extends AbstractInvoker { @Override protected 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 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动态代理为例。
动态代理本质是通过生成字节码的方式将代理对象织入目的对象,本文以JDK动态代理为例。
第一步界说业务方法,即被代理的目的对象:
public interface StudentJDKService { public void addStudent(String name); public void updateStudent(String name);}public class StudentJDKServiceImpl implements StudentJDKService { @Override public void addStudent(String name) { System.out.println("add student=" + name); } @Override public 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; } @Override public 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有两个子类:
JdkProxyFactoryJavassistProxyFactory通过下面源码我们可以分析得到,DUBBO通过InvokerInvocationHandler对象代理了invoker对象:
public class JdkProxyFactory extends AbstractProxyFactory { @Override public T getProxy(Invoker invoker, Class[] interfaces) { return (T) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), interfaces, new InvokerInvocationHandler(invoker)); }}public class JavassistProxyFactory extends AbstractProxyFactory { @Override public T getProxy(Invoker 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; } @Override public 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) { // 文本范例是HTML if(docType == DocTypeEnum.HTML.getValue()) { // 解析逻辑 } // 文本范例是TEXT else 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 { public T parse(String sourceContent);}@Servicepublic class HtmlStrategy implements Strategy { @Override public HtmlContentModel parse(String sourceContent) { return new HtmlContentModel("html"); }}@Servicepublic class TextStrategy implements Strategy { @Override public TextContentModel parse(String sourceContent) { return new TextContentModel("text"); }}第三步界说策略工厂:
@Servicepublic class StrategyFactory implements InitializingBean { private Map strategyMap = new HashMap(); @Resource private Strategy htmlStrategy ; @Resource private Strategy textStrategy ; @Override public 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); }} 第四步界说策略执行器:
@Servicepublic class StrategyExecutor { @Resource private StrategyFactory strategyFactory; public T parse(String sourceContent, Integer type) { Strategy strategy = StrategyFactory.getStrategy(type); return strategy.parse(sourceContent); }}第五步执行测试用例:
public class Test { @Resource private StrategyExecutor executor; @Test public void test() { // 解析HTML HtmlContentModel content1 = (HtmlContentModel) executor.parse("测试内容", DocTypeEnum.HTML.value()); System.out.println(content1); // 解析TEXT TextContentModel 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
com.javafont.spi DataBaseDriver 1.0.0-SNAPSHOT(3) 新建MySQLDriver工程引用上述依赖并实现DataBaseDriver接口
import com.javafont.database.driver.DataBaseDriver;public class MySQLDataBaseDriver implements DataBaseDriver { @Override public 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) 打包上述两个项目
com.javafont.spi MySQLDriver 1.0.0-SNAPSHOT com.javafont.spi OracleDriver 1.0.0-SNAPSHOT(8) 新建测试项目引入上述MySQLDriver、OracleDriver
public class DataBaseConnector { public static void main(String[] args) { ServiceLoader serviceLoader = ServiceLoader.load(DataBaseDriver.class); Iterator 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.InjvmProtocolorg.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.DubboProtocolinjvm=org.apache.dubbo.rpc.protocol.injvm.InjvmProtocol使用时通过扩展点方式加载实现类:
public class ReferenceConfig extends AbstractReferenceConfig { private static final Protocol refprotocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension(); private T createProxy(Map 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 { @Override public void playFootBall() { System.out.println("球员踢球"); }}(3) Decorator
/** * 抽象装饰器 */public abstract class Decorator extends Component { private Component component = null; public Decorator(Component component) { this.component = component; } @Override public void playFootBall() { this.component.playFootBall(); }}(4) ConcreteDecorator
/** * 球袜装饰器 */public class ConcreteDecoratorA extends Decorator { public ConcreteDecoratorA(Component component) { super(component); } /** * 界说球袜装饰逻辑 */ private void decorateMethod() { System.out.println("换上球袜战力值增长"); } /** * 重写父类方法 */ @Override public void playFootBall() { this.decorateMethod(); super.playFootBall(); }}/** * 球鞋装饰器 */public class ConcreteDecoratorB extends Decorator { public ConcreteDecoratorB(Component component) { super(component); } /** * 界说球鞋装饰逻辑 */ private void decorateMethod() { System.out.println("换上球鞋战力值增长"); } /** * 重写父类方法 */ @Override public 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.ProtocolFilterWrapperlistener=org.apache.dubbo.rpc.protocol.ProtocolListenerWrapper通过SPI机制加载扩展点时会使用装饰器装饰具体构件:
public class ReferenceConfig extends AbstractReferenceConfig { private static final Protocol refprotocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension(); private T createProxy(Map 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"; @Override public 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"; @Override public 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"; @Override public boolean filter(String content) { boolean isValid = Boolean.FALSE; if (StringUtils.isEmpty(content)) { return isValid; } isValid = !content.contains(KEY_CONTENT); return isValid; }}具体过滤器已经完成,我们下面构造过滤器责任链路:
@Servicepublic class ContentFilterChain { private List filters = new ArrayList(); @PostConstruct public 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"; @Override protected boolean doFilter(String content) { boolean isValid = Boolean.FALSE; if (StringUtils.isEmpty(content)) { return isValid; } isValid = !content.contains(KEY_CONTENT); return isValid; }}// 省略其它过滤器代码具体过滤器已经完成,我们下面构造过滤器责任链路:
@Servicepublic class FilterHandlerChain { private FilterHandler head = null; private FilterHandler tail = null; @PostConstruct public 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 { @Override public Question generateQuestion(String gradeInfo) { if (!gradeInfo.equals("一年级")) { return null; } Question question = new Question(); question.setId("aaa"); question.setScore(10); return question; }}// 省略其它生成器代码具体生成器已经编写完成,我们下面构造生成器责任链路:
@Servicepublic class QuestionChain { private List generators = new ArrayList(); @PostConstruct public 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 generate(String gradeInfo) { if (CollectionUtils.isEmpty(generators)) { throw new RuntimeException("QuestionChain is empty"); } List questions = new ArrayList(); 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 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 generate(String gradeInfo) { List result = new ArrayList(); /** 执行自身方法 **/ Question question = doGenerate(gradeInfo); if (null != question) { result.add(question); } /** 执行下一个节点链路 **/ if (successor != null && this != successor) { List successorQuestions = successor.generate(gradeInfo); if (null != successorQuestions) { result.addAll(successorQuestions); } } return result; } /** 每个节点生成方法 **/ protected abstract Question doGenerate(String gradeInfo);}public class AaaGenerateHandler extends GenerateHandler { @Override protected Question doGenerate(String gradeInfo) { if (!gradeInfo.equals("一年级")) { return null; } Question question = new Question(); question.setId("aaa"); question.setScore(10); return question; }}// 省略其它生成器代码具体生成器已经完成,我们下面构造生成器责任链路:
@Servicepublic class GenerateChain { private GenerateHandler head = null; private GenerateHandler tail = null; @PostConstruct public 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 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 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 Invoker buildInvokerChain(final Invoker invoker, String key, String group) { // invoker = DubboInvoker Invoker last = invoker; // 查询符合条件过滤器列表 List 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 next = last; // 构造一个简化Invoker last = new Invoker() { @Override public Class getInterface() { return invoker.getInterface(); } @Override public URL getUrl() { return invoker.getUrl(); } @Override public boolean isAvailable() { return invoker.isAvailable(); } @Override public 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); } } @Override public void destroy() { invoker.destroy(); } @Override public String toString() { return invoker.toString(); } }; } } return last; } @Override public Invoker refer(Class type, URL url) throws RpcException { // RegistryProtocol不构造过滤器链路 if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) { return protocol.refer(type, url); } Invoker 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@Setterpublic 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 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 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 { @Override public 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 FUTURES = new ConcurrentHashMap(); private DefaultFuture(Channel channel, Request request, int timeout) { this.channel = channel; this.request = request; // 哀求ID this.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 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()); } } @Override public Object get(int timeout) throws RemotingException { if (timeout 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 { // 接收到服务器相应赋值response response = 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 ExporterChangeableWrapper doLocalExport(final Invoker originInvoker, URL providerUrl) { String key = getCacheKey(originInvoker); ExporterChangeableWrapper exporter = (ExporterChangeableWrapper) bounds.get(key); if (exporter == null) { synchronized (bounds) { exporter = (ExporterChangeableWrapper) bounds.get(key); if (exporter == null) { final Invoker invokerDelegete = new InvokerDelegate(originInvoker, providerUrl); final Exporter strongExporter = (Exporter) protocol.export(invokerDelegete); exporter = new ExporterChangeableWrapper(strongExporter, originInvoker); bounds.put(key, exporter); } } } return exporter; }}
8 文章总结
本文我们团结DUBBO源码分析了模板方法模式、动态代理模式、策略模式、装饰器模式、责任链模式、保护性暂停模式、双重查抄锁模式,我认为在阅读源码时要学习其中优秀的计划模式和代码实例,这样有助于进步代码程度,希望本文对各人有所帮助。
欢迎各人关注今日头条号「JAVA火线」查察更多精彩分享文章,重要包罗源码分析、实际应用、架构思维、职场分享、产物思考
作者: hanghangcool 时间: 2021-9-4 21:06
设计模式在dubbo源码应用
欢迎光临 创意电子 (https://wxcydz.cc/) |
Powered by Discuz! X3.4 |