Spring源码03 - bean注入和生命周期

bean注入和生命周期(面试)

文章目录

  • bean注入和生命周期(面试)
    • 一:getBean的主体思路
      • 1:初步思路
      • 2:SpringBean的主体思路
    • 二:Spring如何解决循环依赖问题
      • 1:三级Map(一成二半三工厂)
        • 1.1:三级缓存的作用
        • 1.2:Bean在三级缓存中的流转
      • 2:源码分析
      • 3:从问题到深入理解
        • 3.1:为什么不能解决构造器的循环依赖
        • 3.2:为什么不能解决prototype作用域循环依赖
        • 3.3:单例代理可能出现问题
        • 3.4:那么其它循环依赖如何解决
        • 3.5:为什么要引入三级缓存
    • 三:Bean生命周期
      • 1:Bean生命周期的过程
      • 2:Bean生命周期源码
        • 2.1:实例化前
        • 2.2:实例化
        • 2.3:推断构造方法
        • 2.4:BeanDefinition的后置处理
        • 2.5:实例化后
        • 2.6:自动注入
        • 2.7:填充属性
        • 2.8:执行Aware回调接口
        • 2.9:初始化前
        • 2.10:初始化
        • 2.11:初始化后

如何从IOC容器已有的BeanDefinition信息,实例化出Bean对象,这里还会包括三块重点内容:

  • BeanFactory中getBean的主体思路
  • Spring如何解决循环依赖问题
  • Spring中Bean的生命周期

在这里插入图片描述

一:getBean的主体思路

前面我们知道BeanFactory定义了Bean容器的规范,其中包含根据bean的名字, Class类型和参数等来得到bean实例。

// 根据bean的名字和Class类型等来得到bean实例    
Object getBean(String name) throws BeansException;    
Object getBean(String name, Class requiredType) throws BeansException;    
Object getBean(String name, Object... args) throws BeansException;
<T> T getBean(Class<T> requiredType) throws BeansException;
<T> T getBean(Class<T> requiredType, Object... args) throws BeansException;

1:初步思路

已经分析了IoC初始化的流程,最终的将Bean的定义即BeanDefinition放到beanDefinitionMap中,本质上是一个ConcurrentHashMap<String, Object>

并且BeanDefinition接口中包含了这个类的Class信息以及是否是单例等

在这里插入图片描述
这样我们初步有了实现Object getBean(String name)这个方法的思路:

  1. 从beanDefinitionMap通过beanName获得BeanDefinition
  2. 从BeanDefinition中获得beanClassName
  3. 通过反射初始化beanClassName的实例instance
    • 构造函数从BeanDefinition的getConstructorArgumentValues()方法获取
    • 属性值从BeanDefinition的getPropertyValues()方法获取
  4. 返回beanName的实例instance

对于单例的信息,如果是无参构造函数的实例还可以放在一个缓存中,这样下次获取这个单例的实例时只需要从缓存中获取,如果获取不到再通过上述步骤获取。

2:SpringBean的主体思路

BeanFactory实现getBean方法在AbstractBeanFactory中,这个方法重载都是调用doGetBean方法进行实现的:

ublic Object getBean(String name) throws BeansException {
    return doGetBean(name, null, null, false);
}
public <T> T getBean(String name, Class<T> requiredType) throws BeansException {
    return doGetBean(name, requiredType, null, false);
}
public Object getBean(String name, Object... args) throws BeansException {
    return doGetBean(name, null, args, false);
}
public <T> T getBean(String name, @Nullable Class<T> requiredType, @Nullable Object... args) throws BeansException {
    return doGetBean(name, requiredType, args, false);
}

doGetBean这个方法很长,我们主要看它的整体思路和设计要点:

/**
 * 获取指定bean的实例,可能是共享的或独立的。
 * @param name 要检索的bean的名称
 * @param requiredType 需要检索的bean的类型
 * @param args 创建bean实例时使用的显式参数(仅在创建新实例而不是检索现有实例时适用)
 * @param typeCheckOnly 该实例是否仅用于类型检查,而不是实际使用
 * @return bean的实例
 * @throws BeansException 如果bean无法创建
 */
@SuppressWarnings("unchecked")
protected <T> T doGetBean(
        String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
        throws BeansException {
    // // 解析bean的真正name,如果bean是工厂类,name前缀会加&,需要去掉
    String beanName = transformedBeanName(name);
    Object beanInstance;

    // 提前检查手动注册的单例缓存 ------------> 注意这行,循环依赖会深入
    Object sharedInstance = getSingleton(beanName);
    // 无参单例的处理
    if (sharedInstance != null && args == null) {
        // ...log
        // 无参单例从缓存中获取
        beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, null);
    } else {
        // 如果当前正在创建这个bean实例,则失败
        // 我们可能处于一个循环引用中
        if (isPrototypeCurrentlyInCreation(beanName)) {
            throw new BeanCurrentlyInCreationException(beanName);
        }
        // 如果 bean definition 存在于父的bean工厂中,委派给父Bean工厂获取
        BeanFactory 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) {
                // 无参数 -> 委托给标准的getBean方法
                return parentBeanFactory.getBean(nameToLookup, requiredType);
            } else {
                return (T) parentBeanFactory.getBean(nameToLookup);
            }
        }

        if (!typeCheckOnly) {
            // 将当前bean实例放入alreadyCreated集合里,标识这个bean准备创建了
            markBeanAsCreated(beanName);
        }

        // bean创建的启动步骤
        StartupStep beanCreation = this.applicationStartup.start("spring.beans.instantiate").tag("beanName", name);
        try {
            if (requiredType != null) {
                beanCreation.tag("beanType", requiredType::toString);
            }
            // 拿到beandefinition -> mbd
            RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
            // 检查一下
            checkMergedBeanDefinition(mbd, beanName, args);

            // 确保当前bean依赖的bean被初始化
            String[] dependsOn = mbd.getDependsOn();
            // 存在依赖问题
            if (dependsOn != null) {
                for (String dep : dependsOn) {
                    // 如果当前的bean对其依赖有依赖性,存在循环依赖
                    if (isDependent(beanName, dep)) {
                        // 循环依赖
                        throw new BeanCreationException(mbd.getResourceDescription(), beanName,
                                "Circular depends-on relationship between '" + beanName + "' and '" + dep + "'");
                    }
                    // 否则注册依赖bean
                    registerDependentBean(dep, beanName);
                    try {
                        // 初始化它依赖的Bean
                        getBean(dep);
                    }
                    // 初始化它依赖的bean失败,没有这个bean,error
                    catch (NoSuchBeanDefinitionException ex) {
                        throw new BeanCreationException(mbd.getResourceDescription(), beanName,
                                "'" + beanName + "' depends on missing bean '" + dep + "'", ex);
                    }
                }
            }

            // 创建bean实例,单例模式
            if (mbd.isSingleton()) {
                sharedInstance = getSingleton(beanName, () -> {
                    try {
                        // 真正创建bean的方法 <-
                        return createBean(beanName, mbd, args);
                    } catch (BeansException ex) {
                        // 显式地从单例缓存中移除实例:可能已经被早早地放入缓存中,以允许循环引用的解析。
                        // 同时移除任何接收到该bean临时引用的bean。
                        destroySingleton(beanName);
                        throw ex;
                    }
                });
                beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
            }
            // 创建bean实例,原型模式
            else if (mbd.isPrototype()) {
                // 如果是原型bean,则创建一个新实例
                Object prototypeInstance = null;
                try {
                    beforePrototypeCreation(beanName);
                    prototypeInstance = createBean(beanName, mbd, args);
                }
                finally {
                    afterPrototypeCreation(beanName);
                }
                beanInstance = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
            }
            // 创建Bean实例:根据bean的scope创建
            else {
                String scopeName = mbd.getScope();
                if (!StringUtils.hasLength(scopeName)) {
                    throw new IllegalStateException("No scope name defined for bean '" + beanName + "'");
                }
                Scope scope = this.scopes.get(scopeName);
                if (scope == null) {
                    throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");
                }
                try {
                    Object scopedInstance = scope.get(beanName, () -> { 
                        beforePrototypeCreation(beanName);
                        try {
                            return createBean(beanName, mbd, args);
                        }
                        finally {
                            afterPrototypeCreation(beanName);
                        }
                    });
                    beanInstance = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
                }
                catch (IllegalStateException ex) {
                    throw new ScopeNotActiveException(beanName, scopeName, ex);
                }
            }
        }
        catch (BeansException ex) {
            beanCreation.tag("exception", ex.getClass().toString());
            beanCreation.tag("message", String.valueOf(ex.getMessage()));
            cleanupAfterBeanCreationFailure(beanName);
            throw ex;
        }
        finally {
            beanCreation.end();
        }
    }
    return adaptBeanInstance(name, beanInstance, requiredType);
}

在这里插入图片描述

二:Spring如何解决循环依赖问题

1:三级Map(一成二半三工厂)

Spring只是解决了单例模式下属性依赖的循环问题;

Spring为了解决单例的循环依赖问题,使用了三级缓存

https://www.bilibili.com/video/BV1AJ4m157MU/?spm_id_from=333.999.0.0&vd_source=3b6f4bdee490858d85d02d32ee8d05e9

1.1:三级缓存的作用

在这里插入图片描述

/** 单例对象缓存池,已经实例化并且属性赋值,这里的对象是成熟对象 */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(256);
 
/** 单例对象缓存池,已经实例化但尚未属性赋值,这里的对象是半成品对象,即未进行依赖注入的代理对象、不同对象 */
private final Map<String, Object> earlySingletonObjects = new HashMap<String, Object>(16);

/** 单例工厂的缓存,存放获取对象的lambda表达式,会根据是否被切AOP返回代理对象与否 */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<String, ObjectFactory<?>>(16);
1.2:Bean在三级缓存中的流转

存在循环引用

在这里插入图片描述

没有循环引用

在这里插入图片描述

假设现在有两个对象形成了循环依赖

public class TestService1 {
    @Autowired
    private TestService2 testService2;
    
    // other;
}

public class TestService2 {
    @Autowired
    private TestService1 testService1;
    
    // other;
}
  1. testService1先到成品map里去找是不是自己已经被创建过实例了啊,如果找到了直接拿了,但是显然没有,所以决定自己创建实例
  2. testService1先将自己放到缓存工厂中,提前暴露自己
  3. testService1准备进行依赖注入,扫描依赖代码的时候发现要依赖testService2,所以要拿到testService2的实例
  4. 对于testService2同理,先到成品map里去找是不是自己已经被创建过实例了啊,如果找到了直接拿了,但是显然没有,所以决定自己创建实例
  5. testService2也将自己放到缓存工厂中,提前暴露自己
  6. testService2也准备进行依赖注入,扫描依赖代码的时候发现要依赖testService1,所以要拿到testService1的实例
  7. 从缓存工厂中发现testService1已经在这里了,testService2成功拿到testService1的实例,完成了所有的依赖注入,初始化完成,成为成品,添加到一级缓存【成品】
  8. testService1也可以完成依赖注入了,因为可以拿到testService2这个的成品实例,也完成初始化,成为成品,添加到一级缓存【成品】

在这里插入图片描述

2:源码分析

回到上面说的 doGetBean() 的这行

// 提前检查手动注册的单例缓存 ------------> 注意这行,循环依赖会深入
Object sharedInstance = getSingleton(beanName);

进入 getSingletion()

@Override
@Nullable
public Object getSingleton(String beanName) {
    return getSingleton(beanName, true);
}
// ================== 下面是获取单例中的源码 ====================
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    // Spring首先从singletonObjects(一级缓存)中尝试获取 -- 对应上图中的蓝色
    Object singletonObject = this.singletonObjects.get(beanName);
    // 若是获取不到而且对象在建立中,则尝试从earlySingletonObjects(二级缓存)中获取
    // 这里说一下isSingletonCurrentlyInCreation
    // 判断当前单例bean是否正在建立中,也就是没有初始化完成
    // 好比A的构造器依赖了B对象因此得先去建立B对象,或则在A的populateBean过程当中依赖了B对象,得先去建立B对象
    // 这时的A就是处于建立中的状态。
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        synchronized (this.singletonObjects) {
            // 尝试从earlySingletonObjects(二级缓存)中获取
            singletonObject = this.earlySingletonObjects.get(beanName);
            // 没从二级缓存中拿到
            // allowEarlyReference -> 是否容许从singletonFactories中经过getObject拿到对象
            if (singletonObject == null && allowEarlyReference) {
                // 从缓存工厂中拿
                ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                // 如果在缓存工厂中
                if (singletonFactory != null) {
                    // 若是仍是获取不到而且容许从singletonFactories经过getObject获取
                    // 则经过singletonFactory.getObject()(三级缓存)获取
                    singletonObject = singletonFactory.getObject();
                    // 若是获取到了则将singletonObject放入到earlySingletonObjects
                    // 也就是将三级缓存提高到二级缓存中
                    this.earlySingletonObjects.put(beanName, singletonObject);
                    this.singletonFactories.remove(beanName);
                }
            }
        }
    }
    return (singletonObject != NULL_OBJECT ? singletonObject : null);
}

纵观整个过程,实现如下:

  1. Spring首先从一级缓存singletonObjects中获取。
  2. 若是获取不到 && 对象正在建立中,就再从二级缓存earlySingletonObjects中获取。
  3. 若是仍是获取不到 && 容许singletonFactories经过getObject()获取,就从三级缓存singletonFactory.getObject()(三级缓存)获取
  4. 若是获取到了则从三级缓存移动到了二级缓存。

从上面三级缓存的分析,咱们能够知道,Spring解决循环依赖的诀窍就在于singletonFactories这个三级cache。这个cache的类型是ObjectFactory:

public interface ObjectFactory<T> {
    T getObject() throws BeansException;
}

在bean建立过程当中,有两处比较重要的匿名内部类实现了该接口。

  • 一处是Spring利用其建立bean的时候
  • 另外一处就是解决循环依赖的时候
addSingletonFactory(beanName, new ObjectFactory<Object>() {
    @Override   
    public Object getObject() throws BeansException {
        return getEarlyBeanReference(beanName, mbd, bean);
    }
});

此处就是解决循环依赖的关键,这段代码发生在createBeanInstance以后,也就是说单例对象此时已经被建立出来的

这个对象已经被生产出来了,虽然还不完美(尚未进行初始化的第二步和第三步),可是已经能被人认出来了(根据对象引用能定位到堆中的对象)

因此Spring此时将这个对象提早曝光出来让你们认识,让你们使用

3:从问题到深入理解

Spring不能解决非单例属性之外的循环依赖,为什么?

3.1:为什么不能解决构造器的循环依赖

在调用构造方法之前还未将其放入三级缓存之中

构造器注入形成的循环依赖: 也就是beanB需要在beanA的构造函数中完成初始化,beanA也需要在beanB的构造函数中完成初始化

这种情况的结果就是两个bean都不能完成初始化,循环依赖难以解决。

Spring解决循环依赖主要是依赖三级缓存,但是的在调用构造方法之前还未将其放入三级缓存之中

因此后续的依赖调用构造方法的时候并不能从三级缓存中获取到依赖的Bean,因此不能解决。

3.2:为什么不能解决prototype作用域循环依赖

不会缓存原型作用域的bean

这种循环依赖同样无法解决,因为spring不会缓存‘prototype’作用域的bean,而spring中循环依赖的解决正是通过缓存来实现的

多实例Bean是每次调用一次getBean都会执行一次构造方法并且给属性赋值,根本没有三级缓存,因此不能解决循环依赖

3.3:单例代理可能出现问题

bean初始化完成之后,后面还有一步去检查第二级缓存 和 原始对象 是否相等,而因为是代理所以不相等导致问题出现

这种注入方式其实也比较常用,比如平时使用:@Async注解的场景,会通过AOP自动生成代理对象。
在这里插入图片描述
🎉 说白了,bean初始化完成之后,后面还有一步去检查:第二级缓存 和 原始对象 是否相等
在这里插入图片描述

3.4:那么其它循环依赖如何解决

对于单例代理对象的循环依赖

这类循环依赖问题解决方法很多,主要有:

  1. 使用 @Lazy 注解,延迟加载

  2. 使用 @DependsOn 注解,指定加载先后关系

  3. 修改文件名称,改变循环依赖类的加载顺序,例如上面的例子,如果这时候把TestService1改个名字,改成:TestService6,其他的都不变。

    @Service
    public class TestService6 {
    
        @Autowired
        private TestService2 testService2;
    
        @Async
        public void test1() {
        }
    }
    

    再重新启动一下程序,神奇般的好了。

    这就涉及spring的bean加载顺序,默认spring是按照文件完整路径递归查找的,按路径+文件名排序,排在前面的先加载。

    所以TestService1比TestService2先加载,而改了文件名称之后,TestService2比TestService6先加载。

在这里插入图片描述

使用@DependsOn产生的循环依赖

这类循环依赖问题要找到@DependsOn注解循环依赖的地方,迫使它不循环依赖就可以解决问题

多例实例导致的循环依赖

这类循环依赖问题可以通过把bean改成单例的,这样的就能成功创建对应的三级缓存

构造器的循环依赖问题

这类循环依赖问题可以通过使用@Lazy解决

3.5:为什么要引入三级缓存

首先当Bean未有循环依赖三级缓存是没有什么意义的,当有循环依赖但Bean并没有AOP代理,则会直接返回原对象,也没有什么意义。

主要在当Bean存在循环依赖并且还有AOP代理时,三级缓存才有效果,三级缓存主要预防Bean有依赖时还可以完成代理增强

而本身Spring设计Bean的代理增强是在Bean初始化完成后的AnnotationAwareAspectJCreator后置处理器中完成的。

提前执行则和设计思路相违背。所以三级缓存主要起预防循环依赖作用,可能是一个补丁机制

三:Bean生命周期

1:Bean生命周期的过程

Spring 只帮我们管理单例模式 Bean 的完整生命周期

对于 prototype 的 bean ,Spring 在创建好交给使用者之后则不会再管理后续的生命周期

在这里插入图片描述

实例化阶段:

  • 如果 BeanFactoryPostProcessor 和 Bean 关联, 则调用postProcessBeanFactory方法.(即首先尝试从Bean工厂中获取Bean)
  • 如果 InstantiationAwareBeanPostProcessor 和 Bean 关联,则调用postProcessBeforeInstantiation方法
  • 根据配置情况调用 Bean 构造方法实例化 Bean

初始化阶段

  • 利用依赖注入完成 Bean 中所有属性值的配置注入
  • 如果 InstantiationAwareBeanPostProcessor 和 Bean 关联,则调用postProcessAfterInstantiation方法和postProcessProperties
  • 调用xxxAware接口
    • 第一类Aware接口
      • 如果 Bean 实现了 BeanNameAware 接口,则 Spring 调用 Bean 的 setBeanName() 方法传入当前 Bean 的 id 值。
      • 如果 Bean 实现了 BeanClassLoaderAware 接口,则 Spring 调用 setBeanClassLoader() 方法传入classLoader的引用。
      • 如果 Bean 实现了 BeanFactoryAware 接口,则 Spring 调用 setBeanFactory() 方法传入当前工厂实例的引用。
    • 第二类Aware接口
      • 如果 Bean 实现了 EnvironmentAware 接口,则 Spring 调用 setEnvironment() 方法传入当前 Environment 实例的引用。
      • 如果 Bean 实现了 EmbeddedValueResolverAware 接口,则 Spring 调用 setEmbeddedValueResolver() 方法传入当前 StringValueResolver 实例的引用。
      • 如果 Bean 实现了 ApplicationContextAware 接口,则 Spring 调用 setApplicationContext() 方法传入当前 ApplicationContext 实例的引用。
  • 如果 BeanPostProcessor 和 Bean 关联,则 Spring 将调用该接口的预初始化方法 postProcessBeforeInitialzation() 对 Bean 进行加工操作
    • 此处非常重要,Spring 的 AOP 就是利用它实现的
  • 如果 Bean 实现了InitializingBean 接口,则 Spring 将调用 afterPropertiesSet() 方法。(或者有执行==@PostConstruct==注解的方法)
    • 如果在配置文件中通过 init-method 属性指定了初始化方法,则调用该初始化方法。
  • 如果 BeanPostProcessor 和 Bean 关联,则 Spring 将调用该接口的初始化方法 postProcessAfterInitialization()。
    • 此时,Bean 已经可以被应用系统使用了。

使用和销毁

  • 如果在 <bean> 中指定了该 Bean 的作用范围为 scope=“singleton”,则将该 Bean 放入 Spring IoC 的缓存池中,将触发 Spring 对该 Bean 的生命周期管理;
  • 如果在 <bean> 中指定了该 Bean 的作用范围为 scope=“prototype”,则将该 Bean 交给调用者,调用者管理该 Bean 的生命周期,Spring 不再管理该 Bean。
  • 如果 Bean 实现了 DisposableBean 接口,则 Spring 会调用 destory() 方法将 Spring 中的 Bean 销毁;(或者有执行==@PreDestroy==注解的方法)
    • 如果在配置文件中通过destory-method 属性指定了 Bean 的销毁方法,则 Spring 将调用该方法对 Bean 进行销毁。

上面就是整个Bean生命周期

Bean的完整生命周期经历了各种方法调用,这些方法可以划分为以下四类:

Bean自身的方法

这个包括了Bean本身调用的方法和通过配置文件中bean标签的init-method和destroy-method指定的方法

Bean级生命周期接口方法

这个包括了BeanNameAware、BeanFactoryAware、ApplicationContextAware… xxxAware

当然也包括InitializingBean和DiposableBean这些接口的方法(可以被@PostConstruct和@PreDestroy注解替代)

容器级生命周期接口方法

这个包括了InstantiationAwareBeanPostProcessor 和 BeanPostProcessor 这两个接口实现,一般称它们的实现类为“后处理器”。

工厂后处理器接口方法

这个包括了AspectJWeavingEnabler, ConfigurationClassPostProcessor, CustomAutowireConfigurer等等非常有用的工厂后处理器接口的方法。

工厂后处理器也是容器级的。在应用上下文装配配置文件之后立即调用。

2:Bean生命周期源码

在这里插入图片描述

对应源码中的方法如下:

  1. 实例化前: InstantiationAwareBeanPostProcessor.postProcessBeforeInstantiation()
  2. 实例化
  3. 实例化后置处理: MergedBeanDefinitionPostProcessor.postProcessMergedBeanDefinition()
  4. 实例化后: InstantiationAwareBeanPostProcessor.postProcessAfterInstantiation()
  5. 自动注入
  6. 填充属性: InstantiationAwareBeanPostProcessor.postProcessProperties()
  7. Aware回调
  8. 初始化前: BeanPostProcessor.postProcessBeforeInitialization()
  9. 初始化
  10. 初始化后: BeanPostProcessor.postProcessAfterInitialization()
2.1:实例化前

当前BeanDefinition对应的类被成功加载之后,就可以进行实例化对象了.

但是在Spring中,实例化对象前,Spring提供了一个扩展点,允许用户来控制是否在某个或者某些Bean实例化前做一些启动动作.

这个扩展点叫InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation()

⚠️ 这里是有返回值的,如果实现了这个并且返回了一个对象,那么后续Spring的依赖注入也就不会进行了,会跳过一些步骤,直接进行初始化后这一步.

public interface InstantiationAwareBeanPostProcessor extends BeanPostProcessor {
    @Nullable
    default Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
        return null;
    }
    ...
}  
2.2:实例化

在这一步就会根据BeanDefinition去创建一个对象了

Supplier创建对象

首先判断BeanDefinition是否设置了Supplier,如果设置了就会调用Supplier#get()得到对象.

@Test
public void testSupplier() {
    // 创建一个注解的应用上下文
    AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
    // 拿到抽象的bean definition
    AbstractBeanDefinition bd = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
    // Supplier方式创建对象
    bd.setInstanceSupplier(UserService::new);
    ctx.registerBeanDefinition("userService", bd);
    UserService userService = ctx.getBean("userService", UserService.class);
    userService.test();
}

工厂方式创建对象

如果没有设置Supplier,就会检查BeanDefinition是否设置了factoryMethod,也就是工厂方法

有两种方式可以设置factoryMethod

<bean id="userService" class="linc.cool.service.UserService" factory-method="createMethod" />
<bean id="commonService" class="linc.cool.service.CommonService"/>
<bean id="userService" factory-bean="commonService" factory-method="createUserService"/>

Spring发现当前BeanDefinition方法设置了工厂方法后,就会区分这两种方式,然后调用工厂方法得到对象

⚠️ 我们通过@Bean所定义的BeanDefinition是存在factoryMethod和factoryBean的,也就是和上面的方式二非常类似

@Bean所注解的方法就是factoryMethod,AppConfig对象就是FactoryBean;但是如果@Bean所注解的方法是static的,那么对应的就是方式一

2.3:推断构造方法

Spring在基于某个类生成Bean的时候, 会通过该类的构造方法去实例化一个对象, 但是如果这个类拥有多个构造方法的时候就会进入以下的判断

  • 只有一个构造方法, 那么不管这个构造方法是有参的还是无参的都会使用这个构造方法
  • 如果存在多个构造方法
    • 在多个构造方法中, 若存在无参构造方法, 则默认使用无参构造方法
      • 若该有参构造方法上有使用 @Autowired注解, 则使用有参构造
    • 若多个构造方法皆为有参构造
      • 若构造方法上都没有加 @Autowired注解, 则报错
      • 若有某一个构造方法上加了 @Autowired注解, 则使用该构造方法
      • 若存在多个构造方法上交了 @Autowired注解, 则报错

确定用哪个构造方法, 确定入参的Bean对象, 这个过程就叫做推断构造方法

额外的,在推断构造方法逻辑中除开回去选择构造方法以及查找入参对象以外,还会去判断是否存在对应的类中是否存在使用@Lookup注解了的方法.

如果存在就会把这个方法封装为LookupOverride对象并且添加到BeanDefinition

public class LookupOverride extends MethodOverride {
    @Nullable
    private final String beanName;

    @Nullable
    private Method method;
    ...
}

@Lookup方法就是方法注入

@Component
public class AService {
    private BService bService;

    public void test() {
        BService bService = createBService();
        System.out.println(bService);
        System.out.println(this.getClass().getName() + ".test()");
    }

    @Lookup // 方法注入
    public BService createBService() {
        return new BService();
    }
}
2.4:BeanDefinition的后置处理

Bean对象实例化出来之后,接下来就应该给对象的属性进行赋值了

在真正给属性赋值之前,Spring又提供了一个扩展点MergedBeanDefinitionPostProcessor#postProcessMergedBeanDefinition()

可以对此事的BeanDefinition进行加工

在Spring源码中,AutowiredAnnotationBeanPostProcessor就是一个MergedBeanDefinitionPostProcessor

在这里插入图片描述

它的postProcessMergedBeanDefinition中会去查找注入点

并缓存在AutowiredAnnotationBeanPostProcessor对象的一个Map中(injectionMetadataCache)

/**
 * 在合并后的Bean定义上执行后处理操作。
 * 该方法会查找并检查给定Bean的自动装配元数据,确保所有的注入成员都已正确配置。
 * 
 * @param beanDefinition 合并后的Bean定义,包含了Bean的所有配置信息。
 * @param beanType Bean的类型。
 * @param beanName Bean的名称。
 */
@Override
public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
    // 查找并获取该Bean的自动装配元数据 -> 查找注入点
    InjectionMetadata metadata = findAutowiringMetadata(beanName, beanType, null);
    // 检查配置成员,确保自动装配能够正确进行
    metadata.checkConfigMembers(beanDefinition);
}

/**
 * 查找并返回给定类的自动装配元数据。
 * 这个方法首先尝试从缓存中获取元数据,如果缓存中的元数据需要更新,则重新构建元数据并更新缓存。
 * 
 * @param beanName Bean的名称。如果名称为空或不存在,则使用类名作为缓存键。
 * @param clazz 需要查找自动装配元数据的类。
 * @param pvs 属性值,如果为null,则表示不需要清空元数据的属性值。
 * @return InjectionMetadata 对象,包含有关如何进行自动装配的信息。
 */

private final Map<String, InjectionMetadata> injectionMetadataCache = new ConcurrentHashMap<>(256);

private InjectionMetadata findAutowiringMetadata(String beanName, Class<?> clazz, @Nullable PropertyValues pvs) {
    // 使用beanName或类名作为缓存键,兼容旧版自定义调用者。
    String cacheKey = (StringUtils.hasLength(beanName) ? beanName : clazz.getName());
    // 首先尝试从并发映射中快速获取元数据,尽量减少锁定。
    InjectionMetadata metadata = this.injectionMetadataCache.get(cacheKey);
    if (InjectionMetadata.needsRefresh(metadata, clazz)) {
        // 如果元数据需要刷新,则进行加锁操作。
        synchronized (this.injectionMetadataCache) {
            metadata = this.injectionMetadataCache.get(cacheKey);
            // 再次检查是否需要刷新元数据,确保数据的准确性。
            if (InjectionMetadata.needsRefresh(metadata, clazz)) {
                // 如果元数据非空,则先清空属性值。
                if (metadata != null) {
                    metadata.clear(pvs);
                }
                // 构建新的自动装配元数据。
                metadata = buildAutowiringMetadata(clazz);
                // 更新缓存。
                this.injectionMetadataCache.put(cacheKey, metadata);
            }
        }
    }
    return metadata;
}
2.5:实例化后

在处理完BeanDefinition之后,Spring又设计了一个扩展点InstantiationAwareBeanPostProcessor#postProcessAfterInstantiation()

public interface InstantiationAwareBeanPostProcessor extends BeanPostProcessor {
    default boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
        return true;
    }
}
2.6:自动注入

自动注入指的是Spring的自动注入

2.7:填充属性

在这个步骤中,就会处理@Autowired、@Resource、@Value等注解,这个具体的循环依赖过程看上面的spring循环依赖哪里

也就是通过InstantiationAwareBeanPostProcessor#postProcessProperties扩展点来实现的

public interface InstantiationAwareBeanPostProcessor extends BeanPostProcessor {
    @Nullable
    default PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) throws BeansException {

        return null;
    }
    ...
}  

我们还可以自己实现一个注入点

package com.study.study_demo_of_spring_boot.processor;

import com.study.study_demo_of_spring_boot.annotation.MyAutowired;
import org.springframework.beans.BeansException;
import org.springframework.beans.PropertyValues;
import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor;

import java.util.stream.Stream;

/**
 * <p>
 * 功能描述:自定义一个注入点,将自己的注解的value值注入给bean
 * </p>
 *
 * @author cui haida
 * @date 2024/04/10/7:09
 */
public class MyAutowiredInstantiationAwareBeanPostProcessor implements InstantiationAwareBeanPostProcessor {
    /**
     * 在bean属性设置完成后,对属性进行后处理的回调方法。
     * 主要用于处理自定义的@Autowired注解,将注解中指定的值设置为字段的值。
     *
     * @param pvs 包含bean属性值的(PropertyValues)对象。
     * @param bean 正在进行属性设置的bean实例。
     * @param beanName bean的名称。
     * @return 返回传入的PropertyValues对象,通常为null或原对象(本方法中未修改)。
     * @throws BeansException 如果处理中发生错误。
     */
    @Override
    public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) throws BeansException {
        // 如果当前bean名称为"AService",则对其进行自定义注解处理
        if ("AService".equals(beanName)) {
            // 遍历bean的所有声明字段
            Stream.of(bean.getClass().getDeclaredFields()).forEach(f -> {
                // 如果字段上存在MyAutowired注解
                if (f.isAnnotationPresent(MyAutowired.class)) {
                    // 获取注解中的value值
                    String value = f.getAnnotation(MyAutowired.class).value();
                    // 设置字段可访问
                    f.setAccessible(true);
                    try {
                        // 将value值设置到字段中
                        f.set(bean, value);
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
        // 返回原始属性值对象
        return pvs;
    }
}
2.8:执行Aware回调接口

完成了属性赋值之后,Spring会执行一些回调接口,下面这三个称为一类Aware接口

  • BeanNameAware: 回传beanName给bean对象
  • BeanClassLoaderAware: 回传classLoader给对象
  • BeabFactoryAware: 回传beanFactory给对象
private void invokeAwareMethods(String beanName, Object bean) {
    if (bean instanceof Aware) {
        if (bean instanceof BeanNameAware) {
            ((BeanNameAware) bean).setBeanName(beanName);
        }
        if (bean instanceof BeanClassLoaderAware) {
            ClassLoader bcl = getBeanClassLoader();
            if (bcl != null) {
                ((BeanClassLoaderAware) bean).setBeanClassLoader(bcl);
            }
        }
        if (bean instanceof BeanFactoryAware) {
            ((BeanFactoryAware) bean).setBeanFactory(AbstractAutowireCapableBeanFactory.this);
        }
    }
}

一类Aware接口

一类Aware接口主要包括BeanNameAwareBeanFactoryAwareBeanClassLoaderAware等,这些接口使得Bean能够获取到Spring容器相关的信息或者对象。

例如,BeanNameAware允许Bean获取它在BeanFactory中的名称,而BeanFactoryAware则允许Bean获取到配置它的BeanFactory实例。

这类接口通常在Bean生命周期的早期被调用,如在Bean的构造方法之后、属性注入之前。

  • BeanNameAware: 允许Bean获取在BeanFactory中配置的名称,通常是XML配置文件中的id或name。
  • BeanFactoryAware: 允许Bean对配置其BeanFactory有感知,可以获取到BeanFactory实例。
  • BeanClassLoaderAware: 允许Bean获取到其类加载器,这在某些需要动态类加载的场景中很有用。

二类Aware接口

二类Aware接口包括EnvironmentAwareEmbeddedValueResolverAwareApplicationContextAware

这些接口使得Bean能够访问到Spring容器的环境信息、属性值解析器或者整个ApplicationContext。

例如,ApplicationContextAware允许Bean获取到它运行所在的ApplicationContext实例

这通常在初始化方法afterPropertiesSet()或自定义的init-method之后调用。

  • EnvironmentAware: 允许Bean获取当前环境的配置信息,比如通过System.getProperty()获取系统属性的值。
  • EmbeddedValueResolverAware: 允许Bean获取Spring容器加载的properties文件中的属性值。
  • ApplicationContextAware: 允许Bean获取当前ApplicationContext的实例,可以用来进行范围更广的信息获取和操作。

二者的区别

一类Aware接口侧重于Bean对Spring容器自身的认知,主要在Bean的创建和配置阶段使用。

二类Aware接口则提供了更广泛的环境信息,通常在Bean初始化和准备使用时调用。

一类Aware接口通常较早被调用,主要用于Bean自身与Spring容器的交互,而二类Aware接口则在Bean初始化后的生命周期方法中被调用,用于处理更复杂的上下文信息。

一类Aware接口的方法通常只有一个参数,而二类Aware接口可能会有多个参数,因为它们需要处理的上下文信息更多。

2.9:初始化前

初始化前也是Spring提供的一个扩展点BeanPostProcessor#postProcessBeforeInitialization

public interface BeanPostProcessor {
    /**
     * 该方法在bean实例化完毕(且已经注入完毕),在afterPropertiesSet或自定义init方法执行之前
     */
    default Object postProcessBeforeInitialization(Object bean, String beanName) {
        // 你可以在这里做一些前置增强
        return bean;
    }
    ...;
}  

利用初始化前,可以对进行了依赖注入的Bean进行处理

在Spring源码中:

  • InitDestroyAnnotationBeanPostProcessor会在初始化前这个步骤中执行@PostConstruct的方法
  • ApplicationContextAwareProcessor会在初始化前这个步骤中进行其他Aware的回调 <— 高级容器进行功能增强,补入二类Aware接口
    • EnvironmentAware:回传环境变量
    • EmbeddedValueResolverAware: 回传占位符解析器
    • ResourceLoaderAware: 回传资源加载器
    • ApplicationEventPublisherAware: 回传事件发布器
    • MessageSourceAware: 回传国际化资源
    • ApplicationStartupAware: 回传应用其他监听对象,可以忽略
    • ApplicationContextAware: 回传Spring容器的ApplicationContext
/**
 * 在Bean初始化之前对其进行后处理。检查给定的Bean是否实现了特定的接口,如果是,则调用相应的设置方法。
 * 这些接口包括:EnvironmentAware, EmbeddedValueResolverAware, ResourceLoaderAware,
 * ApplicationEventPublisherAware, MessageSourceAware, ApplicationContextAware, ApplicationStartupAware。
 * 如果Bean没有实现这些接口,则直接返回该Bean。
 * 
 * @param bean 将要被检查和可能被后处理的Bean实例。
 * @param beanName Bean的名称。
 * @return 经过可能的后处理后的Bean实例,如果没有进行后处理,则返回原始的Bean实例。
 * @throws BeansException 如果处理过程中发生错误。
 */
@Override
@Nullable
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
    // 检查bean是否实现了特定的接口,如果没有则直接返回原bean
	if (!(bean instanceof EnvironmentAware || bean instanceof EmbeddedValueResolverAware ||
			bean instanceof ResourceLoaderAware || bean instanceof ApplicationEventPublisherAware ||
			bean instanceof MessageSourceAware || bean instanceof ApplicationContextAware ||
			bean instanceof ApplicationStartupAware)) {
		return bean;
	}

	AccessControlContext acc = null;

    // 如果系统有安全管理员,则获取访问控制上下文
	if (System.getSecurityManager() != null) {
		acc = this.applicationContext.getBeanFactory().getAccessControlContext();
	}

    // 在有安全管理员的情况下,使用特权执行接口调用;否则直接调用
	if (acc != null) {
		AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
			invokeAwareInterfaces(bean);
			return null;
		}, acc);
	}
	else {
		invokeAwareInterfaces(bean);
	}

	return bean;
}
2.10:初始化

查看当前Bean对象是否实现InitializingBean#afterPropertiesSet()接口了,如果实现了就会调用afterPropertiesSet()方法

执行BeanDefinition中指定的初始化方法

public interface InitializingBean {
    // 初始化方法
    void afterPropertiesSet() throws Exception;
}

常用于进行一些格外操作在初始化的时候或者加载一些启动资源

public class InitConfig implements InitializingBean {
    @Override
    public void afterPropertiesSet() throws Exception {
        // 加载一些初始化资源,这些资源在项目启动的时候被加载,比如数据库连接池,缓存池等
        System.out.println("初始化资源");
        System.out.println("初始化资源");
    }
}
2.11:初始化后

这是Bean创建的生命周期中的最后一个步骤,也是Spring提供的一个扩展点BeanPostProcessor#postProcessAfterInitialization

可以在这个步骤中,对Bean最终进行处理

Spring中的AOP就是基于初始化后进行实现的,初始化后返回的对象才是最终的Bean对象

public interface BeanPostProcessor {
    // ...;
    /**
     * 在afterPropertiesSet或自定义init方法执行之后
     */
    default Object postProcessAfterInitialization(Object bean, String beanName) {
        return bean;
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/957776.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

vscode导入模块不显示类型注解

目录结构&#xff1a; utils.py&#xff1a; import random def select_Jrandom(i:int, m:int) -> int:"""随机选择一个不等于 i 的整数"""j iwhile j i:j int(random.uniform(0, m))return jdef clip_alpha(alpha_j:float, H:float, L:f…

浅谈机器学习之基于RNN进行充值的油费预测

浅谈机器学习之基于RNN进行充值的油费预测 引言 随着智能交通和物联网技术的发展&#xff0c;油费预测已成为研究的热点之一。准确的油费预测不仅能帮助车主合理规划出行成本&#xff0c;还可以为油价波动提供参考依据。近年来&#xff0c;递归神经网络&#xff08;RNN&#…

There is no getter for property named ‘XXX’ in ‘XXXX‘

写了一个POST方法用于新增软件描述信息&#xff0c;报错显示在我的实体类中没有这个属性的getter方法&#xff0c;实体类如下&#xff1a; 报错没有softWare这个属性的getter方法&#xff0c;但是我的实体类中本来就没有这个属性&#xff08;笑哭...) 后面查了许多资料发现&am…

基于springboot+vue的校园二手物品交易系统的设计与实现

开发语言&#xff1a;Java框架&#xff1a;springbootJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包&#xff1a;…

H266/VVC 变换编码中大尺寸变换块高频系数置零技术

大尺寸变换块高频系数置零 近年来视频技术有了飞速的变化&#xff0c;视频的分辨率从 1080P 过渡到 4K&#xff0c;并逐渐向发展 8K。为了适应日益增长的视频分辨率&#xff0c;新的编码技术采用了更大尺寸的变换块来提高编码效率&#xff0c;最大变换块大小变成 64x64。变换块…

5989.数字接龙

5989.数字接龙 小蓝最近迷上了一款名为《数字接龙》的迷宫游戏&#xff0c;游戏在一个大小为 NN 的格子棋盘上展开&#xff0c;其中每一个格子处都有着一个 0…K−10…K−1 之间的整数。 游戏规则如下&#xff1a; 从左上角 (0,0) 处出发&#xff0c;目标是到达右下角 (N−1…

Titans: 学习在测试时记忆 - 论文解读与总结

论文地址&#xff1a;https://arxiv.org/pdf/2501.00663v1 本文介绍了一篇由 Google Research 发表的关于新型神经网络架构 Titans 的论文&#xff0c;该架构旨在解决传统 Transformer 在处理长序列时的局限性。以下是对论文的详细解读&#xff0c;并结合原文图片进行说明&…

账号IP属地:依据手机号还是网络环境?

在数字化生活中&#xff0c;账号的IP属地信息往往成为我们关注的一个焦点。无论是出于安全考虑&#xff0c;还是为了满足某些特定服务的需求&#xff0c;了解账号IP属地的确定方式都显得尤为重要。那么&#xff0c;账号IP属地根据手机号还是网络来确定的呢&#xff1f;本文将深…

微信小程序实现自定义日历功能

文章目录 1. 创建日历组件实现步骤&#xff1a;2. 代码实现过程3. 实现效果图4. 关于作者其它项目视频教程介绍 1. 创建日历组件实现步骤&#xff1a; 创建日历组件&#xff1a;首先&#xff0c;你需要创建一个日历组件&#xff0c;包含显示日期的逻辑。样式设计&#xff1a;为…

YOLOv9改进,YOLOv9检测头融合RFAConv卷积,适合目标检测、分割任务

摘要 空间注意力已广泛应用于提升卷积神经网络(CNN)的性能,但它存在一定的局限性。作者提出了一个新的视角,认为空间注意力机制本质上解决了卷积核参数共享的问题。然而,空间注意力生成的注意力图信息对于大尺寸卷积核来说是不足够的。因此,提出了一种新型的注意力机制—…

【机器学习】深入无监督学习分裂型层次聚类的原理、算法结构与数学基础全方位解读,深度揭示其如何在数据空间中构建层次化聚类结构

&#x1f31f;个人主页&#xff1a;落叶 &#x1f31f;当前专栏: 机器学习专栏 目录 引言 分裂型层次聚类&#xff08;Divisive Hierarchical Clustering&#xff09; 1. 基本原理 2. 分裂型层次聚类的算法步骤 Step 1: 初始化 Step 2: 选择分裂的簇 Step 3: 执行分裂操作…

VirtualBox can‘t enable the AMD-V extension

个人博客地址&#xff1a;VirtualBox cant enable the AMD-V extension | 一张假钞的真实世界 最近一次完成Deepin的系统更新后&#xff0c;进入VirtualBox创建的虚拟机&#xff08;Widows10&#xff09;时&#xff0c;出现以下错误&#xff1a; 根据网址“https://askubuntu.…

[JavaScript] 数组与对象详解

文章目录 数组&#xff08;Array&#xff09;什么是数组数组的常用操作**访问数组元素****修改数组元素****数组的长度****添加和删除元素** 常用数组方法map():filter():reduce():**其他实用方法** 对象&#xff08;Object&#xff09;什么是对象对象的基本操作**访问属性****…

“模板”格式化发布新创诗(为《诗意 2 0 2 5》贡献力量)

预置MarkDown&Html文本&#xff0c;脚本读取f-string模板完成录入嵌套。 (笔记模板由python脚本于2025-01-22 19:19:58创建&#xff0c;本篇笔记适合喜欢分享的达人的coder翻阅) 【学习的细节是欢悦的历程】 博客的核心价值&#xff1a;在于输出思考与经验&#xff0c;而不…

论文速读|Multi-Modal Disordered Representation Learning Network for TBPS.AAAI24

论文地址&#xff1a;Multi-Modal Disordered Representation Learning Network for Description-Based Person Search 代码地址&#xff1a;未开源&#xff08;2025.01.22&#xff09; bib引用&#xff1a; inproceedings{yang2024multi,title{Multi-Modal Disordered Repres…

计算机视觉算法实战——实体物体跟踪

✨个人主页欢迎您的访问 ✨期待您的三连 ✨ ✨个人主页欢迎您的访问 ✨期待您的三连 ✨ ✨个人主页欢迎您的访问 ✨期待您的三连✨ ​ ​​​​​​​ ​ 1. 领域介绍✨✨ 实体物体跟踪&#xff08;Object Tracking&#xff09;是计算机视觉领域中的一个重要研究方向&#x…

C++17 新特性深入解析:constexpr 扩展、if constexpr 和 constexpr lambda

C17 不仅增强了现有特性&#xff0c;还引入了一些全新的编程工具&#xff0c;极大地提升了代码的效率和表达力。在这篇文章中&#xff0c;我们将深入探讨 C17 中与 constexpr 相关的三个重要特性&#xff1a;constexpr 的扩展用法、if constexpr 和 constexpr lambda。这些特性…

IVR:交互式语音应答系统解析及其应用

引言 IVR&#xff08;Interactive Voice Response&#xff09;&#xff0c;即交互式语音应答系统&#xff0c;是一种功能强大的电话自动服务系统。它通过语音识别和按键反馈&#xff0c;使用户与系统之间实现实时交互&#xff0c;为用户提供自助服务、咨询、报告、投诉等多种功…

Observability:最大化可观察性 AI 助手体验的 5 大提示(prompts)

作者&#xff1a;来自 Elastic Zoia_AUBRY 在过去三年担任客户工程师期间&#xff0c;我遇到了数百名客户&#xff0c;他们最常问的问题之一是&#xff1a;“我的数据在 Elastic 中&#xff1b;我该如何利用它获得最大优势&#xff1f;”。 如果这适用于你&#xff0c;那么本…

【Vim Masterclass 笔记25】S10L45:Vim 多窗口的常用操作方法及相关注意事项

文章目录 S10L45 Working with Multiple Windows1 水平分割窗口2 在水平分割的新窗口中显示其它文件内容3 垂直分割窗口4 窗口的关闭5 在同一窗口水平拆分出多个窗口6 关闭其余窗口7 让四个文件呈田字形排列8 光标在多窗口中的定位9 调节子窗口的尺寸大小10 变换子窗口的位置11…