详解Spring简单容器中的Bean基本加载过程

本篇将对定义在 XMl 文件中的 bean,从静态的的定义到变成可以使用的对象的过程,即 bean 的加载和获取的过程进行一个整体的了解,不去深究,点到为止,只求对 Spring IOC 的实现过程有一个整体的感知,具体实现细节留到后面用针对性的篇章进行讲解。

首先我们来引入一个 Spring 入门使用示例,假设我们现在定义了一个类 org.zhenchao.framework.MyBean ,我们希望利用 Spring 来管理类对象,这里我们利用 Spring 经典的 XMl 配置文件形式进行配置:

<?xml version="1.0" encoding="UTF-8"?>
<beansxmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

  <!-- bean的基本配置 -->
  <beanname="myBean"class="org.zhenchao.framework.MyBean"/>

</beans>

我们将上面的配置文件命名为 spring-core.xml,则对象的最原始的获取和使用示例如下:

// 1. 定义资源
Resource resource = new ClassPathResource("spring-core.xml");
// 2. 利用XmlBeanFactory解析并注册bean定义
XmlBeanFactory beanFactory = new XmlBeanFactory(resource);
// 3. 从IOC容器加载获取bean
MyBean myBean = (MyBean) beanFactory.getBean("myBean");
// 4. 使用bean
myBean.sayHello();

上面 demo 虽然简单,但麻雀虽小,五脏俱全,完整的让 Spring 执行了一遍配置文件加载,并获取 bean 的过程。虽然从 Spring 3.1 开始 XmlBeanFactory 已经被置为 Deprecated ,但是 Spring 并没有定义出更加高级的基于 XML 加载 bean 的 BeanFactory,而是推荐采用更加原生的方式,即组合使用 DefaultListableBeanFactory XmlBeanDefinitionReader 来完成上诉过程:

Resource resource = new ClassPathResource("spring-core.xml");
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
reader.loadBeanDefinitions(resource);
MyBean myBean = (MyBean) beanFactory.getBean("myBean");
myBean.sayHello();

后面的分析你将会看到 XmlBeanFactory 实际上是对 DefaultListableBeanFactory 和 XmlBeanDefinitionReader 组合使用方式的封装,所以这里我们仍然将继续分析基于 XmlBeanFactory 加载 bean 的过程。

一. Bean的解析和注册

Bean的加载过程,主要是对配置文件的解析,并注册 bean 的过程,上图是加载过程的时序图,当我们 new XmlBeanFactory(resource) 的时候,已经完成将配置文件包装成了 Spring 定义的资源,并触发解析和注册。 new XmlBeanFactory(resource) 调用的是下面的构造方法:

publicXmlBeanFactory(Resource resource)throwsBeansException{
  this(resource, null);
}

这个构造方法本质上还是继续调用了:

publicXmlBeanFactory(Resource resource, BeanFactory parentBeanFactory)throwsBeansException{
  super(parentBeanFactory);
  // 加载xml资源
  this.reader.loadBeanDefinitions(resource);
}

在这个构造方法里面先是调用了父类构造函数,即 org.springframework.beans.factory.support.DefaultListableBeanFactory 类,这是一个非常核心的类,它包含了基本 IOC 容器所具有的重要功能,是一个 IOC 容器的基本实现。然后是调用了 this.reader.loadBeanDefinitions(resource) ,从这里开始加载配置文件。

Spring 在设计采用了许多程序设计的基本原则,比如迪米特法则、开闭原则,以及接口隔离原则等等,这样的设计为后续的扩展提供了灵活性,也增强了模块的复用性,这也是我看 Spring 源码的动力之一,希望通过阅读学习的过程来提升自己接口设计的能力。Spring 使用了专门的资源加载器对资源进行加载,这里的 reader 就是 org.springframework.beans.factory.xml.XmlBeanDefinitionReader 对象,专门用来加载基于 XML 文件配置的 bean。这里的加载过程为:

  1. 利用 EncodedResource 二次包装资源文件
  2. 获取资源输入流,并构造 InputSource 对象
  3. 获取 XML 文件的实体解析器和验证模式
  4. 加载 XML 文件,获取对应的 Document 对象
  5. 由 Document 对象解析并注册 bean

1.利用 EncodedResource 二次包装资源文件

采用 org.springframework.core.io.support.EncodedResource 对resource 进行二次封装.

2.获取资源输入流,并构造 InputSource 对象

对资源进行编码封装之后,开始真正进入 this.loadBeanDefinitions(new EncodedResource(resource)) 的过程,该方法源码如下:

publicintloadBeanDefinitions(EncodedResource encodedResource)throwsBeanDefinitionStoreException{
  Assert.notNull(encodedResource, "EncodedResource must not be null");
  if (logger.isInfoEnabled()) {
    logger.info("Loading XML bean definitions from " + encodedResource.getResource());
  }

  // 标记正在加载的资源,防止循环引用
  Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
  if (currentResources == null) {
    currentResources = new HashSet<EncodedResource>(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 inputSource = new InputSource(inputStream);
      if (encodedResource.getEncoding() != null) {
        inputSource.setEncoding(encodedResource.getEncoding());
      }
      // 真正开始从XML文件中加载Bean定义
      return this.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();
    }
  }
}

需要知晓的是 org.xml.sax.InputSource 不是 Spring 中定义的类,这个类来自 jdk,是 java 对 XML 实体提供的原生支持。这个方法主要还是做了一些准备工作,按照 Spring 方法的命名相关,真正干活的方法一般都是以 “do” 开头的,这里的 this.doLoadBeanDefinitions(inputSource, encodedResource.getResource()) 就是真正开始加载 XMl 的入口,该方法源码如下:

protectedintdoLoadBeanDefinitions(InputSource inputSource, Resource resource)throwsBeanDefinitionStoreException{
  try {

    // 1. 加载xml文件,获取到对应的Document(包含获取xml文件的实体解析器和验证模式)
    Document doc = this.doLoadDocument(inputSource, resource);

    // 2. 解析Document对象,并注册bean
    return this.registerBeanDefinitions(doc, resource);

  } catch (BeanDefinitionStoreException ex) {
    // 这里是连环catch,省略
  }
}

方面里面的逻辑还是很清晰的,第一步获取 org.w3c.dom.Document 对象,第二步由该对象解析得到 BeanDefinition 对象,并注册到 IOC 容器中。

3.获取 XML 文件的实体解析器和验证模式

this.doLoadDocument(inputSource, resource) 包含了获取实体解析器、验证模式,以及 Document 对象的逻辑,源码如下:

protectedDocumentdoLoadDocument(InputSource inputSource, Resource resource)throwsException{
  return this.documentLoader.loadDocument(
      inputSource,
      this.getEntityResolver(), // 获取实体解析器
      this.errorHandler,
      this.getValidationModeForResource(resource), // 获取验证模式
      this.isNamespaceAware());
}

XML 是半结构化数据,XML 的验证模式用于保证结构的正确性,常见的验证模式有 DTD 和 XSD 两种,获取验证模式的源码如下:

protectedintgetValidationModeForResource(Resource resource){
  int validationModeToUse = this.getValidationMode();
  if (validationModeToUse != VALIDATION_AUTO) {
    // 手动指定了验证模式
    return validationModeToUse;
  }

  // 没有指定验证模式,则自动检测
  int detectedMode = this.detectValidationMode(resource);
  if (detectedMode != VALIDATION_AUTO) {
    return detectedMode;
  }

  // 检测验证模式失败,默认采用XSD验证
  return VALIDATION_XSD;
}

上面源码描述了获取验证模式的执行流程,如果没有手动指定,那么 Spring 会去自动检测。对于 XML 文件的解析,SAX 首先会读取 XML 文件头声明,以获取对应验证文件地址,并下载对应的文件,如果网络不正常,则会影响下载过程,这个时候可以通过注册一个实体解析来实现寻找验证文件的过程。

4.加载 XML 文件,获取对应的 Document 对象

获取对应的验证模式和解析器,解析去就可以加载 Document 对象了,这里本质上调用的是 org.springframework.beans.factory.xml.DefaultDocumentLoader 的 loadDocument() 方法,源码如下:

publicDocumentloadDocument(InputSource inputSource, EntityResolver entityResolver,
               ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {

  DocumentBuilderFactory factory = this.createDocumentBuilderFactory(validationMode, namespaceAware);
  if (logger.isDebugEnabled()) {
    logger.debug("Using JAXP provider [" + factory.getClass().getName() + "]");
  }
  DocumentBuilder builder = this.createDocumentBuilder(factory, entityResolver, errorHandler);
  return builder.parse(inputSource);
}

整个过程类似于我们平常解析 XML 文件的流程。

5.由 Document 对象解析并注册 bean

完成了对 XML 文件的到 Document 对象的解析,我们终于可以解析 Document 对象,并注册 bean 了,这一过程发生在 this.registerBeanDefinitions(doc, resource) 中,源码如下:

publicintregisterBeanDefinitions(Document doc, Resource resource)throwsBeanDefinitionStoreException{
  // 使用DefaultBeanDefinitionDocumentReader构造
  BeanDefinitionDocumentReader documentReader = this.createBeanDefinitionDocumentReader();

  // 记录之前已经注册的BeanDefinition个数
  int countBefore = this.getRegistry().getBeanDefinitionCount();

  // 加载并注册bean
  documentReader.registerBeanDefinitions(doc, createReaderContext(resource));

  // 返回本次加载的bean的数量
  return getRegistry().getBeanDefinitionCount() - countBefore;
}

这里方法的作用是创建对应的 BeanDefinitionDocumentReader,并计算返回了过程中新注册的 bean 的数量,而具体的注册过程,则是由 BeanDefinitionDocumentReader 来完成的,具体的实现位于子类 DefaultBeanDefinitionDocumentReader 中:

publicvoidregisterBeanDefinitions(Document doc, XmlReaderContext readerContext){
  this.readerContext = readerContext;
  logger.debug("Loading bean definitions");

  // 获取文档的root结点
  Element root = doc.getDocumentElement();

  this.doRegisterBeanDefinitions(root);
}

还是按照 Spring 命名习惯,doRegisterBeanDefinitions 才是真正干活的地方,这也是真正开始解析配置的核心所在:

protectedvoiddoRegisterBeanDefinitions(Element root){
  BeanDefinitionParserDelegate parent = this.delegate;
  this.delegate = this.createDelegate(getReaderContext(), root, parent);

  if (this.delegate.isDefaultNamespace(root)) {
    // 处理profile标签(其作用类比pom.xml中的profile)
    String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
    if (StringUtils.hasText(profileSpec)) {
      String[] specifiedProfiles =
          StringUtils.tokenizeToStringArray(profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
      if (!this.getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
        if (logger.isInfoEnabled()) {
          logger.info("Skipped XML bean definition file due to specified profiles [" + profileSpec + "] not matching: " + getReaderContext().getResource());
        }
        return;
      }
    }
  }

  // 解析预处理,留给子类实现
  this.preProcessXml(root);

  // 解析并注册BeanDefinition
  this.parseBeanDefinitions(root, this.delegate);

  // 解析后处理,留给子类实现
  this.postProcessXml(root);

  this.delegate = parent;
}

方法中显示处理了 标签,这个属性在 Spring 中不是很常用,不过在 maven 的 pom.xml 中则很常见,意义也是相同的,就是配置多套环境,从而在部署的时候可以根据具体环境来选择使用哪一套配置。方法中会先去检测是否配置了 profile,如果配置了就需要从上下文环境中确认当前激活了哪一套 profile。

方法在解析并注册 BeanDefinition 前后各设置一个模板方法,留给子类扩展实现,而在 this.parseBeanDefinitions(root, this.delegate) 中执行解析和注册逻辑:

protectedvoidparseBeanDefinitions(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)) {
          // 解析默认标签
          this.parseDefaultElement(ele, delegate);
        } else {
          // 解析自定义标签
          delegate.parseCustomElement(ele);
        }
      }
    }
  } else {
    // 解析自定义标签
    delegate.parseCustomElement(root);
  }
}

方法中判断当前标签是默认标签还是自定义标签,并按照不同的策略去解析,这是一个复杂的过程,后面用文章进行针对性讲解,这里不在往下细究。

到这里我们已经完成了静态配置到动态 BeanDefinition 的解析,这个时候 bean 的定义已经处于内存中,解析去将是探究如何获取并使用 bean 的过程。

二. Bean的获取

在完成了 Bean 的加载过程之后,我们可以调用 beanFactory.getBean("myBean") 方法来获取目标对象,这里本质上调用的是 org.springframework.beans.factory.support.AbstractBeanFactory 的 getBean() 方法,源码如下:

publicObjectgetBean(String name)throwsBeansException{
  return this.doGetBean(name, null, null, false);
}

这里调用 this.doGetBean(name, null, null, false) 来实现具体逻辑,也符合我们的预期,该方法可以看做是获取 bean 的整体框架,一个函数完成了整个过程的模块调度,还是挺复杂的:

protected <T> TdoGetBean(
    final String name, final Class<T> requiredType, final Object[] args, boolean typeCheckOnly) throws BeansException {

  /*
   * 转化对应的beanName
   *
   * 传入的参数可能是alias,也可能是FactoryBean,所以需要进行解析,主要包含以下内容:
   * 1. 去除FactoryBean的修饰符“&”
   * 2. 取指定alias对应的最终的name
   */
  final String beanName = this.transformedBeanName(name);

  Object bean;

  /*
   * 检查缓存或者实例工厂中是否有对应的实例
   *
   * 为什么会一开始就进行检查?
   * 因为在创建单例bean的时候会存在依赖注入的情况,而在创建依赖的时候为了避免循环依赖
   * Spring创建bean的原则是不等bean创建完成就会将创建bean的ObjectFactory提前曝光,即将对应的ObjectFactory加入到缓存
   * 一旦下一个bean创建需要依赖上一个bean,则直接使用ObjectFactory
   */
  Object sharedInstance = this.getSingleton(beanName); // 获取单例
  if (sharedInstance != null && args == null) {
    // 实例已经存在
    if (logger.isDebugEnabled()) {
      if (this.isSingletonCurrentlyInCreation(beanName)) {
        logger.debug("Returning eagerly cached instance of singleton bean '" + beanName + "' that is not fully initialized yet - a consequence of a circular reference");
      } else {
        logger.debug("Returning cached instance of singleton bean '" + beanName + "'");
      }
    }
    // 返回对应的实例
    bean = this.getObjectForBeanInstance(sharedInstance, name, beanName, null);
  } else {
    // 单例实例不存在
    if (this.isPrototypeCurrentlyInCreation(beanName)) {
      /*
       * 只有在单例模式下才会尝试解决循环依赖问题
       * 对于原型模式,如果存在循环依赖,也就是满足this.isPrototypeCurrentlyInCreation(beanName),抛出异常
       */
      throw new BeanCurrentlyInCreationException(beanName);
    }

    BeanFactory parentBeanFactory = this.getParentBeanFactory();
    if (parentBeanFactory != null && !this.containsBeanDefinition(beanName)) {
      // 如果在beanDefinitionMap中(即所有已经加载的类中)不包含目标bean,则尝试从parentBeanFactory中检测
      String nameToLookup = this.originalBeanName(name);
      if (args != null) {
        // 递归到BeanFactory中寻找
        return (T) parentBeanFactory.getBean(nameToLookup, args);
      } else {
        return parentBeanFactory.getBean(nameToLookup, requiredType);
      }
    }

    // 如果不仅仅是做类型检查,则创建bean
    if (!typeCheckOnly) {
      this.markBeanAsCreated(beanName);
    }

    try {
      /*
       * 将存储XML配置的GenericBeanDefinition转换成RootBeanDefinition
       * 如果指定了beanName是子bean的话,同时会合并父类的相关属性
       */
      final RootBeanDefinition mbd = this.getMergedLocalBeanDefinition(beanName);
      this.checkMergedBeanDefinition(mbd, beanName, args);

      // 获取当前bean依赖的bean
      String[] dependsOn = mbd.getDependsOn();
      if (dependsOn != null) {
        // 存在依赖,递归实例化依赖的bean
        for (String dep : dependsOn) {
          if (this.isDependent(beanName, dep)) {
            throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Circular depends-on relationship between '" + beanName + "' and '" + dep + "'");
          }
          // 缓存依赖调用
          this.registerDependentBean(dep, beanName);
          this.getBean(dep);
        }
      }

      // 实例化依赖的bean后,实例化mbd自身
      if (mbd.isSingleton()) {
        // scope == singleton
        sharedInstance = this.getSingleton(beanName, new ObjectFactory<Object>() {
          @Override
          publicObjectgetObject()throwsBeansException{
            try {
              return createBean(beanName, mbd, args);
            } catch (BeansException ex) {
              // Explicitly remove instance from singleton cache: It might have been put there
              // eagerly by the creation process, to allow for circular reference resolution.
              // Also remove any beans that received a temporary reference to the bean.
              destroySingleton(beanName);
              throw ex;
            }
          }
        });
        bean = this.getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
      } else if (mbd.isPrototype()) {
        // scope == prototype
        Object prototypeInstance;
        try {
          this.beforePrototypeCreation(beanName);
          prototypeInstance = this.createBean(beanName, mbd, args);
        } finally {
          this.afterPrototypeCreation(beanName);
        }
        // 返回对应的实例
        bean = this.getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
      } else {
        // 其它scope
        String scopeName = mbd.getScope();
        final Scope scope = this.scopes.get(scopeName);
        if (scope == null) {
          throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");
        }
        try {
          Object scopedInstance = scope.get(beanName, new ObjectFactory<Object>() {
            @Override
            publicObjectgetObject()throwsBeansException{
              beforePrototypeCreation(beanName);
              try {
                return createBean(beanName, mbd, args);
              } finally {
                afterPrototypeCreation(beanName);
              }
            }
          });
          // 返回对应的实例
          bean = this.getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
        } catch (IllegalStateException ex) {
          throw new BeanCreationException(beanName, "Scope '" + scopeName + "' is not active for the current thread; consider defining a scoped proxy for this bean if you intend to refer to it from a singleton", ex);
        }
      }
    } catch (BeansException ex) {
      cleanupAfterBeanCreationFailure(beanName);
      throw ex;
    }
  }

  // 检查需要的类型是否符合bean的实际类型,对应getBean时指定的requireType
  if (requiredType != null && bean != null && !requiredType.isAssignableFrom(bean.getClass())) {
    try {
      return this.getTypeConverter().convertIfNecessary(bean, requiredType);
    } catch (TypeMismatchException ex) {
      if (logger.isDebugEnabled()) {
        logger.debug("Failed to convert bean '" + name + "' to required type '" + ClassUtils.getQualifiedName(requiredType) + "'", ex);
      }
      throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass());
    }
  }
  return (T) bean;
}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • 详解Spring中使用xml配置bean的细节

    整理总结刚学的关于spring的xml配置bean的知识. 在ApplicationContext.xml文件中使用bean节点配置bean,bean的属性id在IOC容器中必须是唯一的. <bean id="helloWorld" class="com.test.spring.beans.HelloWorld"> <property name="name" value="Spring"></pr

  • Spring多种加载Bean方式解析

    1 定义bean的方式 常见的定义Bean的方式有: 通过xml的方式,例如: <bean id="dictionaryRelMap" class="java.util.HashMap"/> 通过注解的方式,在Class上使用@Component等注解,例如 @Component public class xxxServicer{ .... } 通过在@Configuration类下的@Bean的方式,例如 @Configuration public c

  • SpringMVC中使用bean来接收form表单提交的参数时的注意点

    这是前辈们对于SpringMVC接收表单数据记录下来的总结经验: SpringMVC接收页面表单参数 springmvc请求参数获取的几种方法 下面是我自己在使用时发现的,前辈们没有记录的细节和注意点: 使用bean来接收form表单提交的参数时,pojo中必须含有默认的(即空的)构造函数,同时,需要设置到bean中的变量必须有setter方法. 注:以下代码均为示例代码,非本人实际运行代码,请自行补充. 例如:我有一个bean类是User,具有变量username和password.同时,表单

  • Spring配置使用之Bean生命周期详解

    基本概念 Spring 中的 Bean 的生命周期,指的是 Bean 从创建到销毁的过程. 下面来探究下几个有关 Bean 生命周期配置的属性. lazy-init lazy-init 表示延迟加载 Bean,默认在 Spring IoC 容器初始化时会实例化所有在配置文件定义的 Bean,若启用了 lazy-init 则在调用 Bean 时才会去创建 Bean. 定义 Bean: public class Animals { public Animals(){ System.out.print

  • Spring自动扫描无法扫描jar包中bean的解决方法

    发现问题 前几天用eclipse打包了一个jar包,jar包里面是定义的Spring的bean. 然后将jar包放到lib下,设置spring的自动扫描这个jar包中的bean,可谁知根本无法扫描到bean,显示错误就是找不到bean,当时就纳闷儿了,为什么扫描不到,结果搜索之后才发现,用eclipse打包jar包要勾选"Add directory entries"才能被Spring正确扫描到,居然有这个说法,呵呵- 不知道 勾选"Add directory entries&

  • 详解Spring简单容器中的Bean基本加载过程

    本篇将对定义在 XMl 文件中的 bean,从静态的的定义到变成可以使用的对象的过程,即 bean 的加载和获取的过程进行一个整体的了解,不去深究,点到为止,只求对 Spring IOC 的实现过程有一个整体的感知,具体实现细节留到后面用针对性的篇章进行讲解. 首先我们来引入一个 Spring 入门使用示例,假设我们现在定义了一个类 org.zhenchao.framework.MyBean ,我们希望利用 Spring 来管理类对象,这里我们利用 Spring 经典的 XMl 配置文件形式进行

  • 详解在node.js中require方法的加载规则

    require 方法的加载规则 优先从缓存中加载 核心模块 路径形式的模块 第三方模块 一.优先从缓存中加载 main.js:执行加载a.js模块 require('./a') a.js:执行加载b.js模块,并输出a被加载了 require('./b') console.log('a.js 被加载了') b.js:输出b被加载了 console.log('b.js 被加载了') 结果: 可以看出:main去加载a.js,然后a在去加载b.js过程中,并没有打印两次 a.js被加载,Node会直

  • 详解Spring Data JPA中Repository的接口查询方法

    目录 1.查询方法定义详解 2.搜索查询策略 3.查询创建 4.属性表达式 5.特殊参数处理 6.限制查询结果 7. repository方法返回Collections or Iterables 8.repository方法处理Null 9.查询结果流 10.异步查询结果 1.查询方法定义详解 repository代理有两种方式从方法名中派生出特定存储查询. 通过直接从方法名派生查询. 通过使用一个手动定义的查询. 可用的选项取决于实际的商店.然而,必须有一个策略来决定创建什么实际的查询. 2.

  • 详解JavaScript两个实用的图片懒加载优化方法

    目录 一.方法一 二.方法二 InterSectionObserver 一.方法一 重点: 1.getBoundingClientRect().top > window.innerHeight 图片未出现 2.getBoundingClientRect().top < window.innerHeight 图片出现了 HTML: <ul> ...... <li>2222222222</li> <li>2222222222</li> &

  • spring boot微服务场景下apollo加载过程解析

    目录 集成使用 1.添加gradle依赖 2.配置application.properties 必须配置 可选配置 加载过程解析 postProcessEnvironment方法逻辑解析 initialize方法逻辑解析 结语 集成使用 1.添加 gradle 依赖 implementation "com.ctrip.framework.apollo:apollo-client:1.6.0" 2.配置 application.properties apollo 自身的配置共包含 9 项

  • 详解Android 教你打造高效的图片加载框架

    1.概述 优秀的图片加载框架不要太多,什么UIL , Volley ,Picasso,Imageloader等等.但是作为一名合格的程序猿,必须懂其中的实现原理,于是乎,今天我就带大家一起来设计一个加载网络.本地的图片框架.有人可能会说,自己写会不会很渣,运行效率,内存溢出神马的.放心,我们拿demo说话,拼得就是速度,奏事这么任性. 关于加载本地图片,当然了,我手机图片比较少,7000来张: 1.首先肯定不能内存溢出,但是尼玛现在像素那么高,怎么才能保证呢?我相信利用LruCache统一管理你

  • 详解在Docker容器中运行Spring Boot应用

    spring Boot简化了Spring应用的开发过程,遵循约定优先配置的原则提供了各类开箱即用(out-of-the-box)的框架配置.另一方面,Spring Boot还具备将代码直接构建为可执行jar包的能力,这个jar包是一个可以独立运行的部署单元.基于以上特性,现在普遍认为Spring Boot提供了一种快速构造微服务(Micro-Service)的能力. Docker与Spring Boot Docker是一种Linux容器的实现,Linux容器是基于进程的轻量级资源隔离技术,每一个

  • 详解Spring Boot 项目中的 parent

    前面和大伙聊了 Spring Boot 项目的三种创建方式,这三种创建方式,无论是哪一种,创建成功后,pom.xml 坐标文件中都有如下一段引用: <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.4.RELEASE</version> &

  • 详解Spring IOC 容器启动流程分析

    使用 Spring 时,XML 和注解是使用得最多的两种配置方式,虽然是两种完全不同的配置方式,但对于 IOC 容器来说,两种方式的不同主要是在 BeanDefinition 的解析上.而对于核心的容器启动流程,仍然是一致的. AbstractApplicationContext 的 refresh 方法实现了 IOC 容器启动的主要逻辑,启动流程中的关键步骤在源码中也可以对应到独立的方法.接下来以  AbstractApplicationContext 的实现类  ClassPathXmlAp

  • 详解Spring Cloud Zuul中路由配置细节

    上篇文章我们介绍了API网关的基本构建方式以及请求过滤,小伙伴们对Zuul的作用应该已经有了一个基本的认识,但是对于路由的配置我们只是做了一个简单的介绍,本文我们就来看看路由配置的其他一些细节. 首先我们来回忆一下上篇文章我们配置路由规则的那两行代码: zuul.routes.api-a.path=/api-a/** zuul.routes.api-a.serviceId=feign-consumer 我们说当我的访问地址符合/api-a/**规则的时候,会被自动定位到feign-consume

随机推荐