Spring(十二):bean的加载——创建bean

2023-10-26

回顾

前面已经看了在加载Bean的时候,如何去解决循环依赖的问题,解决完循环依赖,就到创建Bean的步骤了,而创建bean的步骤是紧紧接着

创建Bean实例

Bean根据范围会分为几种

  • 单例
  • 原型
  • 自定义范围

每种范围都会有自己不同的生命周期

创建单例Bean

源码如下

// Create bean instance.
//判断是不是单例模式
if (mbd.isSingleton()) {
    //调用getSingleton方法
	sharedInstance = getSingleton(beanName, () -> {
		try {
            //创建Bean
			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.
				//出现异常就销毁Bean
            	destroySingleton(beanName);
				throw ex;
			}
	});
    //调用getObjectForBeanInstance的将bean转化为对应的JavaBean
	beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}

可以看到这里的逻辑只是一个上层调用,主要分为两步

  • 创建单例Bean
  • 将创建的单例Bean转为Java的Object

getSingleton方法

	public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
        //断言beanName不能为Null
		Assert.notNull(beanName, "Bean name must not be null");
        //对singletonObjects容器进行上锁
        //这个容器是存储beanName与beanObject的ConcurrentHashMap
		synchronized (this.singletonObjects) {
            //从容器中根据beanName去获取该beanObject
			Object singletonObject = this.singletonObjects.get(beanName);
            //如果容器中没有就需要进行创建beanObject
			if (singletonObject == null) {
                //如果正在销毁单例
				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!)");
				}
                //如果没有进行销毁单例bean
                //开启日志追踪
				if (logger.isDebugEnabled()) {
					logger.debug("Creating shared instance of singleton bean '" + beanName + "'");
				}
                //创建beanObject的前方法
				beforeSingletonCreation(beanName);
                //newSingleton标志是否创建成功
				boolean newSingleton = false;
				boolean recordSuppressedExceptions = (this.suppressedExceptions == null);
				if (recordSuppressedExceptions) {
					this.suppressedExceptions = new LinkedHashSet<>();
				}
				try {
                    //调用参数传来的ObjectFactory传来的
					singletonObject = singletonFactory.getObject();
                    //将标志位改为true
					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.
					// 如果两个单例Object同时出现,一般来说不会有这个问题,因为synchronic都上锁了
                    singletonObject = this.singletonObjects.get(beanName);
					if (singletonObject == null) {
						throw ex;
					}
				}
                //捕捉创建bean的异常
				catch (BeanCreationException ex) {
                    //如果开启了异常记录
					if (recordSuppressedExceptions) {
						for (Exception suppressedException : this.suppressedExceptions) {
							ex.addRelatedCause(suppressedException);
						}
					}
					throw ex;
				}
				finally {
					if (recordSuppressedExceptions) {
						this.suppressedExceptions = null;
					}
                    //最终执行创建单例的后方法
					afterSingletonCreation(beanName);
				}
                //如果创建成功
				if (newSingleton) {
                    //进行添加进容器中
					addSingleton(beanName, singletonObject);
				}
			}
            //返回singletonObject
			return singletonObject;
		}
	}

可以看到这个方法也是一个比较上层的方法,调用创建Bean的方法,并且对异常做一些捕捉处理

  • 对容器进行上锁
  • 执行创建Bean的前方法
  • 创建Bean,执行传进来的ObjectFactory接口
  • 捕捉异常处理
  • 执行创建Bean的后方法
  • 将创建的singletonObject添加进容器中
  • 返回创建好的singletonObject

ObjectFactory

这是一个函数式接口里面只有一个getObject方法

这个函数式接口是在前面传入一个lambda表达式的

// 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);
}

而且从这里看到一个Lambda表达式的优点,lambda表达式里面的方法可以引用外层的参数,即使不传参进来

可以看到创建Bean和销毁Bean的方法,下面就分析一下这两个方法

createBean

createBean方法由AbstracatAutowireCapableBeanFactory进行实现,从类名大概就可以摸索出这个类去执行一些自动注入的事情

源码如下

	@Override
	protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
			throws BeanCreationException {
		//开启日志
		if (logger.isTraceEnabled()) {
			logger.trace("Creating instance of bean '" + beanName + "'");
		}
      	//存储前面解析出来的RootBeanDefinition
		RootBeanDefinition mbdToUse = mbd;

		//下面这步是确保找到一个适合的class来进行反射转换
		Class<?> resolvedClass = resolveBeanClass(mbd, beanName);
		if (resolvedClass != null && !mbd.hasBeanClass() && mbd.getBeanClassName() != null) {
			//找到了之后
            //新创建RootBeanDefinition
            mbdToUse = new RootBeanDefinition(mbd);
            //并且装配上适合的beanClass
			mbdToUse.setBeanClass(resolvedClass);
		}
		//准备进行方法重写
		// Prepare method overrides.
		try {
			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.
			//如果是使用代理的话,那就创建代理对象进行返回,就是AOP
            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 {
            //如果不是代理的话,调用doCreateBean方法进行创建bean
			Object beanInstance = doCreateBean(beanName, mbdToUse, args);
			if (logger.isTraceEnabled()) {
				logger.trace("Finished creating instance of bean '" + beanName + "'");
			}
            //返回创建的beanInstance
			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);
		}
	}

大概总结一下这个方法

  • 寻找beanDefinition可以进行转化的class对象,确保可以完成bean转化
  • beanDefinition准备方法进行重写
  • 创建beanObject
    • 先尝试进行创建成代理,而不是目标beanObject
    • 如果创建成代理失败,才进行创建目标beanObject

从前面代码看可以知道,调用createBean方法在先经过前处理,下面就先看看前处理是什么操作

createBean的前处理:beforeSingletonCreation

在对存储创建好的beanObject容器上锁后,接下来就是执行前处理了

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

可以看到,前处理仅仅只是做了检查处理

  • 判断该beanName是否位于inCreationCheckExclusions容器中,这个容器里面存储的是不需要检查的beanName
  • 如果位于inCreationCheckExclusions容器中,代表该beanName不需要进行检查
  • 如果不位于里面就代表需要检查,此时再将beanName添加进singletonsCurrentlyInCreatin容器里面,前面已经提到过这个容器是存储正在创建的bean的beanName,也就是创建缓存(检查循环依赖的时候用到过)
  • 总的来说这个方法就是加载单例前,先去记录状态的(添加进singletonsCurrentlyInCreation容器中代表正在创建)

createBean的后处理:afterSingletonCreation

源码如下

	protected void afterSingletonCreation(String beanName) {
		if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.remove(beanName)) {
			throw new IllegalStateException("Singleton '" + beanName + "' isn't currently in creation");
		}
	}

可以看到这端代码其实就是去除单例Bean的状态,因为已经创建好了,就没有必要去保留状态了

  • 判断该beanName是否位于inCreationCheckExclusion容器中,这个容器里面存储的是不需要检查的beanName
  • 如果位于inCreationCheckExclusion容器中,就代表beanName不需要进行检查
  • 如果不位于里面,那么此时就需要将beanName从singletonsCurrentlyInCreation容器中进行移除,也就是去移除加载单例前记录的状态,创建完已经不需要保留状态了

执行ObjectFactory的createBean

进行前处理后,已经可以判断该beanName还没有被创建,下面就可以开始creatBean了,也就是准备去创建bean

源码上面已经看过了大概

源码如下

	@Override
	protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
			throws BeanCreationException {
		//开启日志
		if (logger.isTraceEnabled()) {
			logger.trace("Creating instance of bean '" + beanName + "'");
		}
      	//存储前面解析出来的RootBeanDefinition
		RootBeanDefinition mbdToUse = mbd;

		//下面这步是确保找到一个适合的class来进行反射转换
        //其实就是锁定class,根据设置的class属性或者className来进行解析
		Class<?> resolvedClass = resolveBeanClass(mbd, beanName);
		if (resolvedClass != null && !mbd.hasBeanClass() && mbd.getBeanClassName() != null) {
			//找到了之后
            //新创建RootBeanDefinition
            mbdToUse = new RootBeanDefinition(mbd);
            //并且装配上适合的beanClass
			mbdToUse.setBeanClass(resolvedClass);
		}
		//准备进行方法重写
		// Prepare method overrides.
		try {
            //验证以及准备覆盖的方法
			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.
			//如果是使用代理的话,那就创建代理对象进行返回,就是AOP
            //这里其实是尝试给BeanPostProcessors来替代真正的实例
            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 {
            //如果不是代理的话,调用doCreateBean方法进行创建bean
			Object beanInstance = doCreateBean(beanName, mbdToUse, args);
			if (logger.isTraceEnabled()) {
				logger.trace("Finished creating instance of bean '" + beanName + "'");
			}
            //返回创建的beanInstance
			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);
		}
	}

大概总结一下这个方法

  • 寻找beanDefinition可以进行转化的class对象,确保可以完成bean转化,也就是去锁定class,一般是根据据设置的class属性或者根据beanName来进行解析
  • beanDefinition准备方法进行重写,在配置文件中没有重写的方法,那这两个容器干嘛的呢?
    • 其实前面我们是解析过了bean标签里面的lookup-method以及replace-method,这两个的配置会统一存放在BeanDefinition的methodOverrids属性里,而准备进行重写其实也就是针对这两个属性的!!!
    • 而这两个属性其实就是一个拦截器代理的作用,动态地为当前bean生成代理并使用对应的拦截器为bean做增强处理
  • 创建beanObject
    • 先尝试进行创建成代理beanProcess,而不是目标beanObject
    • 如果创建成代理失败,才进行创建目标beanObject
处理override属性

对应的方法为prepareMethodOverrides,注意是RootBeanDefinition进行调用

在这里插入图片描述
源码如下,并且该方法位于AbstractBeanDefinition中

	public void prepareMethodOverrides() throws BeanDefinitionValidationException {
		// Check that lookup methods exist and determine their overloaded status.
		//判断该beanDefinition有没有overrides属性
        //也就是有没有那两个属性
        if (hasMethodOverrides()) {
            //如果有,获取底层的set集合,即所有的overrids属性,并且每个都去执行prepareMethodOverride方法
			getMethodOverrides().getOverrides().forEach(this::prepareMethodOverride);
		}
	}

在这里插入图片描述
在这里插入图片描述
从底层可以看到,AbstractBeanDefinition使用MethodOverrids对象进行储存这些Overrides属性,并且其底层说白了就是一个Set集合,并且是一个CopyOnWriteArraySet,而且里面存储的元素是MethodOverride,而且其进行判空就是根据底层的set集合是否为空来进行判断的

如果不为空,接下来就要获取这个set集合然后来进行遍历处理了,处理的主要方法为prepareMethodOverride

下面就来看看是如何进行prepare的

源码如下

	protected void prepareMethodOverride(MethodOverride mo) throws BeanDefinitionValidationException {
		//获取对应类中方法名的个数
        //使用class,方法名,通过反射区获取方法名的个数
        int count = ClassUtils.getMethodCountForName(getBeanClass(), mo.getMethodName());
        //如果没有找到方法就抛错
		if (count == 0) {
			throw new BeanDefinitionValidationException(
					"Invalid method override: no method with name '" + mo.getMethodName() +
					"' on class [" + getBeanClassName() + "]");
		}
        //如果有一个方法,对bean进行标记,表明没有发送重载
		else if (count == 1) {
			// Mark override as not overloaded, to avoid the overhead of arg type checking.
            // 标记MethodOverrid,但此时暂未进行覆盖,因为这样做可以避免类型检查的开销
			mo.setOverloaded(false);
		}
	}

可以看到,这里准备对方法进行代理重写,其实本质上是判断类里面对应的方法有没有发生重载!!!

因为,对指定方法名进行代理重写,如果发生了重载,就需要对参数类型进行检查,才可以准确定位到具体代理重写哪个方法,但参数类型进行检查是要进行开销的,但如果没有发生方法重载,就不需要进行参数类型检查的,因为就只有一个方法,所以Spring这里将判断方法是否发生重载的放在了这里

说白了,所谓准备方法代理重写,本质上就是判断有没有发生方法重载,如果没有发生方法重载,那么将overLoaded标志位设为false,当真正准备代理重写方法的时候就不需要进行参数类型检查了

而且关键的是,这里不仅仅对方法重载进行了检查,还对方法的存在性进行了检查!!!

实例化的前置处理

实例化的前置处理的作用是:能不能用beanProcessor来代理,而不是直接去创建目标bean,也就是AOP的功能

对应的方法为:resolveBeforeInstantiation

源码如下

	@Nullable
	protected Object resolveBeforeInstantiation(String beanName, RootBeanDefinition mbd) {
        //使用一个Object对象存储处理结果
		Object bean = null;
        //判度该RootBeanDefinition是否已经被前置处理过
		if (!Boolean.FALSE.equals(mbd.beforeInstantiationResolved)) {
			// Make sure bean class is actually resolved at this point.
            // 判度有没有处理器
			if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
                
				Class<?> targetType = determineTargetType(beanName, mbd);
				if (targetType != null) {
                    //执行前方法
					bean = applyBeanPostProcessorsBeforeInstantiation(targetType, beanName);
					if (bean != null) {
                        //前方法如果返回结果不为空,执行后方法
						bean = applyBeanPostProcessorsAfterInitialization(bean, beanName);
					}
				}
			}
            //修改RootBeanDefinition的前置处理标志位
            //如果bean不为空,代表前置处理是成功的
            //如果bean为空,代表前置处理是不成功的
			mbd.beforeInstantiationResolved = (bean != null);
		}
        //返回前置处理完后的bean
		return bean;
	}

可以看到,实例化的前置处理分为了前后处理

  • 首先去判断当前RootBeanDefinition是否已经进行过前置处理了
    • 没有进行的话就判断是否拥有处理器
      • 如果有处理器的话就执行前后处理
    • 最后将前置处理的标志位重新设置
      • 如果结果不为Null,那就代表前置处理完毕了
      • 如果结果为Null,那就代表代表前置处理是失败的
前处理

在这里插入图片描述
源码如下

	@Nullable
	protected Object applyBeanPostProcessorsBeforeInstantiation(Class<?> beanClass, String beanName) {
		for (InstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().instantiationAware) {
			Object result = bp.postProcessBeforeInstantiation(beanClass, beanName);
			if (result != null) {
				return result;
			}
		}
		return null;
	}

可以看到,前处理是针对所有的InstantiationAwareBeanPostProcessor进行处理,每个都去执行postProcessBeforeInstantiation方法,经过前处理过后,Bea已经不再是BeanDefinition了,已经转变成一个Object了,也就是经过这个方法后,bean或许已经成为了一个代理Bean了

后处理
	@Override
	public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName)
			throws BeansException {

		Object result = existingBean;
		for (BeanPostProcessor processor : getBeanPostProcessors()) {
			Object current = processor.postProcessAfterInitialization(result, beanName);
			if (current == null) {
				return result;
			}
			result = current;
		}
		return result;
	}

从代码中,可以看到,后处理是针对所有的BeanPostProcessor进行处理,每个都去执行postProcessAfterInitialzation方法

Spring规定了在初始化Bean后,需要尽可能地将注册的后处理器的postProcessAfterInitialization方法尽可能应用到该bean中

对于前后处理,现在仅仅知道在创建Bean会执行前置处理,前置处理也分为前后处理即可

创建常规bean

经过实例化的前置处理之后,假如没有返回任何结果,那就代表不能去创建一个代理对象,只能去进行常规的实例化bean了

对应的就是createBean里面的doCreateBean方法

在这里插入图片描述
源码如下

	protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
			throws BeanCreationException {
		// Instantiate the bean.
        //BeanWrapper是bean的一个压缩对象
		BeanWrapper instanceWrapper = null;
        //判断是不是单例
		if (mbd.isSingleton()) {
            //如果是单例的话,先要去清一下缓存
            //Spring使用factoryBeanInstanceCache容器来存储一些未完成的工厂Bean
            //factoryBeanInstanceCache是一个ConcurrentHashMap
			instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
		}
        //如果缓存中不存在
		if (instanceWrapper == null) {
            //根据指定的bean使用对应的策略创建新的实例
            //比如 工厂方法、构造函数自动注入、简单初始化
            //生成一个bean的压缩对象
			instanceWrapper = createBeanInstance(beanName, mbd, args);
		}
        //通过压缩对象去获取bean的Object对象
		Object bean = instanceWrapper.getWrappedInstance();
        //从压缩对象去获取的class
		Class<?> beanType = instanceWrapper.getWrappedClass();
        //如果beanType不是NullBean类型的class对象
		if (beanType != NullBean.class) {
            //将RootBeanDefinition的目标class类型进行锁定
			mbd.resolvedTargetType = beanType;
		}
		
		
       	// Allow post-processors to modify the merged bean definition.
		// 对RootBeanDefinition的postProcessingLock进行上锁
        synchronized (mbd.postProcessingLock) {
            //
			if (!mbd.postProcessed) {
				try {
					applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
				}
				catch (Throwable ex) {
					throw new BeanCreationException(mbd.getResourceDescription(), beanName,
							"Post-processing of merged bean definition failed", ex);
				}
				mbd.postProcessed = true;
			}
		}
		// Eagerly cache singletons to be able to resolve circular references
		// even when triggered by lifecycle interfaces like BeanFactoryAware.
		// 该bean是否需要提前曝光,而且这里的曝光是针对
        // 当前创建的RootBeanDefinition是单例、且允许循环依赖,并且该beanName正在创建中
        // 也就是说这里进行提前曝光是曝光的是单例Bean,并且开启了循环依赖的
        boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
				isSingletonCurrentlyInCreation(beanName));
		if (earlySingletonExposure) {
            //进入到这里就代表要对此时创建的单例Bean进行提前曝光了
			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.
        // 
		Object exposedObject = bean;
		try {
            // 对bean进行填充
			populateBean(beanName, mbd, instanceWrapper);
            // 调用bean的init-method方法,也就是开始进行初始化了
			exposedObject = initializeBean(beanName, exposedObject, mbd);
		}
		catch (Throwable ex) {
			if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) {
				throw (BeanCreationException) ex;
			}
			else {
				throw new BeanCreationException(
						mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex);
			}
		}
		//
		if (earlySingletonExposure) {
            //检测循环依赖(前面看过的getSingleton方法)
            //从一、二、三级缓存中取出该BeanName的单例,
			Object earlySingletonReference = getSingleton(beanName, false);
            //假如检测到循环依赖,也就是已经被提前曝光过了
            //其实这里主要就是检测自己是否已经被曝光了,从之前缓存中取
			if (earlySingletonReference != null) {
                //再判断暴露的bean是否被增强了
                //也就是执行了method-init方法后,是否被代理了
				if (exposedObject == bean) {
                    //如果没被增强,那还是为之前提前曝光的
                    //通过之前在缓存中曝光的与现在的进行比较
                   	//判断
					exposedObject = earlySingletonReference;
				}
                //如果不等于,那就说明被代理了
				else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
                    //再次去检测依赖,这里是去检测依赖的bean是否都已经加载完
					String[] dependentBeans = getDependentBeans(beanName);
					Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
                    //遍历所有依赖bean
					for (String dependentBean : dependentBeans) {
                        //
						if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
							actualDependentBeans.add(dependentBean);
						}
					}
                    //判断依赖的bean是否都已经创建完
                    //如果actualDependentBeans集合不为空,证明依赖的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 {
            //最后根据scope去进行注册bean
            //并且看到这个方法名字IfNecessary
            //也就是说bean可能会进行注册也可能不注册
			registerDisposableBeanIfNecessary(beanName, bean, mbd);
		}
		catch (BeanDefinitionValidationException ex) {
			throw new BeanCreationException(
					mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex);
		}
		//返回bean
		return exposedObject;
	}

常规去创建Bean还挺复杂的

  • 先判断是不是单例
    • 如果是单例,就需要清空一下正在工厂的缓存,并且工厂将缓存里面的BeanWrapper拿出来
  • 如果缓存中没有Wrapper,证明是第一次创建,而且前面没有线程在进行创建,那么就需要去创建BeanWrapper,将BeanDefinition转为BeanWrapper
  • 判断BeanWrapper的class对象是否属于BeanNull.class
    • 如果不属于就将RootBeanDefinition的targetClass设为BeanWrapper的class
    • 锁定RootBeanDefinition的class
  • 进行提前曝光bean,这里是在二三级缓存中添加
  • 对bean进行填充信息
  • bean执行method-init方法,也就是初始化
  • 从单例缓存中取出beanName的缓存,使用缓存中的bean与此时的bean进行比较
    • 判断此时的bean是否被增强了,也就是有没有method-init方法调用,被AOP代理了
      • 如果没被增强,让bean为循环依赖里面的对应bean
      • 如果被增强了,遍历所有的依赖bean,如果有依赖bean还没有创建完,抛错
  • 如果没有出现循环依赖,什么都不做
  • 根据scope去注册bean
  • 返回bean

大概理解了整个创建常规Bean的过程之后,下面就看下每一个步骤了

createBeanInstance

从代码上可以知道,这个方法就是当工厂缓存中没有对应的beanWrapper时,将BeanDefinition转为BeanWrapper的

在这里插入图片描述
源码如下

	protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) {
		// Make sure bean class is actually resolved at this point.
        //解析BeanDefinition的class
		Class<?> beanClass = resolveBeanClass(mbd, beanName);
		//
		if (beanClass != null && !Modifier.isPublic(beanClass.getModifiers()) && !mbd.isNonPublicAccessAllowed()) {
			throw new BeanCreationException(mbd.getResourceDescription(), beanName,
					"Bean class isn't public, and non-public access not allowed: " + beanClass.getName());
		}
		//消费型接口
 		//如果RootBeanDefinition有提供消费型获取Bean的方式
        //使用RootBeanDefinition提供的获取Bean的方式
		Supplier<?> instanceSupplier = mbd.getInstanceSupplier();
		if (instanceSupplier != null) {
			return obtainFromSupplier(instanceSupplier, beanName);
		}
		//如果RootBeanDefinition的工厂方法不为空则使用工厂方法进行初始化策略
		if (mbd.getFactoryMethodName() != null) {
			return instantiateUsingFactoryMethod(beanName, mbd, args);
		}

		// Shortcut when re-creating the same bean...
        //如果RootBeanDefinition既没有自己的获取Bean方式,也没有工厂方法
        //大多数是这种情况
        //resolver判断是否锁定了构造方法
		boolean resolved = false;
        //autowireNecessary判断是不是自动装配
        //其实这个变量是用来判断该RootBeanDefinition是否已经解析过了
		boolean autowireNecessary = false;
        //下面就开始常规创建Bean
        //判断传来的构建参数是否为空,即没有指定的构建参数
		if (args == null) {
            //如果没有构造参数
            //代表肯定是使用无参构造方法来进行创建的
            //对RootBeanDefinition的constructorArgumentLock进行上锁
            //这里其实相当于在对缓存上锁
			synchronized (mbd.constructorArgumentLock) {
                //对于RootBeanDefinition可能会拥有多个构造方法
                //所以这里需要对使用的构造方法进行锁定
                //如果RootBeanDefinition的构造方法和工厂方法不为空
                //RootBeanDefiniton的resolverConstructorOrFactoryMethod其实是一个缓存
                //如果从缓存中存在解析结果,那就会尝试从缓存中取
				if (mbd.resolvedConstructorOrFactoryMethod != null) {
                    //锁定了构造方法
					resolved = true;
                    //自动注入由RootBeanDefinition来决定
                    //constructorArgumentsResolved代表该RootBeanDefinitiony是否被解析过了
					autowireNecessary = mbd.constructorArgumentsResolved;
				}
			}
		}
        //如果锁定了构造方法
		if (resolved) {
            //如果已经解析过
			if (autowireNecessary) {
                //构造函数进行自动注入
				return autowireConstructor(beanName, mbd, null, null);
			}
            //如果没有解析过
			else {
                //使用默认的构造函数构造,也就是无参构造
				return instantiateBean(beanName, mbd);
			}
		}

		// Candidate constructors for autowiring?
        //如果构造参数不为空,那么代表需要判断使用哪种构造方法了
        //获取beanName对应的beanClass的所有构造器
		Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);
        //如果构造器不为空
		if (ctors != null || mbd.getResolvedAutowireMode() == AUTOWIRE_CONSTRUCTOR ||
				mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) {
			//自动注入创建Bean
            return autowireConstructor(beanName, mbd, ctors, args);
		}

		// Preferred constructors for default construction?
		ctors = mbd.getPreferredConstructors();
		if (ctors != null) {
			return autowireConstructor(beanName, mbd, ctors, null);
		}
		//采用默认构造函数创建,也就是无参方法!!!
		// No special handling: simply use no-arg constructor.
		return instantiateBean(beanName, mbd);
	}

从这里可以看到,其实创建常规BeanWrapper分为两种

  • 自动注入
  • 无参方法构造

整个过程大致如下

  • 如果RootBeanDefinition中存在factoryMethodName属性,既在配置文件上添加了factory-method方法,那么Spring就会尝试使用instantiateUsingFactoryMethod方法进行创建Bean,根据RootBeanDefinition中的配置生成Bean的实例
  • 根据有无构造参数来选定创建bean的方法,也就是选用什么构造函数
    • 如果没有构造参数,那就不需要对构造函数进行解析了,不过需要去看缓存里面有没有,先判断resolvedConstructorOrFactoryMethod缓存是否为空,在RootBeanDefinition里面有一个标志位constructorArgumentsResolved,该标志位可以判断这个RootBeanDefinition是否已经解析过(注意只是解析),如果解析过,优先使用autowireConstructor方法进行创建 ,如果缓存中没有就使用**instantiateBean(beanName, mbd);**方法进行创建,该方法调用的是无参构造方法
    • 如果拥有构造参数,那就需要对构造函数进行解析了,不过同样也要去缓存里面看是否已经解析过
      • 首先获取所有的构造器
      • 将所有的构造器传入autowireConstructor方法里面进行解析,选择使用哪个构造器来创建Bean

从这里可以看到,Spring对于BeanWapper的创建还利用了缓存技术,通过RootBeanDefinition的resolvedConstructorOrFactoryMethod来进行缓存解析过的方法,也可以判断出,在两种创建Bean方法之后,应该会添加进缓存里面

在这里插入图片描述

autowireConstructor

从上面代码可以看到,当该Bean已经被解析过,并且缓存存在的话,会使用autowireConstrutor,并且当携带构造参数来进行创建Bean的话,也会使用autowireConstructor方法进行创建Bean

源码如下

	protected BeanWrapper autowireConstructor(
			String beanName, RootBeanDefinition mbd, @Nullable Constructor<?>[] ctors, @Nullable Object[] explicitArgs) {

		return new ConstructorResolver(this).autowireConstructor(beanName, mbd, ctors, explicitArgs);
	}

可以看到,其是交由ConstructorResolver去负责执行的

具体的细节如下

	public BeanWrapper autowireConstructor(String beanName, RootBeanDefinition mbd,
			@Nullable Constructor<?>[] chosenCtors, @Nullable Object[] explicitArgs) {

		BeanWrapperImpl bw = new BeanWrapperImpl();
		this.beanFactory.initBeanWrapper(bw);

		Constructor<?> constructorToUse = null;
		ArgumentsHolder argsHolderToUse = null;
		Object[] argsToUse = null;
		
        //判断传进来的构造参数是否为空
		if (explicitArgs != null) {
            //如果传进来的构造参数不为空,直接使用传进来的参数
			argsToUse = explicitArgs;
		}
		else {
            //如果参数为空
            //就会尝试去配置文件上去获取
			Object[] argsToResolve = null;
            //对缓存进行上锁,因为要利用到缓存,所以这里不允许其他线程对该RootBeanDefinition的缓存修改
			synchronized (mbd.constructorArgumentLock) {
                //调用RootBeanDefinition的resolvedConstructorOrFactoryMethod缓存中取构造器
				constructorToUse = (Constructor<?>) mbd.resolvedConstructorOrFactoryMethod;
                //如果缓存中有,且RootBeanDefinition已经被解析过(通过标志位判断)
				if (constructorToUse != null && mbd.constructorArgumentsResolved) {
					// Found a cached constructor...
                    //取出缓存的之前解析使用的构造参数
					argsToUse = mbd.resolvedConstructorArguments;
                    //如果之前解析使用的构造参数为空
					if (argsToUse == null) {
                        //取出配置文件上的构造参数
						argsToResolve = mbd.preparedConstructorArguments;
					}
				}
			}
            //释放缓存锁了,因为已经对缓存操作完了
            //如果配置文件上的构造参数不为空,则需要使用到配置文件上的构造参数
			if (argsToResolve != null) {
                //对构造参数进行解析
                //其实这里解析的是构造参数的类型,因为配置上面全部为字符串
                //所以需要根据构造函数的参数类型对其进行转化
				argsToUse = resolvePreparedArguments(beanName, mbd, bw, constructorToUse, argsToResolve);
			}
		}
		//此时已经解决了构造参数的问题了
        //下面是选择构造器的问题
        //从这个if开始直到差不多最下面,都是解决构造器问题
        //前面已经尝试过从缓存中取构造器了,并且已经解决了构造参数的问题
        //但如果缓存中没有构造器,或者构造参数仍然为空(配置文件上都没有构造参数)
        //就需要进行处理
		if (constructorToUse == null || argsToUse == null) {
			// Take specified constructors, if any.
            //chosenCtors是传进来的所有构造器
			Constructor<?>[] candidates = chosenCtors;
            //判断有没有构造器
			if (candidates == null) {
                //如果没有构造器
                //取出RootBeanDefinition的class对象
				Class<?> beanClass = mbd.getBeanClass();
				try {
                    //RootBeanDefinition里面有个反射是否可以获取私有属性或方法的标志位(默认为True)
                    //通过这个标志位去获取构造器
                    //如果允许获取私有的,就获取包括私有的所有构造方法
                    //如果不允许获取私有的,只获取声明的构造方法
					candidates = (mbd.isNonPublicAccessAllowed() ?
							beanClass.getDeclaredConstructors() : beanClass.getConstructors());
				}
				catch (Throwable ex) {
					throw new BeanCreationException(mbd.getResourceDescription(), beanName,
							"Resolution of declared constructors on bean Class [" + beanClass.getName() +
							"] from ClassLoader [" + beanClass.getClassLoader() + "] failed", ex);
				}
			}
			//如果通过反射找到了构造器,有且仅有一个构造器
            //并且传进来的构造参数为空,并且RootBeanDefinition从配置文件上没有构造参数
            //那么这里就会猜测是不是无参构造
			if (candidates.length == 1 && explicitArgs == null && !mbd.hasConstructorArgumentValues()) {
				//获取里面唯一的构造器
                Constructor<?> uniqueCandidate = candidates[0];
				//如果构造器里面需要方的参数数量为0,那么久代表是一个无参构造器
                if (uniqueCandidate.getParameterCount() == 0) {
                    //对RootBeanDefinition的constructorArgumentLock进行上锁
					synchronized (mbd.constructorArgumentLock) {
                        //将构造器添加进缓存(这里的缓存只有一个)
						mbd.resolvedConstructorOrFactoryMethod = uniqueCandidate;
                        //将解析的标志位设置为true,代表该RootBeanDefinition已经被解析过了
						mbd.constructorArgumentsResolved = true;
                        //同理将构造参数添加进缓存中,并且为EMPTY_ARGS
						mbd.resolvedConstructorArguments = EMPTY_ARGS;
					}
                    //对缓存操作完了就释放锁
                    //下面就是开始利用无参构造实例化Bean
					bw.setBeanInstance(instantiate(beanName, mbd, uniqueCandidate, EMPTY_ARGS));
					return bw;
				}
                //如果参数数量不为0,代表不是无参构造,不做仍和处理
			}
			//如果不是无参构造,就还需要下一步处理
			// Need to resolve the constructor.
            //此时是既没有被缓存,而且还不是无参构造(之前代码是没有先判断无参构造的)
			boolean autowiring = (chosenCtors != null ||
					mbd.getResolvedAutowireMode() == AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR);
            //声明ConstructorArgumentValues来存储构造参数
			ConstructorArgumentValues resolvedValues = null;

            //minNrOfArgs用于表示构造参数个数
			int minNrOfArgs;
			if (explicitArgs != null) {
                //如果有传来构造参数,以传来的构造参数为主
                //参数的数量就为传进来构造参数的数量
				minNrOfArgs = explicitArgs.length;
			}
			else {
                //取出RootBeanDefinition存储的配置文件的构造参数
				ConstructorArgumentValues cargs = mbd.getConstructorArgumentValues();
                //创建ConstructorArgumentValues,用于存储构造参数
                //注意:只要当传来的构造参数为空时,才创建ConstructArgumentValues
				resolvedValues = new ConstructorArgumentValues();
                //对所有的构造参数进行解析,并且返回解析出来的构造参数个数
				minNrOfArgs = resolveConstructorArguments(beanName, mbd, bw, cargs, resolvedValues);
			}
			//对构造器进行排序,具体规则如下
            //public构造函数优先参数数量降序、非Public构造函数参数数量降序
            //即参数数量越少,优先级越高
			AutowireUtils.sortConstructors(candidates);
            
            //这个minTypeDiffWeight是用来记录构造器的参数类型与构造参数类型的不同的数量的
			int minTypeDiffWeight = Integer.MAX_VALUE;
            //这个ambiguousConstructors是用来记录差异一样的构造器(比之前遇到的构造器更不相似就会淘汰)
			Set<Constructor<?>> ambiguousConstructors = null;
			Deque<UnsatisfiedDependencyException> causes = null;
			//对所有构造器进行遍历
            //此时Candidates已经根据参数数量来排序了
			for (Constructor<?> candidate : candidates) {
                //构造器需要的参数个数 
				int parameterCount = candidate.getParameterCount();
				//这里最重要的判断是,如果前面解析的构造参数的个数大于构造器需要的参数个数
                //就代表找不到了,因为后面的参数个数会更多
				if (constructorToUse != null && argsToUse != null && argsToUse.length > parameterCount) {
					// Already found greedy constructor that can be satisfied ->
					// do not look any further, there are only less greedy constructors left.
					break;
				}
                //如果参数个数少了,Continue下一轮循环
				if (parameterCount < minNrOfArgs) {
					continue;
				}
				//来到这步就代表着
                //参数个数是符合要求的
                //声明ArgumentsHolder来存储参数
				ArgumentsHolder argsHolder;
                //获取参数的类型
				Class<?>[] paramTypes = candidate.getParameterTypes();
                //下面就是要开始匹配参数的class类型
                //当传来的构造参数为Null时,这里才不等于Null
                //因为上面的判断就是当传来的构造参数为Null时,才会去创建ConstructArgumentValue
				if (resolvedValues != null) {
					try {
                        //获取参数的名称
						String[] paramNames = ConstructorPropertiesChecker.evaluate(candidate, parameterCount);
                        //如果参数的名称为空
						if (paramNames == null) {
                            //获取参数名称搜索器
							ParameterNameDiscoverer pnd = this.beanFactory.getParameterNameDiscoverer();
							if (pnd != null) {
                                //使用参数名称搜索器去获取构造器的参数名称
								paramNames = pnd.getParameterNames(candidate);
							}
						}
                        
                        //根据参数名称和数据类型去创建ArgumentsHolder
						argsHolder = createArgumentArray(beanName, mbd, resolvedValues, bw, paramTypes, paramNames,
								getUserDeclaredConstructor(candidate), autowiring, candidates.length == 1);
					}
					catch (UnsatisfiedDependencyException ex) {
						if (logger.isTraceEnabled()) {
							logger.trace("Ignoring constructor [" + candidate + "] of bean '" + beanName + "': " + ex);
						}
						// Swallow and try next constructor.
						if (causes == null) {
							causes = new ArrayDeque<>(1);
						}
						causes.add(ex);
						continue;
					}
				}
                //如果传了构造参数进来
                //那就需要以传进来的构造参数为主
				else {
					// Explicit arguments given -> arguments length must match exactly.
                    //再次检查数量
					if (parameterCount != explicitArgs.length) {
						continue;
					}
                    //如果数量正确,ArgumentsHolder以传进来的参数来创建
					argsHolder = new ArgumentsHolder(explicitArgs);
				}
				//探测是否有不确定性的构造函数存在
                //比如不同构造函数的参数为父子关系
                //返回的是参数类型不同的个数
				int typeDiffWeight = (mbd.isLenientConstructorResolution() ?
						argsHolder.getTypeDifferenceWeight(paramTypes) : argsHolder.getAssignabilityWeight(paramTypes));
				// Choose this constructor if it represents the closest match.
                // 判断当前构造方法的差异是不是小于minTypeDiffWeigh
                //minTypeDiffWeigh记录前面构造方法的最小差异
				if (typeDiffWeight < minTypeDiffWeight) {
                   //如果小于,更新constructotToUser为当前的构造方法
					constructorToUse = candidate;
                    //更新构造参数的承载
					argsHolderToUse = argsHolder;
					argsToUse = argsHolder.arguments;
                    //更新最小的差异
					minTypeDiffWeight = typeDiffWeight;
                    //将前面的那些构造方法记录清空
					ambiguousConstructors = null;
				}
                //如果出现等于的情况
				else if (constructorToUse != null && typeDiffWeight == minTypeDiffWeight) {
					//记录进ambiguousConstructors里面
                    //相当于记录了差异一样的构造器
                    if (ambiguousConstructors == null) {
						ambiguousConstructors = new LinkedHashSet<>();
						ambiguousConstructors.add(constructorToUse);
					}
					ambiguousConstructors.add(candidate);
				}
			}
			//如果最后都没有找到构造器
			if (constructorToUse == null) {
                //抛出异常
				if (causes != null) {
					UnsatisfiedDependencyException ex = causes.removeLast();
					for (Exception cause : causes) {
						this.beanFactory.onSuppressedException(cause);
					}
					throw ex;
				}
				throw new BeanCreationException(mbd.getResourceDescription(), beanName,
						"Could not resolve matching constructor on bean class [" + mbd.getBeanClassName() + "] " +
						"(hint: specify index/type/name arguments for simple parameters to avoid type ambiguities)");
			}
            //如果找到了构造器,但后续出现差异类似的构造器
            //即ambiguousConstructors集合不为空
            //那就无法做出选择
			else if (ambiguousConstructors != null && !mbd.isLenientConstructorResolution()) {
                //抛错处理
				throw new BeanCreationException(mbd.getResourceDescription(), beanName,
						"Ambiguous constructor matches found on bean class [" + mbd.getBeanClassName() + "] " +
						"(hint: specify index/type/name arguments for simple parameters to avoid type ambiguities): " +
						ambiguousConstructors);
			}
			//最后就是将解析的构造函数添加进缓存中
			if (explicitArgs == null && argsHolderToUse != null) {
				argsHolderToUse.storeCache(mbd, constructorToUse);
			}
		}
		//再次判断构造参数是否为空
		Assert.state(argsToUse != null, "Unresolved constructor arguments");
        //进行创建Bean
		bw.setBeanInstance(instantiate(beanName, mbd, constructorToUse, argsToUse));
		return bw;
	}

这个代码逻辑真的长。。。

总结一下,这个方法主要做了两件事情

  • 对参数进行处理
  • 对构造器进行处理

只有先确定了参数才可以确定构造器,大概步骤如下

  • 确定构造函数的参数
    • 根据传进来的explicitArgs,如果不为空,就使用这个
    • 如果为空,先尝试从缓存中获取
    • 缓存中没有,就从配置文件上获取
  • 构造函数的确定
    • 遍历所有的构造器,锁定参数个数与前面确定参数个数一样的构造器
    • 因为此时构造器已经进行排序了,主要是按照参数个数进行降序排,所以,个数只要大于就直接break,个数小于就continue,只对个数一样的进行处理
    • 处理操作如下
      • 获取参数的所有名称,这是因为constructor-arg标签是支持指定参数名称进行设定参数值的情况
      • 根据确定的构造函数转换对应的参数类型
      • 根据前面确认的参数class类型来获取构造函数的差异,因为可能存在class不对应,或者不同构造器的参数存在父子关系
      • 选取最小差异的构造器,后续如果出现更小差异的就进行替换,出现相同差异的存放在Set集合里面(具体来说是一个LinkedHashSet)
      • 如果后面出现了多个最小差异的,抛错处理
  • 最后就是根据选定的构造器与选定的构造参数进行创建Bean
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Spring(十二):bean的加载——创建bean 的相关文章

随机推荐

  • 逆变器原理

    逆变器是把直流电转变为交流电的一种装置 它一般包括逆变桥 控制逻辑和滤波电路组成 主要是把各种直流源转变为交流供交流负载使用 一般直流源有蓄电池 干电池 太阳能电池等 可以应用到不间断电源 UPS 太阳能发电转换等 本次来探讨一下逆变桥的工
  • Unity游戏开发——UnityUGUI打包图集

    现在Unity中使用UGUI实现UI的越来越多 我们项目也才4 6 1升级到5 6 3对项目全面升级 5 x中UGUI已经非常成熟 各种资料各种效果都非常多 由于之前一直习惯用NGUI在使用UGUI还是比较顺利的 都是一个作者写的都是控件式
  • 货币战争

    中国人写的关于货币的书籍 卖的比较好 关于xx家族的阴谋论 金融控制了世界 现在xx家族的财富得有多少 有许多人反驳这本书中写的内容 xx家族早已没落了 但是写的还是不错的 推荐推荐 2013 9 29
  • esp32微型计算机,基于ESP32的几款开发板

    ESP32是乐鑫推出的一款芯片 拥有40nm的工艺 双核32位MCU 2 4GHz双模Wi Fi和蓝牙芯片 主频高达230MHz 计算能力可达600DMIPS 下面为大家介绍一些基于ESP32的开发板 1 Heartypatch 一款基于E
  • 【Kafka】docker部署Kafka集群

    目录 Kafka概述 Kafka集群docker部署流程 简述 环境准备 部署流程 参考文献 Kafka概述 以下概述Kafka内的几个核心概念 可参考官方文档 有兴趣可读 kafka apache org Topic与日志 Topic 就
  • 第四篇、UDP消息发送

    文章目录 前言 一 UDP通信操作 二 代码示例 1 UDP消息发送 总结 前言 上一篇我们共同学习了TCP文件传输 本篇我们来学习UDP消息发送 一 UDP通信操作 发送方 1 建立一个socket连接 2 建立一个包 3 发送包 4 关
  • SSL VPN

    1 SSL工作过程是什么 第一阶段 客户端首先发送client hello消息到服务端 服务端收到client hello信息后 再发送server hello消息到客户端 随机数 32位时间戳 28字节随机序列 用于计算摘要信息和预主密钥
  • Java系列——封装、继承、多态初了解

    目录 一 前言 二 封装 1 什么是封装 2 封装的特点 3 封装的使用 三 继承 1 什么是继承 2 继承的特点 3 继承的优点 4 继承的使用 4 1 继承的格式 4 2 继承的演示 4 3 成员变量 4 4 成员方法 4 5 构造方法
  • Pyhton零售数据分析及产品关联分析

    一 项目背景 总结 项目背景 以购物篮分析为背景 分析某跨国棒球用品零售商的历史订单数据 为企业提供运营及销售策略 项目总结 一 本项目对企业历史订单数据进行以下角度的处理及分析 数据探索及清洗 对6w 订单数据进行探索及清洗处理 为数据构
  • 2022年最新前端面试题,持续更新

    js面试题 1 js数据类型 基本数据类型 Number String Boolean Null Undefined Symbol bigInt 引用数据类型 object Array Date Function RegExp 2 js变量
  • surf特征原理

    前言 也许我们使用过Uiautomator编写过自动化测试脚本 也许我们也使用过Monkey来测试过应用的稳定性 但在使用过程中总觉得有或多或小的问题 用Uiautomator写脚本 总觉得有时候控件没法识别 用Monkey来进行稳定性测试
  • Oracle SQL developer不显示dbms_output.put_line的输出内容

    1 调出dbms输出窗口 2 dbms选择对应的数据库连接 3 工作区SQL文件 定义一个触发器 create trigger newtmpdata 创建触发器newtmpdata 在tmp中添加新数据 after insert 插入操作之
  • yolo算法通俗易懂讲解

    参考 https blog csdn net briblue article details 103149407 depth 1 utm source distribute pc relevant none task utm source
  • .NET[C#]LINQ查询List集合中所有重复的元素如何实现?(转载)

    NET C LINQ查询List集合中所有重复的元素如何实现 转载 方案一 var query lst GroupBy x gt x Where g gt g Count gt 1 Select y gt y Key ToList 如果还需
  • C#,去除字符串指定的之字符,并去掉不可见的 “”一种方法。

    string str1 小王 心情 很 平 静 char chs1 new char 定义一个字符数组 存放上面字符串中不要的内容 和 string result1 str1 Split chs1 StringSplitOptions Re
  • 关于Adapter数据适配器

    适配器 什么是适配器 Android适配器是数据和视图之间的桥梁 以便于数据在View上显示 适配器就像显示器 把复杂的东西按人可以接受的方式来展现 Adapter数据适配器将各种数据以合适的形式绑定到控件上 像listview gridv
  • MyBatis框架——MyBatis执行SQL的两种方式(转载)

    本节主要介绍 MyBatis 执行 SQL 语句的两种方式和它们的区别 MyBatis 有两种执行 SQL 语句的方式 如下 通过 SqlSession 发送 SQL 通过 SqlSession 获取 Mapper 接口 通过 Mapper
  • ajax 无参请求,ajax无参请求学习

    ajax 点击 请求流程 test html代码流程 创建button按钮 为button绑定一个点击事件 函数 在函数内书写ajax代码 2 1 创建ajax对象 2 2 设置onreadystatechange属性 用来监听变化 如果有
  • 数据结构视频教程 -《数据结构视频教程 严蔚敏》

    整个视频打包下载地址 史上最全的数据结构视频教程系列分享之 数据结构视频教程 严蔚敏 转载请保留出处和链接 更多优秀资源请访问 我是码农 严蔚敏老师是清华大学计算机系教授 长期从事数据结构教学和教材建设 本教程是数据结构视频教程中的经典之作
  • Spring(十二):bean的加载——创建bean

    回顾 前面已经看了在加载Bean的时候 如何去解决循环依赖的问题 解决完循环依赖 就到创建Bean的步骤了 而创建bean的步骤是紧紧接着 创建Bean实例 Bean根据范围会分为几种 单例 原型 自定义范围 每种范围都会有自己不同的生命周