(一)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.
翻译下来的意思是:
- Spring IOC 容器就是一个 org.springframework.context.ApplicationContext 的实例化对象
- 容器负责实例化,配置以及装配 bean
那么我们可以这样理解:
- 从代码层次来看,Spring 容器就是一个实现了 ApplicationContext 接口的对象
- 从功能上来看,Spring 容器是 Spring 框架的核心,用来管理对象的。容器将创建的对象,把它们连接在一起,配置它们,并管理它们的整个生命周期从创建到销毁
1.2 容器如何工作
官网上一张图如下:
图可以理解为,Spring 容器通过我们提交的 POJO 对象(Your Business Objects(POJOs))以及配置元数据(Configuration Metadata)产生一个充分配置的可以使用的系统
这里说的配置元数据,实际上就是我们提供的 XML 配置文件,或者通过注解方式提供的一些配置信息
2.Spring bean
2.1 如何实例化一个 bean
从官网上来看,主要有三种方法:
- 构造方法
- 静态工厂方法
- 实例工厂方法
这三种例子官网都有具体的演示,可以通过注解查阅部分源码来验证官网的结论
从代码角度进行分析,这里我们直接定位到
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 这个方法的入口打一个端点,如图:
接下来我们通过代码来分析
我们主要关注进行实例化的几个方法:
- 通过 Supplier 创建 Bean
通过 BeanDefinition 中的 instanceSupplier 直接获取一个是实例化的对象,这个 instanceSupplier 属性我不是很理解,在 XML 标签中以及注解的方式都没有找到方法配置这个属性,后来在 org.springframework.context.support.GenericApplicationContext 这个类中找到了以下两个方法
经过断点测试,发现这种情况下,在实例化对象时会进入上面的 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:
可以发现,代码执行到最后一行,同时我们看代码上面的注释可以知道,当没有进行特殊处理的时候,默认会使用无参构造函数进行对象的实例化
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 {
}
同样,断点也进入了最后一行
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 {
}
在方法入口打个断点如图:
断点结果:
可以发现,通过@Bean方法创建对象时,Spring底层是通过factoryMethod方法进行实例化对象的,Spring会在我们需要实例化这个对象对应的BeanDefinition中记录factoryBeanName是什么(factoryBeanName就是AppConfig),同时记录这个factoryBean中创建对象的factoryMethodName是什么,最后通过factoryBeanName获取一个Bean然后反射调用factoryMethod实例化一个对象。
这里需要注意几个概念:
- 这里所说的通过静态工厂方式通过factoryBeanName获取一个Bean,注意这个Bean,不是一个FactoryBean,也就是说不是一个实现了org.springframework.beans.factory.FacrotyBean接口的Bean。
- 提到一个概念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
断点如下:
可以发现,这种情况也进入了insrantiateUsingFactoryMethod方法中。通过静态工厂方法的方式特殊之处在于,包含了这个静态方法的类,不需要被实例化,不需要被Spring管理,Spring的调用逻辑大概是:
- 通过<bean>标签中的class属性得到一个Class对象
- 通过Class对象获取到对应的方法名称得到Method对象
- 最后反射调用Method.invoke(null, args)
因为是静态方法,方法在执行时,不需要一个对象
7.通过实例工厂方法的方式
测试代码,配置文件不变:
ClassPathXmlApplicationContext cc =
new ClassPathXmlApplicationContext("application.xml");
System.out.println(cc.getBean("clientService"));
断点如下:
还是执行这个方法,这个方法的执行过程和@Bean方式执行的流程是一样的
到这里,这段代码我们算是结合了官网大致过了一遍,其实还遗留了几个问题:
- Spring是如何推断构造函数的?我们在上面验证的都是无参的构造函数,并且只提供了一个构造函数
- Spring是如何推断方法的?不管是静态工厂方法还是实例化工厂方法,我们都只在类中提供了一个和配置匹配的方法名,假设我们对方法进行了重载呢?
要说清楚这两个问题需要比较深入的研究代码,同时进行测试。
总结
- 对象实例化,只是得到一个对象,还不是一个完全的Spring中的bean,我们实例化后的这个对象还没有完成依赖注入,没有走完一系列的生命周期
- Spring官网上说到,在Spring实例化一个对象有三种方式:
构造函数
实例化工厂方法
静态工厂方法
3.自己总结如下结论:
Spring通过解析我们的配置元数据,以及我们提供的类对象得到一个BeanDefiniton对象,通过这个对象可以实例化一个java bean对象。主要流程图如下: