1. 概述

在前面的文章中,我们把 refresh() 中 obtainFreshBeanFactory、invokeBeanFactoryPostProcessors、方法都梳理了一遍,其中还有一个 registerBeanPostProcessors,但个人觉得实际上和 invokeBeanFactoryPostProcessors类似,所以没有单独写一篇文章去描述的,尽管它非常重要,但是你可以自己去分析一下或者看看这篇文章:registerBeanPostProcessors 详解。

refresh() 中另外一个非常重要的方法就是 finishBeanFactoryInitialization,其中涉及到 Bean 实例化过程(即已经注册的 BeanDefinition)以及 BeanPostProcessor(实现了 BeanPostProcessor 接口的 Bean)的执行。

2. 再谈 BeanDefinition

在 Bean 的实例化之前,再来讲讲 BeanDefinition,前面说过 BeanDefinition 描述一个 Bean 的实例信息,但实际上它是一个接口,给这些信息定义了一些 getter 和 setter 方法,作为接口层它不负责定义具体的参数,AbstractBeanDefinition对这个接口进行了实现,如下:

image-20211029153956374

这个时候应该会存在一个疑问,就是为什么 Spring 实例化要通过 BeanDefinition,Claas不也是类对象吗?因为Class 是无法完成 bean 的抽象,比如 bean 的作用域,bean 的注入模型,bean 是否是懒加载等等信息,Class是无法抽象出来的,故而需要一个 BeanDefinition 类来抽象这些信息。

2.1 AbstractBeanDefinition

AbstractBeanDefinitionBeanDefinition 的直接实现类,从类名也知道,它是一个抽象的 BeanDefinition,还不够具体。

AbstractBeanDefinition 已经对 BeanDefinition 方法有了基本实现逻辑,增加了许多新的属性(默认属性)。

2.2 RootBeanDefinition

从 Spring2.5 开始,RootBeanDefinition仅作为运行时的BeanDefinition视图。如果需要编程定义BeanDefinition,那么推荐使用GenericBeanDefinition。

Spring的解释是:GenericBeanDefinition的优势在于,它允许动态定义父依赖项,而不是一个以"硬编码"定义BeanDefinition的角色。也就是说,bean的一般形式是以GenericBeanDefinition的标准形式存在的,在特定的时机,会将GenericBeanDefinition转成RootBeanDefinition。

这段官方的解释第一次看着实让人摸不着头脑,但我们只需要记住这个 RootBeanDefinition 在实例化一个 Bean 的时候,需要与其他的 BeanDefinition 进行合并,也就是说 BeanDefinition 存在父子关系,对应的属性为parentName

而合并的目的就是不同的 BeanDefinition 存在共性,比如这个 Bean 继承了某个类或继承了某个 Bean,亦或是实现了某个接口,而合并过程就是把存在父子关系的 BeanDefinition 的属性合并起来,如果存在相同的属性,则以子Beanfinition的属性为准。

一般情况下,在 bean 的初始化时,会将 BeanDefinition 转换成 RootBeanDefinition。

至于为什么要合并,我觉得可能是为了解析成一个完整的 beandefinition 吧。

栗子

假如有这样一个动物抽象类:

@Data
public abstract class Animal {private String name;
}

然后定义一个子类 Cat:

@Data
public class Cat extends Animal{private String name;
}

然后我们的 xml 配置如下:

<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xmlns:aop="http://www.springframework.org/schema/aop"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop-3.2.xsd"default-lazy-init="false"><bean id="animal" class="net.javatv.bean.Animal" abstract="true"><property name="name" value="animal"></property></bean><bean id="cat" class="net.javatv.bean.Cat" parent="animal"/></beans>

写个测试类:

@Test
public void test3() {ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");Cat cat = applicationContext.getBean(Cat.class);System.out.println(cat.getName());
}

可以看到 cat 的 name 属性也被赋值成了animal:

image-20211029162746482

这种方式常用在一些公共的配置上,如连接池的配置,多数据源的配置等等。而这个合并的过程就在实例化bean 的过程中。

需要注意的是,RootBeanDefinition 可以单独作为一个BeanDefinition,也可以作为其他 BeanDefinition 的父类。但是他不能作为其他 BeanDefinition 的子类,这点在源码中可以很好的体现,在 setParentName 的时候,会抛出一个异常:

image-20211029161335449

2.3 ChildBeanDefinition

ChildBeanDefinition相当于一个子类,不可以单独存在,必须要依赖一个父 BeanDetintion。通过源码发现其最大的区别他的 parentName 属性是通过构造方法设置的,而且并没有提供一个无参构造方法给我们。

从 spring 2.5 开始,提供了一个更好的注册bean definition类GenericBeanDefinition,所以以后推荐使用它(这里就不做过多解释了)。

2.4 GenericBeanDefinition

GenericBeanDefinition 同样继承了 AbstractBeanDefinition,其实也就是一个普通的 BeanDefinition ,和另外 2 个不同的是,它有两个比较重要的子类:

  • AnnotatedGenericBeanDefinition,存储 @Configuration注解注释的类;
  • ScannedGenericBeanDefinition,存储@Component@Service@Controller等注解注释的类。

image-20211029170502493

2.5 BeanDefinition中的属性

  1. id:Bean 的唯一标识名。它必须是合法的 XMLID,在整个 XML 文档中唯一。
  2. name:用来为 id 创建一个或多个别名。它可以是任意的字母符合。多个别名之间用逗号或空格分开。
  3. class:用来定义类的全限定名(包名+类名)。只有子类 Bean 不用定义该属性。
  4. parent:子类 Bean 定义它所引用它的父类 Bean。这时前面的 class 属性失效。子类 Bean 会继承父类 Bean 的所有属性,子类 Bean 也可以覆盖父类 Bean 的属性。注意:子类 Bean 和父类 Bean 是同一个 Java 类。
  5. abstract(默认为false):用来定义 Bean 是否为抽象 Bean。它表示这个 Bean 将不会被实例化,一般用于父类 Bean,因为父类 Bean 主要是供子类 Bean 继承使用。
  6. lazy-init(默认为default):用来定义这个 Bean 是否实现懒初始化。如果为“true”,它将在 BeanFactory 启动时初始化所有的 SingletonBean。反之,如果为“false”,它只在 Bean 请求时才开始创建 SingletonBean。
  7. autowire(自动装配,默认为default):它定义了 Bean 的自动装载方式。
    • no:不使用自动装配功能。2、“byName”:通过 Bean 的属性名实现自动装配。
    • byType:通过 Bean 的类型实现自动装配。
    • constructor:类似于 byType,但它是用于构造函数的参数的自动组装。
    • autodetect:通过 Bean 类的反省机制(introspection)决定是使用 constructor 还是使用 byType。
  8. depends-on(依赖对象):这个 Bean 在初始化时依赖的对象,这个对象会在这个 Bean 初始化之前创建。
  9. init-method:用来定义 Bean 的初始化方法,它会在 Bean 组装之后调用。它必须是一个无参数的方法。
  10. destroy-method:用来定义 Bean 的销毁方法,它在 BeanFactory 关闭时调用。同样,它也必须是一个无参数的方法。它只能应用于 singletonBean。
  11. factory-method:定义创建该 Bean 对象的工厂方法。它用于下面的 factory-bean,表示这个 Bean 是通过工厂方法创建。此时,class 属性失效。
  12. factory-bean:定义创建该 Bean 对象的工厂类。如果使用了“factory-bean”则“class”属性失效。
  13. autowire-candidate:采用 xml 格式配置 bean 时,将<bean/>元素的 autowire-candidate属性设置为 false,这样容器在查找自动装配对象时,将不考虑该 bean,即它不会被考虑作为其它 bean 自动装配的候选者,但是该 bean 本身还是可以使用自动装配来注入其它 bean 的。
  14. MutablePropertyValues:用于封装<property>标签的信息,其实类里面就是有一个 list,list里面是 PropertyValue 对象,PropertyValue 就是一个 name 和 value 属性,用于封装<property>标签的名称和值信息。
  15. ConstructorArgumentValues:用于封装<constructor-arg>标签的信息,其实类里面就是有一个 map,map 中用构造函数的参数顺序作为 key,值作为 value 存储到 map 中。
  16. MethodOverrides:用于封装 lookup-method 和 replaced-method 标签的信息,同样的类里面有一个 Set 对象添加 LookupOverride 对象和 ReplaceOverride 对象。

3. finishBeanFactoryInitialization

AbstractApplicationContext#finishBeanFactoryInitialization()

该方法从名字上就可以知道是用来完成 Bean 工厂初始化的,即 Bean 的实例化,我们进入该方法,主要是看AbstractApplicationContext#preInstantiateSingletons()

image-20211101095025374

进入 preInstantiateSingletons() 方法:

public void preInstantiateSingletons() throws BeansException {if (logger.isTraceEnabled()) {logger.trace("Pre-instantiating singletons in " + this);}// Iterate over a copy to allow for init methods which in turn register new bean definitions.// While this may not be part of the regular factory bootstrap, it does otherwise work fine.//xml解析时,讲过,把所有beanName都缓存到beanDefinitionNames了List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);// Trigger initialization of all non-lazy singleton beans...for (String beanName : beanNames) {//把父BeanDefinition里面的属性拿到子BeanDefinition中,也就是上面讲的BeanDefinition合并//该方法可自行具体研究,这里暂不讨论RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);//如果不是抽象的,单例的,非懒加载的就实例化if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {//判断bean是否实现了 FactoryBean 接口,这里可以先不看if (isFactoryBean(beanName)) {Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);if (bean instanceof FactoryBean) {FactoryBean<?> factory = (FactoryBean<?>) bean;boolean isEagerInit;if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {isEagerInit = AccessController.doPrivileged((PrivilegedAction<Boolean>) ((SmartFactoryBean<?>) factory)::isEagerInit,getAccessControlContext());} else {isEagerInit = (factory instanceof SmartFactoryBean &&((SmartFactoryBean<?>) factory).isEagerInit());}if (isEagerInit) {getBean(beanName);}}} else {//主要看实例化过程,如果不是FactoryBean,则及时普通的BeangetBean(beanName);}}}// Trigger post-initialization callback for all applicable beans...for (String beanName : beanNames) {Object singletonInstance = getSingleton(beanName);if (singletonInstance instanceof SmartInitializingSingleton) {SmartInitializingSingleton smartSingleton = (SmartInitializingSingleton) singletonInstance;if (System.getSecurityManager() != null) {AccessController.doPrivileged((PrivilegedAction<Object>) () -> {smartSingleton.afterSingletonsInstantiated();return null;}, getAccessControlContext());} else {smartSingleton.afterSingletonsInstantiated();}}}
}

3.1 getBean

按照上面的分析过程,进入AbstractBeanFactory#getBean()

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

可以看到调用了doGetBean方法,在 Spring 中一般涉及到 doXxx就是真正干实事的方法,而该方法也不是说从头看到底,我们找到最关键的代码出,如下:

image-20211101112053973

为什么先看这里,在我们学习 Spring 的时候,尽管没看过源码我们也知道 Spring 创建实例默认是单例的,也就是说大多数情况下都是单例 Bean。

3.2 getSingleton

DefaultSingletonBeanRegistry#getSingleton()

初次实例化 bean 通过 getSingleton 方法:

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {Assert.notNull(beanName, "Bean name must not be null");synchronized (this.singletonObjects) {// 从 一级缓存 中拿取实例,如果存在直接返回,或者创建 bean// 常见的就是容器创建的时候创建实例,然后如果通过@Autowired注入,则会在次调用getBean,从缓存中拿取实例Object singletonObject = this.singletonObjects.get(beanName);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!)");}if (logger.isDebugEnabled()) {logger.debug("Creating shared instance of singleton bean '" + beanName + "'");}/*** 把 beanName 添加到 singletonsCurrentlyInCreation Set 容器中,在这个集合里面的 bean 都是正在实例化的* 实际就是实例化还没完成的 BeanName*/beforeSingletonCreation(beanName);boolean newSingleton = false;boolean recordSuppressedExceptions = (this.suppressedExceptions == null);if (recordSuppressedExceptions) {this.suppressedExceptions = new LinkedHashSet<>();}try {// 调到 getObject 方法,完成 bean 的实例化,即调用createBean()方法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;}afterSingletonCreation(beanName);}if (newSingleton) {/*** 到这里,Bean已经完成实例化,然后要做 2 件事* 1、singletonsCurrentlyInCreation 把 beanName 从这个集合中删除* 2、addSingleton,把 bean 缓存到一级缓存中*/addSingleton(beanName, singletonObject);}}return singletonObject;}
}

这里存在 2 种情况,第一是可以直接从缓存中拿取实例,第二种就是需要创建实例,调用getObject(),我们先看第二种情况,调用链路往下为AbstractAutowireCapableBeanFactory#createBean()

3.3 createBean

在该方法中,主要看doCreateBean()

image-20211101150427551

可以看到,这里出现了一个新的接口BeanWrapper,其实就是一个 Bean 的包装器,包括对 Bean 的属性、方法,数据等,同时它还具备属性转换的能力,因为它还得是一个类型转换器,它有唯一的一个实现类是BeanWrapperImpl,具体的可以看看这篇文章:Spring中的BeanWrapper及类型转换。

那么他和 BeanDefinition有什么关系呢?

BeanDefinition(原料)->BeanFactory(工厂)->BeanWrapper(产品)

然后,进入 Bean 实例化核心方法AbstractAutowireCapableBeanFactory#createBeanInstance(),并且包装成 BeanWrapper 对象。

4. 实例化的几种情况

在进入createBeanInstance()方法之前,先来讲讲在实例化过程中的几种情况:

  1. factoryMethodName,即 BeanDefinition 中的 factoryMethodName 属性;
  2. 有 @Autowired 有参构造函数;
  3. 无 @Autowired 有参构造函数;
  4. 无参构造函数。

4.1 factoryMethodName

在 BeanDefinition 有 factory-method 属性的会实例化。在源码中体现如下:

if (mbd.getFactoryMethodName() != null) {return instantiateUsingFactoryMethod(beanName, mbd, args);
}

举个栗子

新建 2 个类,并且不添加 @Conponent 注解:

FactoryMethodBean

public class FactoryMethodBean {public Object method() {return new Handsome();}
}

Handsome

import lombok.Data;@Data
public class Handsome {private String name = "ayue";
}

xml

<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xmlns:aop="http://www.springframework.org/schema/aop"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop-3.2.xsd"default-lazy-init="false"><bean id="factoryMethodBean" class="net.javatv.bean.FactoryMethodBean"/><!-- 没有class属性 --><bean id="handsome" factory-bean="factoryMethodBean" factory-method="method"/></beans>

测试类

@Test
public void test4() {ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");Handsome man = applicationContext.getBean(Handsome.class);System.out.println(man.getName());
}

根据断点定位可以看到:

image-20211101171002709

也就是说 Handsome 类也被实例化了,底层源码就是通过反射来创建实例,这里就不贴源码了,可自行分析。

其中需要注意的是,factoryMethodName 除了上面的方式,还有一种就是类的静态方法也是可以实例化的,如下:

import lombok.Data;@Data
public class Handsome {private String name = "ayue";public static Object method() {return new Handsome();}
}

另外,Spring 会扫描有@Bean注解的方法,然后把方法名称设置到 BeanDefinition 的 factoryMethod 属性中,然后就会调到上面的方法实现 @Bean方法的调用,@Bean的具体流程后续在分析。

4.2 @Autowired 有参构造函数

如果不是 factoryMethodName ,则进入下一个方法determineConstructorsFromBeanPostProcessors,这 个 方法是 BeanPostProcessor 接口类的应用,最终会调到 AutowiredAnnotationBeanPostProcessor 类的方法, 在方法中会扫描有注解的构造函数然后完成装配过程,然后把有有@Autowired 注解的构造函数返回。

举个栗子

实例 A

import lombok.Data;
import org.springframework.stereotype.Component;@Data
@Component
public class A {private String name = "a";
}

实例 B

import lombok.Data;
import org.springframework.stereotype.Component;@Data
@Component
public class B {private String name = "b";
}

@Autowired 有参构造函数

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;@Component
public class AutowiredConstructBean {//只有一个构造函数的情况@Autowiredpublic AutowiredConstructBean(A a, B b) {System.out.println(a.getName());System.out.println(b.getName());}
}

测试结果:

image-20211102090615797

可以看到 A 和 B 都被实例化。

此时有另外一个问题,如果有两个构造函数呢?如下:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;@Component
public class AutowiredConstructBean {@Autowiredpublic AutowiredConstructBean(A a, B b) {System.out.println(a.getName());System.out.println(b.getName());}@Autowiredpublic AutowiredConstructBean(A a) {System.out.println(a.getName());}
}

结果如下:

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'autowiredConstructBean': Invalid autowire-marked constructor: public net.javatv.autowired.AutowiredConstructBean(net.javatv.autowired.A). Found constructor with 'required' Autowired annotation already: public net.javatv.autowired.AutowiredConstructBean(net.javatv.autowired.A,net.javatv.autowired.B)

image-20211102093052981

意思是已经存在了构造器,所以直接抛出异常了。

我们可进入源码分析AbstractAutowireCapableBeanFactory#determineConstructorsFromBeanPostProcessors()

protected Constructor<?>[] determineConstructorsFromBeanPostProcessors(@Nullable Class<?> beanClass, String beanName)throws BeansException {if (beanClass != null && hasInstantiationAwareBeanPostProcessors()) {for (BeanPostProcessor bp : getBeanPostProcessors()) {if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;Constructor<?>[] ctors = ibp.determineCandidateConstructors(beanClass, beanName);if (ctors != null) {return ctors;}}}}return null;
}

然后进入AutowiredAnnotationBeanPostProcessor#determineCandidateConstructors()方法:

1、其中第一个 if 代码块我们可以忽略,即 @Lookup 方法注入,因为不怎么使用了,如果你想了解,可以看这篇文章:Spring中的Lookup

image-20211102094749128

2、获取有参构造函数

image-20211102102700459

从代码 debug 也能看到:

image-20211102102959585

3、判断构造函数是否有 @Autowired 注解

image-20211102103657626

此时,涉及到多个构造函数的情况,如果只有一个且@Autowired 的 required 属性为 true,则正常执行。如果存在多个构造函数且required=true,就会进入requiredConstructor != null为 true,抛出异常,即上面代码演示的情况。

因此,如果要解决多个构造函数不抛异常则需要设置required=false,此时该段代码并不会执行:

image-20211102105703048

代码演示如下:

@Component
public class AutowiredConstructBean {@Autowired(required = false)public AutowiredConstructBean(A a, B b) {System.out.println(a.getName());System.out.println(b.getName());}@Autowired(required = false)public AutowiredConstructBean(A a) {System.out.println("第 2 个构造函数" + a.getName());}
}

再去看看结果:

image-20211102105405066

从结果可以看出,有 2 个参数的构造函数打印出了结果,而 1 个参数的并没有打印出来。这是为什么呢?

这是因为 Spring 做了一个排序操作并且取了参数最多的构造函数来实例化,具体方法在ConstructorResolver#autowireConstructor中的这段代码:

AutowireUtils.sortConstructors(candidates);

同时,在拿到构造函数以后,通过 autowireConstructor()进行参数的解析和实例化,这个过程相对来说比较复杂,我们先知道大致流程即可,但最后实例化的时候仍然调用的是 getBean() 方法来实例化的。

实际上,不管是 Field、Method、还是构造函数中有@Autowired 注解引入的类,都是通过 getBean() 方法进行实例化的。

4.3 无 @Autowired 有参构造函数

对于没有 @Autowired 注解的有参构造函数,当只有一个有参构造函数的时候正常执行,但有 2 个的时候同样会报错,但不同于上述错误。

代码演示:

@Component
public class AutowiredConstructBean {// 去掉@Autowired注解public AutowiredConstructBean(A a, B b) {System.out.println(a.getName());System.out.println(b.getName());}public AutowiredConstructBean(A a) {System.out.println("第 2 个构造函数" + a.getName());}
}

错误显示找不到默认的构造函数:

image-20211102114206559

为什么会出现这种情况,通过源码分析,经过一系列的判断之后走向了如下方法:

image-20211102114548232

即最后返回了空的构造器,所以抛出异常,那么怎么解决呢?

其实只要添加一个无参构造器就行了。

@Component
public class AutowiredConstructBean {public AutowiredConstructBean() {System.out.println("添加无参构造函数");}public AutowiredConstructBean(A a, B b) {System.out.println(a.getName());System.out.println(b.getName());}public AutowiredConstructBean(A a) {System.out.println("第 2 个构造函数" + a.getName());}
}

这也说明了一个问题,就是在 Spring 中,如果你的实体类是有参的构造函数,那么必须添加一个无参的构造函数。如果没有那就是默认的无参构造函数,也就是最常见的第 4 种情况。

4.4 无参构造函数

无参构造函数实例化,就是AbstractAutowireCapableBeanFactory#instantiateBean(),其实现就是简单的反射实现,并且大部分情况都是通过该种方式实现。

5. IOC/DI

在实例化完成之后,需要对类中的属性进行依赖注入操作,对于什么是IOC,什么是DI,似乎不应该在这里讲,不管是面试还是学习 IOC 和 DI 都是耳熟能详的,但可能还是有人对两个概念是模糊不清,所以这里总结一下。

5.1 IOC控制反转

首先说说 IOC,中文翻译为控制反转,它并不是一种技术,而是一种思想,那么什么叫反转?有反转那就有正转。

举个栗子

好比我们吃饭,常规情况下我们需要先去买菜,然后还需要准备调料,厨具等,然后还需要自己来炒菜,这一整个过程相当复杂,并且一旦其中缺了一个环节都不能达到目标,类比到对象中就是如果对象 A 需要对象 B,那么在A中我们就需要通过 new A()的方式来获取实例 B,然后使用完之后还需要将对象销毁,比如数据库连接等,耦合度非常高,这种方式可以理解为正转。

而 IOC 的做法则不同,照样是吃饭,但是我们可以在外卖APP上点外卖,APP上有很多可以供我们选择,而我要做的就是我需要吃什么,外卖员送过来即可,如果送错了,那就抛出异常即可,整个做饭的过程都不需要我们来控制,这里的外卖APP就类似一个容器。

Spring 所倡导的开发方式就是如此,所有的类都会在 Spring 容器中登记,告诉 Spring 你是个什么东西,你需要什么东西,然后 Spring 会在系统运行到适当的时候,把你要的东西主动给你,同时也把你交给其他需要你的东西。所有的类的创建、销毁都由 Spring 来控制,也就是说控制对象生存周期的不再是引用它的对象,而是 Spring。对于某个具体的对象而言,以前是它控制其他对象,现在是所有对象都被 Spring 控制,所以这叫控制反转。

5.2 DI 依赖注入

DI,Dependency Injection,即依赖注入,我把他理解为是对 IOC 的实现,按照依赖和注入我们把它拆分一下:

  1. 谁依赖谁

    应用程序依赖于 IOC 容器,这里的应用程序可以是一个类,也可以是一整个项目;

  2. 为什么需要依赖

    应用程序需要依靠 IOC 容器来提供外部所需要的资源(对象等);

  3. 谁注入谁

    IOC 容器注入了某个需要被依赖的资源(对象等);

  4. 注入了什么

    容器外部所需要的资源(对象等)。

举个栗子

对象 A 需要操作数据库,以前我们总是要在 A 中自己编写代码来获得一个 Connection 连接对象,有了 Spring 我们就只需要告诉 Spring,A 中需要一个 Connection,至于这个 Connection 怎么构造,何时构造,A 不需要知道。在系统运行时,Spring 会在适当的时候制造一个 Connection,然后像打针一样,注射到 A 当中,这样就完成了对各个对象之间关系的控制。A需要依赖 Connection才能正常运行,而这个 Connection是由 Spring注入到A中的,依赖注入的名字就这么来的。那么 DI 是如何实现的呢? Java 1.3之后一个重要特征是反射(reflection),它允许程序在运行的时候动态的生成对象、执行对象的方法、改变对象的属性,Spring 就是通过反射来实现注入的。

而在 Spring 中常见的对类的属性或方法的注入方式就是**@Autowired或者@Resource**注解,因此,在反射之前,需要先进行对类中的注解的收集。

6. 注解的收集

关于对 @Autowired 或者 @Resource注解的收集,我们回到 3.3 的doCreateBean()方法中,在实例化之后,通过调用applyMergedBeanDefinitionPostProcessors()方法来完成注入:

image-20211102152746779

进入该方法可以发现,实际上是通过对 BeanPostProcessor 接口来调用的如下:

image-20211102155654166

其中可以看到有 2 个重要的实现类CommonAnnotationBeanPostProcessorAutowiredAnnotationBeanPostProcessor

6.1 @PostConstruct & @PreDestroy

CommonAnnotationBeanPostProcessor 这个类完成了对@Resource 注解的属性或者方法的收集,此外,这个类还对@PostConstruct@PreDestory 支持。

image-20211102161303309

其构造方法如下:

image-20211102161406397

这也就是为什么我们在项目中可能会看到被@PostConstruct注解标记的方法,因为会在实例化的时候加载注入。

进入postProcessMergedBeanDefinition(),具体的收集过程如下:

1、查看缓存里面有没有 InjectionMetadata 对象;

image-20211102163305622

2、循环遍历类中的所有的方法,判断方法上是否有@PostConstruct注解和@PreDestroy 注解,然后通过反射调用,如下图所示:

image-20211102164447221

6.2 @Resource

1、看缓存里面有没有 InjectionMetadata 对象 。

image-20211102162022902

2、从类中获取所有 Field 对象,循环 field 对象,判断 field 有没有@Resource 注解,如果有注解封装成 ResourceElement 对象。

image-20211102165557630

3、最终把两个 field 和 Method 封装的对象集合封装到 InjectionMetadata 对象中。

image-20211102165904897

6.3 @Autowired & @Value

AutowiredAnnotationBeanPostProcessor类是对 @Autowired 和 @Value 注解的属性和方法的收集,构造方法如下:

image-20211102170251618

其方法 findAutowiringMetadata() 是对注解进行解析:

image-20211103092232516

收集过程同@Resource,也是收集 Field 和 Method 上的注解,然后放到 InjectionMetadata 对象中。

image-20211102171354458

7. 依赖注入

Bean 实例化完成、并且收集完@Resource 和@Autowired 注解以后就开始依赖注入,还是在doCreateBean()方法中:

image-20211102171925640

进入AbstractAutowireCapableBeanFactory#populateBean()方法。

7.1 populateBean

protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) {if (bw == null) {if (mbd.hasPropertyValues()) {throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Cannot apply property values to null instance");}else {// Skip property population phase for null instance.return;}}// Give any InstantiationAwareBeanPostProcessors the opportunity to modify the// state of the bean before properties are set. This can be used, for example,// to support styles of field injection./*** 属性填充判断:这里调用了 InstantiationAwareBeanPostProcessor.postProcessAfterInstantiation方法* 给InstantiationAwareBeanPostProcessor最后一次机会在属性设置前来改变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;}}}}PropertyValues pvs = (mbd.hasPropertyValues() ? mbd.getPropertyValues() : null);int resolvedAutowireMode = mbd.getResolvedAutowireMode();// 自动装配:根据名称或类型自动注入,从Spring2.5开始,开始支持使用注解(@Autowired)来自动装配Bean的属性,所以这里不分析if (resolvedAutowireMode == AUTOWIRE_BY_NAME || resolvedAutowireMode == AUTOWIRE_BY_TYPE) {MutablePropertyValues newPvs = new MutablePropertyValues(pvs);// Add property values based on autowire by name if applicable.if (resolvedAutowireMode == AUTOWIRE_BY_NAME) {// 名称注入autowireByName(beanName, mbd, bw, newPvs);}// Add property values based on autowire by type if applicable.if (resolvedAutowireMode == AUTOWIRE_BY_TYPE) {// 类型注入autowireByType(beanName, mbd, bw, newPvs);}pvs = newPvs;}boolean hasInstAwareBpps = hasInstantiationAwareBeanPostProcessors();// 依赖检查boolean needsDepCheck = (mbd.getDependencyCheck() != AbstractBeanDefinition.DEPENDENCY_CHECK_NONE);PropertyDescriptor[] filteredPds = null;if (hasInstAwareBpps) {if (pvs == null) {pvs = mbd.getPropertyValues();}for (BeanPostProcessor bp : getBeanPostProcessors()) {if (bp instanceof InstantiationAwareBeanPostProcessor) {InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;// 依赖注入过程PropertyValues pvsToUse = ibp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName);if (pvsToUse == null) {if (filteredPds == null) {filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);}// 老版本用这个方式去注入pvsToUse = ibp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName);if (pvsToUse == null) {return;}}pvs = pvsToUse;}}}if (needsDepCheck) {if (filteredPds == null) {filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);}// 依赖检查,对应 depends-on 属性,3.0 已弃用,这里不在分析checkDependencies(beanName, mbd, filteredPds, pvs);}if (pvs != null) {// 将属性应用到bean中,这种一般是 XMl properties的方式,现在基本没有使用,所以不在分析applyPropertyValues(beanName, mbd, bw, pvs);}
}

@Autowired为例,当注入了某个类时,如下:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;@Component
public class AutowiredConstructBean {@Autowiredprivate A a;public String init(){return a.getName();}
}

测试类:

@Test
public void test4() {ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");AutowiredConstructBean bean = applicationContext.getBean(AutowiredConstructBean.class);System.out.println(bean.init());
}

输出:

image-20211103094710436

进入 populateBean()源码,先看这段代码:

image-20211103100114983

它会遍历 InstantiationAwareBeanPostProcessor 类型的接口进行注入,而在上面注解的收集讲到了它的实现类之一就是AutowiredAnnotationBeanPostProcessor,所以当存在@Autowired注入时,则进入AutowiredAnnotationBeanPostProcessor#postProcessProperties()方法。

7.2 postProcessProperties

进入该方法,首先会收集 Bean 中是否存在@Autowired注解,并放到 InjectedElement 中:

image-20211103100741639

如上面例子中的 AutowiredConstructBean 中注入了 A,通过debug可以看到:

image-20211103101336963

7.3 inject

收集到注入对象之后开始注入,进入inject() 方法:

image-20211103102634951

debug 上面的例子可以很明显的看到:

image-20211103102555578

然后在进入element.inject循环注入,我们再看看该方法:

image-20211103103108177

需要注意的是,上面方法中的 target 是为 null 的,是因为在该方法中可以看到注入的方式有两种,字段和方法,所以它有 2 个具体的实现方法,如下:

image-20211103105012461

所以我们这里看字段注入,即内部类 AutowiredFieldElement 中的 inject 方法:

image-20211103110940661

该方法简单来说就是拿到需要注入的实例对象,完成注入即可,而对象的获取就是通过 getBean 方法来获取的,在单例模式下,一般来说容器初始化的时候会创建实例,然后通过 @Autowried 注入时,会直接从缓存中获取。

8. initializeBean

在执行完Bean的创建和IOC的依赖注入之后,然后就进入doCreateBean()中的 initializeBean(),初始化 Bean。

image-20211103191629017

8.1 Aware 接口

首先是对 Aware 接口的调用,Aware 是一个空扩展接口,具体的接口定义由子类实现,通常用于属性设置,如 BeanNameAware、BeanClassLoaderAware、BeanFactoryAware。功能的实现主要在invokeAwareMethods方法中。

image-20211103145225136

当Bean实现了BeanNameAware,BeanClassLoaderAware,BeanFactoryAware三个接口时,就会在Bean初始化的时候去调用对应的set方法,设置对应的属性。具体的扩展暂时没有,所以可以先了解。

另外还有一个最常见的实现类 ApplicationContextAware,也是我们在项目中经常使用到的,也就是Spring上下文,我们可以用它来获取我们需要的 Bean,如下:

@Component
public class SpringContextHolder implements ApplicationContextAware {private static ApplicationContext appContext = null;/*** 通过name获取 Bean.** @param name* @return*/public static Object getBean(String name) {return appContext.getBean(name);}/*** 通过class获取Bean.** @param clazz* @param <T>* @return*/public static <T> T getBean(Class<T> clazz) {return appContext.getBean(clazz);}/*** 通过name,以及Clazz返回指定的Bean** @param name* @param clazz* @param <T>* @return*/public static <T> T getBean(String name, Class<T> clazz) {return appContext.getBean(name, clazz);}@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {if (appContext == null) {appContext = applicationContext;}}
}

而对于 ApplicationContextAware 中的 ApplicationContext 的赋值就在 applyBeanPostProcessorsBeforeInitialization 中。

8.2 applyBeanPostProcessorsBeforeInitialization

在 Bean 初始化前,调用所有 BeanPostProcessors 的 postProcessBeforeInitialization 方法,在 refresh() 中会进行BeanPostProcessors 的注册,即registerBeanPostProcessors,而 BeanPostProcessors 中有 2 个重要的方法。

image-20211103192702440

1、ApplicationContextAwareProcessor,它是 BeanPostProcessors 的一个重要实现类,它在postProcessBeforeInitialization 中对 ApplicationContext 进行了赋值,代码如下:

image-20211103193455218

进入 invokeAwareInterfaces:

image-20211103193557356

很明显可以看到和 initializeBean 中的 invokeAwareMethods 一致。

2、InitDestroyAnnotationBeanPostProcessor,这个实现类主要就是对 @PostConstruct 注解方法的调用,上面讲的是收集,而这里就是具体的调用。

image-20211103200543360

3、ImportAwareBeanPostProcessor,该方法是对 ImportAware 实例进行调用,在 Spring Boot 中,我们经常看到@import就和它有关,这里就不做过多描述,感兴趣可自行研究。

image-20211103201605337

8.3 invokeInitMethods

image-20211103204108017

该方法初始化 Bean 主要有两种情况:

1、Bean 实现了 InitializingBean 接口,则这个 Bean 在初始化的时候会调用 afterPropertiesSet() 方法,你也可以自己实现它,然后做自己想做的事,比如配置文件的解析,缓存预热,缓存数据加载到内存等等。

2、init-method 标签直接注入bean,如下面的例子:

public class InitMethodBean {public void init(){System.out.println("init-method初始化...");}
}

xml 配置

<bean id="initMethodBean" class="net.javatv.autowired.InitMethodBean" init-method="init"/>

测试结果

image-20211103204947652

需要注意的是,Spring 虽然可以通过 InitializingBean 完成 Bean 初始化后对这个 Bean 的回调,但是这种方式要求Bean实现InitializingBean接口。一但Bean实现了InitializingBean接口,那么这个Bean的代码就和Spring耦合到一起了。而 init-method并不依赖于Spring的某个接口,但它是经过反射来执行的,效率上低于InitializingBean

对于init-method,Spring 要求它是一个无参的方法,如果init-method指定的方法中有参数,那么 Spring 将会抛出异常,如下:

image-20211103212914638

另外,init-method指定的方法可以是声明的抛出异常,如下:

public class InitMethodBean {public void init() throws Exception {System.out.println("init-method初始化...");if (true) {throw new Exception("init exception...");}}
}

afterPropertiesSetinit-method 和有@PostConstruct 注解的方法其实核心功能都是一样的,都是在该类实例化和 IOC 做完后调用的,只是调用时序不一样,从上面的代码分析可以知道他们的调用时序为:

@PostConstruc > afterPropertiesSet > init-method

9. 总结

本文主要讲的是 Bean 的实例化,到 Bean 的依赖注入,再到 Bean 的初始化三个过程,还有 BeanPostProcessor 以及 Aware 接口的扩展,当然,其中还有很多没分析到,如 getBean() 方法中的细节,FactoryBean的创建,循环依赖等等,但大致流程可以明确,后续在对具体的细节进行补充。

10. 参考

  • https://www.jianshu.com/p/f2298bacc5d9
  • https://blog.csdn.net/v123411739/article/details/88077817
查看全文
如若内容造成侵权/违法违规/事实不符,请联系编程学习网邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!

相关文章

  1. leetcode-Algorithms-45|跳跃游戏 II

    原题 给你一个非负整数数组 nums &#xff0c;你最初位于数组的第一个位置。数组中的每个元素代表你在该位置可以跳跃的最大长度。你的目标是使用最少的跳跃次数到达数组的最后一个位置。假设你总是可以到达数组的最后一个位置。示例 1:输入: nums [2,3,1,1,4] 输出: 2 解释:…...

    2024/4/15 5:48:00
  2. Could not find tools.jar 问题处理

    转自&#xff1a;https://www.jianshu.com/p/4dc1bc10bd01 Mac升级之后Android 打包遇到 Please check that /Library/Internet Plug-Ins/JavaAppletPlugin.plugin/Contents/Home contains a valid JDK installation.解决方案: 1、首先查看地址 查看 JDK 位置的命令 /usr/l…...

    2024/4/26 12:22:56
  3. Java 内存

    内存分析...

    2024/4/16 2:41:28
  4. 使用SVG标签实现进度条,js计算两个条件并实现进度结果

    HTML代码 <template><div><svg class"ProgressSVG" viewBox"0 0 500 500" xmlns"http://www.w3.org/2000/svg" xmlns-xmlnsXlink"http://www.w3.org/1999/xlink"><defs><clipPath id"cut-off-bottom…...

    2024/4/18 12:07:41
  5. 哈工大编译原理——课程第二课笔记

    第二讲 2-1 字母表 是一个有穷符号集合 如二进制字母表&#xff1a;{0,1} 字母表的运算 两个表的乘积&#xff1a;{0,1}{a,b}{0a,0b,1a,1b} 字母表的n次幂&#xff1a;长度为n的符号串构成的集合 字母表的正闭包&#xff1a;长度正数的符号串构成的集合 字母表的克林闭…...

    2024/4/19 10:30:26
  6. lowbit(x)的原理解析

    lowbit&#xff08;x&#xff09;函数返回x二进制下最低位的1代表的十进制数&#xff0c;比如&#xff08;二进制&#xff09; x1100 ,lowbit(x)代表 100 原理解析 lowbit(x) x & (~x1)假设 x的二进制表示为 11100 ,那么 ~x 表示为 00011 &#xff0c;~x100100 &#…...

    2024/4/26 20:46:59
  7. C语言刷题笔记12

    标题 阶乘之和 描述 输入自然数N&#xff08;N<10&#xff09;&#xff0c;采用双层for循环结构&#xff0c;计算N&#xff01;和ΣN!&#xff0c;并输出结果。 时间限制 1 内存限制 10000 类别 1 输入说明 输入自然数N&#xff08;<10&#xff09;&#xf…...

    2024/4/25 18:28:17
  8. 学习笔记:动手学深度学习 13 softmax回归的简洁实现

    from torch import nn from d2l import torch as d2l batch_size 256 train_iter, test_iter d2l.load_data_fashion_mnist(batch_size) Backend Qt5Agg is interactive backend. Turning interactive mode on. """"拿进数据""" Out[3]: …...

    2024/4/19 21:18:10
  9. C++语言入门3(定义整数与整数输入)

    关于整数&#xff1a; c是一个对定义要求很严格的语言&#xff0c;对于数的定义也有很多种&#xff0c;比如整数&#xff0c;浮点数。整数不言而喻&#xff0c;不含有小数点。关于整数的定义也有很多种&#xff0c;最常用的无疑是int&#xff0c;我们定义整数一般选择的是int。…...

    2024/4/26 10:49:07
  10. 计算机组成原理、操作系统、数据结构和计算机网络--转

    本文摘自知乎。作者:Raynor 链接:https://www.zhihu.com/question/22017267/answer/26468016 来源:知乎 著作权归作者所有,转载请联系作者获得授权。计算机组成原理 这门学科告诉你什么是计算机。 首先,我们可以把计算机分解成最原始的部件——晶体管。晶体管是一种半导体…...

    2024/4/15 5:48:20
  11. Python异步 asyncio之await关键字

    await 后接可等待对象 可等待对象 : 1、 协程对象 2、 Future 3、 Task对象1、协程对象 &#xff1a; 协程函数 : 定义函数时候 async 修饰函数名的函数 协程对象 : 执行协程函数 () 得到的协程对象 示例 : import asyncioasync def func():print("执行了一个函数&quo…...

    2024/4/20 15:49:29
  12. [CQOI2007]余数求和

    问题&#xff1a;余数求和 题意&#xff1a; 给出正整数 nnn 和 kkk&#xff0c;请计算 G(n,k)∑i1nkmodiG(n, k) \sum_{i 1}^n k \bmod iG(n,k)i1∑n​kmodi其中 kmodik\bmod ikmodi 表示 kkk 除以 iii 的余数。 1≤n,k≤10910^9109。 例子&#xff1a; G(10,5)0121055555…...

    2024/4/15 5:48:20
  13. Java调用ssl出现异常:javax.net.ssl.SSLHandshakeException: No appropriate protocol

    在Java8及高版本以上的版本在调用ssl时会出现javax.net.ssl.SSLHandshakeException: No appropriate protocol的异常。 javax.net.ssl.SSLHandshakeException: No appropriate protocol (protocol is disabled or cipher suites are inappropriate)at sun.security.ssl.Handsh…...

    2024/4/25 12:17:25
  14. java基础之多线程的引入

    目录 一&#xff1a;多线程概述 进程 线程 举例 二&#xff1a;并行与并发 三&#xff1a; Java程序的运行原理 四&#xff1a;JVM启动的时候是单线程还是多线程呢&#xff1f; 一&#xff1a;多线程概述 进程 正在运行的程序&#xff0c;是系统进行资源分配和调用的独立…...

    2024/4/25 14:26:05
  15. mips以及c语言中的系统调用

    文章目录0 C语言文件操作1 mips关于文件操作的系统调用参数0 C语言文件操作 #include <string.h> #include <stdio.h> #include <stdlib.h> #include <inttypes.h> // uint16_t //// 打开文件 FILE *fp fopen("file_name.txt", "wb&…...

    2024/4/19 6:23:43
  16. 指针——笔记理解

    众所周知&#xff0c;一个变量有着它固定的作用域&#xff0c;变量也分全局变量和局部变量 如果要在函数中改变主函数中使用的值&#xff0c;有两种方法 一 就是全局变量 但是&#xff0c;若我们要指定面变量进行修改&#xff0c;全局变量就不行了 C中&#xff0c;指针是指…...

    2024/4/25 8:36:03
  17. css学习2

    1、第一种方式 <!doctype html> <html><head><title>第一种方式</title></head><body><!--border:10px solid black;和 border-width:10px;border-style:solid:border-color:black:相等border-width设置边框的宽度 border-style…...

    2024/4/15 5:48:25
  18. 数据库系统概论--第十章:数据库恢复技术

    文章目录1.事务的基本概念(事务定义、事务ACID特性&#xff1a;原子性、一致性、隔离性、持续性&#xff09;2.数据库恢复概述3.故障的种类4.恢复的实现技术5.恢复策略1.事务的基本概念(事务定义、事务ACID特性&#xff1a;原子性、一致性、隔离性、持续性&#xff09; 2.数据库…...

    2024/4/25 0:15:30
  19. 7-82 打印沙漏

    /* 7-82 打印沙漏 (20 分) 本题要求你写个程序把给定的符号打印成沙漏的形状。例如给定17个“*”&#xff0c;要求按下列格式打印************ ***** 所谓“沙漏形状”&#xff0c;是指每行输出奇数个符号&#xff1b;各行符号中心对齐&#xff1b;相邻两行符号数差2&#xff1…...

    2024/4/15 5:48:10
  20. 利用Python实现校园网数据储存并后台自动登录且持续监测网络状态

    改进版 可以推广使用 #!/usr/bin/env python # -*- coding:utf-8 -*- import os import re import subprocess import time import urllib.request import requests import os.path import linecacheclass NovelSpider:def __init__(self):self.session requests.Session()d…...

    2024/4/15 5:49:01

最新文章

  1. 广工电工与电子技术实验报告-8路彩灯循环控制电路

    实验代码 module LED_water (clk,led); input clk; output [7:0] led; reg [7:0] led; integer p; reg clk_1Hz; reg [7:0] current_state, next_state; always (posedge clk) begin if(p25000000-1)begin …...

    2024/4/27 3:33:16
  2. 梯度消失和梯度爆炸的一些处理方法

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

    2024/3/20 10:50:27
  3. javaWeb网上零食销售系统

    1 绪 论 目前&#xff0c;我国的网民数量已经达到7.31亿人&#xff0c;随着互联网购物和互联网支付的普及&#xff0c;使得人类的经济活动进入了一个崭新的时代。淘宝&#xff0c;京东等网络消费平台功能的日益完善&#xff0c;使得人们足不出户就可以得到自己想要的东西。如今…...

    2024/4/22 16:14:13
  4. 巨控科技新品发布:全方位升级,引领智能控制新纪元

    标签: #巨控科技 #智能控制 #新品发布 #GRM560 #OPC560 #NET400 在智能控制领域&#xff0c;巨控科技始终以其前沿技术和创新产品引领着市场的潮流。近日&#xff0c;巨控科技再次以其行业领先的研发实力&#xff0c;推出了三大系列的新产品&#xff0c;旨在为各行各业提供更…...

    2024/4/23 6:25:41
  5. 【外汇早评】美通胀数据走低,美元调整

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

    2024/4/26 18:09:39
  6. 【原油贵金属周评】原油多头拥挤,价格调整

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

    2024/4/26 20:12:18
  7. 【外汇周评】靓丽非农不及疲软通胀影响

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

    2024/4/26 23:05:52
  8. 【原油贵金属早评】库存继续增加,油价收跌

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

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

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

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

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

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

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

    2024/4/26 21:56:58
  12. 【原油贵金属早评】波动率飙升,市场情绪动荡

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

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

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

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

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

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

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

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

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

    2024/4/26 19:03:37
  17. 氧生福地 玩美北湖(上)——为时光守候两千年

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

    2024/4/26 22:01:59
  18. 氧生福地 玩美北湖(中)——永春梯田里的美与鲜

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

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

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

    2024/4/26 23:04:58
  20. 扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!

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

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

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

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

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

    2024/4/26 19:46:12
  23. 广州械字号面膜生产厂家OEM/ODM4项须知!

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

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

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

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

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

    2022/11/19 21:17:18
  26. 错误使用 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
  27. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机...

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

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

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

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

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

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

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

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

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

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

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

    2022/11/19 21:17:10
  33. 电脑桌面一直是清理请关闭计算机,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
  34. 计算机配置更新不起,电脑提示“配置Windows Update请勿关闭计算机”怎么办?

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    2022/11/19 21:16:58
  44. 如何在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