• 天天用 Spring,bean 实例化原理你懂吗?


    来源:小小木的博客
    www.cnblogs.com/wyc1994666/p/10650480.html

    本次主要想写spring bean的实例化相关的内容。创建spring bean 实例是spring bean 生命周期的第一阶段。

    bean 的生命周期主要有如下几个步骤:

    • 创建bean的实例

    • 给实例化出来的bean填充属性

    • 初始化bean

    • 通过IOC容器使用bean

    • 容器关闭时销毁bean

    在实例化bean之前在BeanDefinition里头已经有了所有需要实例化时用到的元数据,接下来spring 只需要选择合适的实例化方法以及策略即可。实例化方法有两大类分别是工厂方法和构造方法实例化,后者是最常见的。

    spring默认的实例化方法就是无参构造函数实例化。

    如我们在xml里定义的 <bean id="xxx" class="yyy"/> 以及用注解标识的bean都是通过默认实例化方法实例化的。

    • 两种实例化方法(构造函数 和 工厂方法)

    • 源码阅读

    • 实例化策略(cglib or 反射)

    两种实例化方

    使用适当的实例化方法为指定的bean创建新实例:工厂方法,构造函数实例化。

    代码演示

    启动容器时会实例化所有注册的bean(lazy-init懒加载的bean除外),对于所有单例非懒加载的bean来说当从容器里获取bean(getBean(String name))的时候不会触发,实例化阶段,而是直接从缓存获取已准备好的bean,而生成bean的时机则是下面这行代码运行时触发的。

    @Test  
    public void testBeanInstance(){          
        // 启动容器  
        ApplicationContext context = new ClassPathXmlApplicationContext("spring-beans.xml");  
    }   
    

    一 使用工厂方法实例化(很少用)

    1.静态工厂方法
    public class FactoryInstance {      
        public FactoryInstance() {  
            System.out.println("instance by FactoryInstance");  
        }  
    }  
      
    public class MyBeanFactory {    public static FactoryInstance getInstanceStatic(){        return new FactoryInstance();  
        }  
    }   
    
    <?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans"  
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">  
    
        <bean id="factoryInstance" class="spring.service.instance.MyBeanFactory" factory-method="getInstanceStatic"/>  
    
    </beans>  
    

    输出结果为:

    instance by FactoryInstance

    2.实例工厂方法
    public class MyBeanFactory {      
    
        /**  
         * 实例工厂创建bean实例  
         *  
         * @return  
         */  
        public FactoryInstance getInstance() {        return new FactoryInstance();  
        }  
    }  
    
    <?xml version="1.0" encoding="UTF-8"?>  
    <beans xmlns="http://www.springframework.org/schema/beans"  
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">  
             
        <!-- 工厂实例 -- >     
        <bean id="myBeanFactory" class="MyBeanFactory"/>  
        <bean id="factoryInstance" factory-bean="myBeanFactory" factory-method="getInstance"/>  
          
    </beans>   
    

    输出结果为:

    instance by FactoryInstance

    二 使用构造函数实例化(无参构造函数 & 有参构造函数)

    1.无参构造函数实例化(默认的)
    public class ConstructorInstance {      
        public ConstructorInstance() {  
            System.out.println("ConstructorInstance none args");  
        }   
    }   
    
    <?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans"  
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">  
          
        <bean id="constructorInstance" class="spring.service.instance.ConstructorInstance"/>  
    </beans>  
    

    输出结果为:

    ConstructorInstance none args

    1.有参构造函数实例化
    public class ConstructorInstance {      
        private String name;      
        public ConstructorInstance(String name) {  
            System.out.println("ConstructorInstance with args");          
            this.name = name;  
        }      
          
        public String getName() {          
            return name;  
        }      
          
        public void setName(String name) {          
            this.name = name;  
        }  
    
    }  
    
    <?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans"  
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">  
             
       <bean id="constructorInstance" class="spring.service.instance.ConstructorInstance">  
            <constructor-arg index="0" name="name" value="test constructor with args"/>  
       </bean>  
          
    </beans>   
    

    输出结果为:

    ConstructorInstance with args

    源码阅读

    下面这段是 有关spring bean生命周期的代码,也是我们本次要讨论的bean 实例化的入口。bean 为什么默认单例?推荐看下。关注Java技术栈公众号在后台回复Spring阅读更多Spring系列教程。

    doCreateBean方法具体实现在doCreateBeanAbstractAutowireCapableBeanFactory类,感兴趣的朋友可以进去看看调用链。

    protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args) {  
      //第一步 创建bean实例 还未进行属性填充和各种特性的初始化  
      BeanWrapper instanceWrapper = null;  
      if (instanceWrapper == null) {  
       instanceWrapper = createBeanInstance(beanName, mbd, args);  
      }  
      final Object bean = (instanceWrapper != null ? instanceWrapper.getWrappedInstance() : null);  
      Class<?> beanType = (instanceWrapper != null ? instanceWrapper.getWrappedClass() : null);  
      
      Object exposedObject = bean;  
      try {  
          // 第二步 进行属性填充  
       populateBean(beanName, mbd, instanceWrapper);  
       if (exposedObject != null) {  
           // 第三步 初始化bean 执行初始化方法  
        exposedObject = initializeBean(beanName, exposedObject, mbd);  
       }  
      }catch (Throwable ex) {  
          //  抛相应的异常  
      }  
      
      // Register bean as disposable.  
      try {  
       registerDisposableBeanIfNecessary(beanName, bean, mbd);  
      }catch (BeanDefinitionValidationException ex) {  
       throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex);  
      }  
      return exposedObject;  
    }   
    

    我们这里只需关注第一步创建bean实例的流程即可
    instanceWrapper = createBeanInstance(beanName, mbd, args);

    protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, Object[] args) {  
      // Make sure bean class is actually resolved at this point.  
      Class<?> beanClass = resolveBeanClass(mbd, beanName);  
            // 使用工厂方法进行实例化  
      if (mbd.getFactoryMethodName() != null)  {  
       return instantiateUsingFactoryMethod(beanName, mbd, args);  
      }  
      // Need to determine the constructor...  
      Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);  
      // 使用带参构造函数初始化  
      if (ctors != null ||  
        mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_CONSTRUCTOR ||  
        mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args))  {  
             
       return autowireConstructor(beanName, mbd, ctors, args);  
      }  
      
      // 默认实例化方式 无参构造实例化  
      return instantiateBean(beanName, mbd);  
    }   
    

    上面代码就是spring 实现bean实例创建的核心代码。这一步主要根据BeanDefinition里的元数据定义决定使用哪种实例化方法,主要有下面三种:

    • instantiateUsingFactoryMethod 工厂方法实例化的具体实现

    • autowireConstructor 有参构造函数实例化的具体实现

    • instantiateBean 默认实例化具体实现(无参构造函数)

    实例化策略(cglib or 反射)

    工厂方法的实例化手段没有选择策略直接用了发射实现的
    实例化策略都是对于构造函数实例化而言的

    上面说到的两构造函数实例化方法不管是哪一种都会选一个实例化策略进行,到底选哪一种策略也是根据BeanDefinition里的定义决定的。

    beanInstance = getInstantiationStrategy().instantiate(mbd, beanName, parent);   
    

    上面这一行代码就是选择实例化策略的代码,进入到上面两种方法的实现之后发现都有这段代码。

    下面选一个instantiateBean的实现来介绍

    protected BeanWrapper instantiateBean(final String beanName, final RootBeanDefinition mbd) {  
      try {  
       Object beanInstance;  
       final BeanFactory parent = this;  
       if (System.getSecurityManager() != null) {  
        beanInstance = AccessController.doPrivileged(new PrivilegedAction<Object>() {  
         @Override  
         public Object run() {  
          return getInstantiationStrategy().instantiate(mbd, beanName, parent);  
         }  
        }, getAccessControlContext());  
       }  
       else {  
           // 在这里选择一种策略进行实例化  
        beanInstance = getInstantiationStrategy().instantiate(mbd, beanName, parent);  
       }  
       BeanWrapper bw = new BeanWrapperImpl(beanInstance);  
       initBeanWrapper(bw);  
       return bw;  
      }  
      catch (Throwable ex) {  
       throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Instantiation of bean failed", ex);  
      }  
    }   
    

    选择使用反射还是cglib

    先判断如果beanDefinition.getMethodOverrides()为空也就是用户没有使用replace或者lookup的配置方法,那么直接使用反射的方式,简单快捷,但是如果使用了这两个特性,在直接使用反射的方式创建实例就不妥了,因为需要将这两个配置提供的功能切入进去,所以就必须要使用动态代理的方式将包含两个特性所对应的逻辑的拦截增强器设置进去,这样才可以保证在调用方法的时候会被相应的拦截器增强,返回值为包含拦截器的代理实例。---引用自《spring 源码深度剖析》这本书

    <bean id="constructorInstance" class="spring.service.instance.ConstructorInstance" >        <lookup-method name="getName" bean="xxx"/>  
        <replaced-method name="getName" replacer="yyy"/>  
    </bean>   
    

    如果使用了lookup或者replaced的配置的话会使用cglib,否则直接使用反射。
    具体lookup-methodreplaced-method的用法可以查阅相关资料。

    public Object instantiate(RootBeanDefinition bd, String beanName, BeanFactory owner) {  
        // Don't override the class with CGLIB if no overrides.  
        if (bd.getMethodOverrides().isEmpty()) {  
          constructorToUse = clazz.getDeclaredConstructor((Class[]) null);  
          return BeanUtils.instantiateClass(constructorToUse);  
        }else {  
          // Must generate CGLIB subclass.  
          return instantiateWithMethodInjection(bd, beanName, owner);  
        }  
    }
    

    由于篇幅省略了部分代码。

    关注公众号Java技术栈回复"面试"获取我整理的2020最全面试题及答案。

    推荐去我的博客阅读更多:

    1.Java JVM、集合、多线程、新特性系列教程

    2.Spring MVC、Spring Boot、Spring Cloud 系列教程

    3.Maven、Git、Eclipse、Intellij IDEA 系列工具教程

    4.Java、后端、架构、阿里巴巴等大厂最新面试题

    觉得不错,别忘了点赞+转发哦!

  • 相关阅读:
    slot 的简单使用(一)匿名插槽
    修改Tooltip 文字提示 的背景色 箭头颜色
    解决vue/cli3.0 语法验证规则 ESLint: Expected indentation of 2 spaces but found 4. (indent)
    洛谷P2014 选课(树形DP+分组背包)
    洛谷P4316 绿豆蛙的归宿(概率DP/期望DP+拓扑排序)
    Atcoder Beginner Contest 144 F- Fork the Road(概率DP/期望DP)
    Atcoder ABC144 Gluttony(贪心+二分)
    洛谷P1352 没有上司的舞会(树形DP+记忆化)
    HDU2476 String painter(区间DP)
    POJ1651 Multiplication Puzzle(区间DP+记忆化搜索)
  • 原文地址:https://www.cnblogs.com/javastack/p/13431216.html
Copyright © 2020-2023  润新知