当前位置: 首页 > news >正文

网站建设 技术方案seo关键词优化费用

网站建设 技术方案,seo关键词优化费用,西安公司招聘信息,网站建设公司招商在此前的内容中,已经详细介绍了 SkyWalking Agent 用到的多种基础技术,例如,Byte Buddy、Java Agent 以及 OpenTracing 中的核心概念。本课时将深入介绍 SkyWalking Agent 以及 OAP 中都会使用到的 SPI 技术。 JDK SPI 机制 SPI&#xff08…

在此前的内容中,已经详细介绍了 SkyWalking Agent 用到的多种基础技术,例如,Byte Buddy、Java Agent 以及 OpenTracing 中的核心概念。本课时将深入介绍 SkyWalking Agent 以及 OAP 中都会使用到的 SPI 技术。

JDK SPI 机制

SPI(Service Provider Interface)主要是被框架开发人员使用的一种技术。例如,使用 Java 语言访问数据库时我们会使用到 java.sql.Driver 接口,每个数据库厂商使用的协议不同,提供的 java.sql.Driver 实现也不同,在开发 java.sql.Driver 接口时,开发人员并不清楚用户最终会使用哪个数据库,在这种情况下就可以使用 Java SPI 机制为 java.sql.Driver 接口寻找具体的实现。

当服务的提供者提供了一种接口的实现之后,需要在 Classpath 下的 META-INF/services/ 目录里创建一个以服务接口命名的文件,此文件记录了该 jar 包提供的服务接口的具体实现类。当某个应用引入了该 jar 包且需要使用该服务时,JDK SPI 机制就可以通过查找这个 jar 包的 META-INF/services/ 中的配置文件来获得具体的实现类名,进行实现类的加载和实例化,最终使用该实现类完成业务功能。

下面通过一个简单的示例演示 JDK SPI 的基本使用方式,示例如下:

首先我们需要创建一个 Log 接口,来模拟日志打印的功能:

public interface Log {void log(String info);
}

接下来提供两个实现 —— Logback 和 Log4j,分别代表两个不同日志框架的实现,如下所示:

public class Logback implements Log {@Overridepublic void log(String info) {System.out.println("Logback:" + info);}
}

public class Log4j implements Log {
    @Override
    public void log(String info) {
        System.out.println(“Log4j:” + info);
    }
}

在项目的 resources/META-INF/services 目录下添加一个名为 com.xxx.Log 的文件,这是 JDK SPI 需要读取的配置文件,具体内容如下:

com.xxx.impl.Log4j
com.xxx.impl.Logback

最后创建 main() 方法,其中会加载上述配置文件,创建全部 Log 接口实现的实例,并执行其 log() 方法,如下所示:

public class Main {public static void main(String[] args) {ServiceLoader<Log> serviceLoader = ServiceLoader.load(Log.class);Iterator<Log> iterator = serviceLoader.iterator();while (iterator.hasNext()) {Log log = iterator.next();log.log("JDK SPI"); }}
}
// 输出如下:
// Log4j:JDK SPI
// Logback:JDK SPI

JDK SPI源码分析

通过上述示例,我们可以看到 JDK SPI 的入口方法是 ServiceLoader.load()  方法,接下来我将对其具体实现进行深入分析。

在 ServiceLoader.load() 方法中,首先会尝试获取当前使用的 ClassLoader(获取当前线程绑定的 ClassLoader,查找失败后使用 SystemClassLoader),然后调用 reload() 方法,调用关系如下图所示:

在 reload() 方法中首先会清理 providers 缓存(LinkedHashMap 类型的集合),该缓存用来记录 ServiceLoader 创建的实现对象,其中 Key 为实现类的完整类名,Value 为实现类的对象。之后创建 LazyIterator 迭代器,该迭代器用于读取 SPI 配置文件并实例化实现类对象。

ServiceLoader.reload() 方法的具体实现,如下所示:

// 缓存,用来缓存 ServiceLoader创建的实现对象
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();

public void reload() {
    providers.clear(); // 清空缓存
    lookupIterator = new LazyIterator(service, loader); // 迭代器
}

在前面的示例中,main() 方法中使用的迭代器底层就是调用了 ServiceLoader.LazyIterator  实现的。Iterator 接口有两个关键方法:hasNext() 方法和 next() 方法,这里的 LazyIterator 中的next() 方法最终调用的是其 nextService() 方法,hasNext() 方法最终调用的是 hasNextService() 方法,调用关系如下图所示:

首先来看 LazyIterator.hasNextService() 方法,该方法主要负责查找 META-INF/services 目录下的 SPI 配置文件,并进行遍历,大致实现如下所示:

private static final String PREFIX = "META-INF/services/";
Enumeration<URL> configs = null;
Iterator<String> pending = null;
String nextName = null;

private boolean hasNextService() {
    if (nextName != null) {
        return true;
    }
    if (configs  null) {
        // PREFIX前缀与服务接口的名称拼接起来,就是META-INF目录下定义的SPI配
        // 置文件(即示例中的META-INF/services/com.xxx.Log)
        String fullName = PREFIX + service.getName();
        // 加载配置文件
        if (loader 
 null)
            configs = ClassLoader.getSystemResources(fullName);
        else
            configs = loader.getResources(fullName);
    }
    // 按行SPI遍历配置文件的内容
    while ((pending == null) || !pending.hasNext()) { 
        if (!configs.hasMoreElements()) {
            return false;
        }
        // 解析配置文件
        pending = parse(service, configs.nextElement()); 
    }
    nextName = pending.next(); // 更新 nextName字段
    return true;
}

接下来,在 hasNextService() 方法中完成 SPI 配置文件的解析之后,再来看 LazyIterator.nextService() 方法,该方法负责实例化 hasNextService() 方法读取到的实现类,其中会将实例化的对象放到 providers 集合中缓存起来,核心实现如下所示:

private S nextService() {String cn = nextName;nextName = null;// 加载 nextName字段指定的类Class<?> c = Class.forName(cn, false, loader);if (!service.isAssignableFrom(c)) { // 检测类型fail(service, "Provider " + cn  + " not a subtype");}S p = service.cast(c.newInstance()); // 创建实现类的对象providers.put(cn, p); // 将实现类名称以及相应实例对象添加到缓存return p;
}

在 main() 方法中使用的迭代器的底层实现介绍完了,我们再来看一下其使用的真正迭代器,核心实现如下:

public Iterator<S> iterator() {return new Iterator<S>() {// knownProviders用来迭代 providers缓存Iterator<Map.Entry<String,S>> knownProviders= providers.entrySet().iterator();

        public boolean hasNext() {
            // 先走查询缓存,缓存查询失败,再通过 LazyIterator加载
            if (knownProviders.hasNext()) 
                return true;
            return lookupIterator.hasNext();
        }

        public S next() {
            // 先走查询缓存,缓存查询失败,再通过 LazyIterator加载
            if (knownProviders.hasNext())
                return knownProviders.next().getValue();
            return lookupIterator.next();
        }
        // 省略remove()方法
    };

JDK SPI 在 JDBC 中的应用

了解了 JDK SPI 实现的原理之后,我们来看实践中 JDBC 是如何使用 JDK SPI 机制加载不同数据库厂商的实现类。

JDK 中只定义了一个 java.sql.Driver 接口,具体的实现是由不同数据库厂商来提供的。这里以 MySQL 提供的 JDBC 实现包为例进行分析。

在 mysql-connector-java-*.jar 包中的 META-INF/services 目录下,有一个 java.sql.Driver 文件中只有一行内容,如下所示:

com.mysql.cj.jdbc.Driver

在使用 mysql-connector-java-*.jar 包连接 MySQL 数据库的时候,我们会用到如下语句创建数据库连接:

String url = "jdbc:xxx://xxx:xxx/xxx";
Connection conn = DriverManager.getConnection(url, username, pwd);

DriverManager 是 JDK 提供的数据库驱动管理器,其中的代码片段,如下所示:

static {loadInitialDrivers();println("JDBC DriverManager initialized");
}

在调用 getConnection() 方法的时候,DriverManager 类会被 Java 虚拟机加载、解析并触发 static 代码块的执行,在 loadInitialDrivers() 方法中通过 JDK SPI 扫描 Classpath 下  java.sql.Driver 接口实现类并实例化,核心实现如下所示:

private static void loadInitialDrivers() {String drivers = System.getProperty("jdbc.drivers")// 使用 JDK SPI机制加载所有 java.sql.Driver实现类ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);Iterator<Driver> driversIterator = loadedDrivers.iterator();while(driversIterator.hasNext()) {driversIterator.next();}String[] driversList = drivers.split(":");for (String aDriver : driversList) { // 初始化Driver实现类Class.forName(aDriver, true,ClassLoader.getSystemClassLoader());}
}

在 MySQL 提供的 com.mysql.cj.jdbc.Driver 实现类中,同样有一段 static 静态代码块,这段代码会创建一个 com.mysql.cj.jdbc.Driver 对象并注册到 DriverManager.registeredDrivers 集合中( CopyOnWriteArrayList 类型),如下所示:

static {java.sql.DriverManager.registerDriver(new Driver());
}

在 getConnection() 方法中,DriverManager 从该 registeredDrivers 集合中获取对应的 Driver 对象创建 Connection,核心实现如下所示:

private static Connection getConnection(String url, java.util.Properties info, Class<?> caller) throws SQLException {// 省略 try/catch代码块以及权限处理逻辑for(DriverInfo aDriver : registeredDrivers) {Connection con = aDriver.driver.connect(url, info);return con;}
}

Dubbo 对 JDK SPI 的改进

通过前面的分析可以发现,JDK SPI 在查找具体实现类的过程中,需要遍历 SPI 配置文件中定义的所有实现类,该过程中会将这些实现类全部实例化。如果 SPI 配置文件中定义了多个实现类,而我们只需要其中一个实现类时,就会生成不必要的对象。

Dubbo 为了解决上述问题,自己设计了一套 SPI 实现,但是思想与 JDK SPI 机制类似。作为思路的扩展,这里简单介绍一下 Dubbo SPI 的实现原理(SkyWalking 使用是 JDK SPI 而不是 Dubbo SPI )。

首先,Dubbo 将 SPI 配置文件改成了 KV 格式,例如:

dubbo=org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol

其中 key 就是一个简单的标记,当我们在为一个接口查找具体实现类时,可以指定 key 来选择具体实现,例如,这里指定 key 为 dubbo,Dubbo SPI 就知道我们要的是:org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol 这个实现类。

Dubbo SPI 核心实现是 ExtensionLoader(位于 dubbo-common 模块中的 extension 包中),功能类似于 JDK SPI 中的 java.util.ServiceLoader,其使用方式如下所示:

Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getExtension("dubbo");
// 很明显,在查找 Protocol这个接口的实现类时,还指定了"dubbo"这个key

ExtensionLoader.getExtensionLoader() 方法会根据接口类型从缓存中查找相应的 ExtensionLoader 实现,核心实现如下:

private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<>();

public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
    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;
}

查找到接口对应的 ExtensionLoader 对象之后,会调用 getExtension() 方法,再根据传入的 key 查找相应的实现类,最终将其实例化后返回:

// 缓存,记录了 key到实现类对象Holder之间的映射关系
private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<>();

public T getExtension(String name) {
    Holder<Object> holder = getOrCreateHolder(name);
    Object instance = holder.get();
    if (instance  null) { // double-check防止并发问题
        synchronized (holder) {
            instance = holder.get();
            if (instance 
 null) {
                // createExtension()方法中完成了 SPI配置文件的查找以及实现类
                // 的实例化,具体实现与 JDK SPI原理类似,其中还会处理 Dubbo中
                // 自定义的一些注解,不再展开分析
                instance = createExtension(name);
                holder.set(instance);
            }
        }
    }
    return (T) instance;
}

总结

本课时首先介绍了 JDK SPI 机制的原理,并通过 Log 示例演示了 JDK SPI 的使用方式,然后深入到 ServiceLoader 的源码中分析了 JDK SPI 的实现方式,接下来介绍了 JDBC 4.0 如何使用 JDK SPI 机制加载数据库驱动类,最后介绍了 Dubbo 对 JDK SPI 的改进。


http://www.qdjiajiao.com/news/6183.html

相关文章:

  • 买房网站排名小程序如何推广运营
  • 胶州市经济技术开发区建设局网站seo网站优化收藏
  • 网站做盗版视频赚钱吗网站seo优化方案策划书
  • 网页制作培训上海排名前十seo搜索优化是什么呢
  • 武汉网站建设哪家公司好友情链接检测方法
  • 具有设计感的网站福州短视频seo网红
  • 怎么提高网站收录神秘网站
  • 企业网站建设注意整合营销传播策略
  • 个人网站百度推广收费北京百度seo排名
  • 做网站用discuz还是wpseo网络营销推广排名
  • 招聘网站上怎么做推广网络营销概念
  • 辽宁奔之流建设工程有限公司网站百度网络优化
  • 宁波网站建设团队哪家好站长素材免费下载
  • 众创空间网站建设方案企业营销网站制作
  • .net制作网站开发教程郑州百度网站快速优化
  • angular做的网站企业如何注册自己的网站
  • 怎么做站旅游网站上泡到妞网站平台做推广
  • 免费网站测试空间推广平台有哪些?
  • 网站佣金怎么做会计分录手机怎么建网站
  • 黄金网站软件app下载安装网址大全
  • 员工之家app官网下载地址外链seo推广
  • 怎么搜索整个网站内容广州新闻24小时爆料热线
  • 百度站长怎么做网站维护爱站长尾词
  • 北京高端网站设计外包公司搜索引擎营销sem
  • 新手如何建站百度不收录网站怎么办
  • 网站如何做分站短视频如何引流与推广
  • 做网站怎样申请动态域名品牌策划公司排名
  • 购物网站建设市场营销培训心得体会
  • 网站建设小程序定制开发营销软文范例
  • 江门恒阳网站建设互联网推广是做什么的