• 你真的了解Spring的依赖查找吗?


    1.写在前面

    前面的博客我们介绍了Spring的总览,今天我们来了解下Spring的依赖查找的相关的内容,我们会从它的前世今生来带你了解下,以及各种类型的查找的方式,同时介绍对应的相对比较安全的查找的方式。以及会介绍一些比较常见的面试题,供大家参考。

    2.依赖查找的今世前生

    那么提到依赖查找,很多人第一个想到的就是Spring中的依赖查找,但是在Spring出来之前,Java中其实已经就有了依赖查找的API了,具体的如下,同时Spring也参考其中的一部分。

    单一类型依赖查找

    • JNDI - javax.naming.Context#lookup(javax.naming.Name)
    • JavaBeans - java.beans.beancontext.BeanContext

    集合类型依赖查找

    • java.beans.beancontext.BeanContext

    层次性依赖查找

    • java.beans.beancontext.BeanContext

    单一类型的依赖查找

    那么我这儿先带大家看下对应的源码,后面再带着大家写几个例子,首先我们先看对应的Java中的Context类,具体的如下:

    在这里插入图片描述

    上面的两个方法都是javax.naming.Context提供的Bean的查找的方法。

    接下来我们继续看一下BeanContext,这个BeanContextSpring参考对应的实现的方式。具体的内容如下:

    在这里插入图片描述

    可以看到这接口继承的了Collection接口,我们可以知道这个接口下面所有的Bean都是存在这个集合下面的,对这个集合进行增删改查。

    集合类型依赖查找

    集合类型查找是根据一个key去查找出来一个集合的类型。

    在这里插入图片描述

    对应的方法如上图所示,返回的是一个object对象,也就是集合的对象也是可以返回的

    层次性依赖查找BeanContext的源码写的比较混乱,这儿我们不做介绍了。

    3.单一类型依赖查找

    单一类型依赖查找接口 - BeanFactory

    • 根据 Bean 名称查找
      • getBean(String)
      • Spring 2.5 覆盖默认参数: getBean(String,Object…)
    • 根据 Bean 类型查找
      • Bean 实时查找
        • Spring 3.0 getBean(Class)
        • Spring 4.1 覆盖默认参数: getBean(Class,Object…)
      • Spring 5.1 Bean 延迟查找
        • getBeanProvider(Class)
        • getBeanProvider(ResolvableType)
    • 根据 Bean 名称 + 类型查找: getBean(String,Class)

    上面的有些的查找的方式我们前面的博客已经介绍过了,上面的覆盖默认参数的方式,我们只要知道有这个方法就行了,不需要做过多的了解,因为这个方法比较危险,它会覆盖默认的参数。

    在这里插入图片描述

    在这里插入图片描述

    我们看一下对应的5.1的延迟查找的API,具体的如下:

    在这里插入图片描述

    这儿我们可以看到返回的是ObjectProvider,我们点开这个实现类的内容,具体的如下:

    在这里插入图片描述

    可以发现这个类是继承了ObjectFactory类的,那么一切都那么好理解了。

    我们写了一个简单的例子就行,具体的代码如下:

    package org.learn.spring.dependency.lookup;
    
    import org.springframework.beans.factory.ObjectProvider;
    import org.springframework.context.annotation.AnnotationConfigApplicationContext;
    import org.springframework.context.annotation.Bean;
    
    // 通过 ObjectProvider 进行依赖查找
    public class ObjectProviderDemo {
    
        public static void main(String[] args) {
            AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
    
            // 将当前类ObjectProviderDemo 作为配置类(configuration class)
            applicationContext.register(ObjectProviderDemo.class);
            // 启动应用上下文
            applicationContext.refresh();
    
            lookupByObjectProvider(applicationContext);
            applicationContext.close();
        }
    
        private static void lookupByObjectProvider(AnnotationConfigApplicationContext applicationContext) {
            ObjectProvider<String> objectProvider = applicationContext.getBeanProvider(String.class);
            System.out.println(objectProvider.getObject());
        }
    
        // 方法的名称就是Bean的名称, Bean的名称helloWorld
        @Bean
        public String helloWorld(){
            return "Hello World";
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    还有一个getBeanProvider(ResolvableType)这个方法我们后面再说,ResolvableType这个类型主要是用来处理泛型的。等到后面我们介绍泛型的时候,我们再来详细的介绍。

    4.集合类型依赖查找

    集合类型依赖查找接口 - ListableBeanFactory

    • 根据 Bean 类型查找
    • 获取同类型 Bean 名称列表
      • getBeanNamesForType(Class)
      • Spring 4.2 getBeanNamesForType(ResolvableType)
    • 获取同类型 Bean 实例列表
      • getBeansOfType(Class) 以及重载方法
    • 通过注解类型查找
      • Spring 3.0 获取标注类型 Bean 名称列表
        • getBeanNamesForAnnotation(Class)
      • Spring 3.0 获取标注类型 Bean 实例列表
        • getBeansWithAnnotation(Class)
      • Spring 3.0 获取指定名称 + 标注类型 Bean 实例
        • findAnnotationOnBean(String,Class)

    上面的API我前面都有或多或少的介绍过,我这儿不做赘述了,从上面的API我们可以得到一个结论就是ListableBeanFactory主要用来查找集合的。而方式有两种一种是通过Bean的类型查找,一种是通过注解的类型去查找。集合的列表主要有两种情况,一种查询Bean的名称的集合,一种是查询Bean的实例的集合。

    5.层次性依赖查找

    层次性依赖查找接口 - HierarchicalBeanFactory 将单一类型的查找和集合类型查找合并到一起去

    • 双亲 BeanFactory: getParentBeanFactory()
    • 层次性查找
      • 根据 Bean 名称查找
        • 基于 containsLocalBean 方法实现
      • 根据 Bean 类型查找实例列表
        • 单一类型: BeanFactoryUtils#beanOfType
        • 集合类型: BeanFactoryUtils#beansOfTypeIncludingAncestors
      • 根据 Java 注解查找名称列表
        • BeanFactoryUtils#beanNamesForTypeIncludingAncestors

    具体的代码如下:

    package org.learn.spring.dependency.lookup;
    
    import org.springframework.beans.factory.BeanFactory;
    import org.springframework.beans.factory.HierarchicalBeanFactory;
    import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
    import org.springframework.beans.factory.support.DefaultListableBeanFactory;
    import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
    import org.springframework.context.annotation.AnnotationConfigApplicationContext;
    
    // HierarchicalBeanFactory 层次查找依赖实例
    public class HierarchicalDependencyLookup {
    
        public static void main(String[] args) {
            AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
    
            // 将当前类ObjectProviderDemo 作为配置类(configuration class)
            applicationContext.register(ObjectProviderDemo.class);
    
            // 1.获取HierarchicalBeanFactory <- ConfigurableBeanFactory <- ConfigurableListableBeanFactory
            ConfigurableListableBeanFactory beanFactory = applicationContext.getBeanFactory();
            System.out.println("当前BeanFactory 的 Parent BeanFactory:" + beanFactory.getParentBeanFactory());
    
            // 2.设置ParentBeanFactory
            HierarchicalBeanFactory parentBeanFactory = createParentBeanFactory();
            beanFactory.setParentBeanFactory(parentBeanFactory);
            System.out.println("当前BeanFactory 的 Parent BeanFactory:" + beanFactory.getParentBeanFactory());
    
            // 启动应用上下文
            applicationContext.refresh();
            displayContainsLocalBean(beanFactory, "user");
            displayContainsLocalBean(parentBeanFactory, "user");
    
            displayContainsBean(beanFactory, "user");
            displayContainsBean(parentBeanFactory, "user");
    
            applicationContext.close();
        }
    
        private static ConfigurableListableBeanFactory createParentBeanFactory() {
            // 创建BeanFactory容器
            DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
            XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
            // XML 配置文件的路径
            String location = "classpath:META-INF/dependency-lookup-context.xml";
            // 加载配置
            reader.loadBeanDefinitions(location);
            return beanFactory;
        }
    
        private static void displayContainsLocalBean(HierarchicalBeanFactory beanFactory, String beanName) {
            System.out.printf("当前 BeanFactory[%s] 是否包含 Local Bean [name : %s] : %s\n", beanFactory, beanName, beanFactory.containsLocalBean(beanName));
        }
    
        private static void displayContainsBean(HierarchicalBeanFactory beanFactory, String beanName) {
            System.out.printf("当前 BeanFactory[%s] 是否包含 Bean [name : %s] : %s\n", beanFactory, beanName, containsBean(beanFactory, beanName));
        }
    
        private static boolean containsBean(HierarchicalBeanFactory beanFactory, String beanName) {
            BeanFactory parentBeanFactory = beanFactory.getParentBeanFactory();
            if (parentBeanFactory instanceof HierarchicalBeanFactory) {
                HierarchicalBeanFactory parentHierarchicalBeanFactory = HierarchicalBeanFactory.class.cast(parentBeanFactory);
                if (containsBean(parentHierarchicalBeanFactory, beanName)) {
                    return true;
                }
            }
            return beanFactory.containsLocalBean(beanName);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68

    上面的代码就是实现了层次的查找,这个时候是有两个工厂,一个是父工厂,一个子工厂,containsBean方法就是通过递归的方式来查找所有的工厂中包含的对应的Bean。

    其实BeanFactoryUtils有相应的实现,具体的的代码如下:

    在这里插入图片描述

    6.延迟依赖查找

    Bean 延迟依赖查找接口

    • org.springframework.beans.factory.ObjectFactory
    • org.springframework.beans.factory.ObjectProvider
      • Spring 5 对 Java 8 特性扩展
      • 函数式接口
        • getIfAvailable(Supplier)
        • ifAvailable(Consumer)
      • Stream 扩展 - stream()

    具体的代码如下:

    package org.learn.spring.dependency.lookup;
    
    import org.learn.spring.ioc.overview.domain.User;
    import org.springframework.beans.factory.ObjectProvider;
    import org.springframework.context.annotation.AnnotationConfigApplicationContext;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Primary;
    
    // 通过 ObjectProvider 进行依赖查找
    public class ObjectProviderDemo {
    
        public static void main(String[] args) {
            AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
    
            // 将当前类ObjectProviderDemo 作为配置类(configuration class)
            applicationContext.register(ObjectProviderDemo.class);
            // 启动应用上下文
            applicationContext.refresh();
    
            lookupByAvailable(applicationContext);
            lookupByStreamOps(applicationContext);
            applicationContext.close();
        }
    
        private static void lookupByStreamOps(AnnotationConfigApplicationContext applicationContext) {
            ObjectProvider<String> objectProvider = applicationContext.getBeanProvider(String.class);
            Iterable<String> stringIterable = objectProvider;
            for (String s : stringIterable) {
                System.out.println(s);
            }
            objectProvider.stream().forEach(System.out::println);
        }
    
        private static void lookupByAvailable(AnnotationConfigApplicationContext applicationContext) {
            ObjectProvider<User> userObjectProvider = applicationContext.getBeanProvider(User.class);
            User user = userObjectProvider.getIfAvailable(User::createUser);
            System.out.println("当前 User 对象:" + user);
        }
    
      
        @Bean
        @Primary
        public String helloWorld() {
            return "Hello World";
        }
    
        @Bean
        public String message() {
            return "Message";
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52

    7.安全依赖查找

    依赖查找安全性对比

    在这里插入图片描述

    注意: 层次性依赖查找的安全性取决于其扩展的单一或集合类型的 BeanFactory 接口

    具体的代码如下:

    package org.learn.spring.dependency.lookup;
    
    import org.learn.spring.ioc.overview.domain.User;
    import org.springframework.beans.BeansException;
    import org.springframework.beans.factory.BeanFactory;
    import org.springframework.beans.factory.ListableBeanFactory;
    import org.springframework.beans.factory.ObjectFactory;
    import org.springframework.beans.factory.ObjectProvider;
    import org.springframework.context.annotation.AnnotationConfigApplicationContext;
    
    // 类型安全查找的示例
    public class TypeSafetyDependencyLookupDemo {
    
        public static void main(String[] args) {
            AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
    
            // 将当前类ObjectProviderDemo 作为配置类(configuration class)
            applicationContext.register(ObjectProviderDemo.class);
            // 启动应用上下文
            applicationContext.refresh();
    
            // 演示BeanFactory#getBean方法的安全性 不安全的
            displayBeanFactoryGetBean(applicationContext);
    
            // 演示ObjectFactory#getObject方法的安全性 不安全的
            displayObjectFactoryGetObject(applicationContext);
    
            // 演示ObjectProvider#getIfAvailable方法的安全性 安全的
            displayObjectProviderGetIfAvailable(applicationContext);
    
            // 演示 ListableBeanFactory#getBeansOfType方法的安全性 安全的 也是会抛出BeansException 但是这个只有在Bean创建失败的时候抛出
            displayListableBeanFactoryGetBeansOfType(applicationContext);
    
            //演示ObjectProvider#StreamOps方法的安全性 安全的
            displayObjectProviderStreamOps(applicationContext);
    
            // 关闭应用上下文
            applicationContext.close();
        }
    
        // 不安全的
        public static void displayBeanFactoryGetBean(BeanFactory beanFactory) {
            printBeansException("displayBeanFactoryGetBean", () -> beanFactory.getBean(User.class));
        }
    
        private static void displayObjectFactoryGetObject(AnnotationConfigApplicationContext applicationContext) {
            // ObjectProvider is ObjectFactory
            ObjectFactory<User> userObjectProvider = applicationContext.getBeanProvider(User.class);
            printBeansException("displayObjectFactoryGetObject", userObjectProvider::getObject);
        }
    
    
        private static void displayObjectProviderGetIfAvailable(AnnotationConfigApplicationContext applicationContext) {
            ObjectProvider<User> userObjectProvider = applicationContext.getBeanProvider(User.class);
            printBeansException("displayObjectProviderGetIfAvailable", userObjectProvider::getIfAvailable);
        }
    
        // 也是会抛出BeansException 但是这个只有在Bean创建失败的时候抛出
        private static void displayListableBeanFactoryGetBeansOfType(ListableBeanFactory beanFactory) {
            printBeansException("displayListableBeanFactoryGetBeansOfType", () -> beanFactory.getBeansOfType(User.class));
        }
    
        private static void displayObjectProviderStreamOps(AnnotationConfigApplicationContext applicationContext) {
            ObjectProvider<User> userObjectProvider = applicationContext.getBeanProvider(User.class);
            printBeansException("displayObjectProviderStreamOps", () -> userObjectProvider.stream().forEach(System.out::println));
        }
    
        private static void printBeansException(String source, Runnable runnable) {
            System.err.println("===================================================================");
            System.err.println("Source from " + source);
            try {
                runnable.run();
            } catch (BeansException e) {
                e.printStackTrace();
            }
        }
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79

    运行结果如下:

    在这里插入图片描述

    可以看出与上面的表格是一样的。

    8.内建可查找的依赖

    AbstractApplicationContext 内建可查找的依赖

    在这里插入图片描述

    注解驱动 Spring 应用上下文内建可查找的依赖( 部分)

    在这里插入图片描述

    注解驱动 Spring 应用上下文内建可查找的依赖( 续)

    在这里插入图片描述

    那么这些类在什么地方注册的呢?具体的如下:

    在这里插入图片描述

    后面在初始化的流程中,我们会详细的分析,上面的代码是AnnotationConfigUtils类中的

    9.依赖查找中的经典异常

    BeansException 子类型

    在这里插入图片描述

    具体的代码如下:

    演示NoUniqueBeanDefinitionException异常

    package org.learn.spring.dependency.lookup;
    
    import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
    import org.springframework.context.annotation.AnnotationConfigApplicationContext;
    import org.springframework.context.annotation.Bean;
    
    // NoUniqueBeanDefinitionException 的异常举例
    public class NoUniqueBeanDefinitionExceptionDemo {
    
        public static void main(String[] args) {
            // 创建BeanFactory 容器
            AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
    
            // NoUniqueBeanDefinitionExceptionDemo 作为配置类(configuration class)
            applicationContext.register(NoUniqueBeanDefinitionExceptionDemo.class);
    
            // 启动应用上下文
            applicationContext.refresh();
    
            try {
                // 由于应用上下文存在两个String类型的Bean,通过单一查找会抛出异常
                applicationContext.getBean(String.class);
            } catch (NoUniqueBeanDefinitionException e) {
                System.err.printf("Spring 应用上下文存在 %d 个 %s 类型的Bean,具体的原因:%s", e.getNumberOfBeansFound(), e.getBeanType(), e.getMessage());
            }
    
            // 关闭应用上下文
            applicationContext.close();
        }
    
        @Bean
        public String bean1() {
            return "bean1";
        }
    
        @Bean
        public String bean2() {
            return "bean2";
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    演示BeanInstantiationException异常

    package org.learn.spring.dependency.lookup;
    
    import org.springframework.beans.factory.config.BeanDefinition;
    import org.springframework.beans.factory.support.BeanDefinitionBuilder;
    import org.springframework.beans.factory.support.BeanDefinitionRegistry;
    import org.springframework.context.annotation.AnnotationConfigApplicationContext;
    
    // BeanInstantiationException 异常的示例
    public class BeanInstantiationExceptionDemo {
    
        public static void main(String[] args) {
            // 创建BeanFactory 容器
            AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
    
            // 注册BeanDefinition Bean Class 是CharSequence的接口
            BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(CharSequence.class);
            applicationContext.registerBeanDefinition("errorBean", beanDefinitionBuilder.getBeanDefinition());
    
            // 启动应用上下文
            applicationContext.refresh();
    
            // 关闭应用上下文
            applicationContext.close();
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    演示BeanCreationException异常

    package org.learn.spring.dependency.lookup;
    
    import org.springframework.beans.factory.InitializingBean;
    import org.springframework.beans.factory.support.BeanDefinitionBuilder;
    import org.springframework.context.annotation.AnnotationConfigApplicationContext;
    
    // BeanCreationException 异常示例
    public class BeanCreationExceptionDemo {
        public static void main(String[] args) {
            // 创建BeanFactory 容器
            AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
    
            // 注册BeanDefinition Bean Class 是POJO的普通类,不过在初始化方法回调时抛出异常
            BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(POJO.class);
            applicationContext.registerBeanDefinition("errorBean", beanDefinitionBuilder.getBeanDefinition());
    
            // 启动应用上下文
            applicationContext.refresh();
    
            // 关闭应用上下文
            applicationContext.close();
        }
    
        static class POJO implements InitializingBean {
    
            @Override
            public void afterPropertiesSet() throws Exception {
                throw new Exception("For purposes....");
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    10.面试题

    10.1 ObjectFactory 与 BeanFactory 的区别?

    ObjectFactory 与 BeanFactory 均提供依赖查找的能力。

    不过 ObjectFactory 仅关注一个或一种类型的 Bean 依赖查找, 并且自身不具备依赖查找的能力, 能力则由 BeanFactory 输出。

    BeanFactory 则提供了单一类型、 集合类型以及层次性等多种依赖查找方式。

    10.2 BeanFactory.getBean 操作是否线程安全?

    BeanFactory.getBean 方法的执行是线程安全的, 操作过程中会增加互斥锁

  • 相关阅读:
    代码随想录算法训练营Day55 | 583. 两个字符串的删除操作、72. 编辑距离、编辑距离总结篇 | Python | 个人记录向
    Android:OkHttp同步请求和异步请求
    Java-网络编程
    [Redis]-四种部署方式
    深度理解事件流和DOM事件流的工作原理
    03_学习springdoc与微服务结合_简述
    码蹄集 - MT3111· 赋值
    Java核心篇,二十三种设计模式(十三),行为型——责任链模式
    QT(37)-mosquitto-MQTT客户端
    299. 猜数字游戏
  • 原文地址:https://blog.csdn.net/qq_36434742/article/details/127968346