第一章 IOC容器的两个接口
在使用Spring的过程中,我们需要书写的第一行代码就为:
ApplicationContext context =new ClassPathXmlApplicationContext("SpringDao.xml");
在学习IOC的时候我们就知道:Spring提供的IOC容器实现的两个接口为BeanFactory接口和ApplicationContext接口。
(面试题)ApplicationContext接口是BeanFactory的子接口并且多了以下功能:
- 与Spring的AOP功能轻松集成
- 消息资源处理(用于国际化)
- 活动发布
- 应用层特定的上下文,例如WebApplicationContext 用于Web应用程序中的。
- 在启动的时候就把所有的Bean全部实例化了,它还可以为Bean配置lazy-init=true来让Bean延迟实例化。但是对于BeanFactory来说,它在启动的时候不会去实例化Bean,只有从容器中拿Bean的时候才会去实例化
一. BeanFactory接口
BeanFactory 接口是 Spring 的“心脏”。它就是 Spring IoC 容器的真面目。Spring 使用该接口来实例化、配置和管理 Bean。它最重要的方法就是 getBean() ,作用是返回特定的名称的Bean,该方法是IOC容器获取bean对象和引发依赖注入的起点。以下是源码:
package org.springframework.beans.factory;
import org.springframework.beans.BeansException;
import org.springframework.core.ResolvableType;
import org.springframework.lang.Nullable;
#该接口会加载存储在配置源中的Bean定义,并使用org.springframework.beans包来配置Bean。
#但是,实现可以根据需要直接在Java代码中直接返回它创建的Java对象。
#定义的存储方式没有任何限制:LDAP,RDBMS,XML,属性文件等。鼓励实现以支持Bean之间的引用(依赖注入)
public interface BeanFactory {
// 用于返回FactoryBean对象用的前缀
String FACTORY_BEAN_PREFIX = "&";
// 返回指定名称的Bean实例。如果找不到该bean,会在父工厂中寻找
Object getBean(String name) throws BeansException;
<T> T getBean(String name, Class<T> 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;
// 获取bean的提供者(对象工厂)
<T> ObjectProvider<T> getBeanProvider(Class<T> requiredType);
<T> ObjectProvider<T> getBeanProvider(ResolvableType requiredType);
// 这个bean工厂是否包含给定名称的bean
boolean containsBean(String name);
// 是否为单例
boolean isSingleton(String name) throws NoSuchBeanDefinitionException;
// 是否为原型
boolean isPrototype(String name) throws NoSuchBeanDefinitionException;
// 指定名字的bean是否和指定的类型匹配
boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException;
boolean isTypeMatch(String name, Class<?> typeToMatch) throws NoSuchBeanDefinitionException;
// 获取指定名字的bean的类型
Class<?> getType(String name) throws NoSuchBeanDefinitionException;
Class<?> getType(String name, boolean allowFactoryBeanInit) throws NoSuchBeanDefinitionException;
// 获取指定名字的bean的所有别名
String[] getAliases(String name);
}
1. String FACTORY_BEAN_PREFIX = “&”
该String类型的变量是用于获取FactoryBean对象。
首先我们需要来了解什么是FactoryBean。FactoryBean属于工厂类接口,用户可以通过实现该接口定制实例化Bean的逻辑:
-
FactoryBean
:FactoryBean 在IOC容器的基础上给 Bean 的实现加上了一个简单工厂模式和装饰模式。任何一个实现了该接口的 bean 对象都为FactoryBean
,这个Bean不是简单的Bean,而是一个能生产或者修饰对象生成的工厂Bean
。
举例说明:首先创建 MyFacrotyBeanTest 类实现FactoryBean
接口,重写getObject()和getObjectType()方法。内有一个其他bean对象:
public class MyFacrotyBeanTest implements FactoryBean {
//其他bean对象的引用
Student studentByFactoryBean;
//对Student这个bean的定制逻辑
//在实际开发中,我们是通过Class.forName(interfaceName)来获得bean对象,再通过代理模式增强。
//将增强的bean的返回
public Object getObject() throws Exception {
return studentByFactoryBean;
}
public Class<?> getObjectType() {
return null;
}
}
将 MyFacrotyBeanTest 加入Spring的容器后,然后通过xml(或者注解)配置该类的属性。通过以下代码返回的是MyFacrotyBeanTest类增强的Student类bean对象(也就是getObject()方法的返回值):
ApplicationContext context =new ClassPathXmlApplicationContext("SpringDao.xml");
//会返回增强的bean对象,也就是 getObject()方法返回的对象
Object bean = context.getBean("MyFacrotyBeanTest");
如果要获取MyFacrotyBeanTest
对象就需要在 id 前加上 & :
Object bean = context.getBean("&MyFacrotyBeanTest");
以下是FactoryBean的源码:
package org.springframework.beans.factory;
public interface FactoryBean<T> {
//返回由FactoryBean创建的Bean实例
//如果isSingleton()返回true,则该实例会放到Spring容器中单实例缓存池中
T getObject() throws Exception;
//返回FactoryBean创建的Bean类型。
Class<?> getObjectType();
//返回由FactoryBean创建的Bean实例的作用域是singleton还是prototype
boolean isSingleton();
}
(面试题)BeanFactory和FactoryBean的区别:
- BeanFactory。是个Factory,也就是IOC容器或对象工厂, 它负责生产和管理bean的一个工厂。在Spring中,BeanFactory是IOC容器的核心接口,它的职责包括:实例化、定位、配置应用程序中的对象及建立这些对象间的依赖。
- FactoryBean。FactoryBean属于工厂类接口,FactoryBean是个Bean。用户可以通过实现该接口定制实例化Bean的逻辑。简单来说它是一个可以对目标bean对象修饰的工厂bean.
2.getBean()方法
该方法为重要方法,在以后单独讲解。
二. ApplicationContext接口
Spring官方说明,绝大多数情况下建议使用ApplicationContext。前文说过ApplicationContext对BeanFactory进行了扩展。
利用MessageSource进行国际化
BeanFactory是不支持国际化功能的,因为BeanFactory没有扩展Spring中MessageResource接口。相反,由于ApplicationContext扩展了MessageResource接口,因而具有消息处理的能力(i18N)。
事件机制(Event)
ApplicationContext的事件机制主要通过ApplicationEvent和ApplicationListener这两个接口来提供的,当ApplicationContext中发布一个事件的时,所有扩展了ApplicationListener的Bean都将会接受到这个事件,并进行相应的处理。 Spring提供了部分内置事件,主要有以下几种:
- ContextRefreshedEvent:ApplicationContext发送该事件时,表示该容器中所有的Bean都已经被装载完成,此ApplicationContext已就绪可用
- ContextStartedEvent:生命周期 beans的启动信号
- ContextStoppedEvent: 生命周期 beans的停止信号
- ContextClosedEvent:ApplicationContext关闭事件,则context不能刷新和重启,从而所有的singleton bean全部销毁(因为singleton bean是存在容器缓存中的)
用户也可根据自己需要来扩展spriong中的事物,扩展的事件都要实现ApplicationEvent接口。
底层资源的访问
ApplicationContext扩展了ResourceLoader(资源加载器)接口,从而可以用来加载多个Resource,而BeanFactory是没有扩展ResourceLoader。
对Web应用的支持
与BeanFactory通常以编程的方式被创建不同的是,ApplicationContext能以声明的方式创建,如使用ContextLoader。当然你也可以使用ApplicationContext的实现之一来以编程的方式创建ApplicationContext实例 。
ApplicationContext常用实现类 | 作用 |
---|---|
AnnotationConfigApplicationContext | 从一个或多个基于java的配置类中加载上下文定义,适用于java注解的方式。 |
ClassPathXmlApplicationContext | 从类路径下的一个或多个xml配置文件中加载上下文定义,适用于xml配置的方式。 |
FileSystemXmlApplicationContext | 从文件系统下的一个或多个xml配置文件中加载上下文定义,也就是说系统盘符中加载xml配置文件。 |
AnnotationConfigWebApplicationContext | 专门为web应用准备的,适用于注解方式。 |
XmlWebApplicationContext | 从web应用下的一个或多个xml配置文件加载上下文定义,适用于xml配置方式。 |
第二章 循环依赖与三级缓存
循环依赖其实就是循环引用,也就是两个或则两个以上的bean互相持有对方,最终形成闭环。比如A依赖于B,B依赖于C,C又依赖于A。注意:循环依赖不是循环调用。循环调用是方法之间的环调用,并且循环调用是无法解决的,除非有结束条件,否则就是死循环最终导致内存溢出错误。
Spring解决循环依赖的类是DefaultSingletonBeanRegistry,用于注册,获得,管理singleton单例对象,它实现了SingletonBeanRegistry接口。该类中就有解决循环依赖的三级缓存:
//一级缓存。用于保存BeanName和创建Bean实例之间的关系。用于存放完全初始化好了的Bean对象
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
// 二级缓存。用于保存BeanName和创建Bean实例之间的关系。与singletonObjects不同之处在于:
//当一个单例Bean对象放里面后,那么当Bean还在创建过程中,就可以通过getBean()方法获取到了.
//也就是说,它是存放尚未填充属性的原始Bean对象,用于检测循环依赖的引用.
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
// 三级缓存。用于保存BeanName和创建Bean的工厂之间的关系。
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
还有其他两个比较重要的集合:
//用于保存正在创建的Bean的标识符
private final Set<String> singletonsCurrentlyInCreation = Collections.newSetFromMap(new ConcurrentHashMap<>(16));
//用于保存已经创建完毕的Bean的标识符
private final Set<String> alreadyCreated = Collections.newSetFromMap(new ConcurrentHashMap<>(256));
一 Spring的循环依赖
关于Spring bean的创建,其本质上还是一个对象的创建,既然是对象,一定要明白一个完整的对象包含两部分:当前对象实例化和对象属性的实例化。
在Spring中,对象的实例化是通过反射实现的,而对象的属性则是在对象实例化之后通过一定的方式设置的。而循环依赖就发生在对象的属性设置的时候。
在Spring的循环依赖中一共有三种情况:构造器注入循环依赖、field属性注入(setter方法注入)循环依赖、prototype field属性注入循环依赖。
构造器注入循环依赖(无法解决)
@Service
public class A {
public A(B b) {
}
}
@Service
public class B {
public B(A a) {
}
}
对于构造器注入循环依赖,Spring本来想以普通框架的身份与我们相处,但是换来的却是BeanCurrentlyInCreationException
异常,因此Spring直接摊牌了:我无法解决构造器注入循环依赖。
原因: Spring解决循环依赖依靠的是Bean的“中间态”这个概念,而这个中间态指的是已经实例化,但还没设置属性的状态。而构造器是用于完成实例化的,所以构造器的循环依赖无法解决。
setter方法循环依赖(可解决)
对于Setter方法的依赖注入,Spring会提前暴露刚完成构造器注入但未完成属性设置的Bean对象来完成的,并且只能解决单例作用域的Bean的循环依赖。通过暴露一个单例工厂方法,从而使其他Bean能引用到该Bean。
prototype field属性注入循环依赖(无法解决)
无法解决。只是由于Spring中对于prototype
作用域的Bean对象不进行缓存,自然也无法提前暴露一个Bean对象供其他Bean对象引用。
原因: 多实例Bean是每次创建都会调用doGetBean方法,根本没有使用一二三级缓存,肯定不能解决循环依赖。
涉及到循环依赖处理的的方法为getSingleton的两个重载方法和getEarlyBeanReference()方法。为了分析方便我们首先建立两个类,让他们首先构成 set 注入的循环依赖:
public class A {
private B b;
public B getB() {
return b;
}
public void setB(B b) {
this.b = b;
}
}
public class B {
private A a;
public A getA() {
return a;
}
public void setA(A a) {
this.a = a;
}
}
1. getSingleton()
当通过A a=(A) context.getBean("A")
来获取目标对象的时候,实际上是调用AbstractBeanFactory
类的getBean()方法,内部又实际调用了doGetBean()
方法。
在doGetBean()
方法内部调用了以下的getSingleton()
方法。首先我们需要知道单例Bean对象只在Spring容器中加载一次,后续如果要使用该对象直接从容器中获取即可。该方法是尝试从缓存中获取对象,而我们的容器才刚创建自然其中没有A对象
,对于A对象
来说该方法就只是走个过场,会直接返回Null
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
//首先从singletonObjects集合中查看是否有A对象
//singletonObjects也就是我们的一级缓存
Object singletonObject = this.singletonObjects.get(beanName);
//如果没有获取到,并且singletonsCurrentlyInCreation集合中包含A对象
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
//从二级缓存中查询,获取Bean的早期引用。
//所谓早期引用是指:实例化完成但是未赋值完成的Bean。
singletonObject = this.earlySingletonObjects.get(beanName);
//二级缓存中不存在,并且允许创建早期引用(二级缓存中添加)
if (singletonObject == null && allowEarlyReference) {
//从三级缓存中查询,获取Bean的早期引用。
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
//如果从三级缓存中查询到了
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
//添加至二级缓存
this.earlySingletonObjects.put(beanName, singletonObject);
//从三级缓存中移除
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
注意isSingletonCurrentlyInCreation(beanName)
是判断singletonsCurrentlyInCreation
集合中是否包含A对象。前文我们说过该集合的作用是:用于保存正在创建的Bean的标识符。由于此时只是从缓存中查询并没有创建对象,自然返回false。
2. getSingleton()的重载
上诉方法执行完毕过后返回null,在缓存中没有找到想要的bean对象后,自然我们需要从头开始bean的加载了,但是我们需要对该bean对象进行类型检测,因为只有在单例情况下才会尝试解决循环依赖:
//如果是Prototype就抛出错误
if (isPrototypeCurrentlyInCreation(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
检测完毕后,Spring使用getSingleton()的重载方法
来进行bean的加载:
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(beanName, "Bean name must not be null");
//全局变量需要同步
synchronized (this.singletonObjects) {
//首先检测Bean对象是否已经加载过,因为其实Singleton模式其实就是复用以创建bean,此处是必要的!
Object singletonObject = this.singletonObjects.get(beanName);
//singletonObject为空才能继续执行
if (singletonObject == null) {
//省略代码
//记录加载状态,也就是将当前正要创建的bean记录在singletonsCurrentlyInCreation缓存中
//与前文isSingletonCurrentlyInCreation(beanName)对应!
beforeSingletonCreation(beanName);
boolean newSingleton = false;
boolean recordSuppressedExceptions = (this.suppressedExceptions == null);
if (recordSuppressedExceptions) {
this.suppressedExceptions = new LinkedHashSet<>();
}
try {
//初始化Bean,真正的创建对象的代码
singletonObject = singletonFactory.getObject();
newSingleton = true;
}
catch (IllegalStateException ex) {
//省略代码
}
catch (BeanCreationException ex) {
//省略代码
}
finally {
if (recordSuppressedExceptions) {
this.suppressedExceptions = null;
}
//当bean加载结束后,A对象就不再是正在创建状态了。
//因此从singletonsCurrentlyInCreation缓存中移除。
afterSingletonCreation(beanName);
}
if (newSingleton) {
//加入singletonObjects缓存,也就是一级缓存。
addSingleton(beanName, singletonObject);
}
}
return singletonObject;
}
}
singletonObject = singletonFactory.getObject()
该段代码其实是调用AbstractBeanFactory
类中的doGetBean()
方法中的一段代码:
sharedInstance = getSingleton(beanName, () -> {
try {
return createBean(beanName, mbd, args);
}
catch (BeansException ex) {
// Explicitly remove instance from singleton cache: It might have been put there
// eagerly by the creation process, to allow for circular reference resolution.
// Also remove any beans that received a temporary reference to the bean.
destroySingleton(beanName);
throw ex;
}
});
重要的是createBean(beanName, mbd, args)
它有如下功能:
- 根据设置的class属性或者根据className来解析class;
- 对override标记进行标记及其验证
-
Object bean = resolveBeforeInstantiation(beanName, mbdToUse)
。此行代码是我们实现AOP功能的关键。并且BeanFactoryPostProcessor
与BeanPostProcessor
在此处出现了!! - 调用
doCreateBean()
方法来真正创建A对象!
doCreateBean()
方法是创建Bean对象的核心方法,该方法里面包括了解析Bean对象的class属性、构造方法、
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
throws BeanCreationException {
// Instantiate the bean.
BeanWrapper instanceWrapper = null;
if (mbd.isSingleton()) {
instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
}
if (instanceWrapper == null) {
//创建原始对象,创建出来的对象没有填充属性
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
//省略代码
//重要代码。解决循环依赖
//
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
if (logger.isTraceEnabled()) {
logger.trace("Eagerly caching bean '" + beanName +
"' to allow for resolving potential circular references");
}
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
// Initialize the bean instance.
Object exposedObject = bean;
try {
populateBean(beanName, mbd, instanceWrapper);
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
catch (Throwable ex) {
if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) {
throw (BeanCreationException) ex;
}
else {
throw new BeanCreationException(
mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex);
}
}
if (earlySingletonExposure) {
Object earlySingletonReference = getSingleton(beanName, false);
if (earlySingletonReference != null) {
if (exposedObject == bean) {
exposedObject = earlySingletonReference;
}
else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
String[] dependentBeans = getDependentBeans(beanName);
Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
for (String dependentBean : dependentBeans) {
if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
actualDependentBeans.add(dependentBean);
}
}
if (!actualDependentBeans.isEmpty()) {
throw new BeanCurrentlyInCreationException(beanName,
"Bean with name '" + beanName + "' has been injected into other beans [" +
StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
"] in its raw version as part of a circular reference, but has eventually been " +
"wrapped. This means that said other beans do not use the final version of the " +
"bean. This is often the result of over-eager type matching - consider using " +
"'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example.");
}
}
}
}
// Register bean as disposable.
try {
registerDisposableBeanIfNecessary(beanName, bean, mbd);
}
catch (BeanDefinitionValidationException ex) {
throw new BeanCreationException(
mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex);
}
return exposedObject;
}