美文网首页it技术java面试
Dubbo源码解析-能入门系列

Dubbo源码解析-能入门系列

作者: AKyS佐毅 | 来源:发表于2018-09-18 23:01 被阅读191次

1、ZooKeeper 可视化工具 zkui

  • 1、简介zkui它提供了一个管理界面,可以针对zookeepr的节点值进行CRUD操作,同时也提供了安全认证。

  • 2、下载安装

    • 1、下载地址 https://github.com/DeemOpen/zkui

    • 2、mvn clean install,执行前需要安装 java 环境,maven环境,执行成功后会生成一个jar文件。

    • 3、将config.cfg复制到上一步生成的jar文件所在目录,然后修改配置文件中的zookeeper地址。

      zkServer=localhost:2181
      
    • 4、执行运行命令

      nohup java -jar zkui-2.0-SNAPSHOT-jar-with-dependencies.jar &
      
    • 5、测试,http://localhost:9090,如能看到如下页面则代表zookeeper安装运行正常。

      username:admin
      password:manager
      
      image image
        Register: dubbo://192.168.103.40:20880/com.of.wangpu.api.service.HelloService?anyhost=true&application=dubbo-provider&dubbo=2.4.10&interface=com.of.wangpu.api.service.HelloService&methods=sayHello&pid=2054&side=provider&timestamp=1535511638784, dubbo version: 2.4.10, current host: 127.0.0.1
        
        Subscribe: provider://192.168.103.40:20880/com.of.wangpu.api.service.HelloService?anyhost=true&application=dubbo-provider&category=configurators&check=false&dubbo=2.4.10&interface=com.of.wangpu.api.service.HelloService&methods=sayHello&pid=2054&side=provider&timestamp=1535511638784, dubbo version: 2.4.10, current host: 127.0.0.1
        2018-08-29 11:00:39.080  INFO 2054 --- 
        
        Notify urls for subscribe url provider://192.168.103.40:20880/com.of.wangpu.api.service.HelloService?anyhost=true&application=dubbo-provider&category=configurators&check=false&dubbo=2.4.10&interface=com.of.wangpu.api.service.HelloService&methods=sayHello&pid=2054&side=provider&timestamp=1535511638784, urls: [empty://192.168.103.40:20880/com.of.wangpu.api.service.HelloService?anyhost=true&application=dubbo-provider&category=configurators&check=false&dubbo=2.4.10&interface=com.of.wangpu.api.service.HelloService&methods=sayHello&pid=2054&side=provider&timestamp=1535511638784], dubbo version: 2.4.10, current host: 127.0.0.1
        2018-08-29 11:00:39.224  INFO 2054 ---
        Register: consumer://192.168.103.40/ com.of.wangpu.api.service.HelloService?application=dubbo-provider&category=consumers&check=false&connected=true&dubbo=2.4.10&interface=com.of.wangpu.api.service.HelloService&methods=sayHello&owner=dubbo-provider&pid=2077&side=consumer&timestamp=1535511731989, dubbo version: 2.4.10, current host: 192.168.103.40
    
        Subscribe: consumer://192.168.103.40/com.of.wangpu.api.service.HelloService?application=dubbo-provider&category=providers,configurators,routers&connected=true&dubbo=2.4.10&interface=com.of.wangpu.api.service.HelloService&methods=sayHello&owner=dubbo-provider&pid=2077&side=consumer&timestamp=1535511731989, dubbo version: 2.4.10, current host: 192.168.103.40
    
     Notify urls for subscribe url consumer://192.168.103.40/com.of.wangpu.api.service.HelloService?application=dubbo-provider&category=providers,configurators,routers&connected=true&dubbo=2.4.10&interface=com.of.wangpu.api.service.HelloService&methods=sayHello&owner=dubbo-provider&pid=2077&side=consumer&timestamp=1535511731989, urls: [dubbo://192.168.103.40:20880/com.of.wangpu.api.service.HelloService?anyhost=true&application=dubbo-provider&dubbo=2.4.10&interface=com.of.wangpu.api.service.HelloService&methods=sayHello&pid=2054&side=provider&timestamp=1535511638784, empty://192.168.103.40/com.of.wangpu.api.service.HelloService?application=dubbo-provider&category=configurators&connected=true&dubbo=2.4.10&interface=com.of.wangpu.api.service.HelloService&methods=sayHello&owner=dubbo-provider&pid=2077&side=consumer&timestamp=1535511731989, empty://192.168.103.40/com.of.wangpu.api.service.HelloService?application=dubbo-provider&category=routers&connected=true&dubbo=2.4.10&interface=com.of.wangpu.api.service.HelloService&methods=sayHello&owner=dubbo-provider&pid=2077&side=consumer&timestamp=1535511731989], dubbo version: 2.4.10, current host: 192.168.103.40
    
      Successed connect to server /192.168.103.40:20880 from NettyClient 192.168.103.40 using dubbo version 2.4.10, channel is NettyChannel [channel=[id: 0x27f1bbe0, /192.168.103.40:53619 => /192.168.103.40:20880]], dubbo version: 2.4.10, current host: 192.168.103.40
    
      Start NettyClient zuoyideMacBook-Pro.local/192.168.103.40 connect to the server /192.168.103.40:20880, dubbo version: 2.4.10, current host: 192.168.103.40
      
        Refer dubbo service com.of.wangpu.api.service.HelloService from url zookeeper://127.0.0.1:2181/com.alibaba.dubbo.registry.RegistryService?anyhost=true&application=dubbo-provider&check=false&connected=true&dubbo=2.4.10&inside.invoker.count=1&inside.invokers=dubbo%3A%2F%2F192.168.103.40%3A20880%2Fcom.of.wangpu.api.service.HelloService%3Fanyhost%3Dtrue%26application%3Ddubbo-provider%26dubbo%3D2.4.10%26interface%3Dcom.of.wangpu.api.service.HelloService%26methods%3DsayHello%26pid%3D2054%26side%3Dprovider%26timestamp%3D1535511638784&interface=com.of.wangpu.api.service.HelloService&methods=sayHello&owner=dubbo-provider&pid=2077&side=consumer&timestamp=1535511731989, dubbo version: 2.4.10, current host: 192.168.103.40

2、Dubbo相关知识点总结

image
  • 服务容器Container负责启动,加载,运行服务提供者。

  • 服务提供者Provider在启动时,向注册中心注册自己提供的服务。

  • 服务消费者Consumer在启动时,向注册中心订阅自己所需的服务。

  • 注册中心Registry返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。

  • 服务消费者Consumer,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。

  • 服务消费者Consumer和提供者Provider,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心Monitor。

  • 1.Dubbo支持哪些协议,每种协议的应用场景,优缺点?

  • dubbo: 单一长连接和NIO异步通讯,适合大并发小数据量的服务调用,以及消费者远大于提供者。传输协议TCP,异步,Hessian序列化;

  • rmi: 采用JDK标准的rmi协议实现,传输参数和返回参数对象需要实现Serializable接口,使用java标准序列化机制,使用阻塞式短连接,传输数据包大小混合,消费者和提供者个数差不多,可传文件,传输协议TCP。 多个短连接,TCP协议传输,同步传输,适用常规的远程服务调用和rmi互操作。在依赖低版本的Common-Collections包,java序列化存在安全漏洞;

  • webservice: 基于WebService的远程调用协议,集成CXF实现,提供和原生WebService的互操作。多个短连接,基于HTTP传输,同步传输,适用系统集成和跨语言调用;

  • http: 基于Http表单提交的远程调用协议,使用Spring的HttpInvoke实现。多个短连接,传输协议HTTP,传入参数大小混合,提供者个数多于消费者,需要给应用程序和浏览器JS调用;

  • hessian: 集成Hessian服务,基于HTTP通讯,采用Servlet暴露服务,Dubbo内嵌Jetty作为服务器时默认实现,提供与Hession服务互操作。多个短连接,同步HTTP传输,Hessian序列化,传入参数较大,提供者大于消费者,提供者压力较大,可传文件;

  • memcache: 基于memcached实现的RPC协议

  • redis: 基于redis实现的RPC协议

  • 2.Dubbo超时时间怎样设置?

    • Dubbo超时时间设置有两种方式:

    • 服务提供者端设置超时时间,在Dubbo的用户文档中,推荐如果能在服务端多配置就尽量多配置,因为服务提供者比消费者更清楚自己提供的服务特性。

    • 服务消费者端设置超时时间,如果在消费者端设置了超时时间,以消费者端为主,即优先级更高。因为服务调用方设置超时时间控制性更灵活。如果消费方超时,服务端线程不会定制,会产生警告。

  • 3.Dubbo有些哪些注册中心?

    • Multicast注册中心: Multicast注册中心不需要任何中心节点,只要广播地址,就能进行服务注册和发现。基于网络中组播传输实现;

    • Zookeeper注册中心: 基于分布式协调系统Zookeeper实现,采用Zookeeper的watch机制实现数据变更;

    • redis注册中心: 基于redis实现,采用key/Map存储,住key存储服务名和类型,Map中key存储服务URL,value服务过期时间。基于redis的发布/订阅模式通知数据变更;

    • Simple注册中心

  • 4.Dubbo集群的负载均衡有哪些策略

    • Dubbo提供了常见的集群策略实现,并预扩展点予以自行实现。

    • Random LoadBalance: 随机选取提供者策略,有利于动态调整提供者权重。截面碰撞率高,调用次数越多,分布越均匀;

    • RoundRobin LoadBalance: 轮循选取提供者策略,平均分布,但是存在请求累积的问题;

    • LeastActive LoadBalance: 最少活跃调用策略,解决慢提供者接收更少的请求;

    • ConstantHash LoadBalance: 一致性Hash策略,使相同参数请求总是发到同一提供者,一台机器宕机,可以基于虚拟节点,分摊至其他提供者,避免引起提供者的剧烈变动;

3、SPI设计目标

  • 面向对象的设计里,模块之间是基于接口编程,模块之间不对实现类进行硬编码。一旦代码里涉及具体实现类,就违法了可拔插的原则。如果需要替换一种实现,就需要修改源码。

    Java SPI 就是提供了这样一个机制:

  • 为了某个接口寻找服务的实现的机制。优点类似IOC的思想,就是装配的控制权移到代码之外。

  • SPI的具体约定如下:

    • 当服务的提供者,提供了服务接口的一种实现之后,在jar包的META-INF/services/目录里同时创建一个以服务接口命名的文件。该文件里就是实现该服务接口的具体实现类。而当外部程序装配这个模块的时候,就能通过该jar包META-INF/services/里的配置文件找到具体的实现类名,并装载实例化,完成模块的注入。jdk提供服务实现查找的一个工具类:java.util.ServiceLoader
  • JDK标准的SPI会一次性实例化扩展点所有实现,如果有扩展实现初始化很耗时,但如果没用上也加载,会很浪费资源.

  • Dubbo增加了对扩展点IoC和AOP的支持,一个扩展点可以直接setter注入其它扩展点。

    dubbo spi有哪些约定?

    • spi 文件 存储路径 在 META-INF\dubbo\internal 目录下 并且文件名为接口的全路径名 就是=接口的包名+接口名
    • 每个spi文件里面的格式定义为: 扩展名=具体的类名,例如 dubbo=com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtoco

4、dubbo自己的SPI实现

  • 1、为该接口 new 一个 ExtensionLoader,然后缓存起来。


    image
  • 2、getAdaptiveExtension() 获取一个扩展类,如果@Adaptive注解在类上就是一个装饰类;如果注解在方法上就是一个动态代理类,例如Protocol$Adaptive对象。

image
  • 3、getExtension(String name) 获取一个指定对象。

    image
public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
    if (type == null)//拓展点类型非空判断
        throw new IllegalArgumentException("Extension type == null");
    if(!type.isInterface()) {//拓展点类型只能是接口
        throw new IllegalArgumentException("Extension type(" + type + ") is not interface!");
    }
    if(!withExtensionAnnotation(type)) {//需要添加spi注解,否则抛异常
        throw new IllegalArgumentException("Extension type(" + type + 
                ") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!");
    }
    //从缓存EXTENSION_LOADERS中获取,如果不存在则新建后加入缓存
    //对于每一个拓展,都会有且只有一个ExtensionLoader与其对应
    ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
    if (loader == null) {
        EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
        loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
    }
    return loader;
}

private ExtensionLoader(Class<?> type) {
    this.type = type;
    //这里会存在递归调用,ExtensionFactory的objectFactory为null,其他的均为AdaptiveExtensionFactory
    //AdaptiveExtensionFactory的factories中有SpiExtensionFactory,SpringExtensionFactory
    //getAdaptiveExtension()这个是获取一个拓展装饰类对象.细节在下篇讲解
    objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
}

其次我们从Dubbo的启动开始分析

ExtensionLoader.getExtensionLoader(Container.class);
    -->this.type = type;
    -->objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
       -->ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension())
           -->this.type = type;
           -->objectFactory = null;
private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<String, Holder<Object>>();

objectFactory作用,为dubbo的IOC提供所有对象。

继续看还有个很重要的类Holder,这个类用于保存一个值,并且给值添加volatile来保证线程的可见性.

image

5、SPI机制的adpative原理 getAdaptiveExtension

image image
  • adaptive注解在类和方法上的区别
  • 1、注解在类上: 代表人工实现编码,即实现了一个装饰类。例如 ExtensionFactory
  • 2、注解在方法上:代表自动生成和编译一个动态的adpatice类,例如: Protocol$Adaptive
ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();//为cachedAdaptiveInstance赋值
   --->createAdaptiveExtension()
       --->getAdaptiveExtensionClass()
           --->getExtensionClasses() //为cachedClasses对象赋值。
             --->loadFile()   //加载配置文件
           --->createAdaptiveExtensionClass。//自动生成和编译一个动态Adaptive类,这是一个代理类。
               --->ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();//获取代码编译器
               --->ompiler.compile(code, classLoader);//加载代理

主要做了以下几件事情:

  • 1.loadExtensionClasses方法判断ExtensionLoader类中的传入的type接口是否标注了SPI注解,并获取SPI注解的值,这个值为接口的默认实现标记。

  • 2.loadFile方法用来加载配置路径下的接口的实现类。比如在调用loadFile方法时,传入的参数DUBBO_INTERNAL_DIRECTORY,DUBBO_DIRECTORY,SERVICES_DIRECTORY。他们都描述了接口实现类配置文件路径,看看3个属性的值如下:

    private static final String SERVICES_DIRECTORY = "META-INF/services/";
    private static final String DUBBO_DIRECTORY = "META-INF/dubbo/"; 
    private static final String DUBBO_INTERNAL_DIRECTORY = DUBBO_DIRECTORY + "internal/";
    

    这里我就以com.alibaba.dubbo.rpc.Protocol接口来研究!哟!是不是感觉跟JDK里面配置不太一样,它按照key=value的形式来保存的,在分析下loadFile方法中的代码,它也是按照key=value的格式来解析出接口的具体实现,将最终解析的数据保存到了传入的map参数extensionClasses中。大家应该感到好奇为什么要做个key=value的配置。打个比方Protocol协议接口在dubbo框架里实现有hession,http,rmi,webservice,dubbo等好几种实现,在程序运行中我们根据配置来使用具体的协议,比方我要使用rmi协议,那我就配置rmi,我想使用dubbo 我就配置dubbo。配置好以后会根据这个属性配置取找相关的具体协议实现。所以这里的key=value应该就是做这个事情的。看看Protocol接口的实现配置

    redis=com.alibaba.dubbo.rpc.protocol.redis.RedisProtocol
    rmi=com.alibaba.dubbo.rpc.protocol.rmi.RmiProtocol
    com.alibaba.dubbo.rpc.protocol.http.HttpProtocol
    hessian=com.alibaba.dubbo.rpc.protocol.hessian.HessianProtocol
    injvm=com.alibaba.dubbo.rpc.protocol.injvm.InjvmProtocol
    memcached=com.alibaba.dubbo.rpc.protocol.memcached.MemcachedProtocol
    thrift=com.alibaba.dubbo.rpc.protocol.thrift.ThriftProtocol
    com.alibaba.dubbo.rpc.protocol.webservice.WebServiceProtocol
    
image
  • 关于loadfile的一些细节
    • 目的: 通过把配置文件META-INF/dubbo/internal/com.alibaba.dubbo.rpc.protocol.webservice.WebServiceProtocol的内容,存储到缓存变量中。

    • cachedAdaptiveClass = clazz; //如果这个class含有Adaptive注解就赋值。例如ExtensionFactory.Protocol在这个环节是没有的。

    • cachedWrapperClasses = new ConcurrentHashSet<Class<?>>();//当这个class不含有Adaptive注解,并且包含目标接口(type)类型。例如protocol里边的spi就只有ProtocolFilterWrapper和ProcolListerWrapper能命中。

    • cacheActivates. //剩下的类,包含Activate注解。

    • cacheNames. // 剩下的类就存储在这里。

      ```
      private Class<?> getAdaptiveExtensionClass() {
             getExtensionClasses();
             if (cachedAdaptiveClass != null) {
                 return cachedAdaptiveClass;
             }
             return cachedAdaptiveClass = createAdaptiveExtensionClass();
      }
      ```
      
    • 如果cachedAdaptiveClass存在,就直接返回。不存在就生成动态类。

    private Class<?> createAdaptiveExtensionClass() {
    String code = createAdaptiveExtensionClassCode();//创建接口的代理类实现
    ClassLoader classLoader = findClassLoader();//获取当前使用的类加载器
    com.alibaba.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();//获取代码编译器
    return compiler.compile(code, classLoader);//加载代理
    }
    
    

此时回忆一下Protocol结构

 /**
     * 暴露远程服务:<br>
     * 1. 协议在接收请求时,应记录请求来源方地址信息:RpcContext.getContext().setRemoteAddress();<br>
     * 2. export()必须是幂等的,也就是暴露同一个URL的Invoker两次,和暴露一次没有区别。<br>
     * 3. export()传入的Invoker由框架实现并传入,协议不需要关心。<br>
     *
     * @param <T>     服务的类型
     * @param invoker 服务的执行体
     * @return exporter 暴露服务的引用,用于取消暴露
     * @throws RpcException 当暴露服务出错时抛出,比如端口已占用
     */
    @Adaptive
    <T> Exporter<T> export(Invoker<T> invoker) throws RpcException;

    /**
     * 引用远程服务:<br>
     * 1. 当用户调用refer()所返回的Invoker对象的invoke()方法时,协议需相应执行同URL远端export()传入的Invoker对象的invoke()方法。<br>
     * 2. refer()返回的Invoker由协议实现,协议通常需要在此Invoker中发送远程请求。<br>
     * 3. 当url中有设置check=false时,连接失败不能抛出异常,并内部自动恢复。<br>
     *
     * @param <T>  服务的类型
     * @param type 服务的类型
     * @param url  远程服务的URL地址
     * @return invoker 服务的本地代理
     * @throws RpcException 当连接服务提供方失败时抛出
     */
    @Adaptive
    <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException;

    /**
     * 释放协议:<br>
     * 1. 取消该协议所有已经暴露和引用的服务。<br>
     * 2. 释放协议所占用的所有资源,比如连接和端口。<br>
     * 3. 协议在释放后,依然能暴露和引用新的服务。<br>
     */

动态生成的协议代理类

    public class Protocol$Adpative implements com.alibaba.dubbo.rpc.Protocol {
        public void destroy() {throw new UnsupportedOperationException("method public abstract void com.alibaba.dubbo.rpc.Protocol.destroy() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");
        }
        public int getDefaultPort() {throw new UnsupportedOperationException("method public abstract int com.alibaba.dubbo.rpc.Protocol.getDefaultPort() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");
        }
        public com.alibaba.dubbo.rpc.Exporter export(com.alibaba.dubbo.rpc.Invoker arg0) throws com.alibaba.dubbo.rpc.RpcException {
            if (arg0 == null) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument == null");
 
            if (arg0.getUrl() == null) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument getUrl() == null");
 
            com.alibaba.dubbo.common.URL url = arg0.getUrl();
 
            //默认选择dubbo协议,否则根据url中带的协议属性来选择对应的协议处理对象,这样可以动态选择不同的协议
            String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() );
 
            if(extName == null) throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])");
 
            //根据拿到的协议key从缓存的map中取协议对象
            com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol)ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
 
            return extension.export(arg0);
        }
        public com.alibaba.dubbo.rpc.Invoker refer(java.lang.Class arg0, com.alibaba.dubbo.common.URL arg1) throws com.alibaba.dubbo.rpc.RpcException {
 
            if (arg1 == null) throw new IllegalArgumentException("url == null");
 
            com.alibaba.dubbo.common.URL url = arg1;
 
            String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() );
 
            if(extName == null) throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])");
 
            //根据拿到的协议key从缓存的map中取协议对象
            com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol)ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
 
            return extension.refer(arg0, arg1);
        }
    }

到这里接口的代理已经生成啦!再回退到createAdaptiveExtension方法中。

injectExtension((T) getAdaptiveExtensionClass().newInstance());

作用:

  • 进入IOC反转控制机制判断接口代理类中是否有需要注入的属性。

6、dubbo自己的IOC和AOP原理

getExtension(String name)

getExtension(String name) //指定对象缓存在cachedInstances;get出来的对象wrapper对象,例如protocol就是ProtocolFilterWrapper和ProtocolListenerWrapper其中一个。
  -->createExtension(String name)
    -->getExtensionClasses()
    -->injectExtension(T instance)//dubbo的IOC反转控制,就是从spi和spring里面提取对象赋值。
      -->objectFactory.getExtension(pt, property)
        -->SpiExtensionFactory.getExtension(type, name)
          -->ExtensionLoader.getExtensionLoader(type)
          -->loader.getAdaptiveExtension()
        -->SpringExtensionFactory.getExtension(type, name)
          -->context.getBean(name)
    -->injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance))//AOP的简单设计

7、dubbo的动态编译

上文我们讲到编译类,那么Dubbo是怎么实现的呢

com.alibaba.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();//获取代码编译器
        return compiler.compile(code, classLoader);//加载代理
image

第一步


image

第二步


image
第三步
image

第四步


image
第五步
image

在看JavassistCompiler 编译类之前,首先需要移动的javac基础知识。下边我们看一个例子

public class CompilerByJavassist {

    public static void main(String[] args) throws Exception {

        // ClassPool:CtClass对象的容器
        ClassPool pool = ClassPool.getDefault();

        // 通过ClassPool生成一个public新类Emp.java
        CtClass ctClass = pool.makeClass("com.study.javassist.Emp");

        // 添加属性
        // 首先添加属性private String ename
        CtField enameField = new CtField(pool.getCtClass("java.lang.String"),
                "ename", ctClass);
        enameField.setModifiers(Modifier.PRIVATE);
        ctClass.addField(enameField);

        // 其次添加熟悉privtae int eno
        CtField enoField = new CtField(pool.getCtClass("int"), "eno", ctClass);
        enoField.setModifiers(Modifier.PRIVATE);
        ctClass.addField(enoField);

        // 为属性ename和eno添加getXXX和setXXX方法
        ctClass.addMethod(CtNewMethod.getter("getEname", enameField));
        ctClass.addMethod(CtNewMethod.setter("setEname", enameField));
        ctClass.addMethod(CtNewMethod.getter("getEno", enoField));
        ctClass.addMethod(CtNewMethod.setter("setEno", enoField));

        // 添加构造函数
        CtConstructor ctConstructor = new CtConstructor(new CtClass[] {},
                ctClass);
        // 为构造函数设置函数体
        StringBuffer buffer = new StringBuffer();
        buffer.append("{\n").append("ename=\"yy\";\n").append("eno=001;\n}");
        ctConstructor.setBody(buffer.toString());
        // 把构造函数添加到新的类中
        ctClass.addConstructor(ctConstructor);

        // 添加自定义方法
        CtMethod ctMethod = new CtMethod(CtClass.voidType, "printInfo",
                new CtClass[] {}, ctClass);
        // 为自定义方法设置修饰符
        ctMethod.setModifiers(Modifier.PUBLIC);
        // 为自定义方法设置函数体
        StringBuffer buffer2 = new StringBuffer();
        buffer2.append("{\nSystem.out.println(\"begin!\");\n")
                .append("System.out.println(ename);\n")
                .append("System.out.println(eno);\n")
                .append("System.out.println(\"over!\");\n").append("}");
        ctMethod.setBody(buffer2.toString());
        ctClass.addMethod(ctMethod);

        //最好生成一个class
        Class<?> clazz = ctClass.toClass();
        Object obj = clazz.newInstance();
        //反射 执行方法
        obj.getClass().getMethod("printInfo", new Class[] {})
                .invoke(obj, new Object[] {});

        // 把生成的class文件写入文件
        byte[] byteArr = ctClass.toBytecode();
        FileOutputStream fos = new FileOutputStream(new File("D://Emp.class"));
        fos.write(byteArr);
        fos.close();
    }
}

此时就不难理解上边第五步的代码了。

相关文章

网友评论

    本文标题:Dubbo源码解析-能入门系列

    本文链接:https://www.haomeiwen.com/subject/pyavnftx.html