精华内容
下载资源
问答
  • 哪一个不是容器对象
    千次阅读
    2016-05-31 15:52:49

    最近项目中就有如题的一个需求,有些对象,不希望将其交给spring容器去管理,想通过自己new的方式去实例化,以便自己更好的去维护这个对象。

    这里非SPRING管理的对象,即自己实例化的对象,用A来代替,SPRING管理的对象用B来代替

    在网上找了很久,最终搞定了,这里先说一个弯路:

    ApplicationContext context = new ClassPathXmlApplicationContext(
                    "classpath*:spring/spring-*.xml");
            return (CommonService) context.getBean("commonService");
    如上图所示,A类想引用B类,于是在A类中写了如上的方法来获取B类,启动项目后发现,一旦引用B类时,spring的所有对象就会再次实例化,通过JVM监控发现,对象快要撑爆了,果断放弃,说明如上方法和WEB容器启动加载不是共享的,是独立的,WEB容器启动加载了spring的所有对象,上面方法在被引用时又加载了,这就重复了,引用多少次,就重复多少次,巨坑啊!!!!


    找寻spring容器的方法,从spring容器里去拿一个刚刚被spring容器实例化的对象,这样就不会重复加载了,拿的是实例化后的共享的一个对象

    定义一个类实现自BeanFactoryAware接口

    public class ServiceLocator
            implements BeanFactoryAware {
    
        private static BeanFactory beanFactory = null;
        private static ServiceLocator serviceLocator = null;
    
        @Override
        public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
            ServiceLocator.beanFactory = beanFactory;
        }
    
        public BeanFactory getBeanFactory() {
            return beanFactory;
        }
    
        public static ServiceLocator getInstance() {
            if (serviceLocator == null) {
                serviceLocator = (ServiceLocator) beanFactory.getBean("serviceLocator");
            }
            return serviceLocator;
        }
    
        public static Object getService(String serviceName) {
            return beanFactory.getBean(serviceName);
        }
    
    }
    这样在A类中,如果要引用B类的话,通过上述类的静态方法getService即可,将bean的名字传入
    private CommonService getCommonService() {
            /*@SuppressWarnings("resource")
            ApplicationContext context = new ClassPathXmlApplicationContext(
                    "classpath*:spring/spring-*.xml");
            return (CommonService) context.getBean("commonService");*/
            return (CommonService) ServiceLocator.getService("commonService");
        }
    如上,亲测成功


    这样,非SPRING管理对象和SPRING管理对象就完美的结合到了一起




    更多相关内容
  • 来,从零手写一个IOC容器

    万次阅读 2022-06-14 17:25:18
    IOC(控制翻转)是程序设计的种思想,其本质就是上端对象不能直接依赖于下端对象,要是依赖的话就要通过抽象来依赖。

    一、简介

    IOC(控制翻转)是程序设计的一种思想,其本质就是上端对象不能直接依赖于下端对象,要是依赖的话就要通过抽象来依赖。比如,上端对象如 BLL 层中,需要调用下端对象的 DAL 层时不能直接调用 DAl 的具体实现,而是通过抽象的方式来进行调用。

    有这么一个场景,项目本来是用 Sqlserver 来进行数据访问的,那么就会有一个 SqlserverDal 对象。BLL 层调用的时候通过 new SqlserverDal(),直接创建一个 SqlserverDal 对象进行数据访问,现在项目又要改为 Mysql 数据库,用 MysqlDal 进行数据访问。这时候就麻烦了,你的 BLL 层将 new SqlserverDal() 全部改为 new MysqlDal()。同理 BLL 层也是这个道理。这么做,从程序的架构而言是相当不合理的。

    这时 IOC 就排上用场了,IOC 的核心理念就是上端对象通过抽象来依赖下端对象,那么我们在 BLL 中,不能直接通过 new SqlserverDal()来创建一个对象,而是通过结构来声明(抽象的形式来进行依赖),当我们替换 MysqlDal 时我们只需让 MysqlDal 也继承这个接口,那么我们 BLL 层的逻辑就不用动了。

    现在又有一个问题,对象我们可以用接口来接收,所有子类出现的地方都可以用父类来替代,这没毛病。但对象的创建还是要知道具体的类型,还是通过之前的 new SqlserverDal() 这种方式创建对象。肯定是不合理的,这里我们还是依赖于细节。这时候 IOC 容器就该上场了,IOC 容器可以理解为一个第三方的类,专门为我们创建对象用的,它不需要关注具体的业务逻辑,也不关注具体的细节。你只需将你需要的创建的对象类型传给它,它就能帮我们完成对象的创建。

    二、.Net 内置 IOC

    接触 .Net Core 的小伙伴可能对容器很熟悉,.Net Core 中将 IOC 容器内置了,创建对象需要先进行注册。如下:

    public void ConfigureServices(IServiceCollection services)
    {
           services.AddTransient<IHomeBll,HomeBll>();
           services.AddTransient<Iservice,LoginService>();
    }
    

    三、手写 IOC 容器

    从 .Net Core 中可以看出,是通过 ServiceCollection 容器帮我们完成对象的创建,我们只需将接口的类型和要创建对象的类型传进去,它就能帮我们完成对象的创建。那么它是什么原理呢,我们能不能创建自已的容器来帮我们完成对象的创建呢? 答案:当然可以。

    3.1 容器雏形

    首先不考虑那么多,先写一个容器,帮我们完成对象的创建工作。如下:

    public interface IHTContainer
    {
        void RegisterType<IT, T>();
        IT Resolve<IT>();
    }
    
    public class HTContainer : IHTContainer
    {
        //创建一个Dictionary数据类型的对象用来存储注册的对象
        private Dictionary<string, Type> TypeDictionary = new Dictionary<string, Type>();
    
        //注册方法,用接口的FullName为key值,value为要创建对象的类型
        public void RegisterType<IT, T>()
        {
            this.TypeDictionary.Add(typeof(IT).FullName, typeof(T));
        }
    
        //创建对象通过传递的类型进行匹配
        public IT Resolve<IT>()
        {
            string key = typeof(IT).FullName;
    
            //获取要创建对象的类型
            Type type = this.TypeDictionary[key];
    
            //这里先不考虑有参构造函数的问题,后面会逐一的解决这些问题
            //通过反射完成对象的创建,这里我们先不考虑参数问题
            return (IT)Activator.CreateInstance(type);
        }
    }
    

    简单调用,现在是通过一个第三方的容器为我们创建对象,并且我们不用依赖于细节,通过接口的类型完成对象的创建,当要将 SqlserverDal 替换为MysqlDal 时,我们只需要在注册的时候将 SqlserverDal 替换为 MysqlDal 即可。

     //实例化容器对象
    IHTContainer container = new HTContainer();
    //注册对象
    container.RegisterType<IDatabase,SqlserverDal>();
    //通过容器完成对象的创建,不体现细节,用抽象完成对象的创建
    IDatabase dal = container.Resolve<IDatabase>();
    dal.Connection("con");
    

    3.2 升级容器(单构造函数)

    上面将传统对象创建的方式,改为使用第三方容器来帮我们完成对象的创建。但这个容器考虑的还不是那么的全面,例如有参构造的问题,以及对象的依赖问题还没有考虑到。接下来继续完善这个容器,这里先不考虑多个构造函数的问题,这里先解决只有一个构造函数场景的参数问题。

    3.2.1 一个参数

    构造函数,单个参数情况。如下:

    public class HTContainer : IHTContainer
    {
        //创建一个Dictionary数据类型的对象用来存储注册的对象
        private Dictionary<string, Type> TypeDictionary = new Dictionary<string, Type>();
    
        //注册方法,用接口的FullName为key值,value为要创建对象的类型
        public void RegisterType<IT, T>()
        {
            this.TypeDictionary.Add(typeof(IT).FullName, typeof(T));
        }
    
        //创建对象通过传递的类型进行匹配
        public IT Resolve<IT>()
        {
            //获取要创建对象的类型
            string key = typeof(IT).FullName;
            Type type = this.TypeDictionary[key];
    
            //这里先考虑只有一个构造函数的场景
            var ctor = type.GetConstructors()[0];
    
            //一个参数的形式
            var paraList = ctor.GetParameters();
            var para = paraList[0];
    
            Type paraInterfaceType = para.ParameterType;// 获取参数接口类型
            Type paraType = this.TypeDictionary[paraInterfaceType.FullName]; //获取依赖对象类型
            object oPara = Activator.CreateInstance(paraType);   //创建参数中所依赖的对象
    
            return (IT)Activator.CreateInstance(type, oPara);   //创建对象并传递所依赖的对象
        }
    }
    

    3.2.2 多个参数

    上面看了构造函数只有一个参数的问题,是通过构造函数的类型创建一个对象,并将这个对象作为参数传递到要实例化的对象中。那么多参数就需要创建多个参数的对象传递到要实例的对象中。如下:

    public class HTContainer : IHTContainer
    {
        //创建一个Dictionary数据类型的对象用来存储注册的对象
        private Dictionary<string, Type> TypeDictionary = new Dictionary<string, Type>();
    
        //注册方法,用接口的FullName为key值,value为要创建对象的类型
        public void RegisterType<IT, T>()
        {
            this.TypeDictionary.Add(typeof(IT).FullName, typeof(T));
        }
    
        //创建对象通过传递的类型进行匹配
        public IT Resolve<IT>()
        {
            //获取要创建对象的类型
            string key = typeof(IT).FullName;
            Type type = this.TypeDictionary[key];
    
            //这里先考虑只有一个构造函数的场景
            var ctor = type.GetConstructors()[0];
    
            //多个参数的形式,声明一个list来存储参数类型的对象
            List<object> paraList = new List<object>();
    
            foreach (var para in ctor.GetParameters())
            {
                Type paraInterfaceType = para.ParameterType;
                Type paraType = this.TypeDictionary[paraInterfaceType.FullName];
                object oPara = Activator.CreateInstance(paraType);
                paraList.Add(oPara);
            }
    
            return (IT)Activator.CreateInstance(type, paraList.ToArray()); //创建对象并传递所依赖的对象数组
        }
    }
    

    3.2.3 循环依赖

    通过上面的两步操作,已经能对构造函数中的参数初始化对象并传递到要实例的对象中,但这只是一个层级的。刚才做的只是解决了这么一个问题,假设要创建 A 对象,A 对象依赖于 B 对象。要做的就是创建了 B 对象作为参数传递给 A 并创建 A 对象,这只是一个层级的。当 B 对象又依赖于 C 对象,C 对象又依赖于 D 对象,这么一直循环下去。这样的场景该怎么解决呢?下面将通过递归的方式来解决这一问题。如下:

    public class HTContainer : IHTContainer
    {
        //创建一个Dictionary数据类型的对象用来存储注册的对象
        private Dictionary<string, Type> TypeDictionary = new Dictionary<string, Type>();
    
        //注册方法,用接口的FullName为key值,value为要创建对象的类型
        public void RegisterType<IT, T>()
        {
            this.TypeDictionary.Add(typeof(IT).FullName, typeof(T));
        }
    
        //创建对象通过传递的类型进行匹配
        public IT Resolve<IT>()
        {
            return (IT)this.ResolveObject(typeof(IT));
        }
    
        //通过递归的方式创建多层级的对象
        private object ResolveObject(Type abstractType)
        {
            //获取要创建对象的类型
            string key = abstractType.FullName;
            Type type = this.TypeDictionary[key];
    
            //这里先考虑只有一个构造函数的场景
            var ctor = type.GetConstructors()[0];
    
            //多个参数的形式
            List<object> paraList = new List<object>();
    
            foreach (var para in ctor.GetParameters())
            {
                Type paraInterfaceType = para.ParameterType;
                Type paraType = this.TypeDictionary[paraInterfaceType.FullName];
    
                //自已调用自己,实现递归操作,完成各个层级对象的创建
                object oPara = ResolveObject(paraInterfaceType);
    
                paraList.Add(oPara);
            }
    
            return (object)Activator.CreateInstance(type, paraList.ToArray());
        }
    }
    

    3.3 升级容器(多构造函数)

    上面只是考虑了只有一个构造函数的问题,那初始化的对象有多个构造函数该如何处理呢,可以像 Autofac 那样选择一个参数最多的构造函数,也可以像 ServiceCollection 那样选择一个参数的超集来进行构造,当然也可以声明一个特性,那个构造函数中标记了这个特性,然后就采用那个构造函数。如下:

    public class HTAttribute : Attribute
    {
    }
    
    public class HTContainer : IHTContainer
    {
        //创建一个Dictionary数据类型的对象用来存储注册的对象
        private Dictionary<string, Type> TypeDictionary = new Dictionary<string, Type>();
    
        //注册方法,用接口的FullName为key值,value为要创建对象的类型
        public void RegisterType<IT, T>()
        {
            this.TypeDictionary.Add(typeof(IT).FullName, typeof(T));
        }
    
        //创建对象通过传递的类型进行匹配
        public IT Resolve<IT>()
        {
            return (IT)this.ResolveObject(typeof(IT));
        }
    
        //通过递归的方式创建多层级的对象
        private object ResolveObject(Type abstractType)
        {
            //获取要创建对象的类型
            string key = abstractType.FullName;
            Type type = this.TypeDictionary[key];
    
            //获取对象的所有构造函数
            var ctorArray = type.GetConstructors();
    
            ConstructorInfo ctor = null;
            //判断构造函数中是否标记了HTAttribute这个特性
            if (ctorArray.Count(c => c.IsDefined(typeof(HTAttribute), true)) > 0)
            {
                //若标记了HTAttribute特性,默认就采用这个构造函数
                ctor = ctorArray.FirstOrDefault(c => c.IsDefined(typeof(HTAttribute), true));
            }
            else
            {
                //若都没有标记特性,那就采用构造函数中参数最多的构造函数
                ctor = ctorArray.OrderByDescending(c => c.GetParameters().Length).FirstOrDefault();
            }
    
            //多个参数的形式
            List<object> paraList = new List<object>();
    
            foreach (var para in ctor.GetParameters())
            {
                Type paraInterfaceType = para.ParameterType;
                Type paraType = this.TypeDictionary[paraInterfaceType.FullName];
                object oPara = ResolveObject(paraInterfaceType);  //自已调用自己,实现递归操作,完成各个层级对象的创建
                paraList.Add(oPara);
            }
    
            return (object)Activator.CreateInstance(type, paraList.ToArray());
        }
    }
    

    四、总结

    通过上面的教程,可以自己写一个 IOC 容器了,也就可以看到 IOC 容器的本质了,其实用了大量的反射。实际生产使用,会有 Singleton、Transient、Scoped 三种生命周期,设计时需要结合 static、httpcontext 等进行实现(这个以后再进行讲解)。如果用现有的 autofac、Microsoft.Extensions.DependencyInjection 等则就不需要啦。

    展开全文
  • 大概的意思是:你申明的是protected BankTockenServiceImpl bankTockenService,但是你从module= "com.mybank.bkebank.biz.shared" 这个spring容器里拿到一个被代理过的对象 是Proxy类型的 解决方法(这里以...

    场景

    当xml文件中存在这样的配置时 

    场景

    而当你在写case使用@XAutowire使用接口注入时,会报一下异常信息

    org.springframework.beans.factory.BeanNotOfRequRequiredTypeException: Bean named 'bankTockenService' is expected to be of type 'e 'com.mybank.bkebank.biz.shared.tocken.BankTockenSernServiceImpl' but was actually of type 'e 'com.sun.proxy.$xy.$Proxy401'

    大概的意思是:你申明的是protected BankTockenServiceImpl bankTockenService,但是你从module= "com.mybank.bkebank.biz.shared" 这个spring容器里拿到一个被代理过的对象 是Proxy类型的

    解决方法(这里以TestNG为例):

    第一步

    在case中使用工具类获取到真正的实现类对象

    @BeforeClass
    @Override
    public void setUp(){
        workOrderTaskApprovalQueryManager = BeanUtil.getTargetBean(workOrderTaskApprovalQueryManager);
    }

    第二步

    在需要使用实现类的地方,将接口强转这里不再多述

    下面是BeanUtil具体实现有兴趣的可以看下

    public class BeanUtil {
    
        public  static <T> T getTargetBean(Object bean) {
            Object object = bean;
            while (AopUtils.isAopProxy(object)) {
                try {
                    object = ((Advised) object).getTargetSource().getTarget();
                } catch (Exception e) {
                    throw new RuntimeException("get target bean failed", e);
                }
            }
            return (T) object;
        }
    }

     

    展开全文
  • 写代码碰到一个这样的情况,在一个没有...然后我们会想到new 一个ApplicationContext对象,再从他里面取,其实这样不是一个有效的解决办法,因为你根本不知道存放在容器中的对象有多少,如果很多,构造一个Application

    写代码碰到一个这样的情况,在一个没有放在IOC容器的内,要从容器中获取对象。

    首先我们知道只有在IOC容器中的对象才能从容器中取其他对象,否则取不了的。意思就是这里通过注解,set等方式注入是无解的。

    然后我们会想到new 一个ApplicationContext对象,再从他里面取,其实这样不是一个有效的解决办法,因为你根本不知道存放在容器中的对象有多少,如果很多,构造一个ApplicationContext是非常消耗资源的,不划算的。所以我们应该想办法获取在项目启动时创建的那个IOC容器的,而不是再次new一个新的。

    这里我们就用到了一个新的类WebApplicationUtis


    通过这个工具类的getWebApplicationContext(ServletContext sc)这个静态方法,传一个servletContext给它,就可以得到WebApplicationContext,然后就可以通过getBean()获取想要的对象了

    展开全文
  • docker容器技术指Docker是一个由GO语言写的程序运行的“容器”(Linux containers, LXCs) containers的中文解释是集装箱。 Docker则实现了一种应用程序级别的隔离,它改变我们基本的开发、操作单元,由直接操作...
  • pod容器简介

    千次阅读 2021-08-19 17:21:19
    1,一个Pod中运行一个容器。"每个pod中一个容器"的模式是最常见的用法,在这种使用方式中,你可以把pod想象成是单个容器的封装,kuberentes管理的是Pod而不是直接管理容器。 2,在一个Pod中同时运行多个容
  • C++ vector容器详解

    万次阅读 多人点赞 2021-10-27 19:31:11
    功能:vector容器的功能和数组非常相似,使用时可以把它看成一个数组 vector和普通数组的区别: 1.数组是静态的,长度不可改变,而vector可以动态扩展,增加长度 2.数组内数据通常存储在栈上,而vector中数
  • 一直以为spring的ioc容器生成的对象都是代理对象,其实这是错误的。spring ioc默认的都是原生对象 只有通过aop增强的对象才是代理对象有@Transactional 注解或者配置文件&lt;aop:config&gt; &lt;...
  • 文章目录1 各个容器1.1 Web容器1.2 Servlet容器1.3 Servlet容器和ServletContext的关系1.4 Spring容器和SpringMVC容器1.5 spring与springmvc容器区别1.6 dubbo容器与spring,springmvc容器区别2 容器间的关系图 ...
  • 阿里面试真题:Spring容器启动流程

    万次阅读 多人点赞 2020-11-30 11:39:04
    看完流程图,我们也先思考一下:在 3.1 中我们知道了如何去初始化一个 IOC 容器,那么接下来就是让这个 IOC 容器真正起作用的时候了:即先扫描出要放入容器的 bean,将其包装成 BeanDefinition 对象,然后通过反射...
  • 自己动手写一个ioc容器

    千次阅读 2018-09-13 20:28:27
    控制反转(Inversion of Control,缩写为IoC),是面向对象编程中的一种设计原则,可以用来减低计算机...通过控制反转,对象在被创建的时候,由一个调控系统内所有对象的外界实体,将其所依赖的对象的引用传递给它...
  • 、IOC容器的设计与实现 1. 获取bean对象 spring的IOC容器主要通过BeanFactory接口的getBean方法来从IOC容器,即BeanFactory的实现类中获取某个bean对象实例,如下为BeanFactory的getBean方法定义: public ...
  • C++容器亲自总结

    千次阅读 多人点赞 2022-02-28 20:01:08
    很简单,容器就是保存其它对象对象,当然这是一个朴素的理解,这种“对象”还包含了一系列处理“其它对象”的方法。 二、容器的种类 1、顺序容器:是一种各元素之间有顺序关系的线性表,是一种线性结构的可序...
  • 如果你想要一个可以盛放各种类型的对象,那么基本上可以说在C++里没有,或者你可以用vector&lt;boost::any&gt;或者其他的什么来模拟,我说那都不怎么好。问题就在于我的类型会在运行时动态的增加,你不可能...
  • 创建动态代理对象bean,并动态注入到spring容器

    万次阅读 多人点赞 2019-04-30 00:48:19
    这里mybatis就用到了JDK动态代理,并且将生成的接口代理对象动态注入到Spring容器中。 这里涉及到几问题。也许有同学会有疑问,我们直接编写好类,加入@Component等注解不是可以注入了吗?或者在配置类(@...
  • 透析Spring(): Spring之IoC容器理解

    千次阅读 2019-02-21 10:45:18
    看懂Spring的IoC容器设计思路,好处,并和DI进行比较等
  • 二维vector需要添加一个空项,可以这么添加: vector<vector<int>> p; p.push_back({}); 也可以初始化一个空的项: vector<vector<int>> p = {{}}; 这样的效果,当输出p时,输出的是 [ ] ...
  • c++容器用法

    千次阅读 2021-07-12 11:10:24
    集合中的每个对象都有一个与之对应的索引,索引用于访问对象。因为vector 容纳着 其他对象,所以它也常被称为容器container。 要想使用 vector,必须包含适当的头文件。需要做如下的声明 #include <vector> ...
  • WEB应用容器详细介绍

    千次阅读 2021-09-30 12:05:12
    没有servlet容器,你也可以用web容器直接访问静态页面,比如安装一个apache等,但是如果要显示jsp/servlet,就要安装一个servlet容器了,但是光有servlet容器是不够的,因为它要被解析成html输出,所以仍需要一个web...
  • 如何设计一个web容器

    万次阅读 多人点赞 2016-02-14 10:20:45
    开发一个web容器涉及很多不同方面不同层面的技术,例如通信层的知识,程序语言层面的知识等等,且一个可用的web容器一个比较庞大的系统,要说清楚需要很长的篇幅,本文旨在介绍如何设计一个web容器,只探讨实现的...
  • 【云原生 | 05】Docker中容器的创建与启停

    千次阅读 多人点赞 2022-06-04 20:19:18
    首先Docker会检查本地是否存在基础镜像,如果本地还没有该...随后,Docker在文件系统内部用这个镜像创建了一个容器。该容器拥有自己的网络、IP地址,以及一个用来和宿主机进行通信的桥接网络接口。..................
  • 如何设计并实现一个ioc容器(转载)

    千次阅读 2016-09-27 22:05:36
    Ioc意味着将你设计好的对象交给容器控制,而不是传统的在你的对象内部直接控制。如何理解好Ioc呢?理解好Ioc的关键是要明确“谁控制谁,控制什么,为何是反转(有反转就应该有正转了),哪些方面反转了”,那我们来...
  • C++二维容器vector的使用及一个经典案例vector(容器)的调用及初始化二维vector的插入元素...需要注意的是,vector不能容纳对象引用作为其元素,因为引用不是一个对象。在C++11中,二维容器的定义式可以写成vector<
  • 同为容器,IoC和Docker有啥不同?

    万次阅读 多人点赞 2022-07-28 09:25:48
    Spring中有容器技术,Docker中也有,容器技术中,能学到哪些思想呢?
  • 【Java 多线程 8】同步容器与并发容器

    万次阅读 多人点赞 2022-05-29 17:11:27
    8专场、24主题、近30位国内外顶级专家深度分享与探讨Java的变革与未来。
  • laravel 服务容器容器概念

    千次阅读 多人点赞 2019-11-06 11:38:11
    Laravel 服务容器 ...一个容器能够装什么,全部取决于你对该容器的定义。当然,有这样一种容器,它存放的不是文本、数值,而是对象对象的描述(类、接口)或者是提供对象的回调,通过这种容器...
  • 在前面,我们讨论到了spring容器创建对象的3种方式以及spring容器创建对象的时机,那么我们现在来讨论一下spring容器创建对象的单/多例  那么 怎样判断spring容器创建的对象是单例还是多例呢? public class ...
  • 容器在创建这些Bean对象的时候同时就会注入这些依赖。这过程是根本上的反转了,不再由Bean本身来控制实例化和定位依赖,而是通过服务定位来控制这过程,也是IoC(控制反转)的由来。org.springframew
  • 在开发的过程中,难免会碰到需要在工具类中使用由spring管理的对象的情况,但是我们都知道,工具类中的静态方法中无法引用spirng容器中的对象(被spring管理的对象不是静态的,静态方法中无法调用),那么该如何才能...
  • 基于最新Spring 5.x,对于基于XML的Spring IoC容器初始化过程中的setConfigLocations设置容器配置信息方法的源码进行了详细分析,最后给出了比较详细的方法调用时序图!

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 512,441
精华内容 204,976
热门标签
关键字:

哪一个不是容器对象