亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

springboot bean循環(huán)依賴實(shí)現(xiàn)以及源碼分析

 更新時(shí)間:2021年06月27日 10:52:58   作者:wang03  
最近在使用Springboot做項(xiàng)目的時(shí)候,遇到了一個(gè)循環(huán)依賴的 問題,所以下面這篇文章主要給大家介紹了關(guān)于springboot bean循環(huán)依賴實(shí)現(xiàn)以及源碼分析的相關(guān)資料,需要的朋友可以參考下

前言

本文基于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>

本文主要聚焦在循環(huán)依賴部分,主要用單例bean來進(jìn)行講解,其他bean實(shí)現(xiàn)的流程不會(huì)過多涉及。

1、什么叫循環(huán)依賴呢

簡(jiǎn)單來說就是springboot容器中的多個(gè)bean,如A、B兩個(gè)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;
}

上面有兩個(gè)bean,分別是ServiceA,ServiceB。ServiceA中需要注入ServiceB的實(shí)例,ServiceB中需要注入ServiceA的實(shí)例,這就是一種典型的循環(huán)依賴,其他還有方法參數(shù)循環(huán)依賴的場(chǎng)景等等,但是它們的內(nèi)部實(shí)現(xiàn)基本是一樣的。

2、具體出現(xiàn)循環(huán)依賴的代碼邏輯

獲取bean的方法

在springboot中默認(rèn)的beanFactory是DefaultListableBeanFactory,在我們獲取bean對(duì)象的時(shí)候,如果bean對(duì)象存在就直接返回,如果不存在,就先創(chuàng)建bean對(duì)象再返回。

我們先看下我們獲取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的方法主要有上面幾個(gè)和它們的重載版本,對(duì)于第3行、第4行、第5行最終都會(huì)調(diào)用到第2行的方法來獲取bean。而它也會(huì)通過調(diào)用doGetBean(在AbstractBeanFactory這個(gè)類中)來獲取bean

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

第1行的方法也會(huì)調(diào)用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 {

這個(gè)方法,這個(gè)方法是protected的,是不對(duì)外提供的。所以我們不能直接調(diào)用它,只能通過上面提供的5個(gè)方法來獲取bean對(duì)象。

下面我們從doGetBean這里來看下serviceA創(chuàng)建的過程

 protected <T> T doGetBean(
   String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
   throws BeansException {
   //如果bean之前存在,這里返回的shareInstance就是非空,就會(huì)從后面的if分支中返回,如果bean之前不存在,就會(huì)執(zhí)行后面的bean創(chuàng)建及注入屬性的過程
   Object sharedInstance = getSingleton(beanName);
   if (sharedInstance != null && args == null) {
   ......
   //如果當(dāng)前不只是檢查,而且是創(chuàng)建bean,這個(gè)參數(shù)就是false,在這里就會(huì)做個(gè)bean創(chuàng)建的標(biāo)記,把beanName 加到alreadyCreated里面去
   if (!typeCheckOnly) {
    markBeanAsCreated(beanName);
   }
    //我們當(dāng)前要?jiǎng)?chuàng)建的bean是單例的,就會(huì)走到這里去,下面我們走到里面的調(diào)用去看看
    // 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) {
                ......
                //這里會(huì)把當(dāng)前bean的名字加入到當(dāng)前正在創(chuàng)建的單例對(duì)象集合singletonsCurrentlyInCreation中
    beforeSingletonCreation(beanName);
    ......
    try {
                    //這里就是調(diào)用上面的return createBean(beanName, mbd, args);這個(gè)方法,我們進(jìn)這里面去看看
     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.
        //在這里獲取要?jiǎng)?chuàng)建的bean的class對(duì)象
  Class<?> resolvedClass = resolveBeanClass(mbd, beanName);
  ......
  try {
            //調(diào)用這里來創(chuàng)建,我們?cè)僮叩竭@里面去看看
            //3個(gè)參數(shù)分別為
            //1、beanName  bean對(duì)象的名字
            //2、mbdToUseRootBeanDefinition對(duì)象,可以認(rèn)為就是bean的元數(shù)據(jù)信息,包含bean的類對(duì)象,bean的類上注解,bean實(shí)際位置路徑等等
            //3、args  bean對(duì)象的構(gòu)造方法的實(shí)參,這里一般是空的
   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 {

   ......
   //真正創(chuàng)建bean對(duì)象是在這里,這里返回的instanceWrapper是bean對(duì)象的類實(shí)例的包裝對(duì)象BeanWrapper
   if (instanceWrapper == null) {
      instanceWrapper = createBeanInstance(beanName, mbd, args);
   }
   //這里的bean就是實(shí)際創(chuàng)建的bean對(duì)象的類實(shí)例
   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等等觸發(fā)的循環(huán)依賴
   //mbd.isSingleton()  表示bean是單例的(這個(gè)是bean對(duì)應(yīng)的類上的,默認(rèn)就是單例),
   //this.allowCircularReferences 允許循環(huán)引用,這個(gè)是beanFactory的成員屬性,默認(rèn)也是true
   //isSingletonCurrentlyInCreation(beanName) 表示是否在當(dāng)前正在創(chuàng)建的bean集合中。beforeSingletonCreation(beanName);我們?cè)谇懊鎴?zhí)行過這句就加到正在創(chuàng)建的bean集合中了
   //這里earlySingletonExposure 就是true了,會(huì)進(jìn)到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) 這個(gè)lambda表達(dá)式存儲(chǔ)到this.singletonFactories集合中
      addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
   }

   // Initialize the bean instance.
   Object exposedObject = bean;
   try {
      //在這里就會(huì)進(jìn)行屬性填充,完成成員注入等等,也就是在這里serviceA這個(gè)bean會(huì)注入serviceB這個(gè)成員屬性,我們走進(jìn)這個(gè)方法去看看
      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對(duì)bean對(duì)象進(jìn)行增強(qiáng)
   //如果屬性注入用的是@Resource,就會(huì)用CommonAnnotationBeanPostProcessor來完成
   //如果屬性注入用的是@Autowired,就會(huì)用AutowiredAnnotationBeanPostProcessor來完成
   //如果是AOP 就會(huì)使用InfrastructureAdvisorAutoProxyCreator來生成對(duì)應(yīng)的代理對(duì)象
   //我們這里使用的是@Autowired,所以會(huì)用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注解來進(jìn)行注入
  InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);
  try {
            //繼續(xù)進(jìn)去看看
   metadata.inject(bean, beanName, pvs);
  }
  ......
 }
public void inject(Object target, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
   ......
   //對(duì)每一個(gè)屬性分別進(jìn)行注入,繼續(xù)進(jìn)去
         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;
     //如果之前緩存過就從緩存取,我們是第一次注入,所以之前沒有緩存,不會(huì)走這個(gè)分支
     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 {
      //會(huì)走這里來解析字段的值,再進(jìn)去
      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) {
   //創(chuàng)建字段的包裝類DependencyDescriptor
   DependencyDescriptor desc = new DependencyDescriptor(field, this.required);

   try {
    //調(diào)用這里完成對(duì)應(yīng)字段值的查找,再進(jìn)去
    value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);
   }
   catch (BeansException ex) {
    throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(field), ex);
   }
   synchronized (this) {
    //獲取到值之后,進(jìn)行緩存
    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 {
   //當(dāng)前的類是一個(gè)普通的class,會(huì)走到這里面,由于我們的bean沒有Lazy注解,所以這里返回時(shí)null,走到下面的if分支
   Object result = getAutowireCandidateResolver().getLazyResolutionProxyIfNecessary(
     descriptor, requestingBeanName);
   if (result == null) {
    //在這里我們看下這里的入?yún)ⅰ?
    //descriptor是包含了需要注入的字段的信息。
    //requestingBeanName是當(dāng)前正在創(chuàng)建的bean的名字serviceA,
    //autowiredBeanNames是當(dāng)前需要注入的字段的對(duì)應(yīng)的bean的名字的集合,這里只有serviceB
    //typeConverter這個(gè)是進(jìn)行注入時(shí)做類型轉(zhuǎn)換的,這里我們可以不用關(guān)注這個(gè)
    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) {
    //又會(huì)調(diào)用到這里,我們?cè)龠M(jìn)入到DependencyDescriptor的resolveCandidate去看看
                //注意:這里的autowiredBeanName是我們需要注入的屬性名這里是serviceB
    instanceCandidate = descriptor.resolveCandidate(autowiredBeanName, type, this);
   }
   ......
 }

 public Object resolveCandidate(String beanName, Class<?> requiredType, BeanFactory beanFactory)
   throws BeansException {
  //看到?jīng)],到這里就出現(xiàn)循環(huán)調(diào)用了,到這里又會(huì)重新調(diào)用beanFactory.getBean("serviceB")去創(chuàng)建serviceB的bean對(duì)象,完成后注入到serivceA對(duì)應(yīng)的Bean上的屬性上來,這時(shí)代碼又會(huì)從本節(jié)開頭的位置開始執(zhí)行,先創(chuàng)建serviceB對(duì)象實(shí)例,再去注入serviceB對(duì)象的serviceA屬性。
        //最終會(huì)執(zhí)行到beanFactory.getBean("serviceA")這里
  return beanFactory.getBean(beanName);
 }

就是下面圖的樣子

3、解決循環(huán)依賴的代碼實(shí)現(xiàn)

接著上面的beanFactory.getBean("serviceA")這行代碼我們繼續(xù)往下看

這次又會(huì)走到這里

 protected <T> T doGetBean(
   String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
   throws BeansException {
  //我們第二部分就是從這里開始的,又走回來了,但這次又會(huì)有所不同
  String beanName = transformedBeanName(name);
  Object beanInstance;

  // Eagerly check singleton cache for manually registered singletons.
  //這次我們這里返回的就不是空了,sharedInstance對(duì)象的值就是對(duì)應(yīng)serviceA的bean對(duì)象了,這次就會(huì)從if分支中返回,而之前我們不會(huì)進(jìn)這里的if分支而是進(jìn)入else分支導(dǎo)致后面出現(xiàn)了循環(huán)依賴的問題,這次我們進(jìn)到這個(gè)方法看看
  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) {
  //再點(diǎn)進(jìn)去
  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);
         //這里由于當(dāng)前的serviceA bean還沒完成創(chuàng)建,所以這里singletonObject返回的是空,
        //再看看 isSingletonCurrentlyInCreation(beanName)這里,由于我們?cè)趧?chuàng)建serviceA過程中有這么一句beforeSingletonCreation(beanName)(不清楚這句的搜索下本文,上面就有講到),所有這個(gè)條件是true。這時(shí)我們就會(huì)進(jìn)入if分支中
  if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
   singletonObject = this.earlySingletonObjects.get(beanName);
           //由于我們是第一次進(jìn)入這里,所以this.earlySingletonObjects.get(beanName)返回的也是null
           //我們的入?yún)?allowEarlyReference是true,會(huì)繼續(xù)進(jìn)到這個(gè)if分支中
   if (singletonObject == null && allowEarlyReference) {
    synchronized (this.singletonObjects) {
     // Consistent creation of early reference within full singleton lock
     singletonObject = this.singletonObjects.get(beanName);
                    //這里的singletonObject還是null,繼續(xù)進(jìn)到if分支
     if (singletonObject == null) {
      singletonObject = this.earlySingletonObjects.get(beanName);
      if (singletonObject == null) {
                            //最終會(huì)走到這里,在創(chuàng)建serviceA對(duì)象之后,屬性注入之前,執(zhí)行了這句 addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean))(不清楚的搜索下本文,上面有說到),所以這里返回的singletonFactory是個(gè)lamdba表達(dá)式,getEarlyBeanReference(beanName, mbd, bean))附帶了3個(gè)參數(shù),第一個(gè)beanName是serivceA,mdb是對(duì)應(yīng)serviceA的附帶serviceA元數(shù)據(jù)信息的RootBeanDefinition對(duì)象,bean就是創(chuàng)建出來的serviceA對(duì)象
       ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
       if (singletonFactory != null) {
                                //這里就會(huì)調(diào)用getEarlyBeanReference(beanName, mbd, bean)對(duì)serviceA對(duì)象進(jìn)行一個(gè)getEarlyBeanReference增強(qiáng)后返回,返回后放置到earlySingletonObjects中,并從singletonFactories中刪除
        singletonObject = singletonFactory.getObject();
        this.earlySingletonObjects中,并從.put(beanName, singletonObject);
        this.singletonFactories.remove(beanName);
       }
      }
     }
    }
   }
  }
  return singletonObject;
 }

最終在serviceA 這個(gè)bean創(chuàng)建完成后,就會(huì)從singletonsCurrentlyInCreation移除掉

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

所以整個(gè)獲取serviceA的流程就是這樣了,

1、首先去創(chuàng)建serviceA這個(gè)bean,

  • 由于它有個(gè)屬性serviceB,在創(chuàng)建完serviceA對(duì)象后,就會(huì)去進(jìn)行serviceB的屬性注入,
  • 這時(shí)由于serviceB之前沒有生成,這時(shí)又會(huì)去創(chuàng)建serviceB這個(gè)bean,
  • 先創(chuàng)建serviceB對(duì)象,然后再進(jìn)行serviceA這個(gè)屬性的注入,
  • 繼續(xù)去獲取serviceA這個(gè)bean,第二次進(jìn)入獲取serviceA的流程,這時(shí)從之前緩存的lambda表達(dá)式中獲取到之前創(chuàng)建的serviceA的引用返回。

2、總結(jié)下關(guān)鍵的代碼點(diǎn)

  • 創(chuàng)建bean對(duì)象之前調(diào)用beforeSingletonCreation(beanName)將bean對(duì)象名字添加到singletonsCurrentlyInCreation集合中
  • 創(chuàng)建bean對(duì)象對(duì)應(yīng)的類實(shí)例后調(diào)用addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));添加到singletonFactories中
  • 在循環(huán)依賴中第二次調(diào)用到創(chuàng)建bean對(duì)象時(shí),調(diào)用getSingleton(beanName, true)時(shí),從singletonFactories中返回對(duì)應(yīng)的早期bean對(duì)象的引用,并添加到earlySingletonObjects中

總結(jié)

到此這篇關(guān)于springboot bean循環(huán)依賴實(shí)現(xiàn)以及源碼分析的文章就介紹到這了,更多相關(guān)springboot bean循環(huán)依賴內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Java利用反射如何查找使用指定注解的類詳解

    Java利用反射如何查找使用指定注解的類詳解

    這篇文章主要給大家介紹了關(guān)于Java利用反射如何查找使用指定注解的類的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。
    2017-09-09
  • Java使用Random類生成隨機(jī)數(shù)示例

    Java使用Random類生成隨機(jī)數(shù)示例

    這篇文章主要介紹了Java使用Random類生成隨機(jī)數(shù),結(jié)合實(shí)例形式分析了java基于Random類生成隨機(jī)數(shù)與遍歷輸出相關(guān)操作技巧,需要的朋友可以參考下
    2019-07-07
  • Java實(shí)現(xiàn)4種微信搶紅包算法(小結(jié))

    Java實(shí)現(xiàn)4種微信搶紅包算法(小結(jié))

    微信紅包是大家經(jīng)常使用的,到現(xiàn)在為止仍然有很多紅包開發(fā)的需求,實(shí)現(xiàn)搶紅包算法也是面試??碱},本文就詳細(xì)的來介紹一下如何實(shí)現(xiàn),感興趣的可以了解一下
    2021-12-12
  • Java中break的第三種用法說明

    Java中break的第三種用法說明

    這篇文章主要介紹了Java中break的第三種用法說明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2020-11-11
  • java中Date日期類型的大小比較方式

    java中Date日期類型的大小比較方式

    這篇文章主要介紹了java中Date日期類型的大小比較方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-07-07
  • Spring?MVC策略模式之MethodArgumentResolver源碼解析

    Spring?MVC策略模式之MethodArgumentResolver源碼解析

    這篇文章主要為大家介紹了Spring?MVC策略模式之MethodArgumentResolver源碼解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-03-03
  • Java優(yōu)化for循環(huán)嵌套的高效率方法

    Java優(yōu)化for循環(huán)嵌套的高效率方法

    這篇文章主要介紹了Java優(yōu)化for循環(huán)嵌套的高效率方法,幫助大家更好的提升java程序性能,感興趣的朋友可以了解下
    2020-09-09
  • Spring Cloud Zipkin服務(wù)端追蹤服務(wù)

    Spring Cloud Zipkin服務(wù)端追蹤服務(wù)

    這篇文章主要介紹了Spring Cloud Zipkin服務(wù)端追蹤服務(wù),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-04-04
  • Java中字符串去重的特性介紹

    Java中字符串去重的特性介紹

    這篇文章主要介紹了Java中字符串去重的特性,是Java8中引入的一個(gè)新特性,至于是否真的用起來順手就見仁見智了...需要的朋友可以參考下
    2015-07-07
  • idea以任意順序debug多線程程序的具體用法

    idea以任意順序debug多線程程序的具體用法

    在idea中使用debug可以讓多個(gè)線程以任意順序執(zhí)行,接下來通過本文給大家介紹idea以任意順序debug多線程程序的具體用法,需要的朋友參考下吧
    2021-08-08

最新評(píng)論