前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >深入解析Java SPI🌟从使用到原理的全面之旅🚀

深入解析Java SPI🌟从使用到原理的全面之旅🚀

原创
作者头像
菜菜的后端私房菜
发布于 2025-01-06 02:09:10
发布于 2025-01-06 02:09:10
2670
举报

深入解析Java SPI🌟从使用到原理的全面之旅🚀

✨前言

Java开发中,我们经常需要一种机制来解耦接口和其实现类,使得系统更加灵活、可扩展

传统的做法是通过硬编码或配置文件指定实现类,但这显然不够优雅且缺乏灵活性

Java SPI(Service Provider Interface)允许开发者将接口的实现从代码中分离出来,在运行时动态加载这些实现

使用SPI能够轻松将服务接口与实现分离解耦,动态加载实现服务,提高模块化,让系统更加灵活易于扩展

本文将从零开始介绍SPI,再到如何使用SPI,然后分析SPI实现原理,最后例举出SPI应用场景,文章导图如下:

导图
导图

🔍SPI简介

Java SPI (Service Provider Interface)是一种服务提供者接口机制,用于在运行时动态加载和使用服务实现

对于不熟悉的同学来说概念可能太抽象、太陌生,简单举个例子:

当我们在使用API(Application Programming Interface)时,我们需要引入三方库的依赖(jar包),在三方库的API中接口和实现都是在被调用方(三方)定义与实现的

比如我们想使用Apache的工具类API,我们需要引用其依赖再使用,其中API的接口与实现都在引用的依赖中定义与实现

而SPI却大大不同,SPI的接口可以在调用方(我们项目中)进行定义,实现类由其他三方库进行实现,在项目中使用时直接使用接口,而无需关心实现类

SPI
SPI

比如JDBC(Java Database Connectivity)中的Driver,基于接口而无需关心具体用哪个数据库的驱动(想用MySQL的就引入MySQL的依赖,想用其他数据库的就引入对应的依赖)

使用SPI不仅能够将接口与实现解耦,还符合面向对象,基于接口(抽象)而无需关心实现,松耦合、易扩展

✍️SPI使用(搭建项目)

使用SPI需要满足以下几个步骤:

  1. 定义SPI接口
  2. 三方依赖(被调用方,服务提供方)中实现SPI接口
  3. 三方依赖(被调用方,服务提供方)编写SPI配置文件(在资源目录创建**/META-INF/services**目录,其下再创建以SPI接口全限定类名的文件,文件内容为实现类的全限定类名)
  4. 调用方引入实现SPI接口的依赖,并使用ServiceLoader加载SPI接口

项目中,我们会简单定义一个数据库相关的接口,其抽象方法返回具体的数据库名,并实现两个三方依赖来实现接口,具体返回MySQL与PgSQL,最后在调用方进行加载实现类并使用

项目结构如下:

项目结构
项目结构

关于SPI的固定使用步骤也体现“约定大于配置”的原则,使用接下来搭建项目查看SPI是如何使用的:

SPI-Common项目定义SPI接口

代码语言:java
AI代码解释
复制
   package com.caicai;
   
   /**
    * @author: 菜菜的后端私房菜
    * @create: 2025/1/5 10:24
    * @description:
    */
   public interface DatabaseInterface {
       String getDatabaseName();
   }

SPI-Provider-MySQL项目具体实现(需要依赖SPI-Common项目,因为要实现接口)

代码语言:java
AI代码解释
复制
   package com.caicai;
   
   /**
    * @author: 菜菜的后端私房菜
    * @create: 2025/1/5 10:28
    * @description:
    */
   public class MySQLDatabase implements DatabaseInterface{
       @Override
       public String getDatabaseName() {
           return "MySQL";
       }
   }

SPI-Provider-MySQL项目在其resource目录下创建/META-INF/services/com.caicai.DatabaseInterface文件并填写com.caicai.MySQLDatabase (实现SPI接口类的全限定类名)

SPI配置文件
SPI配置文件

SPI-Provider-PgSQL项目搭建与SPI-Provider-MySQL同理,只是实现不同

代码语言:java
AI代码解释
复制
   package com.caicai;
   
   /**
    * @author: 菜菜的后端私房菜
    * @create: 2025/1/5 10:31
    * @description:
    */
   public class PgSQLDatabase implements DatabaseInterface{
       @Override
       public String getDatabaseName() {
           return "PgSQL";
       }
   }

SPI-Invoke项目导入两个具体实现的依赖,并使用ServiceLoader加载SPI接口实现类

代码语言:java
AI代码解释
复制
   import java.util.ServiceLoader;
   
   /**
    * @author: 菜菜的后端私房菜
    * @create: 2025/1/5 10:25
    * @description:
    */
   public class SPIDemo {
       public static void main(String[] args) {
           ServiceLoader<DatabaseInterface> serviceLoader = ServiceLoader.load(DatabaseInterface.class);
           for (DatabaseInterface databaseInterface : serviceLoader) {
               System.out.println("使用的数据库:" + databaseInterface.getDatabaseName());
           }
       }
   }
   
   /*
   结果输出:
   使用的数据库:MySQL
   使用的数据库:PgSQL
   */

📚SPI原理(源码分析)

在使用SPI的过程中,有很多约定俗成的规则,比如:

  1. 要在/META-INF/services目录下创建SPI配置文件
  2. SPI配置文件需要用SPI接口的全限定类名命名
  3. SPI配置文件的内容需要是实现类的全限定类名

那么我们在使用的过程中,能不能更改这些规则呢?SPI又是如何加载实现类的呢?

带着这些问题,我们对ServiceLoader进行源码分析:

ServiceLoader

ServiceLoader加载SPI接口时需要存储一些相关信息,如:SPI接口的Class(service)、加载实现类会用到的类加载器(loader)、已加载实现类的缓存(providers)等

ServiceLoader
ServiceLoader

从字符串PREFIX被final修饰可以看出,SPI配置文件的目录 META-INF/services/ 应该是固定不变的

ServiceLoader.load

ServiceLoader.load用于实例化ServiceLoader,但并不会加载SPI接口的具体实现类,而是采用懒加载的方式,迭代时才进行加载

从ServiceLoader.load方法进入,发现类加载使用的是当前线程的类加载器

代码语言:java
AI代码解释
复制
public static <S> ServiceLoader<S> load(Class<S> service) {
    ClassLoader cl = Thread.currentThread().getContextClassLoader();
    return ServiceLoader.load(service, cl);
}

最终load方法会调用的ServiceLoader构造方法进行初始化,然后调用reload方法

ServiceLoader中类加载器为线程上下文类加载器(默认通常是系统/应用程序类加载器),类加载器会在后续对实现类进行加载

当SPI接口为核心类库时(java.sql.Driver以JDBC为例),本由引导类加载器进行加载的职责会交给应用程序类加载器执行

这种父类加载器委托子类加载器加载实现类的方式,打破双亲委派模型,由应用程序类加载器对JDBC驱动实现类进行加载

(不理解类加载器相关知识的同学也不用担心,感兴趣可以查看往期类加载器文章)

代码语言:java
AI代码解释
复制
private ServiceLoader(Class<S> svc, ClassLoader cl) {
    service = Objects.requireNonNull(svc, "Service interface cannot be null");
    //未指定类加载器就采用系统类加载器(应用程序类加载器),否则采用线程上下文的类加载器
    loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
    acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
    reload();
}

reload方法会去清空缓存,并实例化懒加载的迭代器

代码语言:java
AI代码解释
复制
public void reload() {
    //清空缓存
    providers.clear();
    lookupIterator = new LazyIterator(service, loader);
}

可以发现在ServiceLoader.load的过程中,从未去对实现类进行加载

直到迭代ServiceLoader时,才会通过LazyIterator懒加载的方式去加载实现类

ServiceLoader迭代器实现

增强for循环是Java的语法糖,实际上会使用迭代器进行迭代

代码语言:java
AI代码解释
复制
for (DatabaseInterface databaseInterface : serviceLoader) {
    System.out.println("使用的数据库:" + databaseInterface.getDatabaseName());
}

ServiceLoader的迭代器实现主要由knownProviders与lookupIterator来实现

knownProviders就是加载实现类缓存的迭代器,lookupIterator就是懒加载实现类迭代器

代码语言:java
AI代码解释
复制
public Iterator<S> iterator() {
    return new Iterator<S>() {

        Iterator<Map.Entry<String,S>> knownProviders
            = providers.entrySet().iterator();

        public boolean hasNext() {
            if (knownProviders.hasNext())
                return true;
            return lookupIterator.hasNext();
        }

        public S next() {
            if (knownProviders.hasNext())
                return knownProviders.next().getValue();
            return lookupIterator.next();
        }

        public void remove() {
            throw new UnsupportedOperationException();
        }

    };
}

代码比较简单,简单来说就是有缓存优先用缓存,否则使用懒加载迭代器进行加载实现类

迭代器hasNext(读取实现类全限定类名)

迭代器hasNext判断是否有下一个实现时,会检测是否加载过SPI配置文件(META-INF/services + SPI接口全限定类名)

如果为空说明未加载过,使用类加载器去查找SPI配置文件的URL

然后尝试去解析配置文件中的全限定类名,并将结果放入迭代器pending中

代码语言:java
AI代码解释
复制
private boolean hasNextService() {
    if (nextName != null) {
        return true;
    }
    
    //configs是SPI配置文件资源,不为空说明已经加载过
    if (configs == null) {
        try {
            //文件路径:META-INF/services  + SPI接口全限定类名
            String fullName = PREFIX + service.getName();
            if (loader == null)
                configs = ClassLoader.getSystemResources(fullName);
            else
                //查找资源目录下SPI配置文件
                configs = loader.getResources(fullName);
        } catch (IOException x) {
            fail(service, "Error locating configuration files", x);
        }
    }
    
    //pending是实现类全限定类名的迭代器
    while ((pending == null) || !pending.hasNext()) {
        if (!configs.hasMoreElements()) {
            return false;
        }
        //如果实现类迭代器没有下一个值 并且 解析的SPI配置文件有内容就进行解析
        //解析就是读取每一行的全限定类名放入列表最后返回全限定类名的迭代器
        pending = parse(service, configs.nextElement());
    }
    nextName = pending.next();
    return true;
}

简单来说就是未加载全限定类名就去加载全限定类名,加载过就返回下一个全限定类名

迭代器next(加载实现类)

迭代器的next方法,最终会调用nextService通过反射先进行类加载再进行实例化最后加入缓存

代码语言:java
AI代码解释
复制
private S nextService() {
    if (!hasNextService())
        throw new NoSuchElementException();
    String cn = nextName;
    nextName = null;
    Class<?> c = null;
    try {
        //类加载
        c = Class.forName(cn, false, loader);
    } catch (ClassNotFoundException x) {
        fail(service,
             "Provider " + cn + " not found");
    }
    if (!service.isAssignableFrom(c)) {
        fail(service,
             "Provider " + cn  + " not a subtype");
    }
    try {
        //实例化
        S p = service.cast(c.newInstance());
        //添加缓存
        providers.put(cn, p);
        return p;
    } catch (Throwable x) {
        fail(service,
             "Provider " + cn + " could not be instantiated",
             x);
    }
    throw new Error();          // This cannot happen
}

至此SPI的实现原理分析完毕,在其读取、加载实现类的流程中,路径是默认的,使用时需要遵守“约定”

接下来,我们再来分析一个使用SPI应用的案例

🎈应用

SPI 在实际应用中最经典的例子莫过于 JDBC 驱动的加载

当我们使用 DriverManager.getConnection() 方法获取数据库连接时,实际上就是利用了 SPI 来动态加载合适的 JDBC 驱动程序

DriverManager 类初始化时会加载初始化驱动

代码语言:java
AI代码解释
复制
static {
    loadInitialDrivers();
    println("JDBC DriverManager initialized");
}

loadInitialDrivers方法中就会使用ServiceLoader.load初始化,并调用迭代器加载驱动

代码语言:java
AI代码解释
复制
private static void loadInitialDrivers() {
    String drivers;
    try {
        drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
            public String run() {
                return System.getProperty("jdbc.drivers");
            }
        });
    } catch (Exception ex) {
        drivers = null;
    }
    // If the driver is packaged as a Service Provider, load it.
    // Get all the drivers through the classloader
    // exposed as a java.sql.Driver.class service.
    // ServiceLoader.load() replaces the sun.misc.Providers()

    AccessController.doPrivileged(new PrivilegedAction<Void>() {
        public Void run() {
			
            //实例化ServiceLoader
            ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
            Iterator<Driver> driversIterator = loadedDrivers.iterator();

            /* Load these drivers, so that they can be instantiated.
             * It may be the case that the driver class may not be there
             * i.e. there may be a packaged driver with the service class
             * as implementation of java.sql.Driver but the actual class
             * may be missing. In that case a java.util.ServiceConfigurationError
             * will be thrown at runtime by the VM trying to locate
             * and load the service.
             *
             * Adding a try catch block to catch those runtime errors
             * if driver not available in classpath but it's
             * packaged as service and that service is there in classpath.
             */
            try{
                //加载实现类
                while(driversIterator.hasNext()) {
                    driversIterator.next();
                }
            } catch(Throwable t) {
            // Do nothing
            }
            return null;
        }
    });

    println("DriverManager.initialize: jdbc.drivers = " + drivers);

    if (drivers == null || drivers.equals("")) {
        return;
    }
    String[] driversList = drivers.split(":");
    println("number of Drivers:" + driversList.length);
    for (String aDriver : driversList) {
        try {
            println("DriverManager.Initialize: loading " + aDriver);
            Class.forName(aDriver, true,
                    ClassLoader.getSystemClassLoader());
        } catch (Exception ex) {
            println("DriverManager.Initialize: load failed: " + ex);
        }
    }
}

以MySQL的驱动为例,它的实现类为com.mysql.cj.jdbc.Driver,在运行时动态的加载实例化实现类

MySQL驱动
MySQL驱动

🎉总结

SPI机制能够将接口与实现进行解耦,从而降低耦合性、提高模块的扩展性

使用SPI时需要遵守约定,定义SPI接口、配置SPI配置文件

SPI机制由ServiceLoader实现,

ServiceLoader类加载实现类时可能打破双亲委派模型,父类加载器的职责交给子类加载器执行

ServiceLoader迭代器优先采用缓存,没有缓存才进行懒加载SPI接口的实现类

迭代器hasNext判断是否存在下一个元素时,没缓存的情况会去加载SPI配置文件,并一行行解析文件中的全限定类名

迭代器next获取下一个元素时,没缓存的情况会通过反射根据全限定类名进行类加载,再实例化对象,最后放入缓存

SPI低耦合、高扩展的特性被应用在各种框架、中间件中,如JDBC、Tomcat..

最后(不要白嫖,一键三连求求拉~)

😁我是菜菜,热爱技术交流、分享与写作,喜欢图文并茂、通俗易懂的输出知识

📚在我的博客中,你可以找到Java技术栈的各个专栏:Java并发编程与JVM原理、Spring和MyBatis等常用框架及Tomcat服务器的源码解析,以及MySQL、Redis数据库的进阶知识,同时还提供关于消息中间件和Netty等主题的系列文章,都以通俗易懂的方式探讨这些复杂的技术点

🏆除此之外,我还是掘金优秀创作者、腾讯云年度影响力作者、华为云年度十佳博主....

👫我对技术交流、知识分享以及写作充满热情,如果你愿意,欢迎加我一起交流(vx:CaiCaiJava666),也可以持续关注我的公众号:菜菜的后端私房菜,我会分享更多技术干货,期待与更多志同道合的朋友携手并进,一同在这条充满挑战与惊喜的技术之旅中不断前行

🤝如果觉得菜菜写的不错,可以点赞、关注、收藏支持一下~

📖本篇文章被收入专栏 Java,感兴趣的同学可以持续关注喔

📝本篇文章笔记以及案例被收入 Gitee-CaiCaiJava、 Github-CaiCaiJava,除此之外还有更多Java进阶相关知识,感兴趣的同学可以star持续关注喔~

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
7、常见面试口语提问问题汇总
Good morning! It is really my honor to have this opportunity for an interview; I hope I can make a good performance today. I’m confident that I can succeed. Now I will introduce myself briefly I am 24 years old, born in Hubei province. I graduated from Hubei Automotive Industries Institute. My major is software engineering, and I got my bachelor degree after my graduation in the year of 2009. I spend most of my time on study, I have passed CET6, and I have acquired basic knowledge of my major during my school time. In July 2009, I begin working for a state-owned technology company as a software development engineer in wuhan. Because I want to change my working environment, I’d like to find a job which is more challenging. Moreover HP is a global company, so I feel I can gain the most from working in this kind of company environment. That is the reason why I come here to compete for this position. I think I’m a good team player and I’m a person of great honesty to others. Also I am able to work under great pressure. That’s all. Thank you for giving me the chance.
全栈程序员站长
2022/07/31
3980
你对数据库管理员的定义是什么?
你对数据库管理员的定义是什么? By Ben Kubicek, 2017/09/05 今天恰好Steve旅游到了英国,我们将邀请他作为客串社评员。 好的,我知道最简单的答案是数据库管理员(DBA),但是到底这是一个什么角色呢?如果必须要你把DBA描述给一个不懂技术的人,你会怎么描述?数据库管理员可以有很多不同的角色或者作用。在这些角色或作用中我曾经看到过或者做过的包括:安装SQL Server,管理服务器性能,包括磁盘空间利用率,管理备份,控制权限和用户的权利或角色,管理复制和不间断的在多个故障转移群集以及
Woodson
2018/07/18
9300
【黄啊码】上百个AI提示词模板,不用多想,直接收藏【三】
Generate a list of 10 frequently asked questions based on the following content: [内容]
黄啊码
2024/08/09
3300
我是怎么招聘程序员的
很早以前就想写一篇和面试相关的文章了,今天在网络上看到一篇关于如何去面试程序员的英文文章,发现其中有很多和我共鸣的东西,所以仿照其标题通过自己的经历写下了这篇文章。
范蠡
2018/08/17
7010
Oracle 裁员,与其哀怨,不若放下
放上五六年写的两篇旧文。应景 Oracle 最近大 (can) 刀 (wu) 阔 (ren) 斧 (dao) 的裁员,我也有读者不幸中招。《原则》的作者 Ray Dalio 说:
tyrchen
2019/05/07
5240
原则一书的管理干货
... 2) Realize that you have nothing to fear from truth. Understanding, accepting, and knowing how to effectively deal with reality are crucial for achieving success. Having truth on your side is extremely powerful. While the truth itself may be scary—you have a weakness, you have a deadly disease, etc.—knowing the truth will allow you to deal with your situation better. Being truthful, and letting others be truthful with you, allows you to explore your own thoughts and exposes you to the feedback that is essential for your learning. Being truthful is an extension of your freedom to be you; people who are one way on the inside and another on the outside become conflicted and often lose touch with their own values. It’s difficult for them to be happy, and almost impossible for them to be at their best. While the first-order effects of being radically truthful might not be desirable, the second- and third-order effects are great. ... 2) 你要知道,真相没什么可怕的。理解、接受、并了解如何能够有效处理现实问题,这对于取得成功而言至关重要。站在真相一边,就最有说服力。当然,有时真相本身可能会让人惧怕,比如,你暴露了一个弱点或者甚至是身患绝症,而了解真相却能让你更从容地处理事情。对自己坦诚、对他人坦诚,让别人也对自己坦诚,才能更好地了解自己的想法,获得他人的反馈,从而学到知识。诚实,同时也是做自己的自由的延伸。表里不一的人往往会自相矛盾,也容易丢失自己的价值观。他们不易开心,更不可能展现出自己最好的一面。尽管从一级效应的角度来看,过于诚实未免使人难以接受,但是从二、三级效应的角度而言,这样做却收效可观。
CreateAMind
2018/07/24
6140
英文文法学习笔记(7)比较 1.1 比较变化1.2 原级1.3 比较级1.4 最高级2.1 比较变化2.2 原级2.3 比较级2.4 最高级3.1 比较变化3.2 原级3.3 比较级
本篇为第7篇笔记:比较。 一、经典例句 1.1 比较变化 1.2 原级 1.3 比较级 1.4 最高级 二、知识点回顾(对应例句编号) 2.1 比较变化 2.2 原级 2.3 比较级 2.4 最高级
Alfred Zhao
2021/10/09
8400
重磅推荐:程序员海外工作 / 面试手册
大家好,我是TJ 一个励志推荐10000款开源项目与工具的程序员 大家好,我是TJ 最近有不少朋友在聊海外工作的信息,期间推荐了一个开源项目,今天拿出来分享给大家! 项目名称:程序员海外工作/英文面试手册。该项目收集了很多直投海外找工作的资源,比如: 你应该从这些主要站点搜索职位: LinkedIn Glassdoor Indeed 除此之外,还给了一些可能大家不太知道的地区性的求职网站: 🇩🇪 德国、🇪🇸 西班牙:XING 🗾日本:TokyoDev 、JapanDev、DODA、RGF、RECRUIT
程序猿DD
2023/02/24
5120
重磅推荐:程序员海外工作 / 面试手册
Go is not (very) simple, folks
I’ve recently started coding a little bit in Go, mostly out of curiosity. I’d known quite a bit about it beforehand, but never tried it out in practice (there was no need). But now Go is being considered as one of the options for a project in the team where I work and so I thought it would be nice to get a bit of hands on experience.
李海彬
2018/12/21
4690
Go is not (very) simple, folks
python 面试题-收集100+面试题笔试题
前言 收集了100多道 Python 基础练习题,面试题,笔试题,练完这些题 Python 内功大增!适合python初学者和基础不牢的同学练手。 想刷面试题的也可以多看看,答案在网易云平台课程上ht
上海-悠悠
2021/04/12
7.1K0
How To Ask Questions The Smart Way
Table of Contents Introduction Before You Ask When You Ask How To Interpret Answers On Not Reacting Like A Loser Questions Not To Ask Good and Bad Questions If You Can't Get An Answer
阿敏总司令
2019/02/28
6810
外企入职第一封英文邮件_投外企要英文简历吗
一份出色的Resume,是向外企求职的关键之一。不了解有关的常识和程式,不花费相当的心思来展示,以有纯正娴熟的英文功底,决不能获得单位的青睐。在一大堆错误百出、英文表达能力低劣或平庸,毫无针对性和创造性的Resume中,你的那份若能让人眼睛一亮,成功的机会必将大大增加,以下试着结合一个具体的例子给出说明和评述。
全栈程序员站长
2022/11/01
8120
Attention is all you need新翻译架构的测试
摘要总结:本文介绍了技术社区中的一种新型内容编辑工具,该工具使用机器学习和自然语言处理技术来自动生成摘要,从而帮助作者更快速、高效地总结和分享自己的文章。作者通过实践案例展示了如何使用该工具来快速生成摘要,并分享了在实际操作过程中遇到的挑战和解决方案。
sparkexpert
2018/01/09
1.4K0
Attention is all you need新翻译架构的测试
杜克大学的13幅经典逻辑图,改变你的生活轨迹
杜克大学的13幅逻辑图与你一块探讨关于生活的哲学,这13幅图将帮助你更清晰地了解自己的行为、目标及思考问题的方式,让你选择正确的方式去实现心中所想与所需。
宇相
2019/06/17
5360
杜克大学的13幅经典逻辑图,改变你的生活轨迹
golang面试题(带答案)[通俗易懂]
注:引用就是同一份,相当于起了一个别名,就是多起了一个名字而已。 在Go语言中的引用类型有:映射(map),数组切片(slice),通道(channel),方法与函数。 整型,字符串,布尔,数组在当作参数传递时,是传递副本的内存地址,也就是值传递。 2.下面代码输出什么,为什么
全栈程序员站长
2022/09/07
1.4K0
什么是创建区块链公司的最大障碍?
创建区块链公司最大的障碍是什么?最初出现在Quora上:获得和分享知识的地方,使人们能够向他人学习,更好地了解世界。 Chronic联合创始人Samantha Radocchia在Quora做了回应: 当我们第一次启动Chronicled时,这对于区块链业务来说是一个非常独特的时代。没有人真正了解区块链,所以我们面临的最大挑战就是教育人们了解它是什么,为什么需要它以及它如何改变他们的行业。 我曾经打开过大部分对话,“你知道比特币是什么吗?” 如果他们这样做了,我会努力向他们解释区块链。 现在,随着区块链公司
架构师研究会
2018/04/09
1.2K0
美东一公司的郁闷面试题
说是题目可以用不同的语言,但是貌似 Java 是多线程的,用 Java 写肯定容易不少。
HoneyMoose
2023/09/14
1420
美东一公司的郁闷面试题
亚麻BQ
Leaders start with the customer and work backward. They work vigorously to earn and keep customer trust. Although leaders pay attention to competitors, they obsess over customers.
王脸小
2019/10/31
9100
如果吃白食不付钱,就停止提供支持:Apache PLC4X 开源维护者 Christofer Dutz
Apache PLC4X的创建者声称将停止免费支持,要求其开发工作有回报。 又一位开源软件开发人员厌倦了许多公司这副难看的吃相:享用他帮助维护的代码,却分文不掏来支持该项目。 周二,Apache PLC4X的创建者Christofer Dutz表示,如果企业用户不积极掏钱包,他将停止为软件提供社区支持。 他在GitHub上所发的一篇文章中写道:“这个行业似乎喜欢使用PLC4X和普通的开源软件,但似乎并不愿意支持开发这些软件的人。所以,我将停止为PLC4X提供免费的社区支持。” Dutz是Apache P
云头条
2022/03/18
3790
习得性无助的发现和改变
这是你作为软件工程师工作的第一天,你非常兴奋的开始了你的第一次commit。当你的新同事老毕给你介绍代码库时,你不禁注意到了老毕一直在回复企微消息,你问老毕“要不你先忙,我们一会在看?”,“不用,这周我负责oncall,我都习惯了,没事哈,着急的都会打电话的”。你有一丝丝的困惑,在企微消息不断弹出的同时,老毕给你介绍着大厂有限公司的代码库的构成。 一年过去了,你在参加老毕离职趴的路上,口袋里的企微消息不断滴滴响,“老毕咋走了呢,没听他抱怨过啥啊”,一边想着,一边把通知置为了静音,“这周贼累,得好好睡一觉才行” 在路上正走着,你听到把你倒挂的应届毕业生参加完封培快乐的交谈。
hermanzeng
2021/11/16
3770
习得性无助的发现和改变
推荐阅读
相关推荐
7、常见面试口语提问问题汇总
更多 >
LV.0
萨拉扬信息技术有限公司总经理
目录
  • 深入解析Java SPI🌟从使用到原理的全面之旅🚀
    • ✨前言
    • 🔍SPI简介
    • ✍️SPI使用(搭建项目)
    • 📚SPI原理(源码分析)
      • ServiceLoader
      • ServiceLoader.load
      • ServiceLoader迭代器实现
      • 迭代器hasNext(读取实现类全限定类名)
      • 迭代器next(加载实现类)
    • 🎈应用
    • 🎉总结
      • 最后(不要白嫖,一键三连求求拉~)
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档