Spring源码阅读——3

Easter79
• 阅读 723

Spring IoC容器

1、IoC容器概述

控制反转、IoC容器、依赖注入

  • 控制反转:不是什么技术,而是一种设计思想。Ioc意味着将你设计好的对象交给容器控制,而不是传统的在你的对象内部直接控制。
  • IoC容器:IoC 容器控制了对象的创建,获取及对象之间关系的管理。
  • 依赖注入(DI)组件之间依赖关系由容器在运行期决定,形象的说,即由容器动态的将某个依赖关系注入到组件之中依赖注入的目的并非为软件系统带来更多功能,而是为了提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台。

IoC对编程带来的最大改变不是从代码上,而是从思想上,发生了“主从换位”的变化。应用程序原本是老大,要获取什么资源都是主动出击,但是在IoC/DI思想中,应用程序就变成被动的了,被动的等待IoC容器来创建并注入它所需要的资源了。

2、IoC容器的设计与实现——Bean组件与Context组件

  • Bean 组件在Spring 的 org.springframework.beans 包下。这个包下的所有类主要解决了三件事:Bean 的定义、Bean 的创建以及对 Bean 的解析。对 Spring 的使用者来说唯一需要关心的就是 Bean 的定义,其他两个由 Spring 在内部帮你完成了,对你来说是透明的。Spring Bean 的创建时典型的工厂模式,它的顶级接口是 BeanFactory容器,实现了容器的基本功能。
  • Context组件在Spring 的 org.springframework.context 包下,它实际上就是给 Spring 提供一个运行时的环境,用以保存各个对象的状态ApplicationContext 是 Context 的顶级父类,它作为容器的高级系列而存在,应用上下文在简单容器的基础上,增加了许多面向框架的特性,同时对应用环境做了许多适配。

1. Bean组件分析

Bean 组件在 Spring 的 org.springframework.beans 包下。这个包下的所有类主要解决了三件事:Bean 的定义及创建,对 Bean 的解析和注册。对 Spring 的使用者来说唯一需要关心的就是 Bean 的定义,其他两个由 Spring 在内部帮你完成了,对你来说是透明的。

1. BeanFactory继承关系

Spring源码阅读——3

BeanFactory 有三个子类:ListableBeanFactory、HierarchicalBeanFactory 和 AutowireCapableBeanFactory。但是从上图中我们可以发现最终的默认实现类是 DefaultListableBeanFactory,它实现了所有的接口。

每个接口都有他使用的场合,它主要是为了区分在 Spring 内部在操作过程中对象的传递和转化过程中,对对象的数据访问所做的限制

  • ListableBeanFactory 接口表示这些 Bean 是可列表的
  • HierarchicalBeanFactory 表示的是这些 Bean 是有继承关系的,也就是每个 Bean 有可能有父 Bean。
  • AutowireCapableBeanFactory 接口定义 Bean 的自动装配规则。
  • 这三个接口共同定义了 Bean 的集合、Bean 之间的关系、以及 Bean 行为

2. BeanFactory概述

BeanFactory是Spring bean容器的根接口.提供获取bean,是否包含bean,是否单例与原型,获取bean类型,bean 别名的方法

Spring源码阅读——3

Bean 的定义主要由 BeanDefinition 实现,如下图说明了这些类的层次关系:

Spring源码阅读——3

Bean 的定义就是完整的描述了在 Spring 的配置文件中你定义的 节点中所有的信息,包括各种子节点。当 Spring 成功解析你定义的一个 节点后,在 Spring 的内部它就被转化成 BeanDefinition 对象。以后所有的操作都是对这个对象完成的。

Bean 的解析过程非常复杂,功能被分的很细,因为这里需要被扩展的地方很多,必须保证有足够的灵活性,以应对可能的变化。Bean 的解析主要就是对 Spring 配置文件的解析。这个解析过程主要通过下图中的类完成:

Spring源码阅读——3

2.1 Bean Definition从加载、解析、处理、注册到BeanFactory的过程

1、BeanDefinition的加载

使用的xml配置文件的方式来配置bean,首先要读取xml文件。

  • AbstractBeanDefinitionReader类中
    • 加载bean definition,但是这里方法参数resources 所指的资源并不确定是什么样的,有可能是xml文件,也有可能是属性文件,或者是脚本。
      • @Override
        public int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException {
           Assert.notNull(resources, "Resource array must not be null");
           int count = 0;
           for (Resource resource : resources) {
              count += loadBeanDefinitions(resource);//这个方法有几个实现,如下图所示
           }
           return count;
        }

        • Spring源码阅读——3
        • 使用实现类XmlBeanDefinitionReader中的实现,所以这里的资源是xml文件。
  • XmlBeanDefinitionReader类中
    • 从指定的xml文件中加载bean definition,但只是将Resource转化为EncodedResource对象。然后把这个任务递交给它的重载方法来做。

      • @Override
        public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
           return loadBeanDefinitions(new EncodedResource(resource));
        }

    • 重载方法从指定的XML文件中加载bean definition。但还不是真正的加载bean定义,只是做准备

      • public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
           Assert.notNull(encodedResource, "EncodedResource must not be null");
           if (logger.isTraceEnabled()) {
              logger.trace("Loading XML bean definitions from " + encodedResource);
           }

           Set currentResources = this.resourcesCurrentlyBeingLoaded.get();
           if (currentResources == null) {
              currentResources = new HashSet<>(4);
              this.resourcesCurrentlyBeingLoaded.set(currentResources);
           }
           if (!currentResources.add(encodedResource)) {
              throw new BeanDefinitionStoreException(
                    "Detected cyclic loading of " + encodedResource + " - check your import definitions!");
           }
           try {
              InputStream inputStream = encodedResource.getResource().getInputStream();
              try {
                 InputSource inputSource = new InputSource(inputStream);
                 if (encodedResource.getEncoding() != null) {
                    inputSource.setEncoding(encodedResource.getEncoding());
                 }
                 return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
              }
              finally {
                 inputStream.close();
              }
           }
           catch (IOException ex) {
              throw new BeanDefinitionStoreException(
                    "IOException parsing XML document from " + encodedResource.getResource(), ex);
           }
           finally {
              currentResources.remove(encodedResource);
              if (currentResources.isEmpty()) {
                 this.resourcesCurrentlyBeingLoaded.remove();
              }
           }
        }

    • doLoadBeanDefinitions方法从xml 文件中加载的bean definition 。

      • ①、加载指定的文档,得到一个Document对象。

      • ②、将Document对象和Resource交给registerBeanDefinitions(...)方法来完成注册

      • protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
              throws BeanDefinitionStoreException {

           try {
             Document doc = doLoadDocument(inputSource, resource);
              int count = registerBeanDefinitions(doc, resource);

              if (logger.isDebugEnabled()) {
                 logger.debug("Loaded " + count + " bean definitions from " + resource);
              }
              return count;
           }catch(...){...}

    • registerBeanDefinitions方法注册指定的Document对象中的bean definition。这里实际上是创建了一个BeanDefinitionDocumentReader对象然后让它来完成。

      • public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
          BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
           int countBefore = getRegistry().getBeanDefinitionCount();

        //多种实现,如 DefaultBeanDefinitionDocumentReader
           documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
           return getRegistry().getBeanDefinitionCount() - countBefore;
        }

2、BeanDefinition的解析

  • DefaultBeanDefinitionDocumentReader类中
    • DefaultBeanDefinitionDocumentReader是BeanDefinitionDocumentReader的一个实现类。这一步主要是**解析文档解析的是**。比如xml文件是XSD的还是DTD的。

    • 这里主要是获得一个Element对象。这个对象就代表xml文件中的节点。在这个节点下包含着文件中的所有节点。然后将这个Element对像交给的doRegistrerBeanDefinitions(Element)方法来处理。

      • @Override
        public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
           this.readerContext = readerContext;
           doRegisterBeanDefinitions(doc.getDocumentElement());
        }

    • doRegisterBeanDefinitions方法将节点下的每一个相对应的bean definition注册。但是真正做这件事的是另一个方法 parseBeanDefinitions(root, this.delegate);

    • 在调用parseBeanDefinitions(root,this.delegate)方法之前和之后都可以对这个这个方法参数中的Element对象进行处理。

    • protected void doRegisterBeanDefinitions(Element root) {
         BeanDefinitionParserDelegate parent = this.delegate;
         this.delegate = createDelegate(getReaderContext(), root, parent);

         if (this.delegate.isDefaultNamespace(root)) {
            String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
            if (StringUtils.hasText(profileSpec)) {
               String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
                     profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
               // We cannot use Profiles.of(...) since profile expressions are not supported
               // in XML config. See SPR-12458 for details.
               if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
                  if (logger.isDebugEnabled()) {
                     logger.debug("Skipped XML bean definition file due to specified profiles [" + profileSpec +
                           "] not matching: " + getReaderContext().getResource());
                  }
                  return;
               }
            }
         }

         preProcessXml(root);
         parseBeanDefinitions(root, this.delegate);
         postProcessXml(root);

         this.delegate = parent;
      }

    • 用一个for循环遍历节点下的所有子节点,也就是所有的,然后对节点进行解析。注意,刚才是对进行解析。不过这个解析的任务交给parseDefaultElement(Element ele,BeanDefinitionParserDelegate delegate)方法来完成。

    • protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
         if (delegate.isDefaultNamespace(root)) {
            NodeList nl = root.getChildNodes();
            for (int i = 0; i < nl.getLength(); i++) {
               Node node = nl.item(i);
               if (node instanceof Element) {
                  Element ele = (Element) node;
                  if (delegate.isDefaultNamespace(ele)) {
                     parseDefaultElement(ele, delegate);
                  }
                  else {
                     delegate.parseCustomElement(ele);
                  }
               }
            }
         }
         else {
            delegate.parseCustomElement(root);
         }
      }

    • parseDefaultElement方法用到了递归,因为bean是可以嵌套的,所以节点下的每一个节点都可能在嵌套有很多bean。所以它会判断这个bean是不是嵌套bean,如果不是就进行处理,如果是就进行递归。

    • private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
         if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
            importBeanDefinitionResource(ele);
         }
         else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
            processAliasRegistration(ele);
         }
         else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
            processBeanDefinition(ele, delegate);
         }
         else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
            // recurse
            doRegisterBeanDefinitions(ele);
         }
      }

3、BeanDefinition的处理

  • DefaultBeanDefinitionDocumentReader类中
    • processBeanDefinition方法中将Element对象转化成了BeanDefinitionHolder对象。这个BeanDefinitionHolder对象中持有的BeanDefinition实例的引用,还有beanName,还有bean的别名。(BeanDefinitionHolder的创建

    • 然后将BeanDefinitionHolder对象和特定的bean工厂作为参数交给BeanDefinitionReaderUtils类来处理来进行注册

    • protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
         BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
         if (bdHolder != null) {
            bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
            try {
               // Register the final decorated instance.
               BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
            }
            catch (BeanDefinitionStoreException ex) {
               getReaderContext().error("Failed to register bean definition with name '" +
                     bdHolder.getBeanName() + "'", ele, ex);
            }
            // Send registration event.
            getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
         }
      }

4、BeanDefinition的注册

  • BeanDefinitionReaderUtils类中

    • registerBeanDefinition方法先根据BeanDefinitionHolder获取beanName和BeanDefinition,然后将其注册到Bean工厂中的Map对象中。比如bean工厂是DefaultListableBeanFactory。

    • public static void registerBeanDefinition(
            BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
            throws BeanDefinitionStoreException {

         // Register bean definition under primary name.
         String beanName = definitionHolder.getBeanName();
         registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());

         // Register aliases for bean name, if any.
         String[] aliases = definitionHolder.getAliases();
         if (aliases != null) {
            for (String alias : aliases) {
               registry.registerAlias(beanName, alias);
            }
         }
      }

    • registry是一个Bean工厂,可以有多个实现,如DefaultListableBeanFactory等

    • Spring源码阅读——3

  • DefaultListableBeanFactory类中,在这个方法中,将BeanDefinition 注册到了ConcurrentHashMap对象中了。

  • @Override
    public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
          throws BeanDefinitionStoreException {

       Assert.hasText(beanName, "Bean name must not be empty");
       Assert.notNull(beanDefinition, "BeanDefinition must not be null");

       if (beanDefinition instanceof AbstractBeanDefinition) {
          try {
             ((AbstractBeanDefinition) beanDefinition).validate();
          }
          catch (BeanDefinitionValidationException ex) {
             throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
                   "Validation of bean definition failed", ex);
          }
       }
       BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);
       if (existingDefinition != null) {
          if (!isAllowBeanDefinitionOverriding()) {
             throw new BeanDefinitionOverrideException(beanName, beanDefinition, existingDefinition);
          }
          else if (existingDefinition.getRole() < beanDefinition.getRole()) {
             // e.g. was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTURE
             if (logger.isInfoEnabled()) {
                logger.info("Overriding user-defined bean definition for bean '" + beanName +
                      "' with a framework-generated bean definition: replacing [" +
                      existingDefinition + "] with [" + beanDefinition + "]");
             }
          }
          else if (!beanDefinition.equals(existingDefinition)) {
             if (logger.isDebugEnabled()) {
                logger.debug("Overriding bean definition for bean '" + beanName +
                      "' with a different definition: replacing [" + existingDefinition +
                      "] with [" + beanDefinition + "]");
             }
          }
          else {
             if (logger.isTraceEnabled()) {
                logger.trace("Overriding bean definition for bean '" + beanName +
                      "' with an equivalent definition: replacing [" + existingDefinition +
                      "] with [" + beanDefinition + "]");
             }
          }
          **this.beanDefinitionMap.put(beanName, beanDefinition);**
       }
       else {
          if (hasBeanCreationStarted()) {
             // Cannot modify startup-time collection elements anymore (for stable iteration)
             synchronized (this.beanDefinitionMap) {
                **this.beanDefinitionMap.put(beanName, beanDefinition);**
                List updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);
                updatedDefinitions.addAll(this.beanDefinitionNames);
                updatedDefinitions.add(beanName);
                this.beanDefinitionNames = updatedDefinitions;
                if (this.manualSingletonNames.contains(beanName)) {
                   Set updatedSingletons = new LinkedHashSet<>(this.manualSingletonNames);
                   updatedSingletons.remove(beanName);
                   this.manualSingletonNames = updatedSingletons;
                }
             }
          }
          else {
             // Still in startup registration phase
             this.beanDefinitionMap.put(beanName, beanDefinition);
             this.beanDefinitionNames.add(beanName);
             this.manualSingletonNames.remove(beanName);
          }
          this.frozenBeanDefinitionNames = null;
       }

       if (existingDefinition != null || containsSingleton(beanName)) {
          resetBeanDefinition(beanName);
       }
    }

BeanFactroy是一个Spring容器,用于创建,配置,管理bean,bean之间的依赖关系也由BeanFactory负责维护;

BeanFactory对bean的实例化过程,无须bean的调用者关心,调用者只需要通过getBean()方法获得指定bean的引用;

创建BeanFactory的实例时,必须提供bean的详细配置信息,bean的详细信息通过XML文件描述,创建BeanFactory实例时,需要提供XML文件作为参数

BeanDefinition的加载、解析、处理、注册主要涉及到了四个类。
①、XMLBeanDefinitionReader:主要是的任务是把XML文件加载到内存中以Document对象的形式存在。
②、DefaultBeanDefinitionDocumentReader:完成解析和处理的任务。最后将处理得到的BeanDefinitionHolder交给了BeanDefinitionReaderUtils进行注册。
③、BeanDefinitionReaderUtils:BeanDefinitionHolder有了,Bean工厂也有了,它就负责把BeanDefinitionHolder中的BeanDefinition和BeanName等取出来,然后注册到Bean工厂中。

④、DefaultListableBeanFactory(bean工厂):它有一个ConcurrentHashMap成员变量,以beanName为键,BeanDefinition为值保存注册的bean。

=====================================================================================

2. Context组件分析

Context 在 Spring 的 org.springframework.context 包下,给 Spring 提供一个运行时的环境,用以保存各个对象的状态

ApplicationContext 是 Context 的顶级父类,它除了能标识一个应用环境的基本信息外,还实现了六个接口,这六个接口主要是扩展了 Context 的功能。下面是 Context 的类结构图:

Spring源码阅读——3

从上图中可以看出 ApplicationContext 继承了 BeanFactory,这也说明了 Spring 容器中运行的主体对象是 Bean,另外 ApplicationContext 继承了 ResourceLoader 接口,使得 ApplicationContext 可以访问到任何外部资源

ApplicationContext 的子类主要包含两个方面:

  1. ConfigurableApplicationContext 表示该 Context 是可修改的,也就是在构建 Context 中用户可以动态添加或修改已有的配置信息,它下面又有多个子类,其中最经常使用的是可更新的 Context,即 AbstractRefreshableApplicationContext 类
  2. WebApplicationContext 顾名思义,就是为 web 准备的 Context 他可以直接访问到 ServletContext,通常情况下,这个接口使用的少。

Spring源码阅读——3

再往下分就是构建 Context 的文件类型,接着就是访问 Context 的方式。这样一级一级构成了完整的 Context 等级层次。

总体来说 ApplicationContext 必须要完成以下几件事:

  • 标识一个应用环境
  • 利用 BeanFactory 创建 Bean 对象
  • 保存对象关系表
  • 能够捕获各种事件

Context 作为 Spring 的 Ioc 容器,基本上整合了 Spring 的大部分功能,或者说是大部分功能的基础。

3、Core 组件

Core 组件作为 Spring 的核心组件,它其中包含了很多的关键类,其中一个重要组成部分就是定义了资源的访问方式

Resource 相关的类结构如下图所示:

Spring源码阅读——3

  •  Resource 接口封装了各种可能的资源类型,也就是对使用者来说屏蔽了文件类型的不同。
  • 对资源的提供者来说,如何把资源包装起来交给其他人用这也是一个问题,我们看到 Resource 接口继承了 InputStreamSource 接口,这个接口中有个 getInputStream 方法,返回的是 InputStream 类。这样所有的资源都被可以通过 InputStream 这个类来获取,所以也屏蔽了资源的提供者。
  • 另外还有一个问题就是加载资源的问题,也就是资源的加载者要统一,从上图中可以看出这个任务是由 ResourceLoader 接口完成,他屏蔽了所有的资源加载者的差异,只需要实现这个接口就可以加载所有的资源,它的默认实现是 DefaultResourceLoader。

Context 和 Resource 的类关系图:

Spring源码阅读——3

Context 是把资源的加载、解析和描述工作委托给了 ResourcePatternResolver 类来完成,它相当于一个接头人,把资源的加载、解析和资源的定义整合在一起便于其他组件使用。Core 组件中还有很多类似的方式。

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
待兔 待兔
4个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Easter79 Easter79
3年前
spring源码
SpringIOC容器源码解析系列,建议大家按顺序阅读,欢迎讨论(_spring源码均为4.1.6.RELEASE版本_)1.Spring源码IOC容器(一)构建简单IOC容器(https://my.oschina.net/u/2377110/blog/902073)2.Spring源码IOC容器(二)Bean的定位解析注
Easter79 Easter79
3年前
spring核心思想:IOC(控制反转)、DI(依赖注入)和AOP(面向切面编程)
Spring有三大核心思想,分别是控制反转(IOC,InversionOfController),依赖注入(DI,DependencyInjection)和面向切面编程(AOP,AspectOrientedProgramming)。控制反转(IOC,InversionOfController)控制反转不是什么技术,而是一种设计思
Wesley13 Wesley13
3年前
java面试题:Spring
Spring面试时,最好能结合底层代码说出IOC,AOP或SpringMVC的流程,能说出拦截器的底层。如果看过Spring的源码,并能结合设计模式表达,是很大的加分项。IOCQ:讲一下IOCIOC是"控制反转"。IOC将对象的控制权进行分离,交由第三方进行控制。IOC容器负责创建对象,管理对象.详情参见:https:/
Easter79 Easter79
3年前
SpringIOC官方文档解读
IoC容器本章介绍了Spring的控制反转(IoC)容器。1.1。SpringIoC容器和Bean简介本章介绍了反转控制(IoC)原则的Spring框架实现。IoC也称为依赖注入(DI)。在此过程中,对象可以通过①构造函数参数(),②工厂方法的参数③或在构造或从工厂方法返回后在对象实例上设置的属性来定义其依
Wesley13 Wesley13
3年前
JAVA日常记录
11.IOC的优点是什么IOC(InversionofControl)控制反转,将控制权(创建对象和对象之间的依赖关系的权利)交给spring容器。接口驱动设计(InterfaceDrivenDesign)的好处,可以灵活提供不同的子类实现(其实就是解耦),提高程序的灵活性、可扩展性和可维护性。IOC模式将耦合代
Stella981 Stella981
3年前
Spring 学习笔记(二):SpringIoC
1IoC与DIIoC是InversionofControl的简称,也就是控制反转。通常来说,创建对象需要调用者手动创建,也就是newXXX()的方式。当Spring框架出现后,对象的实例不再由调用者创建,而是由Spring容器创建,这样控制权就由调用者转移到Spring容器,控制权发生了反
Easter79 Easter79
3年前
Spring的IOC逐层深入——源码解析之IoC的根本BeanFactory(五)
什么是IoC    IoC容器,最主要的就是完成对象的创建以及维护对象的依赖关系等。    所谓控制反转,包括两部分:一是控制,二是反转,就是把传统方式需要由代码来实现对象的创建、维护对象的依赖关系,反转给容器来帮忙管理和实现。所以我们必须要创建一个容器,同时需要一种描述来让容器创建对象与对象的关系。    Io
小万哥 小万哥
11个月前
深入理解 Spring IoC 和 DI:掌握控制反转和依赖注入的精髓
概述在本文中,我们将介绍IoC(控制反转)和DI(依赖注入)的概念,以及如何在Spring框架中实现它们。什么是控制反转?控制反转是软件工程中的一个原则,它将对象或程序的某些部分的控制权转移给容器或框架。我们最常在面向对象编程的上下文中使用它。与传统编程相
Easter79
Easter79
Lv1
今生可爱与温柔,每一样都不能少。
文章
2.8k
粉丝
5
获赞
1.2k