spring 源码解析之 createBean

前言

经过了前一章的分析(spring 源码解析之 doGetBean)),我们是对获取 bean实例有了大致的理解,上篇主要说当Object sharedInstance = getSingleton(beanName)获取 bean 实例不为空时,如何从工厂获取最终的实例 bean。以及说了一下当 bean 不存在,bean 创建前做了一些准备,如获取父级BeanFactory:getParentBeanFactory、合并beangetMergedLocalBeanDefinition、以及 bean 依赖的处理mbd.getDependsOn()。然后本篇文章才是重头戏,当 bean 缓存不存在的时候,也就是还没创建时,bean 是如何创建的。接下来开卷!

源码解析

getSingleton

java 复制代码
 public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
    Assert.notNull(beanName, "Bean name must not be null");
    synchronized (this.singletonObjects) {
       // 获取单例 bean
       Object singletonObject = this.singletonObjects.get(beanName);
       // bean 实例为空,则创建单例bean
       if (singletonObject == null) {
          // 判断,如果当前beanFactory正在被销毁则直接抛出异常,不允许创建单例bean
          if (this.singletonsCurrentlyInDestruction) {
             throw new BeanCreationNotAllowedException(beanName,
                   "Singleton bean creation not allowed while singletons of this factory are in destruction " +
                   "(Do not request a bean from a BeanFactory in a destroy method implementation!)");
          }
          if (logger.isDebugEnabled()) {
             logger.debug("Creating shared instance of singleton bean '" + beanName + "'");
          }
          // 做一些bean创建前的准备工作: 记录beanName 正在加载的状态(添加到 singletonsCurrentlyInCreation 缓存中),
          // 若bean已经正在加载,则抛出异常。为了解决循环引用的问题
          beforeSingletonCreation(beanName);
          boolean newSingleton = false;
          boolean recordSuppressedExceptions = (this.suppressedExceptions == null);
          if (recordSuppressedExceptions) {
             this.suppressedExceptions = new LinkedHashSet<>();
          }
          try {
             singletonObject = singletonFactory.getObject();
             newSingleton = true;
          }
          catch (IllegalStateException ex) {
             // Has the singleton object implicitly appeared in the meantime ->
             // if yes, proceed with it since the exception indicates that state.
             singletonObject = this.singletonObjects.get(beanName);
             if (singletonObject == null) {
                throw ex;
             }
          }
          catch (BeanCreationException ex) {
             if (recordSuppressedExceptions) {
                for (Exception suppressedException : this.suppressedExceptions) {
                   ex.addRelatedCause(suppressedException);
                }
             }
             throw ex;
          }
          finally {
             if (recordSuppressedExceptions) {
                this.suppressedExceptions = null;
             }
             // 从singletonsCurrentlyInCreation删除正在记录创建中的beanName
             afterSingletonCreation(beanName);
          }
          // 添加到singletonObjects缓存中 从二级缓存 三级缓存中移除
          if (newSingleton) {
             addSingleton(beanName, singletonObject);
          }
       }
       return singletonObject;
    }
 }
  • this.singletonObjects.get(beanName) :再次尝试从缓存中获取bean,若获取到,就大吉大利直接返回。若为空,则需要创建 bean。
  • if (this.singletonsCurrentlyInDestruction) :未获取到检测bena是否正在销毁,若是则抛出异常
  • beforeSingletonCreation 方法中记录bean正在创建的状态将beanName添加到singletonsCurrentlyInCreation集合中)。在循环依赖时可根据此判断。
  • singletonObject = singletonFactory.getObject() :调用 ObjectFactory.getObject() 方法来实例化bean
  • afterSingletonCreation 方法移除bean正在夹杂的状态
  • addSingleton(beanName, singletonObject): 对各种缓存状态做处理。

beforeSingletonCreation

这个方法并非一个空方法,重点是!this.singletonsCurrentlyInCreation.add(beanName), 记录beanName 正在加载的状态(添加到 singletonsCurrentlyInCreation 缓存中)

java 复制代码
 protected void beforeSingletonCreation(String beanName) {
    if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
       throw new BeanCurrentlyInCreationException(beanName);
    }
 }

创建bean - createBean概述

这个方法这行代码才是重头:singletonObject = singletonFactory.getObject();这一行代码实则就是调用doGetBean下的 createBean

createBean是根据给定的bean名称(beanName)和根bean定义(RootBeanDefinition对象mbd)创建并初始化一个Bean实例,上面一系列的操作都是创建bean 做准备工作的。现在开始真正来创建 bean 了,下面看源码

java 复制代码
 protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
       throws BeanCreationException {
 ​
    if (logger.isTraceEnabled()) {
       logger.trace("Creating instance of bean '" + beanName + "'");
    }
    RootBeanDefinition mbdToUse = mbd;
 ​
    // 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.
    // 1. 锁定class, 根据mdb和 beanName 解析出来 bean的class类型
    Class<?> resolvedClass = resolveBeanClass(mbd, beanName);
    if (resolvedClass != null && !mbd.hasBeanClass() && mbd.getBeanClassName() != null) {
       mbdToUse = new RootBeanDefinition(mbd);
       mbdToUse.setBeanClass(resolvedClass);
    }
 ​
    // Prepare method overrides.
    try {
       // 此方法的主要作用是验证和准备Bean的方法重写。
       // 这个方法会遍历Bean的所有方法,如果一个方法只被重载了一次,那么就设置overloaded为false,以避免参数类型的检查。因为这个检查很耗性能
       // 这样在后续调用的时候便可以直接使用找到的方法,而不需要进行方法的参数匹配了。此外,这个方法还可以提前对方法存在性进行验证。
       // 为了优化方法的调用,提高程序的运行效率。
       mbdToUse.prepareMethodOverrides();
    }
    catch (BeanDefinitionValidationException ex) {
       throw new BeanDefinitionStoreException(mbdToUse.getResourceDescription(),
             beanName, "Validation of method overrides failed", ex);
    }
 ​
    try {
       // Give BeanPostProcessors a chance to return a proxy instead of the target bean instance.
       // 调用BeanProcessors的方法来替代真正的实例
       // 给BeanPostProcessors一个返回代理而不是目标bean实例的机会
       // 如果处理器返回了代理,那么就直接返回代理,不再进行后续的初始化操作。
       // 这个方法可以让用户使用处理器来创建 bean 实例,不通过 spring 来创建 bean 实例。
       //InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation : 在bean初始化前调用
       //BeanPostProcessor#postProcessAfterInitialization : 在bean初始化后调用
       Object bean = resolveBeforeInstantiation(beanName, mbdToUse);
       if (bean != null) {
          return bean;
       }
    }
    catch (Throwable ex) {
       throw new BeanCreationException(mbdToUse.getResourceDescription(), beanName,
             "BeanPostProcessor before instantiation of bean failed", ex);
    }
 ​
    try {
       Object beanInstance = doCreateBean(beanName, mbdToUse, args);
       if (logger.isTraceEnabled()) {
          logger.trace("Finished creating instance of bean '" + beanName + "'");
       }
       return beanInstance;
    }
    catch (BeanCreationException | ImplicitlyAppearedSingletonException ex) {
       // A previously detected exception with proper bean creation context already,
       // or illegal singleton state to be communicated up to DefaultSingletonBeanRegistry.
       throw ex;
    }
    catch (Throwable ex) {
       throw new BeanCreationException(
             mbdToUse.getResourceDescription(), beanName, "Unexpected exception during bean creation", ex);
    }
 }

可以看到,createBean 的整体流程大致如下:

  1. 解析并锁定Bean Class :确保在这一阶段解析出实际的Bean类,并在需要时克隆mbd以存储动态解析得到的Class(由于某些原因无法存储在共享合并的bean定义中)。具体做法是调用resolveBeanClass(mbd, beanName)方法。

    • resolveBeanClass(mbd, beanName)方法用于根据给定的RootBeanDefinition (mbd) 和 beanName 来获取实际的Bean类(即Java Class对象)
    • 判断条件resolvedClass != null && !mbd.hasBeanClass() && mbd.getBeanClassName() != null表示:
      • resolvedClass != null: 确保已经成功解析出一个Class对象。
      • !mbd.hasBeanClass(): 检查当前的RootBeanDefinition是否已经包含了一个已知的Class引用(例如,它可能是从XML或注解配置中读取的)。
      • mbd.getBeanClassName() != null: 确认RootBeanDefinition中包含了可用于查找类名的字符串信息。
      • 如果满足这些条件,则创建一个新的RootBeanDefinition实例(mbdToUse),将原始mbd的其他属性复制过来,并使用刚解析得到的Class对象(resolvedClass)来设置新的Bean定义的Class属性
    • 这样做的原因在于,原始的RootBeanDefinition可能存储的是一个类名字符串而不是Class对象本身,而在后续处理过程中,有时需要直接操作Class对象进行更高效的操作,比如反射调用、生成代理类等。
  2. 准备方法重写 :通过调用mbdToUse.prepareMethodOverrides()来验证和准备Bean的方法重写。这个过程会遍历Bean的所有方法,优化后续对方法的调用,提高运行效率,同时提前进行方法存在性验证。

  3. 应用BeanPostProcessor :在真正实例化Bean之前,提供一个机会让注册的InstantiationAwareBeanPostProcessorBeanPostProcessor通过resolveBeforeInstantiation(beanName, mbdToUse)方法返回代理对象替代目标Bean实例。如果处理器返回了非空代理对象,则直接返回该代理对象,不再继续执行后续的Bean初始化流程。

  4. 创建并初始化Bean实例 :如果没有BeanPostProcessor返回代理对象或者返回的对象为空,函数将调用doCreateBean(beanName, mbdToUse, args)方法来实际地创建并初始化Bean实例。此方法内部包含了依赖注入、属性填充以及调用初始化方法等一系列操作。

  5. 异常处理 :在整个创建过程中,函数会捕获并处理可能出现的任何异常,包括但不限于BeanCreationExceptionImplicitlyAppearedSingletonException。若出现异常,函数会抛出相应的异常信息,其中包含有关异常发生的上下文,以便于调试定位问题。 最终,当Bean实例成功创建并初始化完成后,函数返回该Bean实例。

prepareMethodOverrides

这里我又理解了好久,主要一开始看到这里说的方法重写就想到了方法重载与重写的那个,其实这里所说的是不一样的。

这里主要是理解lookup-methodreplaced-method 两个属性与@Override这玩意是不一样的。

  • lookup-method:这种注入方式主要用于注入方法返回结果,也就是说能通过配置方式替换方法返回结果。例如,如果有一个单例模式的bean引用了一个原型模式的bean,我们不希望被引用的原型模式bean被缓存,那么这个时候就需要用到lookup-method注入。
  • replace-method:这种注入方式可以实现方法主体或返回结果的替换。例如,如果你想改变某个方法的实现,你可以使用replace-method注入。
  • @Override:在Java中,当你想要修改继承自父类的某个方法时,可以在子类中重写这个方法。重写的方法必须与父类中的方法有相同的名称、参数列表和返回类型。使用@Override注解可以帮助编译器检查你的方法是否正确地重写了父类的方法。
  • lookup-method和replace-method:这两种方式是Spring框架提供的,用于在运行时动态地改变bean的行为,不知道大家是否记得上一篇(spring 源码解析之 doGetBean)中的AbstractBeanFactory#getObjectFromFactoryBean这个方法,这里就有提到过我们在缓存中获取到的 bean 都是初始状态的,并非最终状态,我们真正需要的是工厂 bean 中定义的 factory-method 方法中返回的 bean,而getObjectForBeanInstance就是完成这个工作的在前面 bean。

总的来说,@Override是在编译时确定的,它要求子类的方法必须与父类的方法完全相同。而Spring的lookup-method和replace-method是在运行时确定的,它们可以动态地改变方法的行为。

看一下源码

java 复制代码
 public void prepareMethodOverrides() throws BeanDefinitionValidationException {
    // Check that lookup methods exist and determine their overloaded status.
    // 判断是否有方法需要重写
    if (hasMethodOverrides()) {
       getMethodOverrides().getOverrides().forEach(this::prepareMethodOverride);
    }
 }
 protected void prepareMethodOverride(MethodOverride mo) throws BeanDefinitionValidationException {
    // 获取对应的类中的对应方法名的个数
    int count = ClassUtils.getMethodCountForName(getBeanClass(), mo.getMethodName());
    // 等于0抛出异常。上面已经验证有方法需要覆盖,这里为0肯定错误
    if (count == 0) {
       throw new BeanDefinitionValidationException(
             "Invalid method override: no method with name '" + mo.getMethodName() +
             "' on class [" + getBeanClassName() + "]");
    }
    else if (count == 1) {
       // 标记 MethodOverride 暂未被覆盖,避免参数类型检查的开销
       // Mark override as not overloaded, to avoid the overhead of arg type checking.
       mo.setOverloaded(false);
    }
 }

在Spring配置中存在lookup-method和replace-method时,这两个配置在加载xml的时候就会统一存放在BeanDefinition中的methodOverrides属性里。当创建bean的时候,AbstractAutowireCapableBeanFactory类会调用prepareMethodOverride方法来准备方法的覆盖。

知道上面这些概念与MethodOverrides这个属性的来历就很简单了,如果没有需要覆盖的,就不做处理,如果有,则判断是否超过 1 个,等于 1 个就

mo.setOverloaded(false);这个方法主要就是验证并准备给定的方法覆盖。为了之后创建 bean 时,是否需要检测参数。因为在创建 bean 的时候,一个方法可能会有多种入参方式,而最终在实例化的时候就要确定使用哪个构造函数,这个检测就很耗性能,所以做一个标记位

resolveBeforeInstantiation

该方法主要是调用 InstantiationAwareBeanPostProcessor 来进行一些处理,这里实际上是给了用户一次代替Spring来创建bean的机会,代码实现上非常简单直接调用的后处理器方法,就是有几个概念这里要提一下的。

java 复制代码
 protected Object resolveBeforeInstantiation(String beanName, RootBeanDefinition mbd) {
    Object bean = null;
    if (!Boolean.FALSE.equals(mbd.beforeInstantiationResolved)) {
       // Make sure bean class is actually resolved at this point.
       // 当前类并非合成类 && 存在 BeanPostProcessor (后处理器)
       if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
          // 1. 获取目标类
          Class<?> targetType = determineTargetType(beanName, mbd);
          if (targetType != null) {
             // 2. 先用实例前的后处理器获取 bean
             bean = applyBeanPostProcessorsBeforeInstantiation(targetType, beanName);
             if (bean != null) {
                // 3. 前置没有则再使用后处理器应用
                bean = applyBeanPostProcessorsAfterInitialization(bean, beanName);
             }
          }
       }
       mbd.beforeInstantiationResolved = (bean != null);
    }
    return bean;
 }

什么是合成类?什么是非合成类?为什么合成类不能使用BeanPostProcessor进行预处理?

  • 合成的含义是:这些构造体是由Java编译器引入的,并且在源代码中没有相应的构造体。这些合成构造体通常是编译器为了实现某些特性(如内部类访问外部类的私有成员)而生成的。例如,当AOP需要创建一个代理来拦截某个类的方法调用时,它可能会生成一个新的类(代理类),这个类在源代码中并不存在,因此它是合成的。这个代理类会包含一些额外的逻辑,比如在调用目标方法前后插入切面的代码。
  • 而非合成的则直接来自源代码
  • InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation()方法是在Bean实例化之前被调用的,它的主要目的是允许我们在Bean实例化之前进行一些自定义的处理。但是,对于合成的类,由于它们是由编译器生成的,因此我们可能无法在Bean实例化之前对它们进行有效的处理(这个属于处理器的知识,后面会讲)。

doCreateBean

见名知意,这是一个创建 bean 的方法,直接下源码

java 复制代码
 protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
       throws BeanCreationException {
 ​
    // Instantiate the bean.
    BeanWrapper instanceWrapper = null;
    // 如果是单例,先从缓存中移除
    if (mbd.isSingleton()) {
       instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
    }
    // 创建 bean 实例,并使用BeanWrapper包装器类来封装这个实例
    if (instanceWrapper == null) {
       instanceWrapper = createBeanInstance(beanName, mbd, args);
    }
    // 获取 bean 实例
    Object bean = instanceWrapper.getWrappedInstance();
    // 获取 bean 的类型
    Class<?> beanType = instanceWrapper.getWrappedClass();
    if (beanType != NullBean.class) {
       // 设置目标解析类型
       mbd.resolvedTargetType = beanType;
    }
 ​
    // Allow post-processors to modify the merged bean definition.
    // 对合并后的bean定义进行同步锁定,确保其仅被后处理器修改一次
    synchronized (mbd.postProcessingLock) {
       // 未经过后置处理器处理
       if (!mbd.postProcessed) {
          try {
             // 应用合并bean定义的后处理器,允许对bean的定义进行扩展或修改
             applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
          }
          catch (Throwable ex) {
             throw new BeanCreationException(mbd.getResourceDescription(), beanName,
                   "Post-processing of merged bean definition failed", ex);
          }
          mbd.markAsPostProcessed();
       }
    }
 ​
    // Eagerly cache singletons to be able to resolve circular references
    // even when triggered by lifecycle interfaces like BeanFactoryAware.
    boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
          isSingletonCurrentlyInCreation(beanName));
    // 如果bean是单例且允许循环引用,在创建过程中就将bean放入单例缓存中,以解决可能存在的循环依赖问题
    if (earlySingletonExposure) {
       if (logger.isTraceEnabled()) {
          logger.trace("Eagerly caching bean '" + beanName +
                "' to allow for resolving potential circular references");
       }
       addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
    }
 ​
    // Initialize the bean instance.
    // 暴露在缓存中的 bean 实例
    Object exposedObject = bean;
    try {
       // 注入属性
       populateBean(beanName, mbd, instanceWrapper);
       // 初始化bean实例
       exposedObject = initializeBean(beanName, exposedObject, mbd);
    }
    catch (Throwable ex) {
       if (ex instanceof BeanCreationException bce && beanName.equals(bce.getBeanName())) {
          throw bce;
       }
       else {
          throw new BeanCreationException(mbd.getResourceDescription(), beanName, ex.getMessage(), ex);
       }
    }
 ​
    // 处理早期单例曝光后的特殊情况
    if (earlySingletonExposure) {
       // 从缓存获取 bean 实例,此时缓存中的 bean 实例已经完成了初始化
       Object earlySingletonReference = getSingleton(beanName, false);
       if (earlySingletonReference != null) {
          // 如果暴露的 bean 实例与一开始的原始 bean 实例相同,那么将 exposeObject 替换为 从缓存中获取已经实例化完成的 bean
          if (exposedObject == bean) {
             exposedObject = earlySingletonReference;
          }
          // allowRawInjectionDespiteWrapping:允许在包装后仍然注入原始类型
          // hasDependentBean:有依赖当前 bean 的其他 bean
          else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
             // 获取依赖当前 bean 的其他 bean
             String[] dependentBeans = getDependentBeans(beanName);
             Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
             for (String dependentBean : dependentBeans) {
                // 移除给定bean名称的singleton实例(如果有的话),但前提是它没有用于类型检查以外的其他目的。
                if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
                   // 能进来 则证明当前bean 的依赖 bean 还没有创建完成,还是使用的原始的 bean
                   actualDependentBeans.add(dependentBean);
                }
             }
             // 如果actualDependentBeans不为空,则证明当前 bean 还有依赖 bean 还没有创建完成,那么抛出异常
             if (!actualDependentBeans.isEmpty()) {
                throw new BeanCurrentlyInCreationException(beanName,
                      "Bean with name '" + beanName + "' has been injected into other beans [" +
                      StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
                      "] in its raw version as part of a circular reference, but has eventually been " +
                      "wrapped. This means that said other beans do not use the final version of the " +
                      "bean. This is often the result of over-eager type matching - consider using " +
                      "'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example.");
             }
          }
       }
    }
 ​
    // Register bean as disposable.
    try {
       // 根据Scopse 注册bean
       registerDisposableBeanIfNecessary(beanName, bean, mbd);
    }
    catch (BeanDefinitionValidationException ex) {
       throw new BeanCreationException(
             mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex);
    }
 ​
    return exposedObject;
 }
 ​
 ============================
   
 protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
   Assert.notNull(singletonFactory, "Singleton factory must not be null");
   synchronized (this.singletonObjects) {
     if (!this.singletonObjects.containsKey(beanName)) {
       this.singletonFactories.put(beanName, singletonFactory);
       this.earlySingletonObjects.remove(beanName);
       this.registeredSingletons.add(beanName);
     }
   }
 }
  1. 处理单例缓存 :首先检查当前要创建的bean是否为单例模式(singleton)。如果是单例,尝试从factoryBeanInstanceCache中移除已存在的bean实例。

  2. 创建bean实例 :如果缓存中没有找到bean实例,则调用createBeanInstance方法创建新的bean实例,并使用BeanWrapper包装器类来封装这个实例,以便后续进行属性设置和访问。重点、很难,后面会说

  3. 类型判断与合并后的bean定义后处理获取bean的实际类型,并更新合并后的bean定义(RootBeanDefinition)中的目标类型信息。对合并后的bean定义进行同步锁定,确保其仅被后处理器修改一次。然后应用合并bean定义的后处理器,允许对bean的定义进行扩展或修改

  4. 早期单例曝光 :如果bean是单例且允许循环引用,在创建过程中就将bean放入单例缓存中,以解决可能存在的循环依赖问题。通过添加一个工厂方法到单例缓存中,该方法会在需要时返回bean的早期引用。在上一篇(spring 源码解析之 doGetBean)就有提到三级缓存相关的,这里就是保存至singletonFactories的地方

  5. 初始化bean实例 :使用populateBean方法完成bean属性的注入,即根据bean定义中的属性配置设置bean的字段值。调用initializeBean方法对bean执行初始化逻辑,包括自定义初始化方法的调用等。 重点、很难,后面会说

  6. 处理早期单例曝光后的特殊情况 :在bean已经初始化后,再次检查早期曝光的单例情况,处理可能出现的因为bean实际已经被包裹而引起的原始类型的循环引用问题。

    • 假设存在如下情况: BeanA和BeanB是两个单例bean,并且互相引用
    • 在创建BeanA的过程中,由于检测到BeanB还在创建中(处于"currently in creation"状态),因此Spring会将BeanB的一个早期引用(可能是原始类型代理对象)放入singleton缓存中,以解决循环引用问题。
    • 然后继续完成BeanA的实例化与初始化,接着开始BeanB的实例化过程。
    • 当BeanB最终被实例化并初始化完成后,此时需要确保所有依赖于BeanB的bean都使用的是最终初始化完成后的BeanB实例,而不是早期曝光的那个可能为代理或者不完整的实例
    • 所以在doCreateBean方法的末尾部分,Spring会再次检查这些早期曝光的单例bean。如果发现有其他bean在依赖于当前bean(如BeanB)的原始类型时,而实际上已经用到了早期曝光的代理对象,则直接 抛异常。
    • 这里的关键在于,在处理循环引用时,Spring必须确保无论何时从缓存中获取bean,都是其完整初始化过的版本,而不是为了打破循环引用而临时插入的早期引用对象。
  7. 注册可销毁bean:最后,如果bean定义中指定了销毁方法,则调用

至此,下一篇将详细解析 doCreateBean,到底是怎么一步步创建bean 的

相关推荐
今天背单词了吗98015 分钟前
算法学习笔记:8.Bellman-Ford 算法——从原理到实战,涵盖 LeetCode 与考研 408 例题
java·开发语言·后端·算法·最短路径问题
天天摸鱼的java工程师18 分钟前
使用 Spring Boot 整合高德地图实现路线规划功能
java·后端
东阳马生架构33 分钟前
订单初版—2.生单链路中的技术问题说明文档
java
咖啡啡不加糖1 小时前
暴力破解漏洞与命令执行漏洞
java·后端·web安全
风象南1 小时前
SpringBoot敏感配置项加密与解密实战
java·spring boot·后端
DKPT1 小时前
Java享元模式实现方式与应用场景分析
java·笔记·学习·设计模式·享元模式
Percep_gan1 小时前
idea的使用小技巧,个人向
java·ide·intellij-idea
缘来是庄1 小时前
设计模式之迭代器模式
java·设计模式·迭代器模式
Liudef061 小时前
基于HTML与Java的简易在线会议系统实现
java·前端·html
JosieBook1 小时前
【Java编程动手学】Java常用工具类
java·python·mysql