springboot bean循环依赖实现以及源码分析

前言

本文基于springboot版本2.5.1

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.1</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

本文主要聚焦在循环依赖部分,主要用单例bean来进行讲解,其他bean实现的流程不会过多涉及。

1、什么叫循环依赖呢

简单来说就是springboot容器中的多个bean,如A、B两个bean,A有属性B需要注入,B有属性A需要注入,形成相互依赖的情况。

看下代码,就是类似下面这种情况

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class ServiceA {
    @Autowired
    private ServiceB serviceB;
}
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class ServiceB {
    @Autowired
    private ServiceA serviceA;
}

上面有两个bean,分别是ServiceA,ServiceB。ServiceA中需要注入ServiceB的实例,ServiceB中需要注入ServiceA的实例,这就是一种典型的循环依赖,其他还有方法参数循环依赖的场景等等,但是它们的内部实现基本是一样的。

2、具体出现循环依赖的代码逻辑

获取bean的方法

在springboot中默认的beanFactory是DefaultListableBeanFactory,在我们获取bean对象的时候,如果bean对象存在就直接返回,如果不存在,就先创建bean对象再返回。

我们先看下我们获取bean的常用方法都有哪些

public <T> T getBean(Class<T> requiredType) throws BeansException
public Object getBean(String name) throws BeansException
public <T> Map<String, T> getBeansOfType(@Nullable Class<T> type) throws BeansException
public Map<String, Object> getBeansWithAnnotation(Class<? extends Annotation> annotationType)
public void preInstantiateSingletons() throws BeansException

常用的获取bean的方法主要有上面几个和它们的重载版本,对于第3行、第4行、第5行最终都会调用到第2行的方法来获取bean。而它也会通过调用doGetBean(在AbstractBeanFactory这个类中)来获取bean

 public Object getBean(String name) throws BeansException {
  return doGetBean(name, null, null, false);
 }

第1行的方法也会调用doGetBean来获取bean

 public <T> T getBean(String name, @Nullable Class<T> requiredType, @Nullable Object... args)
   throws BeansException {

  return doGetBean(name, requiredType, args, false);
 }

所有最终获取bean的方法都是

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

这个方法,这个方法是protected的,是不对外提供的。所以我们不能直接调用它,只能通过上面提供的5个方法来获取bean对象。

下面我们从doGetBean这里来看下serviceA创建的过程

 protected <T> T doGetBean(
   String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
   throws BeansException {
   //如果bean之前存在,这里返回的shareInstance就是非空,就会从后面的if分支中返回,如果bean之前不存在,就会执行后面的bean创建及注入属性的过程
   Object sharedInstance = getSingleton(beanName);
   if (sharedInstance != null && args == null) {
   ......
   //如果当前不只是检查,而且是创建bean,这个参数就是false,在这里就会做个bean创建的标记,把beanName 加到alreadyCreated里面去
   if (!typeCheckOnly) {
    markBeanAsCreated(beanName);
   }
    //我们当前要创建的bean是单例的,就会走到这里去,下面我们走到里面的调用去看看
    // Create bean instance.
    if (mbd.isSingleton()) {
     sharedInstance = getSingleton(beanName, () -> {
      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;
      }
     });
     beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
    }

  }
 public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
  Assert.notNull(beanName, "Bean name must not be null");
  synchronized (this.singletonObjects) {
                ......
                //这里会把当前bean的名字加入到当前正在创建的单例对象集合singletonsCurrentlyInCreation中
    beforeSingletonCreation(beanName);
    ......
    try {
                    //这里就是调用上面的return createBean(beanName, mbd, args);这个方法,我们进这里面去看看
     singletonObject = singletonFactory.getObject();
     newSingleton = true;
    }
    ......
   }
   return singletonObject;
  }
 }
 @Override
 protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
   throws BeanCreationException {
  ......
  // Make sure bean class is actually resolved at this point, and
  // clone the bean definition in case of a dynamically resolved Class
  // which cannot be stored in the shared merged bean definition.
        //在这里获取要创建的bean的class对象
  Class<?> resolvedClass = resolveBeanClass(mbd, beanName);
  ......
  try {
            //调用这里来创建,我们再走到这里面去看看
            //3个参数分别为
            //1、beanName  bean对象的名字
            //2、mbdToUseRootBeanDefinition对象,可以认为就是bean的元数据信息,包含bean的类对象,bean的类上注解,bean实际位置路径等等
            //3、args  bean对象的构造方法的实参,这里一般是空的
   Object beanInstance = doCreateBean(beanName, mbdToUse, args);
   if (logger.isTraceEnabled()) {
    logger.trace("Finished creating instance of bean '" + beanName + "'");
   }
   return beanInstance;
  }
  ......
 }
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
      throws BeanCreationException {

   ......
   //真正创建bean对象是在这里,这里返回的instanceWrapper是bean对象的类实例的包装对象BeanWrapper
   if (instanceWrapper == null) {
      instanceWrapper = createBeanInstance(beanName, mbd, args);
   }
   //这里的bean就是实际创建的bean对象的类实例
   Object bean = instanceWrapper.getWrappedInstance();
   Class<?> beanType = instanceWrapper.getWrappedClass();
   if (beanType != NullBean.class) {
      mbd.resolvedTargetType = beanType;
   }
 ......
   // Eagerly cache singletons to be able to resolve circular references
   // even when triggered by lifecycle interfaces like BeanFactoryAware.
   //看上面的注释大概也能明白, 大概意思就是早期的单例缓存,为了解决由 BeanFactoryAware等等触发的循环依赖
   //mbd.isSingleton()  表示bean是单例的(这个是bean对应的类上的,默认就是单例),
   //this.allowCircularReferences 允许循环引用,这个是beanFactory的成员属性,默认也是true
   //isSingletonCurrentlyInCreation(beanName) 表示是否在当前正在创建的bean集合中。beforeSingletonCreation(beanName);我们在前面执行过这句就加到正在创建的bean集合中了
   //这里earlySingletonExposure 就是true了,会进到if分支中
   boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
         isSingletonCurrentlyInCreation(beanName));
   if (earlySingletonExposure) {
      if (logger.isTraceEnabled()) {
         logger.trace("Eagerly caching bean '" + beanName +
               "' to allow for resolving potential circular references");
      }
      //这句主要是将将() -> getEarlyBeanReference(beanName, mbd, bean) 这个lambda表达式存储到this.singletonFactories集合中
      addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
   }

   // Initialize the bean instance.
   Object exposedObject = bean;
   try {
      //在这里就会进行属性填充,完成成员注入等等,也就是在这里serviceA这个bean会注入serviceB这个成员属性,我们走进这个方法去看看
      populateBean(beanName, mbd, instanceWrapper);
      ......
   }
  ......

   return exposedObject;
}
 protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) {
  ......
  if (hasInstAwareBpps) {
   if (pvs == null) {
    pvs = mbd.getPropertyValues();
   }
   //真正的属性注入是在这里完成的,aop也是在这里来完成的。这里是获取beanFactory中的InstantiationAwareBeanPostProcessor对bean对象进行增强
   //如果属性注入用的是@Resource,就会用CommonAnnotationBeanPostProcessor来完成
   //如果属性注入用的是@Autowired,就会用AutowiredAnnotationBeanPostProcessor来完成
   //如果是AOP 就会使用InfrastructureAdvisorAutoProxyCreator来生成对应的代理对象
   //我们这里使用的是@Autowired,所以会用AutowiredAnnotationBeanPostProcessor来完成注入。我们走到它的postProcessProperties的去看看
   for (InstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().instantiationAware) {
    PropertyValues pvsToUse = bp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName);
    ......
 }
 @Override
 public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
        //这里主要是获取bean的类属性和方法上的org.springframework.beans.factory.annotation.Autowired,org.springframework.beans.factory.annotation.Value注解来进行注入
  InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);
  try {
            //继续进去看看
   metadata.inject(bean, beanName, pvs);
  }
  ......
 }
public void inject(Object target, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
   ......
   //对每一个属性分别进行注入,继续进去
         element.inject(target, beanName, pvs);
      }
   }
}
    @Override
    protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
     Field field = (Field) this.member;
     Object value;
     //如果之前缓存过就从缓存取,我们是第一次注入,所以之前没有缓存,不会走这个分支
     if (this.cached) {
      try {
       value = resolvedCachedArgument(beanName, this.cachedFieldValue);
      }
      catch (NoSuchBeanDefinitionException ex) {
       // Unexpected removal of target bean for cached argument -> re-resolve
       value = resolveFieldValue(field, bean, beanName);
      }
     }
     else {
      //会走这里来解析字段的值,再进去
      value = resolveFieldValue(field, bean, beanName);
     }
     if (value != null) {
      ReflectionUtils.makeAccessible(field);
      field.set(bean, value);
     }
    }
  @Nullable
  private Object resolveFieldValue(Field field, Object bean, @Nullable String beanName) {
   //创建字段的包装类DependencyDescriptor
   DependencyDescriptor desc = new DependencyDescriptor(field, this.required);

   try {
    //调用这里完成对应字段值的查找,再进去
    value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);
   }
   catch (BeansException ex) {
    throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(field), ex);
   }
   synchronized (this) {
    //获取到值之后,进行缓存
    if (!this.cached) {
      ......
     }
     this.cachedFieldValue = cachedFieldValue;
     this.cached = true;
    }
   }
   return value;
  }
 }
 public Object resolveDependency(DependencyDescriptor descriptor, @Nullable String requestingBeanName,
   @Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {

  descriptor.initParameterNameDiscovery(getParameterNameDiscoverer());
  if (Optional.class == descriptor.getDependencyType()) {
   return createOptionalDependency(descriptor, requestingBeanName);
  }
  else if (ObjectFactory.class == descriptor.getDependencyType() ||
    ObjectProvider.class == descriptor.getDependencyType()) {
   return new DependencyObjectProvider(descriptor, requestingBeanName);
  }
  else if (javaxInjectProviderClass == descriptor.getDependencyType()) {
   return new Jsr330Factory().createDependencyProvider(descriptor, requestingBeanName);
  }
  else {
   //当前的类是一个普通的class,会走到这里面,由于我们的bean没有Lazy注解,所以这里返回时null,走到下面的if分支
   Object result = getAutowireCandidateResolver().getLazyResolutionProxyIfNecessary(
     descriptor, requestingBeanName);
   if (result == null) {
    //在这里我们看下这里的入参。
    //descriptor是包含了需要注入的字段的信息。
    //requestingBeanName是当前正在创建的bean的名字serviceA,
    //autowiredBeanNames是当前需要注入的字段的对应的bean的名字的集合,这里只有serviceB
    //typeConverter这个是进行注入时做类型转换的,这里我们可以不用关注这个
    result = doResolveDependency(descriptor, requestingBeanName, autowiredBeanNames, typeConverter);
   }
   return result;
  }
 }
 @Nullable
 public Object doResolveDependency(DependencyDescriptor descriptor, @Nullable String beanName,
   @Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {
   ......
   if (instanceCandidate instanceof Class) {
    //又会调用到这里,我们再进入到DependencyDescriptor的resolveCandidate去看看
                //注意:这里的autowiredBeanName是我们需要注入的属性名这里是serviceB
    instanceCandidate = descriptor.resolveCandidate(autowiredBeanName, type, this);
   }
   ......
 }
 public Object resolveCandidate(String beanName, Class<?> requiredType, BeanFactory beanFactory)
   throws BeansException {
  //看到没,到这里就出现循环调用了,到这里又会重新调用beanFactory.getBean("serviceB")去创建serviceB的bean对象,完成后注入到serivceA对应的Bean上的属性上来,这时代码又会从本节开头的位置开始执行,先创建serviceB对象实例,再去注入serviceB对象的serviceA属性。
        //最终会执行到beanFactory.getBean("serviceA")这里
  return beanFactory.getBean(beanName);
 }

就是下面图的样子

3、解决循环依赖的代码实现

接着上面的beanFactory.getBean("serviceA")这行代码我们继续往下看

这次又会走到这里

 protected <T> T doGetBean(
   String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
   throws BeansException {
  //我们第二部分就是从这里开始的,又走回来了,但这次又会有所不同
  String beanName = transformedBeanName(name);
  Object beanInstance;

  // Eagerly check singleton cache for manually registered singletons.
  //这次我们这里返回的就不是空了,sharedInstance对象的值就是对应serviceA的bean对象了,这次就会从if分支中返回,而之前我们不会进这里的if分支而是进入else分支导致后面出现了循环依赖的问题,这次我们进到这个方法看看
  Object sharedInstance = getSingleton(beanName);
  if (sharedInstance != null && args == null) {
   if (logger.isTraceEnabled()) {
    if (isSingletonCurrentlyInCreation(beanName)) {
     logger.trace("Returning eagerly cached instance of singleton bean '" + beanName +
       "' that is not fully initialized yet - a consequence of a circular reference");
    }
    else {
     logger.trace("Returning cached instance of singleton bean '" + beanName + "'");
    }
   }
   beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, null);
  }
 @Nullable
 public Object getSingleton(String beanName) {
  //再点进去
  return getSingleton(beanName, true);
 }
 protected Object getSingleton(String beanName, boolean allowEarlyReference) {
  // Quick check for existing instance without full singleton lock
  Object singletonObject = this.singletonObjects.get(beanName);
         //这里由于当前的serviceA bean还没完成创建,所以这里singletonObject返回的是空,
        //再看看 isSingletonCurrentlyInCreation(beanName)这里,由于我们在创建serviceA过程中有这么一句beforeSingletonCreation(beanName)(不清楚这句的搜索下本文,上面就有讲到),所有这个条件是true。这时我们就会进入if分支中
  if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
   singletonObject = this.earlySingletonObjects.get(beanName);
           //由于我们是第一次进入这里,所以this.earlySingletonObjects.get(beanName)返回的也是null
           //我们的入参 allowEarlyReference是true,会继续进到这个if分支中
   if (singletonObject == null && allowEarlyReference) {
    synchronized (this.singletonObjects) {
     // Consistent creation of early reference within full singleton lock
     singletonObject = this.singletonObjects.get(beanName);
                    //这里的singletonObject还是null,继续进到if分支
     if (singletonObject == null) {
      singletonObject = this.earlySingletonObjects.get(beanName);
      if (singletonObject == null) {
                            //最终会走到这里,在创建serviceA对象之后,属性注入之前,执行了这句 addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean))(不清楚的搜索下本文,上面有说到),所以这里返回的singletonFactory是个lamdba表达式,getEarlyBeanReference(beanName, mbd, bean))附带了3个参数,第一个beanName是serivceA,mdb是对应serviceA的附带serviceA元数据信息的RootBeanDefinition对象,bean就是创建出来的serviceA对象
       ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
       if (singletonFactory != null) {
                                //这里就会调用getEarlyBeanReference(beanName, mbd, bean)对serviceA对象进行一个getEarlyBeanReference增强后返回,返回后放置到earlySingletonObjects中,并从singletonFactories中删除
        singletonObject = singletonFactory.getObject();
        this.earlySingletonObjects中,并从.put(beanName, singletonObject);
        this.singletonFactories.remove(beanName);
       }
      }
     }
    }
   }
  }
  return singletonObject;
 }

最终在serviceA 这个bean创建完成后,就会从singletonsCurrentlyInCreation移除掉

 public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
    ......
    finally {
     //在这里从singletonsCurrentlyInCreation中移除掉
     afterSingletonCreation(beanName);
    }
    if (newSingleton) {
     //将serviceA bean对象添加到singletonObjects,registeredSingletons中
     //从singletonFactories,earlySingletonObjects中移除掉
     addSingleton(beanName, singletonObject);
    }
   }
   return singletonObject;
  }
 }

所以整个获取serviceA的流程就是这样了,

1、首先去创建serviceA这个bean,

  • 由于它有个属性serviceB,在创建完serviceA对象后,就会去进行serviceB的属性注入,
  • 这时由于serviceB之前没有生成,这时又会去创建serviceB这个bean,
  • 先创建serviceB对象,然后再进行serviceA这个属性的注入,
  • 继续去获取serviceA这个bean,第二次进入获取serviceA的流程,这时从之前缓存的lambda表达式中获取到之前创建的serviceA的引用返回。

2、总结下关键的代码点

  • 创建bean对象之前调用beforeSingletonCreation(beanName)将bean对象名字添加到singletonsCurrentlyInCreation集合中
  • 创建bean对象对应的类实例后调用addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));添加到singletonFactories中
  • 在循环依赖中第二次调用到创建bean对象时,调用getSingleton(beanName, true)时,从singletonFactories中返回对应的早期bean对象的引用,并添加到earlySingletonObjects中

总结

到此这篇关于springboot bean循环依赖实现以及源码分析的文章就介绍到这了,更多相关springboot bean循环依赖内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • spring boot启动时mybatis报循环依赖的错误(推荐)

    自己在做项目时,想使用热部署减少部署时间,于是添加了springboot-devtools 在maven中添加了依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> </dependency> 然后正常的启动项目时发现控制台一直在不停的输出错误,错误如图 不明所以,然后就准备去调

  • 基于SpringBoot构造器注入循环依赖及解决方式

    1. 循环依赖是什么? Bean A 依赖 B,Bean B 依赖 A这种情况下出现循环依赖. Bean A → Bean B → Bean A 更复杂的间接依赖造成的循环依赖如下. Bean A → Bean B → Bean C → Bean D → Bean E → Bean A 2. 循环依赖会产生什么结果? 当Spring正在加载所有Bean时,Spring尝试以能正常创建Bean的顺序去创建Bean. 例如,有如下依赖: Bean A → Bean B → Bean C Spring

  • springboot bean循环依赖实现以及源码分析

    前言 本文基于springboot版本2.5.1 <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.5.1</version> <relativePath/> <!-- lookup parent from repositor

  • 详解SpringBoot启动代码和自动装配源码分析

    目录 一.SpringBoot启动代码主线分析 二.SpringBoot自动装配原理分析 1.自动装配的前置知识@Import 2.@SpringApplication注解分析 2.1@SpringBootConfiguration 2.2@EnableAutoConfiguration ​随着互联网的快速发展,各种组件层出不穷,需要框架集成的组件越来越多.每一种组件与Spring容器整合需要实现相关代码.SpringMVC框架配置由于太过于繁琐和依赖XML文件:为了方便快速集成第三方组件和减少

  • 使用dynamic datasource springboot starter实现多数据源及源码分析

    目录 简介 实操 基本使用 集成druid连接池 service嵌套 为什么切换数据源不生效或事务不生效? 源码分析 整体结构 自动配置怎么实现的 如何集成众多连接池的 DS注解如何被拦截处理的 多数据源动态切换及如何管理多数据源 数据组的负载均衡怎么做的 如何自定义数据配置来源 如何动态增减数据源 总结 简介 前两篇博客介绍了用基本的方式做多数据源,可以应对一般的情况,但是遇到一些复杂的情况就需要扩展下功能了,比如:动态增减数据源.数据源分组,纯粹多库 读写分离 一主多从.从其他数据库或者配置

  • Spring循环引用失败问题源码解析

    目录 前言: 例子 启动容器 加载circulationa AbstractBeanFactory 最终调用BeanDefinitionValueResolver circulationb加载分析 前言: 之前我们有分析过Spring是怎么解决循环引用的问题,主要思路就是三级缓存: Spring在加载beanA的时候会先调用默认的空构造函数(在没有指定构造函数实例化的前提下)得到一个空的实例引用对象,这个时候没有设置任何值,但是Spring会用缓存把它给提前暴露出来,让其他依赖beanA的bea

  • SpringBoot请求处理之常用参数注解介绍与源码分析

    目录 1.注解 2.注解生效相关源码分析 3.Servlet API 4.复杂参数 5.自定义参数 6.类型转换器Converters 1.注解 @PathVariable:将请求url中的占位符参数与控制器方法入参绑定起来(Rest风格请求) @RequestHeader:获取请求头中的参数,通过指定参数 value 的值来获取请求头中指定的参数值 @ModelAttribute:两种用法 用在参数上,会将客户端传递过来的参数按名称注入到指定对象中,并且会将这个对象自动加入ModelMap中,

  • SpringBoot源码分析之bootstrap.properties文件加载的原理

    目录 1.bootstrap的使用 2.bootstrap加载原理分析 2.1 BootstrapApplicationListener 2.2 启动流程梳理 2.3 bootstrap.properties的加载原理   对于SpringBoot中的属性文件相信大家在工作中用的是比较多的,对于application.properties和application.yml文件应该非常熟悉,但是对于bootstrap.properties文件和bootstrap.yml这个两个文件用的估计就比较少了

  • 关于SpringBoot禁止循环依赖解说

    前言: Spring的Bean管理,一直是整个体系中津津乐道的东西.尤其是Bean的循环依赖,更是很多面试官最喜欢考察的2B知识点之一. 但事实上,项目中存在Bean的循环依赖,是代码质量低下的表现.多数人寄希望于框架层来给擦屁股,造成了整个代码的设计越来越糟,最后用一些奇技淫巧来填补犯下的错误. 还好,SpringBoot终于受不了这种滥用,默认把循环依赖给禁用了! 从2.6版本开始,如果你的项目里还存在循环依赖,SpringBoot将拒绝启动! 验证代码小片段: 为了验证这个功能,我们只需要

  • SpringBoot拦截器与文件上传实现方法与源码分析

    目录 一.拦截器 1.创建一个拦截器 2.配置拦截器 二.拦截器原理 三.文件上传 四.文件上传流程 一.拦截器 拦截器我们之前在springmvc已经做过介绍了 大家可以看下[SpringMVC]自定义拦截器和过滤器 为什么在这里还要再讲一遍呢? 因为spring boot里面对它做了简化,大大节省了我们配置那些烦人的xml文件的时间 接下来,我们就通过一个小例子来了解一下拦截器在spring boot中的使用 1.创建一个拦截器 首先我们创建一个拦截器,实现HandlerIntercepto

  • Springboot 2使用外部Tomcat源码分析

    Springboot 使用外部 Tomcat 1.修改 pom.xml,改为打 war 包 <packaging>war</packaging> 2.将 Springboot 内置 tomcat 作用域改为provided <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifac

  • SpringBoot静态资源配置原理(源码分析)

    前言: 我们都知道,SpringBoot启动会默认加载很多xxxAutoConfiguration类(自动配置类) 其中SpringMVC的大都数功能都集中在WebMvcAutoConfiguration类中,根据条件ConditionalOnxxx注册类对象:WebMvcAutoConfiguration满足以下ConditionalOnxxx条件,类是生效的,并把其对象注册到容器中. 那WebMvcAutoConfiguration生效给容器中配置了什么呢? WebMvcAutoConfig

随机推荐