(一)Spring 容器及 Spring Bean

1.Spring 容器

1.1 什么是容器

官网中有一句话

The org.springframework.context.ApplicationContext interface represents the Spring IoC container and is responsible for instantiating, configuring, and assembling the beans.

翻译下来的意思是:

  1. Spring IOC 容器就是一个 org.springframework.context.ApplicationContext 的实例化对象
  2. 容器负责实例化,配置以及装配 bean

那么我们可以这样理解:

  1. 从代码层次来看,Spring 容器就是一个实现了 ApplicationContext 接口的对象
  2. 从功能上来看,Spring 容器是 Spring 框架的核心,用来管理对象的。容器将创建的对象,把它们连接在一起,配置它们,并管理它们的整个生命周期从创建到销毁

1.2 容器如何工作

官网上一张图如下:

Spring源码分析(一)Spring容器及Spring Bean_官网

图可以理解为,Spring 容器通过我们提交的 POJO 对象(Your Business Objects(POJOs))以及配置元数据(Configuration Metadata)产生一个充分配置的可以使用的系统

这里说的配置元数据,实际上就是我们提供的 XML 配置文件,或者通过注解方式提供的一些配置信息

2.Spring bean

2.1 如何实例化一个 bean

从官网上来看,主要有三种方法:

Spring源码分析(一)Spring容器及Spring Bean_spring_02

  1. 构造方法
  2. 静态工厂方法
  3. 实例工厂方法

这三种例子官网都有具体的演示,可以通过注解查阅部分源码来验证官网的结论

从代码角度进行分析,这里我们直接定位到

org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#createBeanInstance

通过运行下面这行代码

public static void main(String[] args)  {
   // 通过配置类扫描
   AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(Appconfig.class);
   System.out.println(ac.getBean(OrderService.class));
}

在 org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#createBeanInstance 这个方法的入口打一个端点,如图:

Spring源码分析(一)Spring容器及Spring Bean_spring_03

接下来我们通过代码来分析

Spring源码分析(一)Spring容器及Spring Bean_spring_04

我们主要关注进行实例化的几个方法:

  1. 通过 Supplier 创建 Bean

通过 BeanDefinition 中的 instanceSupplier 直接获取一个是实例化的对象,这个 instanceSupplier 属性我不是很理解,在 XML 标签中以及注解的方式都没有找到方法配置这个属性,后来在 org.springframework.context.support.GenericApplicationContext 这个类中找到了以下两个方法

Spring源码分析(一)Spring容器及Spring Bean_实例化_05

经过断点测试,发现这种情况下,在实例化对象时会进入上面的 supplier 方法。下面是测试代码

// AnnotationConfigApplicationContext 是 GenericApplicationContext 的一个子类
        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext();
        ac.registerBean("service", Service.class,Service::new);
        ac.refresh();
        System.out.println(ac.getBean("service"));

这个方法一般不常用,这是 Spring 提供的一种方便外部扩展的手段,让开发者能够更加灵活的实例化一个 bean

2. 通过注解的方式来创建 Bean

@Compent,@Service 等注解方式

测试代码:

public static void main(String[] args)  {
   // 通过配置类扫描
   AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(Appconfig.class);
   System.out.println(ac.getBean(OrderService.class));
}


@Component
public class OrderService {
   
}

观察 debug:

Spring源码分析(一)Spring容器及Spring Bean_spring_06

可以发现,代码执行到最后一行,同时我们看代码上面的注释可以知道,当没有进行特殊处理的时候,默认会使用无参构造函数进行对象的实例化

3. 通过普通 XML 的方式

同@Component 注解

4. 通过@Configuration 注解的方式

public class Test {


   public static void main(String[] args)  {
      // 通过配置类扫描
      AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(Appconfig.class);
      // 这里将测试对象换为config即可,同时记得将条件断点更改为beanName.equlas("appconfig")
      System.out.println(ac.getBean(Appconfig.class));
   }
}


@Configuration
public class Appconfig {

 }

Spring源码分析(一)Spring容器及Spring Bean_官网_07

同样,断点也进入了最后一行

5. 通过@Bean 方式

public class Test {


   public static void main(String[] args) {
      AnnotationConfigApplicationContext ac =
            new AnnotationConfigApplicationContext(Appconfig.class);
      System.out.println(ac.getBean("service"));
   }
}


@Configuration
@ComponentScan("com.luban.beanDefinition")
public class Appconfig {
   @Bean
   public Service service() {
      return new Service();
   }
 }


public class Service {
}

在方法入口打个断点如图:

Spring源码分析(一)Spring容器及Spring Bean_spring_08

断点结果:

Spring源码分析(一)Spring容器及Spring Bean_spring_09

可以发现,通过@Bean方法创建对象时,Spring底层是通过factoryMethod方法进行实例化对象的,Spring会在我们需要实例化这个对象对应的BeanDefinition中记录factoryBeanName是什么(factoryBeanName就是AppConfig),同时记录这个factoryBean中创建对象的factoryMethodName是什么,最后通过factoryBeanName获取一个Bean然后反射调用factoryMethod实例化一个对象。

这里需要注意几个概念:

  1. 这里所说的通过静态工厂方式通过factoryBeanName获取一个Bean,注意这个Bean,不是一个FactoryBean,也就是说不是一个实现了org.springframework.beans.factory.FacrotyBean接口的Bean。
  2. 提到一个概念BeanDefinition,它就是Spring对自己所管理的Bean的一个抽象。之后会出一个专题专门讲解

6. 通过静态工厂方法的方式

测试代码:

public class Test {


   public static void main(String[] args) {
      ClassPathXmlApplicationContext cc =
            new ClassPathXmlApplicationContext("application.xml");
      System.out.println(cc.getBean("MyFactoryBean"));
   }
}

application.xml:

<?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">
   <!--  <bean id="myServiceImpl" class="com.luban.service.Service"/>-->


   <!-- the factory bean, which contains a method called get() -->
   <bean id="service" class="com.luban.service.Service">
      <!-- inject any dependencies required by this locator bean -->
   </bean>


   <!-- 测试实例工厂方法创建对象-->
   <bean id="clientService"
        factory-bean="service"
        factory-method="get"/>


   <!--测试静态工厂方法创建对象-->
   <bean id="MyFactoryBean"
        class="com.luban.service.MyFactoryBean"
        factory-method="staticGet"/>
</bean

断点如下:

Spring源码分析(一)Spring容器及Spring Bean_官网_10

可以发现,这种情况也进入了insrantiateUsingFactoryMethod方法中。通过静态工厂方法的方式特殊之处在于,包含了这个静态方法的类,不需要被实例化,不需要被Spring管理,Spring的调用逻辑大概是:

  1. 通过<bean>标签中的class属性得到一个Class对象
  2. 通过Class对象获取到对应的方法名称得到Method对象
  3. 最后反射调用Method.invoke(null, args)

因为是静态方法,方法在执行时,不需要一个对象

7.通过实例工厂方法的方式

测试代码,配置文件不变:

ClassPathXmlApplicationContext cc =
        new ClassPathXmlApplicationContext("application.xml");
    System.out.println(cc.getBean("clientService"));

断点如下:

Spring源码分析(一)Spring容器及Spring Bean_官网_11

还是执行这个方法,这个方法的执行过程和@Bean方式执行的流程是一样的

到这里,这段代码我们算是结合了官网大致过了一遍,其实还遗留了几个问题:

  1. Spring是如何推断构造函数的?我们在上面验证的都是无参的构造函数,并且只提供了一个构造函数
  2. Spring是如何推断方法的?不管是静态工厂方法还是实例化工厂方法,我们都只在类中提供了一个和配置匹配的方法名,假设我们对方法进行了重载呢?

要说清楚这两个问题需要比较深入的研究代码,同时进行测试。

总结

  1. 对象实例化,只是得到一个对象,还不是一个完全的Spring中的bean,我们实例化后的这个对象还没有完成依赖注入,没有走完一系列的生命周期
  2. Spring官网上说到,在Spring实例化一个对象有三种方式:

构造函数

实例化工厂方法

静态工厂方法

3.自己总结如下结论:

Spring通过解析我们的配置元数据,以及我们提供的类对象得到一个BeanDefiniton对象,通过这个对象可以实例化一个java bean对象。主要流程图如下:

Spring源码分析(一)Spring容器及Spring Bean_实例化_12