extension_extensions - CSDN
精华内容
参与话题
  • extension(类扩展) 1、进行一个类扩展

    extension(类扩展)

    这里写图片描述

    简单来说,extension在.m文件中添加,所以其权限为private,所以只能拿到源码的类添加extension。另外extension是编译时决议,和interface和implement里的代码融合在一块了一般。


    category(类别)

    category能在不继承类的情况下给类动态添加方法。

    1、创建category

    这里写图片描述

    关于@dynamic的特性及用法可参考:
    https://blog.csdn.net/qq_28446287/article/details/79094491


    2、category的优缺点

    • 可以将类的实现代码分散到多个不同的文件或框架中
      这里写图片描述

    • 创建对私有方法的前向引用
      OC语法中,你不能对一个类的方法定义为private,只有+、-,对实例变量可以private、public。
      具体可参考此文档http://www.cnblogs.com/stevenwuzheng/p/5457487.html

    • 向对象添加非正式协议
      对NSObject进行一个类别叫做非正式协议,可以只实现想要的方法


    3、category在runtime中的源码

    typedef struct objc_category *Category;
    struct objc_category 
    {
        //类别的名字
        char * _Nonnull category_name                            OBJC2_UNAVAILABLE;
        //该类的名字
        char * _Nonnull class_name                               OBJC2_UNAVAILABLE;
        //实例方法列表
        struct objc_method_list * _Nullable instance_methods     OBJC2_UNAVAILABLE;
        //类方法列表
        struct objc_method_list * _Nullable class_methods        OBJC2_UNAVAILABLE;
        //协议列表
        struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;
    }                                                            OBJC2_UNAVAILABLE;

    明显看出,Category没有容纳变量的地方。


    4、category的原理

    objective-c的运行依赖runtime,runtime依赖于dyld动态加载,查看objc-os.mm文件发现其调用栈如下:

    void _objc_init(void)
    {
        static bool initialized = false;
        if (initialized) return;
        initialized = true;
    
        // fixme defer initialization until an objc-using image is found?
        environ_init();
        tls_init();
        static_init();
        lock_init();
        exception_init();
    
        // Register for unmap first, in case some +load unmaps something
        _dyld_register_func_for_remove_image(&unmap_image);
        dyld_register_image_state_change_handler(dyld_image_state_bound,
                                                 1/*batch*/, &map_2_images);
        dyld_register_image_state_change_handler(dyld_image_state_dependents_initialized, 0/*not batch*/, &load_images);
    }
    

    category被附加到类上是在map_images的时候发生的。在new-ABI的标准下,_objc_init函数里调用的map_iamges最终会调用objc-runtime-new.mm中的_read_images函数。_read_images中部分代码:

     // Discover categories. 
        for (EACH_HEADER) {
            category_t **catlist = 
                _getObjc2CategoryList(hi, &count);
            for (i = 0; i < count; i++) {
                category_t *cat = catlist[i];
                Class cls = remapClass(cat->cls);
    
                if (!cls) {
                    // Category's target class is missing (probably weak-linked).
                    // Disavow any knowledge of this category.
                    catlist[i] = nil;
                    if (PrintConnecting) {
                        _objc_inform("CLASS: IGNORING category \?\?\?(%s) %p with "
                                     "missing weak-linked target class", 
                                     cat->name, cat);
                    }
                    continue;
                }
    
                // Process this category. 
                // First, register the category with its target class. 
                // Then, rebuild the class's method lists (etc) if 
                // the class is realized. 
                bool classExists = NO;
                if (cat->instanceMethods ||  cat->protocols  
                    ||  cat->instanceProperties) 
                {
                    addUnattachedCategoryForClass(cat, cls, hi);
                    if (cls->isRealized()) {
                        remethodizeClass(cls);
                        classExists = YES;
                    }
                    if (PrintConnecting) {
                        _objc_inform("CLASS: found category -%s(%s) %s", 
                                     cls->nameForLogging(), cat->name, 
                                     classExists ? "on existing class" : "");
                    }
                }
    
                if (cat->classMethods  ||  cat->protocols  
                    /* ||  cat->classProperties */) 
                {
                    addUnattachedCategoryForClass(cat, cls->ISA(), hi);
                    if (cls->ISA()->isRealized()) {
                        remethodizeClass(cls->ISA());
                    }
                    if (PrintConnecting) {
                        _objc_inform("CLASS: found category +%s(%s)", 
                                     cls->nameForLogging(), cat->name);
                    }
                }
            }
        }
    
        ts.log("IMAGE TIMES: discover categories");

    从代码可以看出:

    将category和它的主类(或元类)注册到哈希表中.
    如果主类(或元类)已经实现,那么重建它的方列表。

    category中的实例方法和属性被整合到主类中,而类方法被整合到元类中。而协议被同时整合到了主类和元类中。

    /***********************************************************************
    * remethodizeClass
    * Attach outstanding categories to an existing class.
    * Fixes up cls's method list, protocol list, and property list.
    * Updates method caches for cls and its subclasses.
    * Locking: runtimeLock must be held by the caller
    **********************************************************************/
    static void remethodizeClass(Class cls)
    {
        category_list *cats;
        bool isMeta;
    
        runtimeLock.assertWriting();
    
        isMeta = cls->isMetaClass();
    
        // Re-methodizing: check for more categories
        if ((cats = unattachedCategoriesForClass(cls, false/*not realizing*/))) {
            if (PrintConnecting) {
                _objc_inform("CLASS: attaching categories to class '%s' %s", 
                             cls->nameForLogging(), isMeta ? "(meta)" : "");
            }
    
            attachCategories(cls, cats, true /*flush caches*/);        
            free(cats);
        }
    }

    如注释所说,此函数将未处理的category整合到主类(或元类),整合cls的方法、协议、属性列表,更新cls及其子类的方法缓存。

    查看其中的attachCategories函数:

    // Attach method lists and properties and protocols from categories to a class.
    // Assumes the categories in cats are all loaded and sorted by load order, 
    // oldest categories first.
    static void 
    attachCategories(Class cls, category_list *cats, bool flush_caches)
    {
        if (!cats) return;
        if (PrintReplacedMethods) printReplacements(cls, cats);
    
        bool isMeta = cls->isMetaClass();
    
        // fixme rearrange to remove these intermediate allocations
        method_list_t **mlists = (method_list_t **)
            malloc(cats->count * sizeof(*mlists));
        property_list_t **proplists = (property_list_t **)
            malloc(cats->count * sizeof(*proplists));
        protocol_list_t **protolists = (protocol_list_t **)
            malloc(cats->count * sizeof(*protolists));
    
        // Count backwards through cats to get newest categories first
        int mcount = 0;
        int propcount = 0;
        int protocount = 0;
        int i = cats->count;
        bool fromBundle = NO;
        while (i--) {
            auto& entry = cats->list[i];
    
            method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
            if (mlist) {
                mlists[mcount++] = mlist;
                fromBundle |= entry.hi->isBundle();
            }
    
            property_list_t *proplist = entry.cat->propertiesForMeta(isMeta);
            if (proplist) {
                proplists[propcount++] = proplist;
            }
    
            protocol_list_t *protolist = entry.cat->protocols;
            if (protolist) {
                protolists[protocount++] = protolist;
            }
        }
    
        auto rw = cls->data();
    
        prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
        rw->methods.attachLists(mlists, mcount);
        free(mlists);
        if (flush_caches  &&  mcount > 0) flushCaches(cls);
    
        rw->properties.attachLists(proplists, propcount);
        free(proplists);
    
        rw->protocols.attachLists(protolists, protocount);
        free(protolists);
    }

    通过while循环,遍历所有的category,得每个category的方法列表mlist、proplist和protolist并存入主类(或元类)的mlists、proplists和protolists中。从而更新类的数据字段data()中mlist、proplist和protolist的值。

    category没有替换掉原来类的方法,也就是说如果category和原来类有method1,那么在将category整合到原来类之后,类的method_list会有两个method1

    category中的method1会被放在类的method_list前面,而原来类的method1被放 到了method_list后面,在调用时候会先调用method_list前面的,所以看起来是将原来类的method1覆盖了,实际上并不是那么回事。


    5、category的两个面试题

    3.1 一个类和它的category同时拥有一个方法,在调用时候调用哪一个?答案参考“2、category的原理”

    3.2 一个类有多个category并都拥有同一个方法,在调用时候调用哪一个?答案参考“2、category的原理”

    举个例子:

    //#import "type.m"
    - (void)test
    {
        NSLog(@"type class!!");
    }
    
    //#import "type+xxxx.m"
    - (void)test
    {
        NSLog(@"category xxxx");
    }
    
    //#import "type+xxxx1.m"
    - (void)test
    {
        NSLog(@"category xxxx1");
    }
    
    //#import "type+xxxx2.m"
    - (void)test
    {
        NSLog(@"category xxxx2");
    }

    这里写图片描述
    这里写图片描述
    这里写图片描述

    可以知道,输出的结果跟compile source文件中的category的.m文件放置顺序有关。且放最底部的时候输出(主类.m文件的放置不影响,理由参考”2、category的原理”)


    6、category动态添加变量

    @interface type (xxxx)
    
    @property (nonatomic, assign)  NSInteger number;
    
    @end
    
    
    static void *numberKey = &numberKey;
    
    @implementation type (xxxx)
    
    - (void)setNumber:(NSInteger)number
    {
        objc_setAssociatedObject(self, &numberKey, [NSString stringWithFormat:@"%ld",number], OBJC_ASSOCIATION_ASSIGN);
    
    }
    
    - (NSInteger)number
    {
        return [objc_getAssociatedObject(self, &numberKey) integerValue];
    }
    
    @end
    

    objc_setAssociatedObject和objc_getAssociatedObject的描述可参考:https://www.cnblogs.com/liuting-1204/p/6526342.html

    展开全文
  • extension 生成的对象

    2019-12-27 16:56:49
    版本 # cat /etc/centos-release CentOS Linux release 7.4.1708 (Core) # # # su - postgres $ $ $ psql psql (10.10) Type "help" for help. postgres=# select version(); ...

    版本

    # cat /etc/centos-release
    CentOS Linux release 7.4.1708 (Core) 
    # 
    # 
    # su - postgres
    $ 
    $ 
    $ psql
    psql (10.10)
    Type "help" for help.
    
    postgres=# select version();
                                                     version                                                  
    ----------------------------------------------------------------------------------------------------------
     PostgreSQL 10.10 on x86_64-pc-linux-gnu, compiled by gcc (GCC) 4.8.5 20150623 (Red Hat 4.8.5-36), 64-bit
    (1 row)
    
    postgres=# 
    
    

    extension

    生成 pg_pathman extension

    postgres=# create extension pg_pathman;
    
    postgres=# \dx
                                          List of installed extensions
        Name     | Version |   Schema   |                            Description                            
    -------------+---------+------------+-------------------------------------------------------------------
     pg_pathman  | 1.5     | public     | Partitioning tool for PostgreSQL
     plpgsql     | 1.0     | pg_catalog | PL/pgSQL procedural language
    (2 rows)
    
    

    查看 extension 生成对象

    postgres=# \dx+ pg_pathman
                                      Objects in extension "pg_pathman"
                                             Object description                                          
    -----------------------------------------------------------------------------------------------------
     event trigger pathman_ddl_trigger
     function add_range_partition(regclass,anyelement,anyelement,text,text)
     function add_to_pathman_config(regclass,text)
     function add_to_pathman_config(regclass,text,text)
     function alter_partition(regclass,text,regnamespace,text)
     function append_partition_internal(regclass,regtype,text,anyarray,text,text)
     function append_range_partition(regclass,text,text)
     function attach_range_partition(regclass,regclass,anyelement,anyelement)
     function build_check_constraint_name(regclass)
     function build_hash_condition(regtype,text,integer,integer)
     function build_range_condition(regclass,text,anyelement,anyelement)
     function build_sequence_name(regclass)
     function check_boundaries(regclass,text,anyelement,anyelement)
     function check_range_available(regclass,anyelement,anyelement)
     function check_security_policy(regclass)
     function copy_foreign_keys(regclass,regclass)
     function create_hash_partitions_internal(regclass,text,integer,text[],text[])
     function create_hash_partitions(regclass,text,integer,boolean,text[],text[])
     function create_naming_sequence(regclass)
     function create_range_partitions_internal(regclass,anyarray,text[],text[])
     function create_range_partitions(regclass,text,anyarray,text[],text[],boolean)
     function create_range_partitions(regclass,text,anyelement,anyelement,integer,boolean)
     function create_range_partitions(regclass,text,anyelement,interval,integer,boolean)
     function create_single_range_partition(regclass,anyelement,anyelement,text,text)
     function debug_capture()
     function detach_range_partition(regclass)
     function disable_pathman_for(regclass)
     function drop_naming_sequence(regclass)
     function drop_partitions(regclass,boolean)
     function drop_range_partition_expand_next(regclass)
     function drop_range_partition(regclass,boolean)
     function generate_range_bounds(anyelement,anyelement,integer)
     function generate_range_bounds(anyelement,interval,integer)
     function get_base_type(regtype)
     function get_hash_part_idx(integer,integer)
     function get_number_of_partitions(regclass)
     function get_parent_of_partition(regclass)
     function get_partition_cooked_key(regclass)
     function get_partition_key(regclass)
     function get_partition_key_type(regclass)
     function get_partition_type(regclass)
     function get_part_range(regclass,anyelement)
     function get_part_range(regclass,integer,anyelement)
     function get_plain_schema_and_relname(regclass)
     function get_tablespace(regclass)
     function invoke_on_partition_created_callback(regclass,regclass,regprocedure)
     function invoke_on_partition_created_callback(regclass,regclass,regprocedure,anyelement,anyelement)
     function is_date_type(regtype)
     function is_operator_supported(regtype,text)
     function is_tuple_convertible(regclass,regclass)
     function merge_range_partitions(regclass[])
     function _partition_data_concurrent(regclass,anyelement,anyelement,integer)
     function partition_data(regclass)
     function partition_table_concurrently(regclass,integer,double precision)
     function pathman_config_params_trigger_func()
     function pathman_ddl_trigger_func()
     function pathman_set_param(regclass,text,anyelement)
     function pathman_version()
     function prepare_for_partitioning(regclass,text,boolean)
     function prepend_partition_internal(regclass,regtype,text,anyarray,text,text)
     function prepend_range_partition(regclass,text,text)
     function prevent_data_modification(regclass)
     function prevent_part_modification(regclass)
     function replace_hash_partition(regclass,regclass,boolean)
     function set_auto(regclass,boolean)
     function set_enable_parent(regclass,boolean)
     function set_init_callback(regclass,regprocedure)
     function set_interval(regclass,anyelement)
     function set_spawn_using_bgw(regclass,boolean)
     function show_cache_stats()
     function show_concurrent_part_tasks()
     function show_partition_list()
     function split_range_partition(regclass,anyelement,text,text)
     function stop_concurrent_part_task(regclass)
     function validate_expression(regclass,text)
     function validate_interval_value(regclass,text,integer,text)
     function validate_part_callback(regprocedure,boolean)
     function validate_relname(regclass)
     table pathman_config
     table pathman_config_params
     view pathman_cache_stats
     view pathman_concurrent_part_tasks
     view pathman_partition_list
    (83 rows)
    
    

    开启参数 log_statement =‘all’ 后,可以从日志里抓取到对应的SQL。

    SELECT e.extname AS "Name", e.extversion AS "Version", n.nspname AS "Schema", c.description AS "Description"
    FROM pg_catalog.pg_extension e 
         LEFT JOIN pg_catalog.pg_namespace n 
    	           ON n.oid = e.extnamespace 
    	 LEFT JOIN pg_catalog.pg_description c 
    	           ON c.objoid = e.oid 
    			   AND c.classoid = 'pg_catalog.pg_extension'::pg_catalog.regclass
    ORDER BY 1;
    
    
    SELECT e.extname, e.oid
      FROM pg_catalog.pg_extension e
     WHERE e.extname OPERATOR(pg_catalog.~) '^(pg_pathman)$'
     ORDER BY 1;
    
    SELECT pg_catalog.pg_describe_object(classid, objid, 0) AS "Object description"
      FROM pg_catalog.pg_depend
     WHERE refclassid = 'pg_catalog.pg_extension'::pg_catalog.regclass 
       AND refobjid = '24619' AND deptype = 'e'
     ORDER BY 1;
    
    
    

    做些改进后,可以查看所有extension 的表、视图对象

    SELECT pe.extoid,pe.extname,
    	   pe.extnspoid,pe.extnspname,
    	   pc.oid as reloid,pc.relname,pc.relkind,
    	   pn.oid as relnspoid,pn.nspname as relnspname,
    	   pg_catalog.pg_describe_object(pd.classid, pd.objid, 0) as  objdescription,
    	   pd.classid, pd.objid
      FROM pg_catalog.pg_depend pd,
    	   (  SELECT e.oid as extoid,e.extname , 
    					   e.extversion , 
    					   n.oid as extnspoid,n.nspname as extnspname ,c.description 
    			FROM pg_catalog.pg_extension e 
    					  LEFT JOIN pg_catalog.pg_namespace n 
    								 ON n.oid = e.extnamespace 
    					  LEFT JOIN pg_catalog.pg_description c 
    								 ON c.objoid = e.oid 
    									   AND c.classoid = 'pg_catalog.pg_extension'::pg_catalog.regclass
    			ORDER BY 1 ) pe,
    		pg_catalog.pg_class pc,
    		pg_catalog.pg_namespace pn
      WHERE pd.refclassid = 'pg_catalog.pg_extension'::pg_catalog.regclass 
        AND pd.refobjid = pe.extoid
        AND pd.deptype = 'e'
        AND pd.classid = (select pc0.oid from pg_class pc0 where pc0.oid= 'pg_catalog.pg_class'::pg_catalog.regclass)
        AND pd.objid = pc.oid
        AND pc.relnamespace = pn.oid
    ORDER BY 1;
    
    

    参考:

    展开全文
  • 扩展点加载机制(ExtensionLoader)

    万次阅读 2015-04-08 21:52:50
    概述来源: Dubbo的扩展点加载从JDK标准的SPI(Service Provider Interface)扩展点发现机制加强而来。Dubbo改进了JDK标准的SPI的以下问题: + JDK标准的SPI会一次性实例化扩展点所有实现,如果有扩展实现初始化很...

    概述

    来源:
    Dubbo的扩展点加载从JDK标准的SPI(Service Provider Interface)扩展点发现机制加强而来。

    Dubbo改进了JDK标准的SPI的以下问题:

    • JDK标准的SPI会一次性实例化扩展点所有实现,如果有扩展实现初始化很耗时,但如果没用上也加载,会很浪费资源。

    • 如果扩展点加载失败,连扩展点的名称都拿不到了。比如:JDK标准的ScriptEngine,通过getName();获取脚本类型的名称,但如果RubyScriptEngine因为所依赖的jruby.jar不存在,导致RubyScriptEngine类加载失败,这个失败原因被吃掉了,和ruby对应不起来,当用户执行ruby脚本时,会报不支持ruby,而不是真正失败的原因。

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

    约定:
    在扩展类的jar包内,放置扩展点配置文件:META-INF/dubbo/接口全限定名,内容为:配置名=扩展实现类全限定名,多个实现类用换行符分隔。
    (注意:这里的配置文件是放在你自己的jar包内,不是dubbo本身的jar包内,Dubbo会全ClassPath扫描所有jar包内同名的这个文件,然后进行合并)

    扩展Dubbo的协议示例:
    在协议的实现jar包内放置文本文件:META-INF/dubbo/com.alibaba.dubbo.rpc.Protocol,内容为:

    xxx=com.alibaba.xxx.XxxProtocol

    实现内容:

    package com.alibaba.xxx;
    
    import com.alibaba.dubbo.rpc.Protocol;
    public class XxxProtocol implemenets Protocol {
    
        // ...
    }

    注意: 扩展点使用单一实例加载(请确保扩展实现的线程安全性),Cache在ExtensionLoader中

    特性

    • 扩展点自动包装
    • 扩展点自动装配
    • 扩展点自适应
    • 扩展点自动激活

    相关文档可以参考dubbo的官方文档 ,本文主要通过分析相关的源代码来体会dubbo的扩展点框架提供的特性。


    源码分析

    dubbo的扩展点框架主要位于这个包下:

    com.alibaba.dubbo.common.extension

    大概结构如下:

    com.alibaba.dubbo.common.extension
     |
     |--factory
     |     |--AdaptiveExtensionFactory   #稍后解释
     |     |--SpiExtensionFactory        #稍后解释
     |
     |--support
     |     |--ActivateComparator
     |
     |--Activate  #自动激活加载扩展的注解
     |--Adaptive  #自适应扩展点的注解
     |--ExtensionFactory  #扩展点对象生成工厂接口
     |--ExtensionLoader   #扩展点加载器,扩展点的查找,校验,加载等核心逻辑的实现类
     |--SPI   #扩展点注解

    其中最核心的类就是ExtensionLoader,几乎所有特性都在这个类中实现,先来看下他的结构:

    ExtensionLoader

    ExtensionLoader没有提供public的构造方法,但是提供了一个public staticgetExtensionLoader,这个方法就是获取ExtensionLoader实例的工厂方法。其public成员方法中有三个比较重要的方法:

    • getActivateExtension :根据条件获取当前扩展可自动激活的实现
    • getExtension : 根据名称获取当前扩展的指定实现
    • getAdaptiveExtension : 获取当前扩展的自适应实现

    这三个方法将会是我们重点关注的方法;* 每一个ExtensionLoader实例仅负责加载特定SPI扩展的实现*。因此想要获取某个扩展的实现,首先要获取到该扩展对应的ExtensionLoader实例,下面我们就来看一下获取ExtensionLoader实例的工厂方法getExtensionLoader

    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!");
        }
    
        // 先从静态缓存中获取对应的ExtensionLoader实例
        ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
        if (loader == null) {
            EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type)); // 为Extension类型创建ExtensionLoader实例,并放入静态缓存
            loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
        }
        return loader;
    }

    该方法需要一个Class类型的参数,该参数表示希望加载的扩展点类型,该参数必须是接口,且该接口必须被@SPI注解注释,否则拒绝处理。检查通过之后首先会检查ExtensionLoader缓存中是否已经存在该扩展对应的ExtensionLoader,如果有则直接返回,否则创建一个新的ExtensionLoader负责加载该扩展实现,同时将其缓存起来。可以看到对于每一个扩展,dubbo中只会有一个对应的ExtensionLoader实例。

    接下来看下ExtensionLoader的私有构造函数:

    private ExtensionLoader(Class<?> type) {
        this.type = type;
    
        // 如果扩展类型是ExtensionFactory,那么则设置为null
        // 这里通过getAdaptiveExtension方法获取一个运行时自适应的扩展类型(每个Extension只能有一个@Adaptive类型的实现,如果没有dubbo会动态生成一个类)
        objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
    }

    这里保存了对应的扩展类型,并且设置了一个额外的objectFactory属性,他是一个ExtensionFactory类型,ExtensionFactory主要用于加载扩展的实现:

    @SPI
    public interface ExtensionFactory {
    
        /**
         * Get extension.
         * 
         * @param type object type.
         * @param name object name.
         * @return object instance.
         */
        <T> T getExtension(Class<T> type, String name);
    
    }

    同时ExtensionFactory也被@SPI注解注释,说明他也是一个扩展点,从前面com.alibaba.dubbo.common.extension包的结构图中可以看到,dubbo内部提供了两个实现类:SpiExtensionFactoryAdaptiveExtensionFactory,实际上还有一个SpringExtensionFactory,不同的实现可以已不同的方式来完成扩展点实现的加载,这块稍后再来学习。从ExtensionLoader的构造函数中可以看到,如果要加载的扩展点类型是ExtensionFactory是,object字段被设置为null。由于ExtensionLoader的使用范围有限(基本上局限在ExtensionLoader中),因此对他做了特殊对待:在需要使用ExtensionFactory的地方,都是通过对应的自适应实现来代替。

    默认的ExtensionFactory实现中,AdaptiveExtensionFactotry@Adaptive注解注释,也就是它就是ExtensionFactory对应的自适应扩展实现(每个扩展点最多只能有一个自适应实现,如果所有实现中没有被@Adaptive注释的,那么dubbo会动态生成一个自适应实现类),也就是说,所有对ExtensionFactory调用的地方,实际上调用的都是AdpativeExtensionFactory,那么我们看下他的实现代码:

    @Adaptive
    public class AdaptiveExtensionFactory implements ExtensionFactory {
    
        private final List<ExtensionFactory> factories;
    
        public AdaptiveExtensionFactory() {
            ExtensionLoader<ExtensionFactory> loader = ExtensionLoader.getExtensionLoader(ExtensionFactory.class);
            List<ExtensionFactory> list = new ArrayList<ExtensionFactory>();
            for (String name : loader.getSupportedExtensions()) { // 将所有ExtensionFactory实现保存起来
                list.add(loader.getExtension(name));
            }
            factories = Collections.unmodifiableList(list);
        }
    
        public <T> T getExtension(Class<T> type, String name) {
            // 依次遍历各个ExtensionFactory实现的getExtension方法,一旦获取到Extension即返回
            // 如果遍历完所有的ExtensionFactory实现均无法找到Extension,则返回null
            for (ExtensionFactory factory : factories) {
                T extension = factory.getExtension(type, name);
                if (extension != null) {
                    return extension;
                }
            }
            return null;
        }
    
    }

    看完代码大家都知道是怎么回事了,这货就相当于一个代理入口,他会遍历当前系统中所有的ExtensionFactory实现来获取指定的扩展实现,获取到扩展实现或遍历完所有的ExtensionFactory实现。这里调用了ExtensionLoadergetSupportedExtensions方法来获取ExtensionFactory的所有实现,又回到了ExtensionLoader类,下面我们就来分析ExtensionLoader的几个重要的实例方法。

    方法调用流程

    getExtension

    getExtension(name)
        -> createExtension(name) #如果无缓存则创建
            -> getExtensionClasses().get(name) #获取name对应的扩展类型
            -> 实例化扩展类
            -> injectExtension(instance) # 扩展点注入
            -> instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance)) #循环遍历所有wrapper实现,实例化wrapper并进行扩展点注入  

    getAdaptiveExtension

    public T getAdaptiveExtension()
        -> createAdaptiveExtension() #如果无缓存则创建
            -> getAdaptiveExtensionClass().newInstance() #获取AdaptiveExtensionClass
                -> getExtensionClasses() # 加载当前扩展所有实现,看是否有实现被标注为@Adaptive
                -> createAdaptiveExtensionClass() #如果没有实现被标注为@Adaptive,则动态创建一个Adaptive实现类
                    -> createAdaptiveExtensionClassCode() #动态生成实现类java代码
                    -> compiler.compile(code, classLoader) #动态编译java代码,加载类并实例化
            -> injectExtension(instance)

    getActivateExtesion
    该方法有多个重载方法,不过最终都是调用了三个参数的那一个重载形式。其代码结构也相对剪短,就不需要在列出概要流程了。


    详细代码分析

    getAdaptiveExtension
    从前面ExtensionLoader的私有构造函数中可以看出,在选择ExtensionFactory的时候,并不是调用getExtension(name)来获取某个具体的实现类,而是调用getAdaptiveExtension来获取一个自适应的实现。那么首先我们就来分析一下getAdaptiveExtension这个方法的实现吧:

    public T getAdaptiveExtension() {
        Object instance = cachedAdaptiveInstance.get(); // 首先判断是否已经有缓存的实例对象
        if (instance == null) {
            if(createAdaptiveInstanceError == null) {
                synchronized (cachedAdaptiveInstance) {
                    instance = cachedAdaptiveInstance.get();
                    if (instance == null) {
                        try {
                            instance = createAdaptiveExtension(); // 没有缓存的实例,创建新的AdaptiveExtension实例
                            cachedAdaptiveInstance.set(instance);
                        } catch (Throwable t) {
                            createAdaptiveInstanceError = t;
                            throw new IllegalStateException("fail to create adaptive instance: " + t.toString(), t);
                        }
                    }
                }
            }
            else {
                throw new IllegalStateException("fail to create adaptive instance: " + createAdaptiveInstanceError.toString(), createAdaptiveInstanceError);
            }
        }
    
        return (T) instance;
    }

    首先检查缓存的adaptiveInstance是否存在,如果存在则直接使用,否则的话调用createAdaptiveExtension方法来创建新的adaptiveInstance并且缓存起来。也就是说对于某个扩展点,每次调用ExtensionLoader.getAdaptiveExtension获取到的都是同一个实例。

    private T createAdaptiveExtension() {
        try {
            return injectExtension((T) getAdaptiveExtensionClass().newInstance()); // 先获取AdaptiveExtensionClass,在获取其实例,最后进行注入处理
        } catch (Exception e) {
            throw new IllegalStateException("Can not create adaptive extenstion " + type + ", cause: " + e.getMessage(), e);
        }
    }

    createAdaptiveExtension方法中,首先通过getAdaptiveExtensionClass方法获取到最终的自适应实现类型,然后实例化一个自适应扩展实现的实例,最后进行扩展点注入操作。先看一个getAdaptiveExtensionClass方法的实现:

    private Class<?> getAdaptiveExtensionClass() {
        getExtensionClasses(); // 加载当前Extension的所有实现,如果有@Adaptive类型,则会赋值为cachedAdaptiveClass属性缓存起来
        if (cachedAdaptiveClass != null) {
            return cachedAdaptiveClass;
        }
        return cachedAdaptiveClass = createAdaptiveExtensionClass(); // 没有找到@Adaptive类型实现,则动态创建一个AdaptiveExtensionClass
    }

    他只是简单的调用了getExtensionClasses方法,然后在判adaptiveCalss缓存是否被设置,如果被设置那么直接返回,否则调用createAdaptiveExntesionClass方法动态生成一个自适应实现,关于动态生成自适应实现类然后编译加载并且实例化的过程这里暂时不分析,留到后面在分析吧。这里我们看getExtensionClassses方法:

    private Map<String, Class<?>> getExtensionClasses() {
        Map<String, Class<?>> classes = cachedClasses.get(); // 判断是否已经加载了当前Extension的所有实现类
        if (classes == null) {
            synchronized (cachedClasses) {
                classes = cachedClasses.get();
                if (classes == null) {
                    classes = loadExtensionClasses(); // 如果还没有加载Extension的实现,则进行扫描加载,完成后赋值给cachedClasses变量
                    cachedClasses.set(classes);
                }
            }
        }
        return classes;
    }

    getExtensionClasses方法中,首先检查缓存的cachedClasses,如果没有再调用loadExtensionClasses方法来加载,加载完成之后就会进行缓存。也就是说对于每个扩展点,其实现的加载只会执行一次。我们看下loadExtensionClasses方法:

    private Map<String, Class<?>> loadExtensionClasses() {
        final SPI defaultAnnotation = type.getAnnotation(SPI.class);
        if(defaultAnnotation != null) {
            String value = defaultAnnotation.value(); // 解析当前Extension配置的默认实现名,赋值给cachedDefaultName属性
            if(value != null && (value = value.trim()).length() > 0) {
                String[] names = NAME_SEPARATOR.split(value);
                if(names.length > 1) { // 每个扩展实现只能配置一个名称
                    throw new IllegalStateException("more than 1 default extension name on extension " + type.getName()
                            + ": " + Arrays.toString(names));
                }
                if(names.length == 1) cachedDefaultName = names[0];
            }
        }
    
        // 从配置文件中加载扩展实现类
        Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();
        loadFile(extensionClasses, DUBBO_INTERNAL_DIRECTORY);
        loadFile(extensionClasses, DUBBO_DIRECTORY);
        loadFile(extensionClasses, SERVICES_DIRECTORY);
        return extensionClasses;
    }

    从代码里面可以看到,在loadExtensionClasses中首先会检测扩展点在@SPI注解中配置的默认扩展实现的名称,并将其赋值给cachedDefaultName属性进行缓存,后面想要获取该扩展点的默认实现名称就可以直接通过访问cachedDefaultName字段来完成,比如getDefaultExtensionName方法就是这么实现的。从这里的代码中又可以看到,具体的扩展实现类型,是通过调用loadFile方法来加载,分别从一下三个地方加载:

    • META-INF/dubbo/internal/
    • META-INF/dubbo/
    • META-INF/services/

    那么这个loadFile方法则至关重要了,看看其源代码:

    private void loadFile(Map<String, Class<?>> extensionClasses, String dir) {
        String fileName = dir + type.getName(); // 配置文件名称,扫描整个classpath
        try {
            // 先获取该路径下所有文件
            Enumeration<java.net.URL> urls;
            ClassLoader classLoader = findClassLoader();
            if (classLoader != null) {
                urls = classLoader.getResources(fileName);
            } else {
                urls = ClassLoader.getSystemResources(fileName);
            }
            if (urls != null) {
                // 遍历这些文件并进行处理
                while (urls.hasMoreElements()) {
                    java.net.URL url = urls.nextElement(); // 获取配置文件路径
                    try {
                        BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream(), "utf-8"));
                        try {
                            String line = null;
                            while ((line = reader.readLine()) != null) { // 一行一行读取(一行一个配置)
                                final int ci = line.indexOf('#');
                                if (ci >= 0) line = line.substring(0, ci);
                                line = line.trim();
                                if (line.length() > 0) {
                                    try {
                                        String name = null;
                                        int i = line.indexOf('='); // 等号分割
                                        if (i > 0) {
                                            name = line.substring(0, i).trim(); // 扩展名称
                                            line = line.substring(i + 1).trim(); // 扩展实现类
                                        }
                                        if (line.length() > 0) {
                                            Class<?> clazz = Class.forName(line, true, classLoader); // 加载扩展实现类
                                            if (! type.isAssignableFrom(clazz)) { // 判断类型是否匹配
                                                throw new IllegalStateException("Error when load extension class(interface: " +
                                                        type + ", class line: " + clazz.getName() + "), class " 
                                                        + clazz.getName() + "is not subtype of interface.");
                                            }
                                            if (clazz.isAnnotationPresent(Adaptive.class)) { // 判断该实现类是否@Adaptive,是的话不会放入extensionClasses/cachedClasses缓存
                                                if(cachedAdaptiveClass == null) { // 第一个赋值给cachedAdaptiveClass属性
                                                    cachedAdaptiveClass = clazz;
                                                } else if (! cachedAdaptiveClass.equals(clazz)) { // 只能有一个@Adaptive实现,出现第二个就报错了
                                                    throw new IllegalStateException("More than 1 adaptive class found: "
                                                            + cachedAdaptiveClass.getClass().getName()
                                                            + ", " + clazz.getClass().getName());
                                                }
                                            } else { // 不是@Adaptive类型
                                                try {
                                                    clazz.getConstructor(type); // 判断是否Wrapper类型
                                                    Set<Class<?>> wrappers = cachedWrapperClasses;
                                                    if (wrappers == null) {
                                                        cachedWrapperClasses = new ConcurrentHashSet<Class<?>>();
                                                        wrappers = cachedWrapperClasses;
                                                    }
                                                    wrappers.add(clazz); //放入到Wrapper实现类缓存中
                                                } catch (NoSuchMethodException e) { //不是Wrapper类型,普通实现类型
                                                    clazz.getConstructor();
                                                    if (name == null || name.length() == 0) {
                                                        name = findAnnotationName(clazz);
                                                        if (name == null || name.length() == 0) {
                                                            if (clazz.getSimpleName().length() > type.getSimpleName().length()
                                                                    && clazz.getSimpleName().endsWith(type.getSimpleName())) {
                                                                name = clazz.getSimpleName().substring(0, clazz.getSimpleName().length() - type.getSimpleName().length()).toLowerCase();
                                                            } else {
                                                                throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + url);
                                                            }
                                                        }
                                                    }
                                                    String[] names = NAME_SEPARATOR.split(name); // 看是否配置了多个name
                                                    if (names != null && names.length > 0) {
                                                        Activate activate = clazz.getAnnotation(Activate.class); // 是否@Activate类型
                                                        if (activate != null) {
                                                            cachedActivates.put(names[0], activate);// 是则放入cachedActivates缓存
                                                        }
    
                                                        // 遍历所有name
                                                        for (String n : names) {
                                                            if (! cachedNames.containsKey(clazz)) {
                                                                cachedNames.put(clazz, n); // 放入Extension实现类与名称映射缓存,每个class只对应第一个名称有效
                                                            }
                                                            Class<?> c = extensionClasses.get(n);
                                                            if (c == null) {
                                                                extensionClasses.put(n, clazz); // 放入到extensionClasses缓存,多个name可能对应一个Class
                                                            } else if (c != clazz) { // 存在重名
                                                                throw new IllegalStateException("Duplicate extension " + type.getName() + " name " + n + " on " + c.getName() + " and " + clazz.getName());
                                                            }
                                                        }
                                                    }
                                                }
                                            }
                                        }
                                    } catch (Throwable t) {
                                        IllegalStateException e = new IllegalStateException("Failed to load extension class(interface: " + type + ", class line: " + line + ") in " + url + ", cause: " + t.getMessage(), t);
                                        exceptions.put(line, e);
                                    }
                                }
                            } // end of while read lines
                        } finally {
                            reader.close();
                        }
                    } catch (Throwable t) {
                        logger.error("Exception when load extension class(interface: " +
                                            type + ", class file: " + url + ") in " + url, t);
                    }
                } // end of while urls
            }
        } catch (Throwable t) {
            logger.error("Exception when load extension class(interface: " +
                    type + ", description file: " + fileName + ").", t);
        }
    }

    代码比较长,大概的事情呢就是解析配置文件,获取扩展点实现对应的名称和实现类,并进行分类处理和缓存。当loadFile方法执行完成之后,以下几个变量就会被附上值:

    • cachedAdaptiveClass : 当前Extension类型对应的AdaptiveExtension类型(只能一个)
    • cachedWrapperClasses : 当前Extension类型对应的所有Wrapper实现类型(无顺序)
    • cachedActivates : 当前Extension实现自动激活实现缓存(map,无序)
    • cachedNames : 扩展点实现类对应的名称(如配置多个名称则值为第一个)

    loadExtensionClasses方法执行完成之后,还有一下变量被赋值:

    • cachedDefaultName : 当前扩展点的默认实现名称

    getExtensionClasses方法执行完成之后,除了上述变量被赋值之外,还有以下变量被赋值:

    • cachedClasses : 扩展点实现名称对应的实现类(一个实现类可能有多个名称)

    其实也就是说,在调用了getExtensionClasses方法之后,当前扩展点对应的实现类的一些信息就已经加载进来了并且被缓存了。后面的许多操作都可以直接通过这些缓存数据来进行处理了。

    回到createAdaptiveExtension方法,他调用了getExtesionClasses方法加载扩展点实现信息完成之后,就可以直接通过判断cachedAdaptiveClass缓存字段是否被赋值盘确定当前扩展点是否有默认的AdaptiveExtension实现。如果没有,那么就调用createAdaptiveExtensionClass方法来动态生成一个。在dubbo的扩展点框架中大量的使用了缓存技术。

    创建自适应扩展点实现类型和实例化就已经完成了,下面就来看下扩展点自动注入的实现injectExtension

    private T injectExtension(T instance) {
        try {
            if (objectFactory != null) {
                for (Method method : instance.getClass().getMethods()) {
                    if (method.getName().startsWith("set")
                            && method.getParameterTypes().length == 1
                            && Modifier.isPublic(method.getModifiers())) {// 处理所有set方法
                        Class<?> pt = method.getParameterTypes()[0];// 获取set方法参数类型
                        try {
                            // 获取setter对应的property名称
                            String property = method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : "";
                            Object object = objectFactory.getExtension(pt, property); // 根据类型,名称信息从ExtensionFactory获取
                            if (object != null) { // 如果不为空,说set方法的参数是扩展点类型,那么进行注入
                                method.invoke(instance, object);
                            }
                        } catch (Exception e) {
                            logger.error("fail to inject via method " + method.getName()
                                    + " of interface " + type.getName() + ": " + e.getMessage(), e);
                        }
                    }
                }
            }
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return instance;
    }

    这里可以看到,扩展点自动注入的一句就是根据setter方法对应的参数类型和property名称从ExtensionFactory中查询,如果有返回扩展点实例,那么就进行注入操作。到这里getAdaptiveExtension方法就分析完毕了。

    getExtension

    这个方法的主要作用是用来获取ExtensionLoader实例代表的扩展的指定实现。已扩展实现的名字作为参数,结合前面学习getAdaptiveExtension的代码,我们可以推测,这方法中也使用了在调用getExtensionClasses方法的时候收集并缓存的数据,其中涉及到名字和具体实现类型对应关系的缓存属性是cachedClasses。具体是是否如我们猜想的那样呢,学习一下相关代码就知道了:

    public T getExtension(String name) {
        if (name == null || name.length() == 0)
            throw new IllegalArgumentException("Extension name == null");
        if ("true".equals(name)) {  // 判断是否是获取默认实现
            return getDefaultExtension();
        }
        Holder<Object> holder = cachedInstances.get(name);// 缓存
        if (holder == null) {
            cachedInstances.putIfAbsent(name, new Holder<Object>());
            holder = cachedInstances.get(name);
        }
        Object instance = holder.get();
        if (instance == null) {
            synchronized (holder) {
                instance = holder.get();
                if (instance == null) {
                    instance = createExtension(name);// 没有缓存实例则创建
                    holder.set(instance);// 缓存起来
                }
            }
        }
        return (T) instance;
    }

    接着看createExtension方法的实现:

    private T createExtension(String name) {
        Class<?> clazz = getExtensionClasses().get(name); // getExtensionClass内部使用cachedClasses缓存
        if (clazz == null) {
            throw findException(name);
        }
        try {
            T instance = (T) EXTENSION_INSTANCES.get(clazz); // 从已创建Extension实例缓存中获取
            if (instance == null) {
                EXTENSION_INSTANCES.putIfAbsent(clazz, (T) clazz.newInstance());
                instance = (T) EXTENSION_INSTANCES.get(clazz);
            }
            injectExtension(instance); // 属性注入
    
            // Wrapper类型进行包装,层层包裹
            Set<Class<?>> wrapperClasses = cachedWrapperClasses;
            if (wrapperClasses != null && wrapperClasses.size() > 0) {
                for (Class<?> wrapperClass : wrapperClasses) {
                    instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
                }
            }
            return instance;
        } catch (Throwable t) {
            throw new IllegalStateException("Extension instance(name: " + name + ", class: " +
                    type + ")  could not be instantiated: " + t.getMessage(), t);
        }
    }

    从代码中可以看到,内部调用了getExtensionClasses方法来获取当前扩展的所有实现,而getExtensionClassse方法会在第一次被调用的时候将结果缓存到cachedClasses变量中,后面的调用就直接从缓存变量中获取了。这里还可以看到一个缓存EXTENSION_INSTANCES,这个缓存是ExtensionLoader的静态成员,也就是全局缓存,存放着所有的扩展点实现类型与其对应的已经实例化的实例对象(是所有扩展点,不是某一个扩展点),也就是说所有的扩展点实现在dubbo中最多都只会有一个实例。

    拿到扩展点实现类型对应的实例之后,调用了injectExtension方法对该实例进行扩展点注入,紧接着就是遍历该扩展点接口的所有Wrapper来对真正的扩展点实例进行Wrap操作,都是对通过将上一次的结果作为下一个Wrapper的构造函数参数传递进去实例化一个Wrapper对象,最后总返回回去的是Wrapper类型的实例而不是具体实现类的实例。

    这里或许有一个疑问: 从代码中看,不论instance是否存在于EXTENSION_INSTANCE,都会进行扩展点注入和Wrap操作。那么如果对于同一个扩展点,调用了两次createExtension方法的话,那不就进行了两次Wrap操作么?

    如果外部能够直接调用createExtension方法,那么确实可能出现这个问题。但是由于createExtension方法是private的,因此外部无法直接调用。而在ExtensionLoader类中调用它的getExtension方法(只有它这一处调用),内部自己做了缓存(cachedInstances),因此当getExtension方法内部调用了一次createExtension方法之后,后面对getExtension方法执行同样的调用时,会直接使用cachedInstances缓存而不会再去调用createExtension方法了。

    getActivateExtension

    getActivateExtension方法主要获取当前扩展的所有可自动激活的实现。可根据入参(values)调整指定实现的顺序,在这个方法里面也使用到getExtensionClasses方法中收集的缓存数据。

    public List<T> getActivateExtension(URL url, String[] values, String group) {
        List<T> exts = new ArrayList<T>();
        List<String> names = values == null ? new ArrayList<String>(0) : Arrays.asList(values); // 解析配置要使用的名称
    
        // 如果未配置"-default",则加载所有Activates扩展(names指定的扩展)
        if (! names.contains(Constants.REMOVE_VALUE_PREFIX + Constants.DEFAULT_KEY)) {
            getExtensionClasses(); // 加载当前Extension所有实现,会获取到当前Extension中所有@Active实现,赋值给cachedActivates变量
            for (Map.Entry<String, Activate> entry : cachedActivates.entrySet()) { // 遍历当前扩展所有的@Activate扩展
                String name = entry.getKey();
                Activate activate = entry.getValue();
                if (isMatchGroup(group, activate.group())) { // 判断group是否满足,group为null则直接返回true
                    T ext = getExtension(name); // 获取扩展示例
    
                    // 排除names指定的扩展;并且如果names中没有指定移除该扩展(-name),且当前url匹配结果显示可激活才进行使用
                    if (! names.contains(name)
                            && ! names.contains(Constants.REMOVE_VALUE_PREFIX + name) 
                            && isActive(activate, url)) {
                        exts.add(ext);
                    }
                }
            }
            Collections.sort(exts, ActivateComparator.COMPARATOR); // 默认排序
        }
    
        // 对names指定的扩展进行专门的处理
        List<T> usrs = new ArrayList<T>();
        for (int i = 0; i < names.size(); i ++) { // 遍历names指定的扩展名
            String name = names.get(i);
            if (! name.startsWith(Constants.REMOVE_VALUE_PREFIX)
                    && ! names.contains(Constants.REMOVE_VALUE_PREFIX + name)) { // 未设置移除该扩展
                if (Constants.DEFAULT_KEY.equals(name)) { // default表示上面已经加载并且排序的exts,将排在default之前的Activate扩展放置到default组之前,例如:ext1,default,ext2
                    if (usrs.size() > 0) { // 如果此时user不为空,则user中存放的是配置在default之前的Activate扩展
                        exts.addAll(0, usrs); // 注意index是0,放在default前面
                        usrs.clear(); // 放到default之前,然后清空
                    }
                } else {
                    T ext = getExtension(name);
                    usrs.add(ext);
                }
            }
        }
        if (usrs.size() > 0) { // 这里留下的都是配置在default之后的
            exts.addAll(usrs); // 添加到default排序之后
        }
        return exts;
    }

    总结

    基本上将dubbo的扩展点加载机制学习了一遍,有几点可能需要注意的地方:

    • 每个ExtensionLoader实例只负责加载一个特定扩展点实现
    • 每个扩展点对应最多只有一个ExtensionLoader实例
    • 对于每个扩展点实现,最多只会有一个实例
    • 一个扩展点实现可以对应多个名称(逗号分隔)
    • 对于需要等到运行时才能决定使用哪一个具体实现的扩展点,应获取其自使用扩展点实现(AdaptiveExtension)
    • @Adaptive注解要么注释在扩展点@SPI的方法上,要么注释在其实现类的类定义上
    • 如果@Adaptive注解注释在@SPI接口的方法上,那么原则上该接口所有方法都应该加@Adaptive注解(自动生成的实现中默认为注解的方法抛异常)
    • 每个扩展点最多只能有一个被AdaptiveExtension
    • 每个扩展点可以有多个可自动激活的扩展点实现(使用@Activate注解)
    • 由于每个扩展点实现最多只有一个实例,因此扩展点实现应保证线程安全
    • 如果扩展点有多个Wrapper,那么最终其执行的顺序不确定(内部使用ConcurrentHashSet存储)

    TODO:

    • 学习一下动态生成AdaptiveExtension类的实现过程
      官方文档描述动态生成的AdaptiveExtension代码如下:
    package <扩展点接口所在包>;
    
    public class <扩展点接口名>$Adpative implements <扩展点接口> {
        public <有@Adaptive注解的接口方法>(<方法参数>) {
            if(是否有URL类型方法参数?) 使用该URL参数
            else if(是否有方法类型上有URL属性) 使用该URL属性
            # <else 在加载扩展点生成自适应扩展点类时抛异常,即加载扩展点失败!>
    
            if(获取的URL == null) {
                throw new IllegalArgumentException("url == null");
            }
    
            根据@Adaptive注解上声明的Key的顺序,从URL获致Value,作为实际扩展点名。
            如URL没有Value,则使用缺省扩展点实现。如没有扩展点, throw new IllegalStateException("Fail to get extension");
    
            在扩展点实现调用该方法,并返回结果。
        }
    
        public <有@Adaptive注解的接口方法>(<方法参数>) {
            throw new UnsupportedOperationException("is not adaptive method!");
        }
    }

    规则如下:

    • 先在URL上找@Adaptive注解指定的Extension名;
    • 如果不设置则缺省使用Extension接口类名的点分隔小写字串(即对于Extension接口com.alibaba.dubbo.xxx.YyyInvokerWrapper的缺省值为String[] {“yyy.invoker.wrapper”})。
    • 使用默认实现(@SPI指定),如果没有设定缺省扩展,则方法调用会抛出IllegalStateException。
    展开全文
  • extension

    2019-07-31 15:45:32
    extension与OC中分类差不多 1.创建方式 名字写成,类+分类名 1.如给Bundle创建分类创建nameSpace属性 //extension 类{} extension Bundle{ var nameSpace:String { return infoDictionary?[...

    extension与OC中分类差不多

    1.创建方式 

    名字写成,类+分类名
    

     1.如给Bundle创建分类创建nameSpace属性

    //extension 类{}
    extension Bundle{
        var nameSpace:String {
       return infoDictionary?["CFBundleName"] as? String ?? ""
        }
    }
    //也可以写在类文件中,单创建一个extension方法 extension 类{}
    

     

    转载于:https://www.cnblogs.com/TheYouth/p/6435580.html

    展开全文
  • Postgresql中的extension

    千次阅读 2014-10-17 08:30:20
    Packaging Related Objects into an Extension
  • 【C# / Extension】 扩展方法

    千次阅读 2018-03-27 15:05:03
    扩展方法简介 扩展方法使你能够向现有类型“添加”方法,而无需创建新的派生类型、重新编译或以其他方式修改原始类型。 扩展方法是一种特殊的静态方法,但可以像扩展类型上的实例方法一样进行调用。...
  • Chromium扩展(Extension)机制简要介绍和学习计划

    万次阅读 多人点赞 2017-01-06 11:39:47
    Chromium提供了一种Extension机制,用来增强浏览器功能。我们可以将Extension看作是一种运行在Chromium中的应用。这种应用的开发语言是JavaScript,并且UI通过HTML描述。通过使用Chromium提供的API,Extension可以...
  • Chromium扩展(Extension)通信机制分析

    万次阅读 热门讨论 2017-01-06 11:33:34
    Chromium的Extension由Page和Content Script组成。如果将Extension看作是一个App,那么Page和Content Script就是Extension的Module。既然是Module,就避免不了需要相互通信。也正是由于相互通信,使得它们形成一个...
  • Chromium扩展(Extension)加载过程分析

    万次阅读 热门讨论 2017-01-06 11:35:42
    Chromium在启动的时候,会根据当前用户的Profile创建一个Extension Service。Extension Service在创建过程中,会加载当前已经安装的所有Extension,并且将它们注册在一个Extension Registry中。以后通过这个...
  • 本例需求 : iOS通过NetworkExtension实现本地连接并成功拦截IP数据包pakcet. 建议:建议阅读本文前先仔细阅读并理解下App extension原理,有助于在项目中解决很多问题。App extension总结 注意:由于简大叔对XXX...
  • iOS App Extension 介绍

    万次阅读 2020-04-05 00:15:59
    App Extension (应用扩展)
  • Action Extension

    千次阅读 2016-04-11 14:54:50
    Action extension的创建于实现
  • Chromium扩展(Extension)的页面(Page)加载过程分析

    万次阅读 热门讨论 2017-01-06 11:34:43
    Chromium的Extension Page其实就是网页,因此它们的加载过程与普通网页相同。常见的Extension Page有Background Page和Popup Page。其中,Background Page在浏览器窗口初始化完成后自动加载,之后运行在后台中。...
  • Chromium扩展(Extension)的Content Script加载过程分析

    万次阅读 热门讨论 2017-01-06 11:36:49
    Chromium的Extension由Page和Content Script组成。Page有UI和JS,它们加载在自己的Extension Process中渲染和执行。Content Script只有JS,这些JS是注入在宿主网页中执行的。Content Script可以访问宿主网页的DOM ...
  • iOS 应用扩展 - Today Extension

    万次阅读 2020-04-14 14:32:35
    1、创建Today Extension 2、共享数据 3、使用宿主App中的文件 4、扩展中打开宿主App 5、补充:
  • 这里主要是对App Extension的一些介绍以及详细给大家介绍一下Share Extension,后期会添加其他的Extension介绍。 2.开始 主要对App Extension和Share Extension进行介绍。请继续往下看: 2.1: Ap
  • 摘要:因为作者水平有限,暂且只是测试Extension扩展用例, 一个典型的extension包含多个SQL对象。举个例子,如果我们创建一个新的数据类型(data type),那可能同时还需要与之相关的新函数(function)、新的操作符...
  • nginx: [warn] duplicate extension "html", content type: "text/html", previous content type: "text/html" in /etc/nginx/mime.types:3 nginx: [warn] duplicate extension "htm", content type: "text...
  • Chrome Extension插件开发概述

    万次阅读 2019-05-13 10:15:23
    目录一、前言一、Chrome Extension还是Chrome Plugin?二、为什么选择Chrome浏览器?三、关于Chrome Extension四、开发调试1. 调试弹出页(popup)2. 调试选项页(option)3. 调试后台页(background)4. 调试内容脚本...
  • Jenkis邮件通知:Email Extension Plugin

    千次阅读 2018-06-06 10:01:52
    Email Extension Plugin
1 2 3 4 5 ... 20
收藏数 344,654
精华内容 137,861
关键字:

extension