导语
在使用Spring 中最为核心的操作就是Bean的创建以及使用。下面就来带着大家一起来分析一下关于Spring的Bean的加载相关的知识
根据框架源码的一贯习惯,真正执行操作的并不是这个getBean()的方法,而是其中的以do开头的方法。那么就进入到其中看看都有那些操作?
进入到AbstractBeanFactory 类中会看到根据Bean的名称获取Bean对象的操作是通过了如下的一层封装。其中确实是有一个doGetBean()的方法。下面就来看看这个doGetBean方法。
@Override
public Object getBean(String name) throws BeansException {
return doGetBean(name, null, null, false);
}
在这个方法中传入了四个参数
protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
@Nullable final Object[] args, boolean typeCheckOnly) throws BeansException
对于上面的这些参数,先不做过多的介绍,在具体调用到这些参数的时候就会知道它们具体的操作是什么了。
进入该方法之后,对于对应的BeanName进行了提取,也许有人会问,在使用的时候我们传入的不就是我们定义的Bean Name么,直接进行查找不就可以了么,为什么还要进行多余的提取操作呢?这里,对于Bane Name的提取其实是为了解决一个问题,在我们使用的时候并不是所有的Bean都进行Name的设计,在很多的场景下,我们只是对我们能操作的Bean或者是需要的地方进行的name 的配置,但是在很多的场景下都是没有以类名小写直接作为BeanName 存在。为了统一 默认的BeanName和自定义的BeanName所以进行了一个BeanName的提取操作。根据传入的BeanName来确定一个可用的BeanName。
//提取对应的beanName
final String beanName = transformedBeanName(name);
Object bean;
接下来会进行从缓存中检查对应实例的操作,在创建单实例Bean的时候回存在依赖注入的操作,如果已经存在了对应的Bean实例就会出现循环注入的错误。Spring 的原则就是在创建Bean的时候不等到Bean创建完成就将BeanFactory进行曝光,也将BeanFactory加入到对应的缓存中,从而告诉其他调用,有对应的BeanFactory来创建该对象,如果需要进行创建的话直接可以从缓存中进行获取到该BeanFactory进行创建对象。代码如下。
Object sharedInstance = getSingleton(beanName);
在缓存中获取的到Bean的原始状态,需要对Bean对象进行实例化。这里需要说明的就是在操作的时候其实在缓存中放入的就是Bean的原始状态,并不是最终需要的Bean对象。从缓存中获取到之后还需要进一步的进行加工。会看到,下面代码首先对缓存中获取到的对象进行判断,最终调用了bean = getObjectForBeanInstance(sharedInstance, name, beanName, null); 方法返回了一个bean,而这个Bean就是最终需要的Bean对象。
在上面的代码中看到了能从缓存中获取到原始的Bean对象的情况,那么在缓存中没有对应的BeanName对应的Bean对象的时候又如何进行处理呢?我们都知道,在原型模式下,如果一个对象A以对象B作为属性,而在对象B中又以对象A作为属性那么就会出现循环依赖。所以在进行操作的时候先进行了一个循环依赖的检查
会看到在进行完循环依赖检查之后,进行了ParentBeanFactory的检查。这有点类似于双亲委托机制。但是在进入之前首先做了一个判空的操作,也就是说如果parentBeanFactory为空的话什么都是没有用的,这里还有另外的一个判断就是!containsBeanDefinition(beanName),也就是容器中已经加载的Bean中去寻找,如果也没有找到。也就用到了另外的一个概念BeanDefinition 。
从XML配置文件中读取到的Bean的配置是存储在一个通用的GenericBeanDefinition的定义中,这里需要转换成RootBeanDefinition,同时如果父类不为空的话需要对父类属性进行合并操作。
在Bean的初始化过程中可能会用到某些属性,而这些属性可能是动态配置的,并且可能已经配置成了其他的Bean依赖,这个时候就需要先加载依赖的Bean,所以在Spring的加载顺序就是在初始化某个Bean的时候先会初始化这个Bean所对应的依赖。
在Spring中支持多种作用域的配置,但是在Spring本身的支持中只支持了多实例和单实例两种方式,默认使用的单实例。在这个步骤中其实就是根据不同的作用域来初始化Bean。这里需要注意的是初始化Bean和初始化BeanDefinition是不一样的。
当Bean被初始化之后整个的操作就已经基本结束了,但是还有一种情况需要进行考虑,就是类型转换的问题,当一个对象属性是String的时候,但是传入的类型确实一个Integer,那么这个时候就会需要类型的转换操作。当然这里不只是基本数据类型之间的转换操作。也可以以是其他引用类型之间的转换。到最后就可以调用return方法进行类型返回Bean对象的返回了。
一般情况下,Spring 是通过反射机制利用bean的Class属性指定的实现类来实例化Bean对象,在某些情况下,实例化bean 过程比较复杂,如果按照传统的方式,则需要在 bean中提供大量的配置信息,配置方式的灵活性也受到了限制,这个时候采用编码的方式也许会得到一个很好的解决方案。Spring 为此提供了一个FactoryBean的工厂类接口,用户可以通过实现该接口实现定制化Bean的逻辑。
FactoryBean 接口对于Spring 架构来说占有重要的地位,Spring 自身就提供了70多个FactoryBean 的实现,它们隐藏了实例化一些复杂Bean的细节,给上层应用带来便利,从Spring 3.0 开始,FactoryBean开始提供泛型。通过查看源码可以知道FactoryBean中提供了三个方法
public interface FactoryBean<T> {
@Nullable
T getObject() throws Exception;
@Nullable
Class<?> getObjectType();
default boolean isSingleton() {
return true;
}
}
当配置文件中bean的Class属性配置的实现类是FactoryBean的时候,通过getBean方法返回的不是FactoryBean本身,而是通过getObjectType()方法返回的对象。也就是说相当于getObjectType()方法代理了getBean的方法。
小例子
首先来创建一个Car类型
public class Car {
private int maxSpeed;
private String brand;
private double price;
public Car(int maxSpeed, String brand, double price) {
this.maxSpeed = maxSpeed;
this.brand = brand;
this.price = price;
}
public Car() {
}
public int getMaxSpeed() {
return maxSpeed;
}
public void setMaxSpeed(int 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;
}
@Override
public String toString() {
return "Car{" +
"maxSpeed=" + maxSpeed +
", brand='" + brand + '\'' +
", price=" + price +
'}';
}
}
第二步继承FactoryBean实现了CarFactoryBean 类
public class CarFactoryBean implements FactoryBean {
private String carInfo;
public Object 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;
}
public Class<?> getObjectType() {
return Car.class;
}
public boolean isSingleton() {
return false;
}
public String getCarInfo() {
return carInfo;
}
public void setCarInfo(String carInfo) {
this.carInfo = carInfo;
}
}
第三步,编写XML的配置文件
<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="car" class="com.core.charp2.demo02.bean.CarFactoryBean">
<property name="carInfo" value="地狱猫,400,20000000">property>
bean>
beans>
第四步,编写测试类
public class Test {
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:demo2.xml");
Car car = (Car) applicationContext.getBean("car");
System.out.println(car.toString());
}
}
结果分析,这里当在测试主类中调用了getBean方法之后,返回的确实是在配置文件中配置的内容。整个的过程应该是getBean()调用doGetBean方法来进行反射加载的操作,但是在调用反射的时候发现CarFactoryBean 实现了 FactoryBean接口,所以容器本身就调用了getObject()方法进行了返回,这样就直接获取到了Car对象,因为在上面的代码中可以看到在getObject()方法中返回的就是一个Car对象。那么问题来了,在之前使用Bean配置文件的时候都是直接返回配置文件中配置的对象,那么我们怎么获取到CarFactoryBean对象呢?在分析BeanFactory的时候在源码中会看到如下的一段代码,在注释中明确的说明,如果想要获取FactoryBean对象需要在getBean()方法的时候加入&符号。也就是getBean("&car"),这样的操作就会获取到CarFactroyBean 对象了。
通过上面的分析,了解了BeanFactory和FactoryBean的区别,并且对用户使用的Bean对象与BeanDefinition等都有了新的认识。BeanDefinition的创建只是一层粗加工的结果,getBean中的操作才是精细加工的效果。