Spring面试相关

第一章 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的区别:

  1. BeanFactory。是个Factory,也就是IOC容器或对象工厂, 它负责生产和管理bean的一个工厂。在Spring中,BeanFactory是IOC容器的核心接口,它的职责包括:实例化、定位、配置应用程序中的对象及建立这些对象间的依赖。
  2. 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循环依赖的坑出不来,被我diss了

一 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)它有如下功能:

  1. 根据设置的class属性或者根据className来解析class;
  2. 对override标记进行标记及其验证
  3. Object bean = resolveBeforeInstantiation(beanName, mbdToUse)此行代码是我们实现AOP功能的关键。并且BeanFactoryPostProcessorBeanPostProcessor在此处出现了!!
  4. 调用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;
	}

第三章 getBean()方法

2. 循环依赖与三级缓存

3. bean的生命周期

4. Spring是如何实现依赖注入的?

5. 扩展Spring

6. Spring IoC 的实现机制。

7.BeanFactoryPostProcessor与BeanPostProcessor

8. @Autowired写在变量上和构造器上的区别

上一篇:IOC容器源码分析


下一篇:AOP原理