前言

本系列全部基于 Spring 5.2.2.BUILD-SNAPSHOT 版本。因为 Spring 整个体系太过于庞大,所以只会进行关键部分的源码解析。

本篇文章主要介绍 Spring IoC 容器是怎么加载 bean 的。
正文

我们先看一下Spring IoC BeanDefinition 的加载和注册一文中获取 bean 的实例代码:

Copy
public class BeanDefinitionDemo {

public static void main(String[] args) {DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);reader.loadBeanDefinitions("META-INF/bean-definition.xml");User user = beanFactory.getBean("user", User.class);System.err.println(user);
}

}

通过 beanFactory.getBean() 这个方法就获取了在 XML 中定义的 bean,下面我们就重点分析这个方法背后做了什么操作。

在正式开始之前,我们先了解一下 FactoryBean 及其用法。
FactoryBean 介绍#

FactoryBean 接口对于 Spring 框架来说占有重要的地位,Spring 自身就提供了70多个 FactoryBean 的实现。它们隐藏了一下复杂 bean 的细节,给上层应用带来了便利。下面是该接口的定义:

Copy
public interface FactoryBean {

// 返回由FactoryBean创建的bean实例,如果isSingleton()返回true,
// 则该实例会放到Spring容器中单例缓存池中
@Nullable
T getObject() throws Exception;// 返回FactoryBean创建的bean类型
@Nullable
Class<?> getObjectType();// 返回由FactoryBean创建的bean实例的作用域是singleton还是prototype
default boolean isSingleton() {return true;
}

}

当配置文件中 的 class 属性配置的实现类时 FactoryBean 时,通过 getBean() 返回的不是 FactoryBean 本身,而是 FactoryBean#getObject() 所返回的对象,相当于 FactoryBean#getObject() 代理了 getBean()。下面用简单的代码演示一下:

首先定义一个 Car 实体类:

Copy
public class Car {

private Integer maxSpeed;
private String brand;
private Double price;public Integer getMaxSpeed() {return maxSpeed;
}public void setMaxSpeed(Integer maxSpeed) {this.maxSpeed = maxSpeed;
}public String getBrand() {return brand;
}public void setBrand(String brand) {this.brand = brand;
}public Double getPrice() {return price;
}public void setPrice(Double price) {this.price = price;
}

}

上面的实体类,如果用传统方式配置,每一个属性都会对应一个 元素标签。如果用 FactoryBean 的方式实现就会灵活一点,下面通过逗号分隔的方式一次性的为 Car 的所有属性配置值。

Copy
public class CarFactoryBean implements FactoryBean {

private String carInfo;@Override
public Car getObject() throws Exception {Car car = new Car();String[] infos = carInfo.split(",");car.setBrand(infos[0]);car.setMaxSpeed(Integer.valueOf(infos[1]));car.setPrice(Double.valueOf(infos[2]));return car;
}@Override
public Class<?> getObjectType() {return Car.class;
}@Override
public boolean isSingleton() {return true;
}public String getCarInfo() {return carInfo;
}public void setCarInfo(String carInfo) {this.carInfo = carInfo;
}

}

接下来,我们在 XML 中配置。

Copy


最后看下测试代码和运行结果:

Copy
@Test
public void factoryBeanTest() {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
reader.loadBeanDefinitions(“META-INF/factory-bean.xml”);
Car car = beanFactory.getBean(“car”, Car.class);
System.out.println(car);
CarFactoryBean carFactoryBean = beanFactory.getBean("&car", CarFactoryBean.class);
System.out.println(carFactoryBean);
}

可以看到如果 beanName 前面加上 & 获取的是 FactoryBean 本身,不加获取的 getObject() 返回的对象。

FactoryBean 的特殊之处在于它可以向容器中注册两个 bean,一个是它本身,一个是 FactoryBean.getObject() 方法返回值所代表的 bean。

bean 的加载#
AbstractBeanFactory#getBean#

Copy
public T getBean(String name, Class requiredType) throws BeansException {
// 调用doGetBean方法(方法以do开头实际做操作的方法)
return doGetBean(name, requiredType, null, false);
}

/**

  • @param name bean的名称

  • @param requiredType bean的类型

  • @param args 显示传入的构造参数

  • @param typeCheckOnly 是否仅仅做类型检查
    */
    protected T doGetBean(final String name, @Nullable final Class requiredType,
    @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
    // 获取bean的实际名称,见下文详解
    final String beanName = transformedBeanName(name);
    Object bean;

    // 直接尝试从缓存获取或 singletonFactories 中的 ObjectFactory 中获取,见下文详解
    Object sharedInstance = getSingleton(beanName);
    if (sharedInstance != null && args == null) {
    // 检查bean是否是FactoryBean的实现。不是直接返回bean,
    // 是的话首先检查beanName是否以&开头,如果是返回FactoryBean本身,
    // 不是调用FactoryBean#getObject()返回对象,见下文详解
    bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
    }

    else {
    // 只有在单例情况下才会去尝试解决循环依赖,原型模式下,如果存在A中有
    // B属性,B中有A属性,那么当依赖注入时,就会产生当A还未创建完的时候
    // 对于B的创建而在此返回创建A,造成循环依赖
    if (isPrototypeCurrentlyInCreation(beanName)) {
    throw new BeanCurrentlyInCreationException(beanName);
    }

      // 检查当前bean的BeanDefinition是否在当前的bean工厂中,// 不在递归调用父工厂的getBean()去获取beanBeanFactory parentBeanFactory = getParentBeanFactory();if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {String nameToLookup = originalBeanName(name);if (parentBeanFactory instanceof AbstractBeanFactory) {return ((AbstractBeanFactory) parentBeanFactory).doGetBean(nameToLookup, requiredType, args, typeCheckOnly);}else if (args != null) {return (T) parentBeanFactory.getBean(nameToLookup, args);}else if (requiredType != null) {return parentBeanFactory.getBean(nameToLookup, requiredType);}else {return (T) parentBeanFactory.getBean(nameToLookup);}}// 如果不是仅仅做类型检查,则是创建bean,这里要进行记录if (!typeCheckOnly) {// 记录bean已经创建过markBeanAsCreated(beanName);}try {// 合并BeanDefinition,见下文详解final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);checkMergedBeanDefinition(mbd, beanName, args);// 实例化bean前先实例化依赖bean,也就是depends-on属性中配置的beanNameString[] dependsOn = mbd.getDependsOn();if (dependsOn != null) {for (String dep : dependsOn) {// 检查是否循环依赖,即当前bean依赖dep,dep依赖当前bean,见下文详解if (isDependent(beanName, dep)) {throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Circular depends-on relationship between '" + beanName + "' and '" + dep + "'");}// 将dep和beanName的依赖关系放入到缓存中,见下文详解registerDependentBean(dep, beanName);try {// 获取依赖dep对应的bean实例,如果还未创建实例,则先去创建getBean(dep);}catch (NoSuchBeanDefinitionException ex) {throw new BeanCreationException(mbd.getResourceDescription(), beanName,"'" + beanName + "' depends on missing bean '" + dep + "'", ex);}}}// 如果 bean 的作用域是单例if (mbd.isSingleton()) {// 创建和注册单例 bean,见下文详解sharedInstance = getSingleton(beanName, () -> {try {// 创建 bean 实例return createBean(beanName, mbd, args);}catch (BeansException ex) {destroySingleton(beanName);throw ex;}});// 获取bean实例bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);}// bean 的作用域是原型else if (mbd.isPrototype()) {Object prototypeInstance = null;try {// 原型 bean 创建前回调,// 默认实现是将 beanName 保存到 prototypesCurrentlyInCreation 缓存中beforePrototypeCreation(beanName);// 创建 bean 实例prototypeInstance = createBean(beanName, mbd, args);}finally {// 原型 bean 创建后回调,// 默认实现是将 beanName 从prototypesCurrentlyInCreation 缓存中移除afterPrototypeCreation(beanName);}// 获取bean实例bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);}// 自定义作用域else {// 获取自定义作用域名称String scopeName = mbd.getScope();// 获取作用域对象final Scope scope = this.scopes.get(scopeName);// 如果为空表示作用域未注册,抛出异常if (scope == null) {throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");}try {// 其他 Scope 的 bean 创建// 新建了一个 ObjectFactory,并且重写了 getObject 方法Object scopedInstance = scope.get(beanName, () -> {// 调用原型 bean 创建前回调beforePrototypeCreation(beanName);try {// 创建 bean 实例,下篇文章详解return createBean(beanName, mbd, args);}finally {// 调用原型 bean 创建后回调afterPrototypeCreation(beanName);}});// 获取bean实例bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);}catch (IllegalStateException ex) {throw new BeanCreationException(beanName, "Scope '" + scopeName + "' is not active for the current thread; consider " + "defining a scoped proxy for this bean if you intend to refer to it from a singleton",ex);}}}catch (BeansException ex) {cleanupAfterBeanCreationFailure(beanName);throw ex;}
    

    }

    // 检查所需的类型是否与实际 bean 实例的类型匹配
    if (requiredType != null && !requiredType.isInstance(bean)) {
    try {
    // 如果类型不等,进行转换,转换失败抛出异常;转换成功直接返回
    T convertedBean = getTypeConverter().convertIfNecessary(bean, requiredType);
    if (convertedBean == null) {
    throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass());
    }
    return convertedBean;
    }
    catch (TypeMismatchException ex) {
    throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass());
    }
    }
    // 返回 bean 实例
    return (T) bean;
    }

上面方法就是获取 bean 的整个流程,下面我们对其调用的其它主要方法来一一分析。
转换对应的 beanName#
AbstractBeanFactory#transformedBeanName#

Copy
protected String transformedBeanName(String name) {
return canonicalName(BeanFactoryUtils.transformedBeanName(name));
}

// BeanFactoryUtils.java
public static String transformedBeanName(String name) {
Assert.notNull(name, “‘name’ must not be null”);
// 如果name不是&开头,直接返回
if (!name.startsWith(BeanFactory.FACTORY_BEAN_PREFIX)) {
return name;
}
// 去除name的&前缀
return transformedBeanNameCache.computeIfAbsent(name, beanName -> {
do {
beanName = beanName.substring(BeanFactory.FACTORY_BEAN_PREFIX.length());
}
while (beanName.startsWith(BeanFactory.FACTORY_BEAN_PREFIX));
return beanName;
});
}

// SimpleAliasRegistry.java
public String canonicalName(String name) {
String canonicalName = name;
String resolvedName;
// 如果name是别名,则会循环去查找bean的实际名称
do {
resolvedName = this.aliasMap.get(canonicalName);
if (resolvedName != null) {
canonicalName = resolvedName;
}
}
while (resolvedName != null);
return canonicalName;
}

尝试从单例缓存获取 bean#
AbstractBeanFactory#getSingleton#

Copy
public Object getSingleton(String beanName) {
// allowEarlyReference设置为true表示允许早期依赖
return getSingleton(beanName, true);
}

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 先从一级缓存中,检查单例缓存是否存在
Object singletonObject = this.singletonObjects.get(beanName);
// 如果为空,并且当前bean正在创建中,锁定全局变量进行处理
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
// 从二级缓存中获取
singletonObject = this.earlySingletonObjects.get(beanName);
// 二级缓存为空 && bean允许提前曝光
if (singletonObject == null && allowEarlyReference) {
// 从三级缓存中获取bean对应的ObjectFactory
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
// 调用预先设定的getObject(),获取bean实例
singletonObject = singletonFactory.getObject();
// 放入到二级缓存中,并从三级缓存中删除
// 这时bean已经实例化完但还未初始化完
// 在该bean未初始化完时如果有别的bean引用该bean,可以直接从二级缓存中取出返回
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}

上面方法主要就是尝试从缓存中获取 bean,缓存有三级,这也是 Spring 解决循环依赖的关键所在;后续会在 循环依赖 中重点讲述。
获取 bean 实例对象#
AbstractBeanFactory#getObjectForBeanInstance#

Copy
protected Object getObjectForBeanInstance(Object beanInstance, String name, String beanName, @Nullable RootBeanDefinition mbd) {

// name 是否以 & 开头
if (BeanFactoryUtils.isFactoryDereference(name)) {// 如果是 null 直接返回if (beanInstance instanceof NullBean) {return beanInstance;}// beanName 以 & 开头,但又不是 FactoryBean 类型,抛出异常if (!(beanInstance instanceof FactoryBean)) {throw new BeanIsNotAFactoryException(beanName, beanInstance.getClass());}// 设置 isFactoryBean 为 trueif (mbd != null) {mbd.isFactoryBean = true;}// 返回 bean 实例return beanInstance;
}// name 不是 & 开头,并且不是 FactoryBean 类型,直接返回
if (!(beanInstance instanceof FactoryBean)) {return beanInstance;
}// 到这里就代表name不是&开头,且是FactoryBean类型
// 即获取FactoryBean.getObject()方法返回值所代表的bean
Object object = null;
if (mbd != null) {	mbd.isFactoryBean = true;
}
else {// 从缓存中获取实例object = getCachedObjectForFactoryBean(beanName);
}
if (object == null) {// 将 beanInstance 强转成 FactoryBeanFactoryBean<?> factory = (FactoryBean<?>) beanInstance;// 合并 BeanDefinitionif (mbd == null && containsBeanDefinition(beanName)) {mbd = getMergedLocalBeanDefinition(beanName);}boolean synthetic = (mbd != null && mbd.isSynthetic());// 获取实例object = getObjectFromFactoryBean(factory, beanName, !synthetic);
}
return object;

}

// FactoryBeanRegistrySupport.java
protected Object getObjectFromFactoryBean(FactoryBean<?> factory, String beanName, boolean shouldPostProcess) {
// 如果是单例 bean,并且已经存在缓存中
if (factory.isSingleton() && containsSingleton(beanName)) {
// 加锁
synchronized (getSingletonMutex()) {
// 从缓存中获取
Object object = this.factoryBeanObjectCache.get(beanName);
if (object == null) {
// 调用 FactoryBean 的 getObject() 获取实例
object = doGetObjectFromFactoryBean(factory, beanName);
Object alreadyThere = this.factoryBeanObjectCache.get(beanName);
// 如果该 beanName 已经在缓存中存在,则将 object 替换成缓存中的
if (alreadyThere != null) {
object = alreadyThere;
}
else {
if (shouldPostProcess) {
// 如果当前 bean 还在创建中,直接返回
if (isSingletonCurrentlyInCreation(beanName)) {
return object;
}
// 单例 bean 创建前回调
beforeSingletonCreation(beanName);
try {
// 对从 FactoryBean 获得给定对象的后置处理,默认按原样返回
object = postProcessObjectFromFactoryBean(object, beanName);
}
catch (Throwable ex) {
throw new BeanCreationException(beanName,
“Post-processing of FactoryBean’s singleton object failed”, ex);
}
finally {
// 单例 bean 创建后回调
afterSingletonCreation(beanName);
}
}
if (containsSingleton(beanName)) {
// 将 beanName 和 object 放到 factoryBeanObjectCache 缓存中
this.factoryBeanObjectCache.put(beanName, object);
}
}
}
// 返回实例
return object;
}
}
else {
// 调用 FactoryBean 的 getObject() 获取实例
Object object = doGetObjectFromFactoryBean(factory, beanName);
if (shouldPostProcess) {
try {
// 对从 FactoryBean 获得给定对象的后置处理,默认按原样返回
object = postProcessObjectFromFactoryBean(object, beanName);
}
catch (Throwable ex) {
throw new BeanCreationException(beanName, “Post-processing of FactoryBean’s object failed”, ex);
}
}
// 返回实例
return object;
}
}

// FactoryBeanRegistrySupport.java
private Object doGetObjectFromFactoryBean(final FactoryBean<?> factory, final String beanName) throws BeanCreationException {

Object object;
try {// 调用 getObject() 获取实例object = factory.getObject();
}
// 省略异常处理...// 如果 object 为 null,并且当前 singleton bean 正在创建中,抛出异常
if (object == null) {if (isSingletonCurrentlyInCreation(beanName)) {throw new BeanCurrentlyInCreationException(beanName, "FactoryBean which is currently in creation returned null from getObject");}object = new NullBean();
}
// 返回 object 实例
return object;

}

上面代码总结起来就是:如果 beanName 以 & 开头,直接返回 FactoryBean 实例;否则调用 getObject() 方法获取实例,然后执行 postProcessObjectFromFactoryBean() 回调,可以在回调方法中修改实例,默认按原样返回。
合并 bean 定义元信息#
AbstractBeanFactory#getMergedLocalBeanDefinition#

下文将合并后的 BeanDefinition 简称为 MergedBeanDefinition 。

Copy
protected RootBeanDefinition getMergedLocalBeanDefinition(String beanName) throws BeansException {
// 从缓存获取MergedBeanDefinition
RootBeanDefinition mbd = this.mergedBeanDefinitions.get(beanName);
// 如果存在MergedBeanDefinition,并且不是过期的,直接返回
if (mbd != null && !mbd.stale) {
return mbd;
}
// 获取已经注册的BeanDefinition然后去合并
return getMergedBeanDefinition(beanName, getBeanDefinition(beanName));
}

protected RootBeanDefinition getMergedBeanDefinition(String beanName, BeanDefinition bd)
throws BeanDefinitionStoreException {
// 顶级bean获取合并后的BeanDefinition
return getMergedBeanDefinition(beanName, bd, null);
}

/**

  • @param containingBd 如果是嵌套bean该值为顶级bean,如果是顶级bean该值为null
    */
    protected RootBeanDefinition getMergedBeanDefinition(
    String beanName, BeanDefinition bd, @Nullable BeanDefinition containingBd)
    throws BeanDefinitionStoreException {
    // 加锁
    synchronized (this.mergedBeanDefinitions) {
    // 本次的RootBeanDefinition
    RootBeanDefinition mbd = null;
    // 以前的RootBeanDefinition
    RootBeanDefinition previous = null;

     // 如果bean是顶级bean,直接获取MergedBeanDefinitionif (containingBd == null) {mbd = this.mergedBeanDefinitions.get(beanName);}// 没有MergedBeanDefinition || BeanDefinition过期了if (mbd == null || mbd.stale) {previous = mbd;// 如果bean没有parentif (bd.getParentName() == null) {// 如果bd本身就是RootBeanDefinition直接复制一份,否则创建一个if (bd instanceof RootBeanDefinition) {mbd = ((RootBeanDefinition) bd).cloneBeanDefinition();}else {mbd = new RootBeanDefinition(bd);}}else {	// bean有parentBeanDefinition pbd;try {// 获取parent bean的实际名称String parentBeanName = transformedBeanName(bd.getParentName());if (!beanName.equals(parentBeanName)) {// 当前beanName不等于它的parentBeanName// 获取parent的MergedBeanDefinitionpbd = getMergedBeanDefinition(parentBeanName);}else {// 如果parentBeanName与bd的beanName相同,则拿到父BeanFactory// 只有在存在父BeanFactory的情况下,才允许parentBeanName与自己相同BeanFactory parent = getParentBeanFactory();if (parent instanceof ConfigurableBeanFactory) {// 如果父BeanFactory是ConfigurableBeanFactory类型// 则通过父BeanFactory获取parent的MergedBeanDefinitionpbd = ((ConfigurableBeanFactory) parent).getMergedBeanDefinition(parentBeanName);}else {// 如果父BeanFactory不是ConfigurableBeanFactory,抛出异常throw new NoSuchBeanDefinitionException(parentBeanName,
    

“Parent name '” + parentBeanName + “’ is equal to bean name '” + beanName + “’: cannot be resolved without an AbstractBeanFactory parent”);
}
}
}
catch (NoSuchBeanDefinitionException ex) {
throw new BeanDefinitionStoreException(bd.getResourceDescription(), beanName, “Could not resolve parent bean definition '” + bd.getParentName() + “’”, ex);
}
// 使用父MergedBeanDefinition构建一个新的RootBeanDefinition对象(深拷贝)
mbd = new RootBeanDefinition(pbd);
// 覆盖与parent相同的属性
mbd.overrideFrom(bd);
}

        // 如果bean没有设置scope属性,默认是singletonif (!StringUtils.hasLength(mbd.getScope())) {mbd.setScope(RootBeanDefinition.SCOPE_SINGLETON);}// 当前bean是嵌套bean && 顶级bean的作用域不是单例 && 当前bean的作用域是单例// 这里总结起来就是,如果顶层bean不是单例的,那么嵌套bean也不能是单例的if (containingBd != null && !containingBd.isSingleton() && mbd.isSingleton()) {// 设置当前bean的作用域和顶级bean一样mbd.setScope(containingBd.getScope());}// 当前bean是顶级bean && 缓存bean的元数据(该值默认为true)if (containingBd == null && isCacheBeanMetadata()) {// 将当前bean的MergedBeanDefinition缓存起来this.mergedBeanDefinitions.put(beanName, mbd);}}// 以前的RootBeanDefinition不为空,拷贝相关的BeanDefinition缓存if (previous != null) {copyRelevantMergedBeanDefinitionCaches(previous, mbd);}return mbd;
}

}

上面代码主要是获取 MergedBeanDefinition ,主要步骤如下:

首先从缓存中获取 bean 的 MergedBeanDefinition,如果存在并且未过期直接返回。不存在或者已过期的 MergedBeanDefinition ,获取已经注册的 BeanDefinition 去作为顶级 bean 合并。bean 没有 parent (就是 XML 中的 parent 属性),直接封装成 RootBeanDefinition 。bean 有 parent ,先去获取父 MergedBeanDefinition ,然后覆盖和合并与 parent 相同的属性。注意:这里只有 abstract、scope、lazyInit、autowireMode、dependencyCheck、dependsOn 、factoryBeanName、factoryMethodName、initMethodName、destroyMethodName 会覆盖,而 constructorArgumentValues、propertyValues、methodOverrides 会合并。如果没有设置作用域,默认作用域为 singleton 。缓存 MergedBeanDefinition 。

上文中提到如果 bean 有 parent,会合并一些属性,这里我们稍微展示一下合并后的 propertyValues:

首先定义一个 SuperUser 继承上面定义的 User,如下:

Copy
public class SuperUser extends User {

private String address;public String getAddress() {return address;
}public void setAddress(String address) {this.address = address;
}@Override
public String toString() {return "SuperUser{" +"address='" + address + '\'' +'}';
}

}

然后我们在 XML 文件中配置一下,如下:

Copy


然后下图是我 Debug 的截图,可以看到 superUser 的 propertyValues 合并了 user 的 id 和 name 属性。

上文还提到了嵌套 bean ,下面我们简单看一下什么是嵌套 bean。

在 Spring 中,如果某个 bean 所依赖的 bean 不想被 Spring 容器直接访问,可以使用嵌套 bean。和普通的 bean 一样,使用 bean 元素来定义嵌套的 bean,嵌套 bean 只对它的外部 bean 有效,Spring 无法直接访问嵌套 bean ,因此定义嵌套 bean 也无需指定 id 属性。如下配置片段是一个嵌套 bean 示例:

采用上面的配置形式可以保证嵌套 bean 不能被容器访问,因此不用担心其他程序修改嵌套 bean。外部 bean 的用法和使用结果和以前没有区别。

嵌套 bean 提高了 bean 的内聚性,但是降低了程序的灵活性。只有在确定无需通过 Spring 容器访问某个 bean 实例时,才考虑使用嵌套 bean 来定义。

寻找依赖#
DefaultSingletonBeanRegistry#isDependent#

Copy
protected boolean isDependent(String beanName, String dependentBeanName) {
// 加锁
synchronized (this.dependentBeanMap) {
// 检测beanName和dependentBeanName是否有循环依赖
return isDependent(beanName, dependentBeanName, null);
}
}

private boolean isDependent(String beanName, String dependentBeanName, @Nullable Set alreadySeen) {
// 如果当前bean已经检测过,直接返回false
if (alreadySeen != null && alreadySeen.contains(beanName)) {
return false;
}
// 解析别名,获取实际的beanName
String canonicalName = canonicalName(beanName);
// 获取canonicalName所依赖beanName集合
Set dependentBeans = this.dependentBeanMap.get(canonicalName);
// 如果为空,两者还未确定依赖关系,返回false
if (dependentBeans == null) {
return false;
}
// 如果dependentBeanName已经存在于缓存中,两者已经确定依赖关系,返回true
if (dependentBeans.contains(dependentBeanName)) {
return true;
}
// 循环检查,即检查依赖canonicalName的所有beanName是否被dependentBeanName依赖(即隔层依赖)
for (String transitiveDependency : dependentBeans) {
if (alreadySeen == null) {
alreadySeen = new HashSet<>();
}
// 将已经检查过的记录下来,下次直接跳过
alreadySeen.add(beanName);
if (isDependent(transitiveDependency, dependentBeanName, alreadySeen)) {
return true;
}
}
return false;
}

DefaultSingletonBeanRegistry#registerDependentBean#

Copy
public void registerDependentBean(String beanName, String dependentBeanName) {
// 解析别名,获取实际的beanName
String canonicalName = canonicalName(beanName);
// 加锁
synchronized (this.dependentBeanMap) {
// 获取canonicalName依赖beanName集合,如果为空默认创建一个LinkedHashSet当做默认值
Set dependentBeans =
this.dependentBeanMap.computeIfAbsent(canonicalName, k -> new LinkedHashSet<>(8));
// 如果dependentBeanName已经记录过了,直接返回
if (!dependentBeans.add(dependentBeanName)) {
return;
}
}
// 加锁
synchronized (this.dependenciesForBeanMap) {
// 这里是和上面的dependentBeanMap倒过来,key为dependentBeanName
Set dependenciesForBean =
this.dependenciesForBeanMap.computeIfAbsent(dependentBeanName, k -> new LinkedHashSet<>(8));
dependenciesForBean.add(canonicalName);
}
}
http://www.yundongbLog.com
下面我们举个A、B的 depends-on 属性都是对方的例子:

首先获取A,调用 isDependent() 方法,因为第一次获取A,所以 dependentBeanMap 中没有记录依赖关系,直接返回 false;接着调用registerDependentBean(),这里会向 dependentBeanMap 中反过来存储依赖关系,也就是以B为 key ,value 是一个包含A的 Set集合。

接着会调用 getBean() 方法获取B,首先调用 isDependent() 方法,因为在获取A时已经存储了B的依赖关系,所以获取到的dependentBeans 的集合中包含A,所以直接返回true,抛出循环引用异常。

这个方法又引入了一个跟 dependentBeanMap 类似的缓存 dependenciesForBeanMap。这两个缓存很容易搞混,这里再举一个简单的例子:A 依赖 B,那么 dependentBeanMap 存放的是 key 为 B,value 为含有 A 的 Set;而 dependenciesForBeanMap 存放的是key 为 A,value 为含有 B 的 Set。
创建和注册单例 bean#
DefaultSingletonBeanRegistry#getSingleton#

Copy
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(beanName, “Bean name must not be null”);
// 加锁
synchronized (this.singletonObjects) {
Object singletonObject = this.singletonObjects.get(beanName);
// 缓存中不存在当前 bean,也就是当前 bean 第一次创建
if (singletonObject == null) {
// 如果当前正在销毁 singletons,抛出异常
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 之前的回调
beforeSingletonCreation(beanName);
boolean newSingleton = false;
boolean recordSuppressedExceptions = (this.suppressedExceptions == null);
if (recordSuppressedExceptions) {
this.suppressedExceptions = new LinkedHashSet<>();
}
try {
// 获取 bean 实例,在此处才会去真正调用创建 bean 的方法
singletonObject = singletonFactory.getObject();
newSingleton = true;
}
// 省略异常处理…
finally {
if (recordSuppressedExceptions) {
this.suppressedExceptions = null;
}
// 创建单例 bean 之后的回调
afterSingletonCreation(beanName);
}
if (newSingleton) {
// 将 singletonObject 放入缓存
addSingleton(beanName, singletonObject);
}
}
// 返回 bean 实例
return singletonObject;
}
}

// 单例 bean 创建前的回调方法,默认实现是将 beanName 加入到当前正在创建 bean 的缓存中,
// 这样便可以对循环依赖进行检测
protected void beforeSingletonCreation(String beanName) {
if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
}

// 单例 bean 创建后的回调方法,默认实现是将 beanName 从当前正在创建 bean 的缓存中移除
protected void afterSingletonCreation(String beanName) {
if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.remove(beanName)) {
throw new IllegalStateException(“Singleton '” + beanName + “’ isn’t currently in creation”);
}
}

protected void addSingleton(String beanName, Object singletonObject) {
synchronized (this.singletonObjects) {
// 这边bean已经初始化完成了,放入一级缓存
this.singletonObjects.put(beanName, singletonObject);
// 移除三级缓存
this.singletonFactories.remove(beanName);
// 移除二级缓存
this.earlySingletonObjects.remove(beanName);
// 将 beanName 添加到已注册 bean 缓存中
this.registeredSingletons.add(beanName);
}
}

自定义作用域示例#

我们实现一个 ThreadLocal 级别的作用域,也就是同一个线程内 bean 是同一个实例,不同线程的 bean 是不同实例。首先我们继承 Scope 接口实现,其中方法。如下:

Copy
public class ThreadLocalScope implements Scope {
https://www.kokangwang.com
/** scope 名称,在 XML 中的 scope 属性就配置此名称 */
public static final String SCOPE_NAME = “thread-local”;

private final NamedThreadLocal<Map<String, Object>> threadLocal = new NamedThreadLocal<>("thread-local-scope");/**
* 返回实例对象,该方法被 Spring 调用
*/
@Override
public Object get(String name, ObjectFactory<?> objectFactory) {Map<String, Object> context = getContext();Object object = context.get(name);if (object == null) {object = objectFactory.getObject();context.put(name, object);}return object;
}/**
* 获取上下文 map
*/
@NonNull
private Map<String, Object> getContext() {Map<String, Object> map = threadLocal.get();if (map == null) {map = new HashMap<>();threadLocal.set(map);}return map;
}@Override
public Object remove(String name) {	return getContext().remove(name);
}@Override
public void registerDestructionCallback(String name, Runnable callback) {// TODO
}@Override
public Object resolveContextualObject(String key) {Map<String, Object> context = getContext();return context.get(key);
}@Override
public String getConversationId() {return String.valueOf(Thread.currentThread().getId());
}

}

上面的 ThreadLocalScope 重点关注下 get() 即可,该方法是被 Spring 调用的。

然后在 XML 中配置 bean 的 scope 为 thread-local。如下:

Copy




https://www.yunyoublog.com
接着我们测试一下。测试类:

Copy
@Test
public void test() throws InterruptedException {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
// 注册自定义作用域
beanFactory.registerScope(ThreadLocalScope.SCOPE_NAME, new ThreadLocalScope());
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
reader.loadBeanDefinitions(“META-INF/custom-bean-scope.xml”);
for (int i = 0; i < 3; i++) {
Thread thread = new Thread(() -> {
User user = beanFactory.getBean(“user”, User.class);
System.err.printf("[Thread id :%d] user = %s%n", Thread.currentThread().getId(), user.getClass().getName() + “@” + Integer.toHexString(user.hashCode()));
User user1 = beanFactory.getBean(“user”, User.class);
System.err.printf("[Thread id :%d] user1 = %s%n", Thread.currentThread().getId(), user1.getClass().getName() + “@” + Integer.toHexString(user1.hashCode()));
});
thread.start();
thread.join();
}
}

说一下我们这里的主要思路,新建了三个线程,查询线程内 user bean 是否相等,不同线程是否不等。

结果如下图:

总结

本文主要介绍了 getBean() 方法流程,我们可以重新梳理一下思路:

获取 bean 实际名称,如果缓存中存在直接取出实际 bean 返回。
缓存中不存在,判断当前工厂是否有 BeanDefinition ,没有递归去父工厂创建 bean。
合并 BeanDefinition ,如果 depends-on 不为空,先去初始化依赖的 bean。
如果 bean 的作用域是单例,调用 createBean() 方法创建实例,这个方法会执行 bean 的其它生命周期回调,以及属性赋值等操作;接着执行单例 bean 创建前后的生命周期回调方法,并放入 singletonObjects 缓存起来。
如果 bean 的作用域是原型,调用 createBean() 方法创建实例,并执行原型 bean 前后调用生命周期回调方法。
如果 bean 的作用域是自定义的,获取对应的 Scope 对象,调用重写的 get() 方法获取实例,并执行原型 bean 前后调用生命周期回调方法。
最后检查所需的类型是否与实际 bean 实例的类型匹配,如果不等进行转换,最后返回实例。

关于 createBean() 方法的细节,会在后续文章中进行分析。

最后,我模仿 Spring 写了一个精简版,代码会持续更新。地址:https://github.com/leisurexi/tiny-spring。
查看全文
如若内容造成侵权/违法违规/事实不符,请联系编程学习网邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!

相关文章

  1. 基于vue2和mpvue开发小程序:(创建项目和less语法)

    初始化一个mpvue项目首先安装Node.js 和 vue-cli脚手架工具 npm install -g vue-cli@2.9创建一个基于 mpvue-quickstart 模板的新项目 vue init mpvue/mpvue-quickstart 项目名称然后按照提示运行: 中间会询问一下问题:前面几项直接回车 直到:询问是否安装 vuex 和 ESLint…...

    2024/4/25 0:24:15
  2. 回文质数

    https://www.luogu.com.cn/problem/P1217本人所有文章都受版权保护,著作权归本人(Joseph_tony)所有,未经授权,禁止转载,若不得不转载,请注明原文网址链接!谢谢配合!题目描述 因为 151 既是一个质数又是一个回文数(从左到右和从右到左是看一样的),所以 151 是回文质…...

    2024/4/25 0:24:08
  3. http请求的四部分(请求行,请求头,空行,请求体)

    抓包工具抓取数据 第一部分 :请求行 第二部分:请求头 第三部分:空行 第四部分:请求体(body)请求行例如:GET /index.html HTTP/1.1get方法将数据拼接在url后面,传递参数受限请求方法:GET、POST、HEAD、PUT、DELETE、OPTIONS、TRACE、CONNECT协议版本 1.1 请求头(key val…...

    2024/5/3 4:02:58
  4. 剑指Offer-11 旋转数组中的最小数字(二分法)

    题目描述 把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如,数组 [3,4,5,1,2] 为 [1,2,3,4,5] 的一个旋转,该数组的最小值为1。 示例 1: 输入:[3,4,5,1,2] 输出:1示例 2: 输入:[1,1…...

    2024/5/3 6:10:17
  5. HTML5系列代码:特殊的链接方式

    border-bottom 简写属性把下边框的所有属性设置到一个声明中。 可以按顺序设置如下属性: border-bottom-width border-bottom-style border-bottom-color 如果不设置其中的某个值,也不会出问题,比如 border-bottom:solid #ff0000; 也是允许的。<html><head><…...

    2024/4/25 0:24:08
  6. Linux解决:系统主机名为bogon方法

    一、问题描述 1.在虚拟机安装Linux系统中,版本为CentOS 6.10,在开启系统的过程中,发现主机名未bogon 2.便查看vim /etc/sysconfig/network 配置,发现里面HOSTNAME值正常。查看vim /etc/hosts 也没有异常。二、问题 通过百度搜索以下相关资料: (1)bogon是指那些不该出现在…...

    2024/5/3 5:34:47
  7. Docker学习笔记七:导入导出容器

    将“容器的文件系统”保存到tar包 docker export是将“容器的文件系统”导出为一个tar包。注意是操作的对象是容器!它的具体语法如下: docker export [OPTIONS] CONTAINER其中:docker export: Docker将容器导出到tar包的命令关键词; OPTIIONS: 命令选项,-o指定写到一个文件…...

    2024/4/25 0:24:02
  8. 使用Lambda作为参数和返回值

    1.使用Lambda作为参数 java.lang.Runnable就是接口就是一个函数式接口,假设有一个startThread方法使用该接口作为参数,那么就可以使用Lambda进行传参,这种情况其实和Thread类的构造方法参数Runnable没有根本区别。 package com.wcy.demo3.LambdaTest;/*例如java.lang.Runnab…...

    2024/4/14 20:40:09
  9. 前端基础知识简单了解

    转自:https://www.cnblogs.com/langqq/p/10484499.html 来源:前端面试常问的基础问题大全 1、es6的新特性 参考:https://www.jianshu.com/p/390a65d7a353 新增变量声明方式:let 块级作用域 const常量声明 promise 箭头函数:不需要function关键字来创建函数,可以省略retu…...

    2024/4/14 20:40:08
  10. C#连接Excel数据处理

    C#读取Excel数据 代码片 using System; using System.Collections.Generic; using System.Data.SqlClient; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Data.OleDb;//excel表数据操作 using System.Data; using System.Globalization;…...

    2024/4/18 7:58:42
  11. EventBus3.x的正确打开方式

    EventBus的基本使用我就不介绍了,可以看GitHub地址。 EventBus内部默认使用的是运行时反射,反射当然是影响性能的。为此EventBus3.0做出了优化,但是只升级EventBus版本并没有用到优化策略,需要我们加些代码才行。 1.在gradle文件添加如下: android {defaultConfig {javaCo…...

    2024/4/18 8:22:30
  12. Maven optional关键字透彻图解

    写在前面 本来想写一篇「如何自定义Spring Boot Starter」,但是为了更好理解 Starter 的一些设计理念和其中的关键点,所以提前将一些细节内容单独提取出来讲解说明 在 Maven pom.xml 中,你经常会看到依赖项中有类似下面的代码: <dependency><groupId>sample.Pro…...

    2024/4/26 4:39:24
  13. 数据结构基础——数值转换问题

    问题描述:这是数据结构课程设计中对栈的基础应用,将十进制数N转换成D进制数。PS:因为小杨同学最近学习了数据结构,就将这些分享给大家了!做的可能不是太好,欢迎留言指导呀!这题最简单的方法是采用除D取余法。因为这个方法最先产生的余数是起转换结果得最低位,正好符合栈…...

    2024/4/25 0:24:01
  14. 设计院小程序多项目切换设计思路和实践

    我们用一个小程序管理多个项目,服务端是engineercms,每个项目下面是分多个标段。如下图。如何做好多项目的切换?主要解决的是每个项目的树状目录不同。有的项目2个标,有的n个标,有的项目分级目录2级,有的3级,还有财务登记,打卡,设代日志……是将这些放到数据库中呢还是…...

    2024/4/25 0:24:02
  15. ul li 实现点击下拉菜单栏效果

    <!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8"><title>下拉</title><script>//........var ul = $(this).siblings(ul);//遍历a元素的兄弟元素(限制为ul)</script> <script>//.…...

    2024/4/25 0:24:05
  16. 考研英语 长难句训练day85

    例1: It has been clear for some time that tech giants seem to pay less tax than might be expected given their revenues.given prep.考虑到 tax n.税款;税 revenues n.财政收入;税收收入;收益1.找主句的谓语动词:been 主句: It has been clear for some time 一段…...

    2024/4/25 0:23:58
  17. JDK动态代理介绍与使用

    一、介绍JDK动态代理是代理模式的一种实现方式。因为它是基于接口来做代理的,所以也常被称为接口代理。在JDK动态代理中有两个重要的角色:InvocationHandler(Interface) 用户实现这个接口,用来编写代理类处理的核心逻辑。 Proxy(Class) 用来创建一个代理实例,此时需要用到上…...

    2024/4/25 0:23:56
  18. Go 运算符的优先级及其它运算符

    运算符的优先级优先级表:运算符有不同的优先级,所谓优先级就是表达式运算中的运算顺序。如下表,上一行的运算总是优先于下一行。只有单目运算,赋值运算是从右往左运算的。梳理了一个大概的优先级1:括号,++, -- 2: 单目运算3:算术运算符4:移位运算5:关系运算符6:位运算…...

    2024/4/28 13:37:07
  19. elementui+vue切换主题颜色

    安装elementui的自定义主题工具npm i element-theme -g npm i element-theme-chalk -D初始化变量文件et -i element-variables.scss在根目录下会产生element-variables.scss,找到这个文件,更改primary$--color-primary: #02fd0a !default;命令行输入et,编译完成之后在主目录下…...

    2024/4/25 0:23:57
  20. 【HTML DOM】Node.nodeValue的用法

    目录结构:contents structure [+] 语法 注意 详述 实例 参考文章Note.noteValue 属性返回或设置当前属性的值。 语法value = node.nodeValue;如果有值的话,value则是一个包含当前节点值的字符串,如果没有则是null。 注意 对于document文档自身来说,nodeValue返回null。对于…...

    2024/4/25 0:24:00

最新文章

  1. UI-Diffuser——使用生成式扩散模型的UI原型设计算法解析

    概述。 移动UI是影响参与度的一个重要因素&#xff0c;例如用户对应用的熟悉程度和使用的便利性。如果你有一个类似的应用程序&#xff0c;你可能会选择一个具有现代、好看的设计的应用程序&#xff0c;而不是一个旧的设计。然而&#xff0c;要从头开始研究什么样的UI最适合应…...

    2024/5/3 9:51:24
  2. 梯度消失和梯度爆炸的一些处理方法

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

    2024/3/20 10:50:27
  3. [C++][算法基础]模拟队列(数组)

    实现一个队列&#xff0c;队列初始为空&#xff0c;支持四种操作&#xff1a; push x – 向队尾插入一个数 x&#xff1b;pop – 从队头弹出一个数&#xff1b;empty – 判断队列是否为空&#xff1b;query – 查询队头元素。 现在要对队列进行 M 个操作&#xff0c;其中的每…...

    2024/5/1 13:02:53
  4. Oracle备份和还原的几种方式

    1、使用数据泵方式 exp demo/demoorcl buffer1024 filed&#xff1a;\back.dmp fully demo&#xff1a;用户名、密码 buffer: 缓存大小 file: 具体的备份文件地址 full: 是否导出全部文件 ignore: 忽略错误&#xff0c;如果表已经存在&#xff0c;则也是覆盖 exp demo/de…...

    2024/4/30 4:18:57
  5. 【外汇早评】美通胀数据走低,美元调整

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

    2024/5/1 17:30:59
  6. 【原油贵金属周评】原油多头拥挤,价格调整

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

    2024/5/2 16:16:39
  7. 【外汇周评】靓丽非农不及疲软通胀影响

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

    2024/4/29 2:29:43
  8. 【原油贵金属早评】库存继续增加,油价收跌

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

    2024/5/2 9:28:15
  9. 【外汇早评】日本央行会议纪要不改日元强势

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

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

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

    2024/4/27 14:22:49
  11. 【外汇早评】美欲与伊朗重谈协议

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

    2024/4/28 1:28:33
  12. 【原油贵金属早评】波动率飙升,市场情绪动荡

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

    2024/4/30 9:43:09
  13. 【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试

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

    2024/4/27 17:59:30
  14. 【原油贵金属早评】市场情绪继续恶化,黄金上破

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

    2024/5/2 15:04:34
  15. 【外汇早评】美伊僵持,风险情绪继续升温

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

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

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

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

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

    2024/4/29 20:46:55
  18. 氧生福地 玩美北湖(中)——永春梯田里的美与鲜

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

    2024/4/30 22:21:04
  19. 氧生福地 玩美北湖(下)——奔跑吧骚年!

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

    2024/5/1 4:32:01
  20. 扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!

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

    2024/4/27 23:24:42
  21. 「发现」铁皮石斛仙草之神奇功效用于医用面膜

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

    2024/4/28 5:48:52
  22. 丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者

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

    2024/4/30 9:42:22
  23. 广州械字号面膜生产厂家OEM/ODM4项须知!

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

    2024/5/2 9:07:46
  24. 械字号医用眼膜缓解用眼过度到底有无作用?

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

    2024/4/30 9:42:49
  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