Erlo

长文详解:DUBBO源码使用了哪些设计模式

时间:2021-05-26   阅读:55次   来源:开源中国
页面报错
点赞

欢迎大家关注公众号「JAVA前线」查看更多精彩分享文章,主要包括源码分析、实际应用、架构思维、职场分享、产品思考等等,同时欢迎大家加我个人微信「java_front」一起交流学习

0 文章概述

DUBBO作为RPC领域优秀开源的框架在业界十分流行,本文我们阅读其源码并对其使用到的设计模式进行分析。需要说明的是本文所说的设计模式更加广义,不仅包括标准意义上23种设计模式,还有一些常见经过检验的代码模式例如双重检查锁模式、多线程保护性暂停模式等等。

1 模板方法

模板方法模式定义一个操作中的算法骨架,一般使用抽象类定义算法骨架。抽象类同时定义一些抽象方法,这些抽象方法延迟到子类实现,这样子类不仅遵守了算法骨架约定,也实现了自己的算法。既保证了规约也兼顾灵活性。这就是用抽象构建框架,用实现扩展细节。

DUBBO源码中有一个非常重要的核心概念Invoker,我们可以理解为执行器或者说一个可执行对象,能够根据方法的名称、参数得到相应执行结果,这个特性体现了代理模式我们后面章节再说,本章节我们先分析其中的模板方法模式。

public abstract class AbstractInvoker<Timplements Invoker<T{

    @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<Textends AbstractInvoker<T{

    @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<Textends AbstractInvoker<T{

    @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动态代理为例。

第一步定义业务方法,即被代理的目标对象:

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有两个子类:

JdkProxyFactory
JavassistProxyFactory

通过下面源码我们可以分析得到,DUBBO通过InvokerInvocationHandler对象代理了invoker对象:

public class JdkProxyFactory extends AbstractProxyFactory {

    @Override
    public  getProxy(Invoker invoker, Class[] interfaces) {
        return (T) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), interfaces, new InvokerInvocationHandler(invoker));
    }
}

public class JavassistProxyFactory extends AbstractProxyFactory {

    @Override
    public  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<T extends BaseModel{
    public T parse(String sourceContent);
}

@Service
public class HtmlStrategy implements Strategy {

    @Override
    public HtmlContentModel parse(String sourceContent) {
        return new HtmlContentModel("html");
    }
}

@Service
public class TextStrategy implements Strategy {

    @Override
    public TextContentModel parse(String sourceContent) {
        return new TextContentModel("text");
    }
}

第三步定义策略工厂:

@Service
public 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);
   }
} 

第四步定义策略执行器:

@Service
public class StrategyExecutor<T extends BaseModel{

    @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

<dependency>
  <groupId>com.javafont.spigroupId>
  <artifactId>DataBaseDriverartifactId>
  <version>1.0.0-SNAPSHOTversion>
dependency>

(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) 打包上述两个项目

<dependency>
  <groupId>com.javafont.spigroupId>
  <artifactId>MySQLDriverartifactId>
  <version>1.0.0-SNAPSHOTversion>
dependency>

<dependency>
  <groupId>com.javafont.spigroupId>
  <artifactId>OracleDriver
                    
                         
                 
        

评论留言

还没有评论留言,赶紧来抢楼吧~~

吐槽小黑屋()

* 这里是“吐槽小黑屋”,所有人可看,只保留当天信息。

  • Erlo吐槽

    Erlo.vip2021-08-04 23:07:11Hello、欢迎使用吐槽小黑屋,这就是个吐槽的地方。
  • 返回顶部

      给这篇文章打个标签吧~

      棒极了 糟糕透顶 好文章 PHP JAVA JS 小程序 Python SEO MySql 确认