https://blog.csdn.net/zxd1435513775/article/details/120935494?spm=1001.2014.3001.5501

Spring源码系列(四)——ConfigurationClassPostProcessor功能解析

https://blog.csdn.net/zxd1435513775/article/details/121113933?spm=1001.2014.3001.5501

Spring源码系列(六)——容器的刷新(下)

在第四篇和第六篇中,都分析到了一个方法,就是getBean(),这行代码的意思是:从容器中获取Bean,如果没有,则去创建Bean。所以在查看Spring源码时,只要看到这个方法,就要知道是去实例化Bean了。

这个方法的具体实现位于AbstractBeanFactory类中,下面就来分析吧。

一、getBean()

下面来看一下这个方法的详细代码:

// 根据name去获取对应的Bean实例
public Object getBean(String name) throws BeansException {return doGetBean(name, null, null, false);
}protected <T> T doGetBean(String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly){/*** 下面这个方法是通过 name 去获取 beanName,这里为什么不使用 name 直接作为 beanName呢?* 有两个原因:* 1、name 的值可能会以 & 字符开头,表明调用者想获取 FactoryBean 本身,而非 FactoryBean 实现类所创建的 bean。* 在 BeanFactory 中,FactoryBean 的实现类和其他的 bean 存储方式是一致的,即 <beanName, bean>,而 beanName 中是没有 & 这个字符的。* 所以需要将 name 的首字符 & 移除,这样才能从缓存里取到 FactoryBean 实例。* 2、还是别名的问题,转换需要 &beanName*/String beanName = transformedBeanName(name);Object bean;/*** 下面这个getSingleton()方法,在上面getBean()的时候也会调用,在Bean初始化的时候会调用* 为什么需要这么做呢?* 也就是说Spring容器在Bean初始化的时候先获取这个Bean对象,判断这个对象是否被实例化好了,* 是不是已经被创建了。普通情况下绝对为空,但有一种情况可能不为空* 从Spring容器中获取一个Bean,由于Spring中Bean容器是用一个map(singletonObjects)来存储的* 所以可以理解getSingleton(beanName)等于beanMap.get(beanName)* 由于getBean()方法会在Spring环境初始化的时候(就是对象被创建的时候调用一次)调用一次* 还会在Bean初始化的时候再调用一次* 此处为getSingleton()第一次调用,意思是去缓存里查询该beanName有没有被创建*/Object sharedInstance = getSingleton(beanName); // 此方法详解,看下面第二部分// 此处拿到sharedInstance,不为空,表示已经实例化了,但属性有没有填充不确定// 在分析FactoryBean的时候,分析了getObjectForBeanInstance()方法,下面我们就分析else的情况if (sharedInstance != null && args == null) {/*** 如果 sharedInstance 是普通的单例 bean,下面的方法会直接返回。* 但如果 sharedInstance 是 FactoryBean 类型的,则需调用 getObject 工厂方法获取真正的* bean 实例。如果用户想获取 FactoryBean 本身,这里也不会做特别的处理,直接返回即可。* 毕竟 FactoryBean 的实现类本身也是一种 bean,只不过具有一点特殊的功能而已。*/bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);} else {// 判断是否是原型Bean,如果是原型,不应该在Spring容器初始化的时候创建// 为什么要直接抛异常呢?因为在refresh()方法中的finishBeanFactoryInitialization()方法中// 已经对bean进行了判断,BeanDefinition不是抽象的,不是懒加载的,是单列的才会进入执行到此处// 所以会抛异常if (isPrototypeCurrentlyInCreation(beanName)) {throw new BeanCurrentlyInCreationException(beanName);}// 检查Spring工厂中是否存在bean定义BeanFactory parentBeanFactory = getParentBeanFactory();if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {// Not found -> check parent.String nameToLookup = originalBeanName(name);if (parentBeanFactory instanceof AbstractBeanFactory) {return ((AbstractBeanFactory) parentBeanFactory).doGetBean(nameToLookup, requiredType, args, typeCheckOnly);} else if (args != null) {// Delegation to parent with explicit args.return (T) parentBeanFactory.getBean(nameToLookup, args);} else {// No args -> delegate to standard getBean method.return parentBeanFactory.getBean(nameToLookup, requiredType);}}// 此参数为方法传入进来if (!typeCheckOnly) {// 添加到alreadyCreated set集合当中,表示他已经创建过一次markBeanAsCreated(beanName);}try {// 如果给的bean的定义是有父类的,则通过与父bean合并,返回给定顶级bean的RootBeanDefinition。// 在使用xml配置Bean时,有个属性parent属性来指定父类RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);// 再次检查,检查合并后的bean是不是抽象的checkMergedBeanDefinition(mbd, beanName, args);// 获取所依赖的bean,保证对当前bean所依赖的bean进行实例化。// 此处的意思是,被依赖的Bean要先进行实例化String[] dependsOn = mbd.getDependsOn();if (dependsOn != null) {for (String dep : dependsOn) {if (isDependent(beanName, dep)) {// 抛异常代码略}registerDependentBean(dep, beanName);try {// 又一次调用getBean()方法,此处获取的依赖bean,递归调用,获取即创建getBean(dep);} catch (NoSuchBeanDefinitionException ex) {// 抛异常代码略}}}// 此处才开始创建Bean,也是此处进行了getSingleton()方法的第二次调用if (mbd.isSingleton()) {// 此处为getSingleton()第二次调用,两处调用,形参不一样// 先执行getSingleton()方法,在该方法中,可以调用lamda表达式sharedInstance = getSingleton(beanName, () -> {try {// 此方法看下面第四部分return createBean(beanName, mbd, args);} catch (BeansException ex) {destroySingleton(beanName);throw ex;}});bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);} else if (mbd.isPrototype()) {// It's a prototype -> create a new instance.Object prototypeInstance = null;try {beforePrototypeCreation(beanName);prototypeInstance = createBean(beanName, mbd, args);} finally {afterPrototypeCreation(beanName);}bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);} else {String scopeName = mbd.getScope();if (!StringUtils.hasLength(scopeName)) {// 抛异常代码略}Scope scope = this.scopes.get(scopeName);if (scope == null) {// 抛异常代码略}try {Object scopedInstance = scope.get(beanName, () -> {beforePrototypeCreation(beanName);try {return createBean(beanName, mbd, args);}finally {afterPrototypeCreation(beanName);}});bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);} catch (IllegalStateException ex) {// 抛异常代码略}}} catch (BeansException ex) {cleanupAfterBeanCreationFailure(beanName);throw ex;}}// Check if required type matches the type of the actual bean instance.if (requiredType != null && !requiredType.isInstance(bean)) {try {T convertedBean = getTypeConverter().convertIfNecessary(bean, requiredType);if (convertedBean == null) {// 抛异常代码略}return convertedBean;} catch (TypeMismatchException ex) {// 抛异常代码略}}return (T) bean;
}

二、第一次调用getSingleton()

下面来看下这个方法详细代码:DefaultSingletonBeanRegistry类中

// 第一处getSingleton()是调用这个方法,根据beanName去singletonObjects中查询是否已经被实例化
protected Object getSingleton(String beanName, boolean allowEarlyReference) {// 从singletonObjects中获取bean,如果不为空直接返回,不再进行实例化工作Object singletonObject = this.singletonObjects.get(beanName);// isSingletonCurrentlyInCreation()方法判断当前beanName是不是正在创建中// 如果为null并且正在创建中if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {synchronized (this.singletonObjects) {// 去earlySingletonObjects中拿,注意这行集合里面存放的对象还没有被填充属性singletonObject = this.earlySingletonObjects.get(beanName);// 如果也没拿到,说明还没有创建完成if (singletonObject == null && allowEarlyReference) {// 去存放Bean工厂的集合里拿,看看是不是Bean工厂ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);if (singletonFactory != null) {singletonObject = singletonFactory.getObject();this.earlySingletonObjects.put(beanName, singletonObject);// 此集合是为了解决循环引用问题this.singletonFactories.remove(beanName);}}}}return singletonObject;
}public boolean isSingletonCurrentlyInCreation(String beanName) {// 正在创建的Bean会被放到这个singletonsCurrentlyInCreation集合中return this.singletonsCurrentlyInCreation.contains(beanName);
}

这里要特别说一下singletonObjectsearlySingletonObjectssingletonsCurrentlyInCreationsingletonFactories四个作用,很关键!!!

  • singletonObjects是个Map<String, Object>,用于存放完全初始化好的 Bean,从这个Map中取出的 Bean可以直接使用
  • earlySingletonObjects是个Map<String, Object>,用于存放原始的Bean对象,用于解决Bean的循环依赖问题,注意:存到里面的对象还没有被填充属性,即还没有完成初始化!!!
  • singletonsCurrentlyInCreation是个Set<String>,用于存放正在创建中的Bean
  • singletonFactories是个Map<String, ObjectFactory<?>>,用于存放 Bean工厂对象,也用于解决循环依赖

三、第二次调用getSingleton()

// 第二处getSingleton()是调用这个方法,传进来的第二个参数为lamda表达式
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {synchronized (this.singletonObjects) {// 先从singletonObjects中拿取BeanObject singletonObject = this.singletonObjects.get(beanName);// 不为空的话,直接返回if (singletonObject == null) {if (this.singletonsCurrentlyInDestruction) {// 抛异常代码略}// 将beanName添加到singletonsCurrentlyInCreation这样一个set集合中// 表示beanName对应的bean正在创建中// 创建之前把beanName放到这个singletonsCurrentlyInCreation集合中// 在上面第一次调用getSingleto()代码时,有个条件就是当前的beanName是不是在创建中beforeSingletonCreation(beanName);boolean newSingleton = false;boolean recordSuppressedExceptions = (this.suppressedExceptions == null);if (recordSuppressedExceptions) {this.suppressedExceptions = new LinkedHashSet<>();}try {// 分析到此处终于要去创建对象了!!!!!// 此处的singletonFactory是传进来的,即为lamda表达式,去调用createBean()函数,创建Bean// 下面要看下第四部分singletonObject = singletonFactory.getObject();newSingleton = true;} catch (IllegalStateException ex) {// 抛异常代码略} finally {if (recordSuppressedExceptions) {this.suppressedExceptions = null;}afterSingletonCreation(beanName);}// 将创建好的Bean放入singletonObjects中if (newSingleton) {// 此方法紧接着下面addSingleton(beanName, singletonObject);}}return singletonObject;}
}protected void addSingleton(String beanName, Object singletonObject) {synchronized (this.singletonObjects) {// 创建好的Bean,放入到singletonObjectsthis.singletonObjects.put(beanName, singletonObject);// 下面这两个集合是什么时候放进去的呢?想想哦~~~~在doCreateBean()方法中// 从singletonFactories中移除// 存放 bean工厂对象解决循环依赖this.singletonFactories.remove(beanName);// 从earlySingletonObjects中移除// 存放原始的bean对象用于解决循环依赖,注意:存到里面的对象还没有被填充属性this.earlySingletonObjects.remove(beanName);// 创建好的Bean,放入到registeredSingletonsthis.registeredSingletons.add(beanName);}
}

四、createBean()

下面来看一下createBean()的详细代码:AbstractAutowireCapableBeanFactory

// 此方法完成:创建bean实例、填充bean实例、调用后处理器等
protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args){RootBeanDefinition mbdToUse = mbd;// 做校验,不重要Class<?> resolvedClass = resolveBeanClass(mbd, beanName);if (resolvedClass != null && !mbd.hasBeanClass() && mbd.getBeanClassName() != null) {mbdToUse = new RootBeanDefinition(mbd);mbdToUse.setBeanClass(resolvedClass);}// 处理 lookup-method 和 replace-method 配置,Spring 将这两个配置统称为 override methodtry {mbdToUse.prepareMethodOverrides();} catch (BeanDefinitionValidationException ex) {// 抛异常略}try {// 在 bean 初始化前应用后置处理,如果后置处理返回的 bean 不为空,则直接返回// 让 BeanPostProcessors 有机会返回一个代理而不是目标bean实例。// 注意下这个后置处理器:InstantiationAwareBeanPostProcessorObject bean = resolveBeforeInstantiation(beanName, mbdToUse);if (bean != null) {return bean;}} catch (Throwable ex) {// 抛异常略}try {// 调用doCreateBean 创建bean// 请看下面第四部分Object beanInstance = doCreateBean(beanName, mbdToUse, args);return beanInstance;} catch (BeanCreationException | ImplicitlyAppearedSingletonException ex) {throw ex;} catch (Throwable ex) {// 抛异常略}
}

五、doCreateBean()

下面来看一下doCreateBean()的详细代码:AbstractAutowireCapableBeanFactory

protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) {// 实例化beanBeanWrapper instanceWrapper = null;if (mbd.isSingleton()) {instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);}if (instanceWrapper == null) {/*** 创建 bean 实例,并将实例包裹在 BeanWrapper 实现类对象中返回。* createBeanInstance中包含三种创建 bean 实例的方式:*   1. 通过工厂方法创建 bean 实例*   2. 通过构造方法自动注入(autowire by constructor)的方式创建 bean 实例*   3. 通过无参构造方法方法创建 bean 实例* 若 bean 的配置信息中配置了 lookup-method 和 replace-method,则会使用 CGLIB* 增强 bean 实例。* createBeanInstance()方法看第六小点*/instanceWrapper = createBeanInstance(beanName, mbd, args);}// 到此处,Bean实例已经创建了!!!!!Object bean = instanceWrapper.getWrappedInstance();Class<?> beanType = instanceWrapper.getWrappedClass();if (beanType != NullBean.class) {mbd.resolvedTargetType = beanType;}// 后处理器修改合并的bean定义synchronized (mbd.postProcessingLock) {if (!mbd.postProcessed) {try {applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);} catch (Throwable ex) {// 抛异常略}mbd.postProcessed = true;}}// BeanDefinition是单例的,支持循环引用,并且正在创建// 看到allowCircularReferences这个属性,是不是猜想Spring可以配置是否支持循环引用呢?boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName));if (earlySingletonExposure) {// 将正在创建的Bean放到集合中,此时Bean的属性还没赋值addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));}// 开始初始化Bean对象了,即属性填充Object exposedObject = bean;try {// 属性填充,非常重要!!!!!看第八小点populateBean(beanName, mbd, instanceWrapper);// 执行后置处理器,aop就是在这里完成的处理,看第九小点exposedObject = initializeBean(beanName, exposedObject, mbd);} catch (Throwable ex) {// 抛异常代码略}if (earlySingletonExposure) {Object earlySingletonReference = getSingleton(beanName, false);if (earlySingletonReference != null) {if (exposedObject == bean) {exposedObject = earlySingletonReference;} else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {String[] dependentBeans = getDependentBeans(beanName);Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);for (String dependentBean : dependentBeans) {if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {actualDependentBeans.add(dependentBean);}}if (!actualDependentBeans.isEmpty()) {// 抛异常代码略}}}}// Register bean as disposable.try {registerDisposableBeanIfNecessary(beanName, bean, mbd);} catch (BeanDefinitionValidationException ex) {// 抛异常代码略}return exposedObject;
}protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {synchronized (this.singletonObjects) {if (!this.singletonObjects.containsKey(beanName)) {// 放到singletonFactories中this.singletonFactories.put(beanName, singletonFactory);// 在第一次调用getSingleton()方法是put// 移除earlySingletonObjects中this.earlySingletonObjects.remove(beanName);// 放到registeredSingletons中this.registeredSingletons.add(beanName);}}
}

六、createBeanInstance()

// 使用适当的实例化策略,为指定的bean创建一个新实例:工厂方法、构造函数自动装配,简单实例化。
protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) {// 获取BeanDefinition所转化的类ClassClass<?> beanClass = resolveBeanClass(mbd, beanName);// 检测一个类的访问权限,spring默认情况下对于非public的类是不允许访问的。if (beanClass != null && !Modifier.isPublic(beanClass.getModifiers()) && !mbd.isNonPublicAccessAllowed()) {// 抛异常略}Supplier<?> instanceSupplier = mbd.getInstanceSupplier();if (instanceSupplier != null) {return obtainFromSupplier(instanceSupplier, beanName);}// 如果工厂方法不为空,则通过工厂方法构建 bean 对象if (mbd.getFactoryMethodName() != null) {return instantiateUsingFactoryMethod(beanName, mbd, args);}/*** 从Spring的原始注释可以知道这个是一个Shortcut,什么意思呢?* 当多次构建同一个 bean 时,可以使用这个Shortcut,* 也就是说不再需要每次推断应该使用哪种方式构造bean* 比如在多次构建同一个prototype类型的 bean 时,就可以走此处的shortcut* 这里的 resolved 和 mbd.constructorArgumentsResolved 将会在 bean 第一次实例* 化的过程中被设置*/// 一个类可能有多个构造器,所以Spring得根据参数个数、类型确定需要调用的构造器// 在使用构造器创建实例后,Spring会将解析过后确定下来的构造器或工厂方法保存在缓存中,避免再次创建相同bean时再次解析boolean resolved = false;boolean autowireNecessary = false;if (args == null) {synchronized (mbd.constructorArgumentLock) {if (mbd.resolvedConstructorOrFactoryMethod != null) {// 已经解析过class的构造器resolved = true;// 如果已经解析了构造方法的参数,则必须要通过一个带参构造方法来实例autowireNecessary = mbd.constructorArgumentsResolved;}}}if (resolved) {// 已经解析过class的构造器,使用已经解析好的构造器if (autowireNecessary) {// 构造函数自动注入return autowireConstructor(beanName, mbd, null, null);} else {// 通过默认的无参构造方法进行return instantiateBean(beanName, mbd);}}// 由后置处理器决定返回哪些构造方法,决定用哪个构造方法来进行实例化,此方法为选择构造函数的过程// 需要根据参数解析、确定构造函数Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);// 解析的构造器不为空 || 注入类型为构造函数自动注入 || bean定义中有构造器参数 || 传入参数不为空if (ctors != null || mbd.getResolvedAutowireMode() == AUTOWIRE_CONSTRUCTOR ||mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) {// 构造函数自动注入return autowireConstructor(beanName, mbd, ctors, args);}// 使用默认的无参构造方法进行初始化return instantiateBean(beanName, mbd);
}

createBeanInstance()方法的代码逻辑如下:

  • 如果Bean定义中存在 InstanceSupplier,会使用这个回调接口创建对象,应该是3.X以后新加的,3.X的源码中没有
  • 根据配置的factoryMethodNamefactory-mtehod创建Bean
  • 解析构造函数并进行实例化

因为一个类可能有多个构造函数,所以需要根据配置文件中配置的参数或者传入的参数确定最终调用的构造函数,因为判断过程会比较消耗性能,所以Spring会将解析、确定好的构造函数缓存到BeanDefinition中的resolvedConstructorOrFactoryMethod字段中。在下次创建相同Bean的时候,会直接从RootBeanDefinition中的属性resolvedConstructorOrFactoryMethod缓存的值获取,避免再次解析。

七、autowireConstructor()

public BeanWrapper autowireConstructor(String beanName, RootBeanDefinition mbd,@Nullable Constructor<?>[] chosenCtors, @Nullable Object[] explicitArgs) {// 创建一个BeanWrapperImpl 前面外部返回的BeanWrapper 其实就是这个BeanWrapperImpl// 因为BeanWrapper是个接口BeanWrapperImpl bw = new BeanWrapperImpl();this.beanFactory.initBeanWrapper(bw);Constructor<?> constructorToUse = null;ArgumentsHolder argsHolderToUse = null;Object[] argsToUse = null;// 如果getBean()中传入的参数不为空,那么就使用传入的参数// 确定参数值列表// argsToUse可以有两种办法设置// 第一种通过beanDefinition设置// 第二种通过xml设置if (explicitArgs != null) {argsToUse = explicitArgs;} else {// 否则就需要解析配置文件中的参数Object[] argsToResolve = null;synchronized (mbd.constructorArgumentLock) {// 获取已解析的构造方法,一般不会有,因为构造方法一般会提供一个// 除非有多个,那么才会存在已经解析完成的构造方法constructorToUse = (Constructor<?>) mbd.resolvedConstructorOrFactoryMethod;if (constructorToUse != null && mbd.constructorArgumentsResolved) {// 在缓存中找到了构造器,就继续从缓存中寻找缓存的构造器参数argsToUse = mbd.resolvedConstructorArguments;if (argsToUse == null) {// 没有缓存的参数,就需要获取配置文件中配置的参数argsToResolve = mbd.preparedConstructorArguments;}}}// 如果缓存中没有缓存的参数的话,即argsToResolve不为空,就需要解析配置的参数if (argsToResolve != null) {// 解析参数类型,比如将配置的String类型转换成int、boolean等类型argsToUse = resolvePreparedArguments(beanName, mbd, bw, constructorToUse, argsToResolve);}}// 如果没有缓存,就需要从构造函数开始解析if (constructorToUse == null) {// 如果没有已经解析的构造方法,则需要去解析构造方法// 判断构造方法是否为空,判断是否根据构造方法自动注入boolean autowiring = (chosenCtors != null ||mbd.getResolvedAutowireMode() == AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR);ConstructorArgumentValues resolvedValues = null;// 定义了最小参数个数// 如果你给构造方法的参数列表给定了具体的值// 那么这些值的个数就是构造方法参数的个数int minNrOfArgs;if (explicitArgs != null) {// getBean方法传入的参数minNrOfArgs = explicitArgs.length;} else {// 配置文件中的配置的参数ConstructorArgumentValues cargs = mbd.getConstructorArgumentValues();// 用于承载解析后的构造函数参数的值resolvedValues = new ConstructorArgumentValues();/*** 确定构造方法参数数量,假设有如下配置:*     <bean id="scorpios" class="com.scorpios.Scorpios">*         <constructor-arg index="0" value="str1"/>*         <constructor-arg index="1" value="1"/>*         <constructor-arg index="2" value="str2"/>*     </bean>** 在通过spring内部给了一个值得情况那么表示你的构造方法的最小参数个数一定* minNrOfArgs = 3*/// 解析配置文件中的参数,并且返回参数个数minNrOfArgs = resolveConstructorArguments(beanName, mbd, bw, cargs, resolvedValues);}// 如果传入的构造器数组不为空,就使用传入的构造器参数,否则通过反射获取class中定义的构造器Constructor<?>[] candidates = chosenCtors;if (candidates == null) {Class<?> beanClass = mbd.getBeanClass();try {// 使用public的构造器或者所有构造器candidates = (mbd.isNonPublicAccessAllowed() ?beanClass.getDeclaredConstructors() : beanClass.getConstructors());}}// 根据构造方法的访问权限级别和参数数量进行排序// 怎么排序的呢?/***  有限反问权限,继而参数个数*  这个自己可以写个测试去看看到底是不是和我说的一样* 1. public Scorpios(Object o1, Object o2, Object o3)* 2. public Scorpios(Object o1, Object o2)* 3. public Scorpios(Object o1)* 4. protected Scorpios(Integer i, Object o1, Object o2, Object o3)* 5. protected Scorpios(Integer i, Object o1, Object o2)* 6. protected Scorpios(Integer i, Object o1)*/// 给构造函数排序,public构造函数优先、参数数量降序排序AutowireUtils.sortConstructors(candidates);//定义了一个差异变量,这个变量很有分量,后面有注释int minTypeDiffWeight = Integer.MAX_VALUE;Set<Constructor<?>> ambiguousConstructors = null;LinkedList<UnsatisfiedDependencyException> causes = null;//循环所有的构造方法for (Constructor<?> candidate : candidates) {Class<?>[] paramTypes = candidate.getParameterTypes();/*** 这个判断别看只有一行代码理解起来很费劲* 首先constructorToUse != null这个很好理解* 前面已经说过首先constructorToUse主要是用来装已经解析过了并且在使用的构造方法* 只有在它等于空的情况下,才有继续的意义,因为下面如果解析到了一个符合的构造方法* 就会赋值给这个变量。故而如果这个变量不等于null就不需要再进行解析了,说明spring已经* 找到一个合适的构造方法,直接使用便可以* argsToUse.length > paramTypes.length这个代码就相当复杂了* 首先假设 argsToUse = [1,"luban",obj],需要3个参数的构造函数* 那么会去匹配到上面的构造方法的1和5* 由于构造方法1有更高的访问权限,所有选择1,尽管5看起来更加匹配* 但是我们看2,直接参数个数就不对所以直接忽略*/if (constructorToUse != null && argsToUse.length > paramTypes.length) {break;}if (paramTypes.length < minNrOfArgs) {continue;}// 封装解析到的参数信息ArgumentsHolder argsHolder;if (resolvedValues != null) {try {// 判断是否加了ConstructorProperties注解如果加了则把值取出来// 可以写个代码测试一下// @ConstructorProperties(value = {"xxx", "111"})String[] paramNames = ConstructorPropertiesChecker.evaluate(candidate, paramTypes.length);if (paramNames == null) {ParameterNameDiscoverer pnd = this.beanFactory.getParameterNameDiscoverer();if (pnd != null) {//获取构造方法参数名称列表/*** 假设你有一个(String scorpios,Object hua)* 则paramNames=[scorpios,hua]*/paramNames = pnd.getParameterNames(candidate);}}// 获取构造方法参数值列表/*** 这个方法比较复杂* 因为spring只能提供字符串的参数值* 故而需要进行转换* argsHolder所包含的值就是转换之后的*/argsHolder = createArgumentArray(beanName, mbd, resolvedValues, bw, paramTypes, paramNames,getUserDeclaredConstructor(candidate), autowiring);}} else {// Explicit arguments given -> arguments length must match exactly.if (paramTypes.length != explicitArgs.length) {continue;}argsHolder = new ArgumentsHolder(explicitArgs);}/*** typeDiffWeight 差异量,何谓差异量呢?* argsHolder.arguments和paramTypes之间的差异* 每个参数值的类型与构造方法参数列表的类型之间的差异* 通过这个差异量来衡量或者确定一个合适的构造方法** 值得注意的是constructorToUse=candidate* 第一次循环一定会typeDiffWeight < minTypeDiffWeight,因为minTypeDiffWeight的值非常大,Integer.MAX_VALUE* 然后每次循环会把typeDiffWeight赋值给minTypeDiffWeight(minTypeDiffWeight = typeDiffWeight)* else if (constructorToUse != null && typeDiffWeight == minTypeDiffWeight)* 第一次循环肯定不会进入这个* 第二次如果进入了这个分支代表什么?* 代表有两个构造方法都符合我们要求?那么spring就迷茫了* spring迷茫了怎么办?* ambiguousConstructors.add(candidate);* ambiguousConstructors=null 非常重要?* 为什么重要,因为需要清空* 这也解释了为什么它找到两个符合要求的方法不直接抛异常的原因* 如果这个ambiguousConstructors一直存在,spring会在循环外面去exception*/// 因为不同构造函数的参数个数相同,而且参数类型为父子关系,所以需要找出类型最符合的一个构造函数//Spring用一种权重的形式来表示类型差异程度,差异权重越小越优先int typeDiffWeight = (mbd.isLenientConstructorResolution() ?argsHolder.getTypeDifferenceWeight(paramTypes) : argsHolder.getAssignabilityWeight(paramTypes));// 当前构造函数最为匹配的话,清空先前ambiguousConstructors列表if (typeDiffWeight < minTypeDiffWeight) {constructorToUse = candidate;argsHolderToUse = argsHolder;argsToUse = argsHolder.arguments;minTypeDiffWeight = typeDiffWeight;ambiguousConstructors = null;} else if (constructorToUse != null && typeDiffWeight == minTypeDiffWeight) {// 存在相同权重的构造器,将构造器添加到一个ambiguousConstructors列表变量中// 注意,这时候constructorToUse 指向的仍是第一个匹配的构造函数if (ambiguousConstructors == null) {ambiguousConstructors = new LinkedHashSet<>();ambiguousConstructors.add(constructorToUse);}ambiguousConstructors.add(candidate);}}// 循环结束,没有找打合适的构造方法if (constructorToUse == null) {//如果没有匹配的构造函数,抛出异常。略}// 如果ambiguousConstructors还存在则异常?为什么会在上面方法中直接exception?// 上面注释当中有说明else if (ambiguousConstructors != null && !mbd.isLenientConstructorResolution()) {// 如果存在多个构造函数匹配程度相同,并且BeanDefinition中设置isLenientConstructorResolution为false(默认值为true),// 表示构造器创建为严格模式的话,会抛出异常。异常代码略}// 这一步就是将解析好的构造函数放入缓存resolvedConstructorOrFactoryMethod,如果需要的话也会缓存参数// 并设置constructorArgumentsResolved为true,表示已经解析过构造函数if (explicitArgs == null) {/** 缓存相关信息,比如:*   1. 已解析出的构造方法对象 resolvedConstructorOrFactoryMethod*   2. 构造方法参数列表是否已解析标志 constructorArgumentsResolved*   3. 参数值列表 resolvedConstructorArguments 或 preparedConstructorArguments*   这些信息可用在其他地方,用于进行快捷判断*/argsHolderToUse.storeCache(mbd, constructorToUse);}}try {// 使用反射创建实例 lookup-method 通过CGLIB增强bean实例final InstantiationStrategy strategy = beanFactory.getInstantiationStrategy();Object beanInstance;if (System.getSecurityManager() != null) {final Constructor<?> ctorToUse = constructorToUse;final Object[] argumentsToUse = argsToUse;beanInstance = AccessController.doPrivileged((PrivilegedAction<Object>) () ->strategy.instantiate(mbd, beanName, beanFactory, ctorToUse, argumentsToUse),beanFactory.getAccessControlContext());} else {beanInstance = strategy.instantiate(mbd, beanName, this.beanFactory, constructorToUse, argsToUse);}bw.setBeanInstance(beanInstance);return bw;}
}

autowireConstructor()这个方法代码非常长,总体的功能逻辑如下

  • 确定参数
    • 如果调用getBean()方法时传入的参数不为空,则可以直接使用传入的参数
    • 再尝试从缓存中获取参数
    • 否则,需要解析<bean>节点配置的构造器参数
  • 确定构造函数。根据第一步中确定下来的参数,接下来的任务就是根据参数的个数、类型来确定最终调用的构造函数。首先是根据参数个数匹配,把所有构造函数根据参数个数升序排序,再去筛选参数个数匹配的构造函数,因为配置文件中可以通过参数位置索引,也可以通过参数名称来设定参数值,如<constructor name="title">,所以还需要解析参数的名称。最后,根据解析好的参数名称、参数类型、实际参数就可以确定构造函数,并且将参数转换成对应的类型
  • 根据确定的构造函数转换成对应的参数类型
  • 构造函数不确定性的验证。因为有一些构造函数的参数类型为父子关系,所以Spring会做一次验证
  • 如果条件符合(传入参数为空),将解析好的构造函数、参数放入缓存
  • 根据实例化策略将构造函数、参数实例化Bean

这个方法就是为了找到最合适的构造函数来实例化Bean,需要来确定构造函数的参数个数。

八、populateBean()

protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) {if (bw == null) {if (mbd.hasPropertyValues()) {// 抛异常略} else {// 空对象直接返回return;}}// 给InstantiationAwareBeanPostProcessors最后一次机会在属性注入前修改Bean的属性值// 具体通过调用postProcessAfterInstantiation方法,如果调用返回false,表示不必继续进行依赖注入,直接返回if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {for (BeanPostProcessor bp : getBeanPostProcessors()) {if (bp instanceof InstantiationAwareBeanPostProcessor) {InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;if (!ibp.postProcessAfterInstantiation(bw.getWrappedInstance(), beanName)) {return;}}}}// pvs是一个MutablePropertyValues实例,里面实现了PropertyValues接口,提供属性的读写操作实现,同时可以通过调用构造函数实现深拷贝PropertyValues pvs = (mbd.hasPropertyValues() ? mbd.getPropertyValues() : null);// 根据Bean配置的依赖注入方式完成注入,默认是0,即不走以下逻辑,所有的依赖注入都需要在xml文件中有显式的配置// 如果设置了相关的依赖装配方式,会遍历Bean中的属性,根据类型或名称来完成相应注入,无需额外配置int resolvedAutowireMode = mbd.getResolvedAutowireMode();if (resolvedAutowireMode == AUTOWIRE_BY_NAME || resolvedAutowireMode == AUTOWIRE_BY_TYPE) {// 深拷贝当前已有的配置MutablePropertyValues newPvs = new MutablePropertyValues(pvs);// 根据名称进行注入if (resolvedAutowireMode == AUTOWIRE_BY_NAME) {autowireByName(beanName, mbd, bw, newPvs);}// 根据类型进行注入if (resolvedAutowireMode == AUTOWIRE_BY_TYPE) {autowireByType(beanName, mbd, bw, newPvs);}// 结合注入后的配置,覆盖当前配置pvs = newPvs;}// 容器是否注册了InstantiationAwareBeanPostProcessorboolean hasInstAwareBpps = hasInstantiationAwareBeanPostProcessors();// 是否进行依赖检查boolean needsDepCheck = (mbd.getDependencyCheck() != AbstractBeanDefinition.DEPENDENCY_CHECK_NONE);if (hasInstAwareBpps || needsDepCheck) {if (pvs == null) {pvs = mbd.getPropertyValues();}// 过滤出所有需要进行依赖检查的属性编辑器PropertyDescriptor[] filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);if (hasInstAwareBpps) {for (BeanPostProcessor bp : getBeanPostProcessors()) {// 如果有相关的后置处理器,进行后置处理if (bp instanceof InstantiationAwareBeanPostProcessor) {InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;pvs = ibp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName);if (pvs == null) {return;}}}}if (needsDepCheck) {// 检查是否满足相关依赖关系,对应的depends-on属性,需要确保所有依赖的Bean先完成初始化checkDependencies(beanName, mbd, filteredPds, pvs);}}if (pvs != null) {// 将pvs上所有的属性填充到BeanWrapper对应的Bean实例中,注意到这一步,TestBean的student属性还是RuntimeBeanReference,即还未解析实际的Student实例applyPropertyValues(beanName, mbd, bw, pvs);}
}

Spring的装配问题:

public static final int AUTOWIRE_NO = 0;
public static final int AUTOWIRE_BY_NAME = 1;
public static final int AUTOWIRE_BY_TYPE = 2;
public static final int AUTOWIRE_CONSTRUCTOR = 3;

Spring默认的装配类型是AUTOWIRE_NO,表示不默认装配,除非你在代码中手动添加@Autowired、@Resouce等注解手动注入
AUTOWIRE_BY_TYPE表示为按类型装配,在实例化类的时候是根据属性是否具有set方法,如果有,则进行装配

九、initializeBean()

protected Object initializeBean(String beanName, Object bean, @Nullable RootBeanDefinition mbd) {// 权限校验if (System.getSecurityManager() != null) {AccessController.doPrivileged((PrivilegedAction<Object>) () -> {invokeAwareMethods(beanName, bean);return null;}, getAccessControlContext());} else {invokeAwareMethods(beanName, bean);}Object wrappedBean = bean;if (mbd == null || !mbd.isSynthetic()) {// 调用BeanPostProcessor的postProcessBeforeInitialization()方法wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);}try {// 调用初始化方法,先执行实现InitializingBean接口的方法,再执行自定义的init-method方法invokeInitMethods(beanName, wrappedBean, mbd);} catch (Throwable ex) {// 抛异常略}if (mbd == null || !mbd.isSynthetic()) {// 调用BeanPostProcessor的postProcessAfterInitialization()方法wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);}return wrappedBean;
}

十. invokeInitMethods()

protected void invokeInitMethods(String beanName, Object bean, @Nullable RootBeanDefinition mbd) {// 是不是实现了InitializingBeanboolean isInitializingBean = (bean instanceof InitializingBean);if (isInitializingBean && (mbd == null || !mbd.isExternallyManagedInitMethod("afterPropertiesSet"))) {if (System.getSecurityManager() != null) {try {AccessController.doPrivileged((PrivilegedExceptionAction<Object>) () -> {((InitializingBean) bean).afterPropertiesSet();return null;}, getAccessControlContext());} catch (PrivilegedActionException pae) {throw pae.getException();}} else {// 调用afterPropertiesSet()方法((InitializingBean) bean).afterPropertiesSet();}}if (mbd != null && bean.getClass() != NullBean.class) {// 是不是有自定义的init-method方法String initMethodName = mbd.getInitMethodName();if (StringUtils.hasLength(initMethodName) &&!(isInitializingBean && "afterPropertiesSet".equals(initMethodName)) &&!mbd.isExternallyManagedInitMethod(initMethodName)) {// 执行自定义的init-method方法invokeCustomInitMethod(beanName, bean, mbd);}}
}

十一、小结

以上的getBea()方法终于分析完了,其中doCreateBean()方法比较重要吧,要重点看一下,autowireConstructor()方法比较难理解,但知道它的作用是什么就好了.

下面来用一张图总结一下这个过程吧。

在这里插入图片描述

上图中,红蓝色的框表示的循环依赖的问题,此处简单描述一下。

XY相互依赖,先实例化X,在实例化X的过程中会发现X依赖了Y,于是就去实例化Y,就是图中的红色虚线

在实例化Y的过程中,发现Y又依赖于X,但此时的X正在创建,被保存在singletonFactories中,所以就把singletonFactories中的X赋值给了Y,这样Y的依赖就解决了,所以Y的实例化就结束了,就会把Y正常返回

下面就继续实例化X的流程了。

查看全文
如若内容造成侵权/违法违规/事实不符,请联系编程学习网邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!

相关文章

  1. ssh连接linux之后出现/usr/bin/xauth: error in locking authority file /home/*/.Xauthority

    ssh连接linux之后出现/usr/bin/xauth: error in locking authority file /home/*/.Xauthority,Read-only file system错误的解决方法 1.首先尝试重启&#xff0c;有时候重启就好了&#xff0c;如果不行尝试2。 2.mount命令查看哪个模块输入只读&#xff0c;然后执行&#xff1…...

    2024/4/15 3:52:01
  2. 关于Got permission denied while trying to connect to the Docker daemon socket at unix:///解决方案

    今天在试docker的时候发现了一个问题&#xff1a; 按理说直接加sudo就好了&#xff0c;但是每次都加sudo就很麻烦&#xff0c;所以我们可以这样&#xff1a; 输入指令&#xff1a;sudo chmod arw /var/run/docker.sock 然后再运行就好了&#xff1a;...

    2024/4/24 7:37:08
  3. Android Studio 下拉框Spinner实例

    1. 功能 下拉框相当常见了&#xff0c;例如选择省、市、县&#xff1b;例如选择部门&#xff1b;选择员工。用处一般是多选一。 2. 显示 下拉框一般有两种形式&#xff0c;一种是下拉显示可选项&#xff0c;一种是弹窗显示可选项。通过调整参数spinnerMode&#xff0c;dropd…...

    2024/4/15 3:52:37
  4. 完美卸载SQL Server2014数据库

    1.在运行中输入services.msc,然后找到所有跟Sql Server有关的服务&#xff0c;并且停止这些服务。 2.从控制面板卸载。 3.选择实例时&#xff0c;有多少ID就删除多少次&#xff0c;如果有两个ID&#xff0c;删除完一轮后&#xff0c;再回到这里在删除一遍。 4.到删除进度时间…...

    2024/4/18 20:16:52
  5. log4j打印日志到控制台

    log4j.rootLoggerWARN,console #输出到控制台 log4j.appender.consoleorg.apache.log4j.ConsoleAppender log4j.appender.console.layoutorg.apache.log4j.PatternLayout log4j.appender.console.layout.ConversionPattern[%-5p][%-22d{yyyy/MM/dd HH:mm:ssS}][%l]%m%n log4j.a…...

    2024/4/23 15:57:06
  6. Cocos Creator 笔记记录1-动态的创建节点

    方法一&#xff1a; newArrow() { let arrow new cc.Node(); let sprite :cc.Sprite arrow.addComponent(cc.Sprite); sprite.spriteFrame this.arrowIcon; arrow.parent this.node; //设置arrow出现的位置 arrow.setPosition(this.getNewStarPositio…...

    2024/4/15 3:52:42
  7. SpringBoot中集成JSP如何创建webapp

    SpringBoot中集成JSP如何创建webapp 在我创建SpringBoot工程之后&#xff0c;想要集成jsp文件&#xff0c;但是对于jsp文件存放的位置是在webapp中&#xff0c;但是由于SpringBoot项目在创建时&#xff0c;不会为我们自动创建webapp文件夹&#xff0c;这时候我们就要通过自行创…...

    2024/4/25 10:00:21
  8. Python 商品筛选【简单易懂,代码可以直接运行】

    Python 商品筛选【简单易懂&#xff0c;代码可以直接运行】 有如下商品价格&#xff1a;568,239,368,425,121,219,834,1263,26&#xff0c;请输入随意一个价格区间进行商品的筛选&#xff0c;并能够对筛选出的商品进行从大到小和从小到大进行排序&#xff0c;并求出这个区间的…...

    2024/4/19 2:32:27
  9. Socket通信原理

    Socket的位置&#xff1a; Socket是什么&#xff1a; Socket是应用层与TCP/IP协议族通信的中间软件抽象层&#xff0c;它是一组接口。在设计模式中&#xff0c;Socket其实就是一个门面模式&#xff0c;它把复杂的TCP/IP协议族隐藏在socket接口后面&#xff0c;对用户来说&#…...

    2024/4/19 6:49:23
  10. vue/elementUI 输入框disabled颜色问题解决

    elementUI本身输入框el-input自带了disabled属性&#xff0c;但是当你需要覆盖其样式或者自己写一个自己的my-el-input时&#xff0c;不妨用下面的代码&#xff0c;注意opacity 1表示不透明&#xff0c;cursor&#xff1a;表示滑动过去鼠标为禁止样式 /deep/ input[disabled],…...

    2024/4/19 16:33:58
  11. java中的类和对象(重点)超详细

    java中的类和对象 1、类与对象的初步认知 2、类和对象的实例化 3、类的成员 3.1、字段&#xff0c;属性&#xff0c;成员变量 3.2、方法 3.3、static 关键字 3.4、小结4、封装 4.1、private实现封装 4.2、getter 和 setter方法5、构造方法 5.1、基本方法 5.2、this 关键字…...

    2024/4/20 13:23:09
  12. Please enter a commit message to explain why this merge is necessary、git面板操作

    Please enter a commit message to explain why this merge is necessary, 在pull或者合并分支的时候有时会遇到这个界面。可以不管(直接下面3,4步)&#xff0c;如果要输入解释的话就需要: 1.按键盘字母 i 进入insert模式 2.修改最上面那行黄色合并信息,可以不修改 3.按键盘左…...

    2024/4/15 3:52:37
  13. Error response from daemon: You cannot remove a running container 96683146ee5bcc37ef65707f559ffcd37a

    解决方法&#xff1a; sudo docker ps -a根据ID进行删除&#xff1a; sudo docker rm -f 96683146ee5b至此问题已解决...

    2024/4/15 4:55:52
  14. 指针(进阶)习题

    目录 1.实现一个计算器&#xff0c;计算整形变量的加减乘除&#xff08;使用回调函数&#xff09; 2.使用qsort函数排序整型&#xff08;实现升序&#xff09; 3.使用回调函数&#xff0c;模拟实现qsort&#xff08;采用冒泡的方式 4.易混易错sizeof和strlen 习题1 习题2 习…...

    2024/4/25 22:21:42
  15. CentOS 8.0开放防火墙端口

    查看防火墙某个端口是否开放 firewall-cmd --query-port80/tcp 开放防火墙端口80 firewall-cmd --zonepublic --add-port80/tcp --permanent 关闭80端口 firewall-cmd --zonepublic --remove-port80/tcp --permanent 配置立即生效 firewall-cmd --reload 查看防火墙状态 sy…...

    2024/4/26 2:07:31
  16. 车载式绿篱修剪机 大型液压绿篱机 绿化修剪中分带边坡灌木杂草

    在城市绿化建设工作中&#xff0c;我们尝用到的就是绿篱修剪机&#xff0c;在效率和修剪效果上都是比较推荐的方式。 高速公路修剪机特征&#xff1a;车载式绿篱修剪机其特征在于&#xff0c;修剪机设置在承载车的后部&#xff0c;所述的修剪机包括底盘、立臂、配重、横臂、修…...

    2024/4/26 0:43:59
  17. 市县两个选择框联动

    问题描述&#xff1a; 遇到一个需求&#xff0c;就是市和县要做成两个选择框&#xff0c;并保持它们数据的联动。使用vue element ui的实现方法如下&#xff1a; <template><div class"app-container"><el-form label-position"left" ref…...

    2024/4/19 17:23:02
  18. 【《自动控制原理(田玉平)》|课本知识点整理(三)】第 5 章 控制系统的时域运动分析

    文章目录 5.1 控制系统的时域响应5.1.2 离散时间系统输出响应5.2 控制系统瞬态性能分析5.2.1 瞬态性能指标的定义5.2.2 一阶系统瞬态性能分析5.2.3 典型二阶系统瞬态性能分析1. 典型二阶系统的传递函数2. 典型二阶系统的单位阶跃响应3. 典型二阶系统瞬态性能指标4. 二阶系统计算…...

    2024/4/19 12:49:56
  19. 殷人昆 数据结构 用面向对象方法与C++语言描述 第二版 课后题答案详解 完整版

    殷人昆 数据结构 用面向对象方法与C语言描述 第二版 课后题答案详解 完整版。 这是完整版本的&#xff0c;每一题都有&#xff0c;某些网站给的 并不全面。缺斤少两。 需要的可以加微信&#xff1a;waiwnn2030.进行分享 hi&#xff0c;这是我用百度网盘分享的内容~复制这…...

    2024/4/15 3:52:27
  20. 制图小技巧:巧用Python和ELK瞬间完成总图建筑名称标注

    各位小伙伴周一好&#xff0c;又到了每周一次的制图教室啦。经过前面两次制图教程的分享&#xff0c;相信大家对于白模填色和写实渲染这两种表达方式肯定有了较好的掌握。 那么今天我们就转战制图技巧篇&#xff0c;和童鞋们聊一下总平面图中的建筑名称标注问题。 对于总平面图…...

    2024/4/19 19:34:58

最新文章

  1. mysql中join内外连接查询例子

    文章目录 join关键字概要举例using 与 on 区别 join关键字 在MySQL中&#xff0c;JOIN 是一种用于将两个或多个表中的行联合起来的操作。 连接&#xff08;join&#xff09;就是将一张表中的行按照某个条件&#xff08;连接条件&#xff09;与另一张表中的行连接起来形成一个新…...

    2024/4/26 16:34:25
  2. 梯度消失和梯度爆炸的一些处理方法

    在这里是记录一下梯度消失或梯度爆炸的一些处理技巧。全当学习总结了如有错误还请留言&#xff0c;在此感激不尽。 权重和梯度的更新公式如下&#xff1a; w w − η ⋅ ∇ w w w - \eta \cdot \nabla w ww−η⋅∇w 个人通俗的理解梯度消失就是网络模型在反向求导的时候出…...

    2024/3/20 10:50:27
  3. IOS手机耗电量测试

    1. 耗电量原始测试方法 1.1 方法原理&#xff1a; 根据iPhone手机右上角的电池百分比变化来计算耗电量。 1.2实际操作&#xff1a; 在iOS通用设置中打开电池百分比数值显示&#xff0c;然后操作30分钟&#xff0c;60分钟&#xff0c;90分钟&#xff0c;看开始时和结束时电池…...

    2024/4/25 2:14:05
  4. ASP.NET Core 标识(Identity)框架系列(一):如何使用 ASP.NET Core 标识(Identity)框架创建用户和角色?

    前言 ASP.NET Core 内置的标识&#xff08;identity&#xff09;框架&#xff0c;采用的是 RBAC&#xff08;role-based access control&#xff0c;基于角色的访问控制&#xff09;策略&#xff0c;是一个用于管理用户身份验证、授权和安全性的框架。 它提供了一套工具和库&…...

    2024/4/26 14:55:59
  5. springboot 项目整合easy-captcha验证码功能

    效果 1、验证码使用easy-captcha,在pom文件增加依赖 <!-- google 验证码 --><dependency><groupId>com.github.whvcse</groupId><artifactId>easy-captcha</artifactId></dependency> 2、增加获取kaptcha的ctrl package com.*.*.s…...

    2024/4/24 13:16:37
  6. 【外汇早评】美通胀数据走低,美元调整

    原标题:【外汇早评】美通胀数据走低,美元调整昨日美国方面公布了新一期的核心PCE物价指数数据,同比增长1.6%,低于前值和预期值的1.7%,距离美联储的通胀目标2%继续走低,通胀压力较低,且此前美国一季度GDP初值中的消费部分下滑明显,因此市场对美联储后续更可能降息的政策…...

    2024/4/25 11:51:20
  7. 【原油贵金属周评】原油多头拥挤,价格调整

    原标题:【原油贵金属周评】原油多头拥挤,价格调整本周国际劳动节,我们喜迎四天假期,但是整个金融市场确实流动性充沛,大事频发,各个商品波动剧烈。美国方面,在本周四凌晨公布5月份的利率决议和新闻发布会,维持联邦基金利率在2.25%-2.50%不变,符合市场预期。同时美联储…...

    2024/4/25 18:39:24
  8. 【外汇周评】靓丽非农不及疲软通胀影响

    原标题:【外汇周评】靓丽非农不及疲软通胀影响在刚结束的周五,美国方面公布了新一期的非农就业数据,大幅好于前值和预期,新增就业重新回到20万以上。具体数据: 美国4月非农就业人口变动 26.3万人,预期 19万人,前值 19.6万人。 美国4月失业率 3.6%,预期 3.8%,前值 3…...

    2024/4/25 18:38:39
  9. 【原油贵金属早评】库存继续增加,油价收跌

    原标题:【原油贵金属早评】库存继续增加,油价收跌周三清晨公布美国当周API原油库存数据,上周原油库存增加281万桶至4.692亿桶,增幅超过预期的74.4万桶。且有消息人士称,沙特阿美据悉将于6月向亚洲炼油厂额外出售更多原油,印度炼油商预计将每日获得至多20万桶的额外原油供…...

    2024/4/25 18:39:23
  10. 【外汇早评】日本央行会议纪要不改日元强势

    原标题:【外汇早评】日本央行会议纪要不改日元强势近两日日元大幅走强与近期市场风险情绪上升,避险资金回流日元有关,也与前一段时间的美日贸易谈判给日本缓冲期,日本方面对汇率问题也避免继续贬值有关。虽然今日早间日本央行公布的利率会议纪要仍然是支持宽松政策,但这符…...

    2024/4/25 18:39:22
  11. 【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响

    原标题:【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响近日伊朗局势升温,导致市场担忧影响原油供给,油价试图反弹。此时OPEC表态稳定市场。据消息人士透露,沙特6月石油出口料将低于700万桶/日,沙特已经收到石油消费国提出的6月份扩大出口的“适度要求”,沙特将满…...

    2024/4/25 18:39:22
  12. 【外汇早评】美欲与伊朗重谈协议

    原标题:【外汇早评】美欲与伊朗重谈协议美国对伊朗的制裁遭到伊朗的抗议,昨日伊朗方面提出将部分退出伊核协议。而此行为又遭到欧洲方面对伊朗的谴责和警告,伊朗外长昨日回应称,欧洲国家履行它们的义务,伊核协议就能保证存续。据传闻伊朗的导弹已经对准了以色列和美国的航…...

    2024/4/25 18:39:20
  13. 【原油贵金属早评】波动率飙升,市场情绪动荡

    原标题:【原油贵金属早评】波动率飙升,市场情绪动荡因中美贸易谈判不安情绪影响,金融市场各资产品种出现明显的波动。随着美国与中方开启第十一轮谈判之际,美国按照既定计划向中国2000亿商品征收25%的关税,市场情绪有所平复,已经开始接受这一事实。虽然波动率-恐慌指数VI…...

    2024/4/25 16:48:44
  14. 【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试

    原标题:【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试美国和伊朗的局势继续升温,市场风险情绪上升,避险黄金有向上突破阻力的迹象。原油方面稍显平稳,近期美国和OPEC加大供给及市场需求回落的影响,伊朗局势并未推升油价走强。近期中美贸易谈判摩擦再度升级,美国对中…...

    2024/4/26 16:00:35
  15. 【原油贵金属早评】市场情绪继续恶化,黄金上破

    原标题:【原油贵金属早评】市场情绪继续恶化,黄金上破周初中国针对于美国加征关税的进行的反制措施引发市场情绪的大幅波动,人民币汇率出现大幅的贬值动能,金融市场受到非常明显的冲击。尤其是波动率起来之后,对于股市的表现尤其不安。隔夜美国股市出现明显的下行走势,这…...

    2024/4/25 18:39:16
  16. 【外汇早评】美伊僵持,风险情绪继续升温

    原标题:【外汇早评】美伊僵持,风险情绪继续升温昨日沙特两艘油轮再次发生爆炸事件,导致波斯湾局势进一步恶化,市场担忧美伊可能会出现摩擦生火,避险品种获得支撑,黄金和日元大幅走强。美指受中美贸易问题影响而在低位震荡。继5月12日,四艘商船在阿联酋领海附近的阿曼湾、…...

    2024/4/25 18:39:16
  17. 【原油贵金属早评】贸易冲突导致需求低迷,油价弱势

    原标题:【原油贵金属早评】贸易冲突导致需求低迷,油价弱势近日虽然伊朗局势升温,中东地区几起油船被袭击事件影响,但油价并未走高,而是出于调整结构中。由于市场预期局势失控的可能性较低,而中美贸易问题导致的全球经济衰退风险更大,需求会持续低迷,因此油价调整压力较…...

    2024/4/25 0:00:17
  18. 氧生福地 玩美北湖(上)——为时光守候两千年

    原标题:氧生福地 玩美北湖(上)——为时光守候两千年一次说走就走的旅行,只有一张高铁票的距离~ 所以,湖南郴州,我来了~ 从广州南站出发,一个半小时就到达郴州西站了。在动车上,同时改票的南风兄和我居然被分到了一个车厢,所以一路非常愉快地聊了过来。 挺好,最起…...

    2024/4/25 4:19:21
  19. 氧生福地 玩美北湖(中)——永春梯田里的美与鲜

    原标题:氧生福地 玩美北湖(中)——永春梯田里的美与鲜一觉醒来,因为大家太爱“美”照,在柳毅山庄去寻找龙女而错过了早餐时间。近十点,向导坏坏还是带着饥肠辘辘的我们去吃郴州最富有盛名的“鱼头粉”。说这是“十二分推荐”,到郴州必吃的美食之一。 哇塞!那个味美香甜…...

    2024/4/25 18:39:14
  20. 氧生福地 玩美北湖(下)——奔跑吧骚年!

    原标题:氧生福地 玩美北湖(下)——奔跑吧骚年!让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 啊……啊……啊 两…...

    2024/4/25 18:39:12
  21. 扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!

    原标题:扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!扒开伪装医用面膜,翻六倍价格宰客!当行业里的某一品项火爆了,就会有很多商家蹭热度,装逼忽悠,最近火爆朋友圈的医用面膜,被沾上了污点,到底怎么回事呢? “比普通面膜安全、效果好!痘痘、痘印、敏感肌都能用…...

    2024/4/25 2:10:52
  22. 「发现」铁皮石斛仙草之神奇功效用于医用面膜

    原标题:「发现」铁皮石斛仙草之神奇功效用于医用面膜丽彦妆铁皮石斛医用面膜|石斛多糖无菌修护补水贴19大优势: 1、铁皮石斛:自唐宋以来,一直被列为皇室贡品,铁皮石斛生于海拔1600米的悬崖峭壁之上,繁殖力差,产量极低,所以古代仅供皇室、贵族享用 2、铁皮石斛自古民间…...

    2024/4/25 18:39:00
  23. 丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者

    原标题:丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者【公司简介】 广州华彬企业隶属香港华彬集团有限公司,专注美业21年,其旗下品牌: 「圣茵美」私密荷尔蒙抗衰,产后修复 「圣仪轩」私密荷尔蒙抗衰,产后修复 「花茵莳」私密荷尔蒙抗衰,产后修复 「丽彦妆」专注医学护…...

    2024/4/25 13:19:01
  24. 广州械字号面膜生产厂家OEM/ODM4项须知!

    原标题:广州械字号面膜生产厂家OEM/ODM4项须知!广州械字号面膜生产厂家OEM/ODM流程及注意事项解读: 械字号医用面膜,其实在我国并没有严格的定义,通常我们说的医美面膜指的应该是一种「医用敷料」,也就是说,医用面膜其实算作「医疗器械」的一种,又称「医用冷敷贴」。 …...

    2024/4/25 18:38:58
  25. 械字号医用眼膜缓解用眼过度到底有无作用?

    原标题:械字号医用眼膜缓解用眼过度到底有无作用?医用眼膜/械字号眼膜/医用冷敷眼贴 凝胶层为亲水高分子材料,含70%以上的水分。体表皮肤温度传导到本产品的凝胶层,热量被凝胶内水分子吸收,通过水分的蒸发带走大量的热量,可迅速地降低体表皮肤局部温度,减轻局部皮肤的灼…...

    2024/4/25 18:38:57
  26. 配置失败还原请勿关闭计算机,电脑开机屏幕上面显示,配置失败还原更改 请勿关闭计算机 开不了机 这个问题怎么办...

    解析如下&#xff1a;1、长按电脑电源键直至关机&#xff0c;然后再按一次电源健重启电脑&#xff0c;按F8健进入安全模式2、安全模式下进入Windows系统桌面后&#xff0c;按住“winR”打开运行窗口&#xff0c;输入“services.msc”打开服务设置3、在服务界面&#xff0c;选中…...

    2022/11/19 21:17:18
  27. 错误使用 reshape要执行 RESHAPE,请勿更改元素数目。

    %读入6幅图像&#xff08;每一幅图像的大小是564*564&#xff09; f1 imread(WashingtonDC_Band1_564.tif); subplot(3,2,1),imshow(f1); f2 imread(WashingtonDC_Band2_564.tif); subplot(3,2,2),imshow(f2); f3 imread(WashingtonDC_Band3_564.tif); subplot(3,2,3),imsho…...

    2022/11/19 21:17:16
  28. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机...

    win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”问题的解决方法在win7系统关机时如果有升级系统的或者其他需要会直接进入一个 等待界面&#xff0c;在等待界面中我们需要等待操作结束才能关机&#xff0c;虽然这比较麻烦&#xff0c;但是对系统进行配置和升级…...

    2022/11/19 21:17:15
  29. 台式电脑显示配置100%请勿关闭计算机,“准备配置windows 请勿关闭计算机”的解决方法...

    有不少用户在重装Win7系统或更新系统后会遇到“准备配置windows&#xff0c;请勿关闭计算机”的提示&#xff0c;要过很久才能进入系统&#xff0c;有的用户甚至几个小时也无法进入&#xff0c;下面就教大家这个问题的解决方法。第一种方法&#xff1a;我们首先在左下角的“开始…...

    2022/11/19 21:17:14
  30. win7 正在配置 请勿关闭计算机,怎么办Win7开机显示正在配置Windows Update请勿关机...

    置信有很多用户都跟小编一样遇到过这样的问题&#xff0c;电脑时发现开机屏幕显现“正在配置Windows Update&#xff0c;请勿关机”(如下图所示)&#xff0c;而且还需求等大约5分钟才干进入系统。这是怎样回事呢&#xff1f;一切都是正常操作的&#xff0c;为什么开时机呈现“正…...

    2022/11/19 21:17:13
  31. 准备配置windows 请勿关闭计算机 蓝屏,Win7开机总是出现提示“配置Windows请勿关机”...

    Win7系统开机启动时总是出现“配置Windows请勿关机”的提示&#xff0c;没过几秒后电脑自动重启&#xff0c;每次开机都这样无法进入系统&#xff0c;此时碰到这种现象的用户就可以使用以下5种方法解决问题。方法一&#xff1a;开机按下F8&#xff0c;在出现的Windows高级启动选…...

    2022/11/19 21:17:12
  32. 准备windows请勿关闭计算机要多久,windows10系统提示正在准备windows请勿关闭计算机怎么办...

    有不少windows10系统用户反映说碰到这样一个情况&#xff0c;就是电脑提示正在准备windows请勿关闭计算机&#xff0c;碰到这样的问题该怎么解决呢&#xff0c;现在小编就给大家分享一下windows10系统提示正在准备windows请勿关闭计算机的具体第一种方法&#xff1a;1、2、依次…...

    2022/11/19 21:17:11
  33. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”的解决方法...

    今天和大家分享一下win7系统重装了Win7旗舰版系统后&#xff0c;每次关机的时候桌面上都会显示一个“配置Windows Update的界面&#xff0c;提示请勿关闭计算机”&#xff0c;每次停留好几分钟才能正常关机&#xff0c;导致什么情况引起的呢&#xff1f;出现配置Windows Update…...

    2022/11/19 21:17:10
  34. 电脑桌面一直是清理请关闭计算机,windows7一直卡在清理 请勿关闭计算机-win7清理请勿关机,win7配置更新35%不动...

    只能是等着&#xff0c;别无他法。说是卡着如果你看硬盘灯应该在读写。如果从 Win 10 无法正常回滚&#xff0c;只能是考虑备份数据后重装系统了。解决来方案一&#xff1a;管理员运行cmd&#xff1a;net stop WuAuServcd %windir%ren SoftwareDistribution SDoldnet start WuA…...

    2022/11/19 21:17:09
  35. 计算机配置更新不起,电脑提示“配置Windows Update请勿关闭计算机”怎么办?

    原标题&#xff1a;电脑提示“配置Windows Update请勿关闭计算机”怎么办&#xff1f;win7系统中在开机与关闭的时候总是显示“配置windows update请勿关闭计算机”相信有不少朋友都曾遇到过一次两次还能忍但经常遇到就叫人感到心烦了遇到这种问题怎么办呢&#xff1f;一般的方…...

    2022/11/19 21:17:08
  36. 计算机正在配置无法关机,关机提示 windows7 正在配置windows 请勿关闭计算机 ,然后等了一晚上也没有关掉。现在电脑无法正常关机...

    关机提示 windows7 正在配置windows 请勿关闭计算机 &#xff0c;然后等了一晚上也没有关掉。现在电脑无法正常关机以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;关机提示 windows7 正在配…...

    2022/11/19 21:17:05
  37. 钉钉提示请勿通过开发者调试模式_钉钉请勿通过开发者调试模式是真的吗好不好用...

    钉钉请勿通过开发者调试模式是真的吗好不好用 更新时间:2020-04-20 22:24:19 浏览次数:729次 区域: 南阳 > 卧龙 列举网提醒您:为保障您的权益,请不要提前支付任何费用! 虚拟位置外设器!!轨迹模拟&虚拟位置外设神器 专业用于:钉钉,外勤365,红圈通,企业微信和…...

    2022/11/19 21:17:05
  38. 配置失败还原请勿关闭计算机怎么办,win7系统出现“配置windows update失败 还原更改 请勿关闭计算机”,长时间没反应,无法进入系统的解决方案...

    前几天班里有位学生电脑(windows 7系统)出问题了&#xff0c;具体表现是开机时一直停留在“配置windows update失败 还原更改 请勿关闭计算机”这个界面&#xff0c;长时间没反应&#xff0c;无法进入系统。这个问题原来帮其他同学也解决过&#xff0c;网上搜了不少资料&#x…...

    2022/11/19 21:17:04
  39. 一个电脑无法关闭计算机你应该怎么办,电脑显示“清理请勿关闭计算机”怎么办?...

    本文为你提供了3个有效解决电脑显示“清理请勿关闭计算机”问题的方法&#xff0c;并在最后教给你1种保护系统安全的好方法&#xff0c;一起来看看&#xff01;电脑出现“清理请勿关闭计算机”在Windows 7(SP1)和Windows Server 2008 R2 SP1中&#xff0c;添加了1个新功能在“磁…...

    2022/11/19 21:17:03
  40. 请勿关闭计算机还原更改要多久,电脑显示:配置windows更新失败,正在还原更改,请勿关闭计算机怎么办...

    许多用户在长期不使用电脑的时候&#xff0c;开启电脑发现电脑显示&#xff1a;配置windows更新失败&#xff0c;正在还原更改&#xff0c;请勿关闭计算机。。.这要怎么办呢&#xff1f;下面小编就带着大家一起看看吧&#xff01;如果能够正常进入系统&#xff0c;建议您暂时移…...

    2022/11/19 21:17:02
  41. 还原更改请勿关闭计算机 要多久,配置windows update失败 还原更改 请勿关闭计算机,电脑开机后一直显示以...

    配置windows update失败 还原更改 请勿关闭计算机&#xff0c;电脑开机后一直显示以以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;配置windows update失败 还原更改 请勿关闭计算机&#x…...

    2022/11/19 21:17:01
  42. 电脑配置中请勿关闭计算机怎么办,准备配置windows请勿关闭计算机一直显示怎么办【图解】...

    不知道大家有没有遇到过这样的一个问题&#xff0c;就是我们的win7系统在关机的时候&#xff0c;总是喜欢显示“准备配置windows&#xff0c;请勿关机”这样的一个页面&#xff0c;没有什么大碍&#xff0c;但是如果一直等着的话就要两个小时甚至更久都关不了机&#xff0c;非常…...

    2022/11/19 21:17:00
  43. 正在准备配置请勿关闭计算机,正在准备配置windows请勿关闭计算机时间长了解决教程...

    当电脑出现正在准备配置windows请勿关闭计算机时&#xff0c;一般是您正对windows进行升级&#xff0c;但是这个要是长时间没有反应&#xff0c;我们不能再傻等下去了。可能是电脑出了别的问题了&#xff0c;来看看教程的说法。正在准备配置windows请勿关闭计算机时间长了方法一…...

    2022/11/19 21:16:59
  44. 配置失败还原请勿关闭计算机,配置Windows Update失败,还原更改请勿关闭计算机...

    我们使用电脑的过程中有时会遇到这种情况&#xff0c;当我们打开电脑之后&#xff0c;发现一直停留在一个界面&#xff1a;“配置Windows Update失败&#xff0c;还原更改请勿关闭计算机”&#xff0c;等了许久还是无法进入系统。如果我们遇到此类问题应该如何解决呢&#xff0…...

    2022/11/19 21:16:58
  45. 如何在iPhone上关闭“请勿打扰”

    Apple’s “Do Not Disturb While Driving” is a potentially lifesaving iPhone feature, but it doesn’t always turn on automatically at the appropriate time. For example, you might be a passenger in a moving car, but your iPhone may think you’re the one dri…...

    2022/11/19 21:16:57