我们都知道Spring的核心就是AOP和IoC,Spring官网同时也说明它的思想是:BOP(Bean Oriented Programming)。今天我们就先讲讲IoC容器创建的时候都干了什么东西?原理是什么样的?以Spring5.0代码为例,进行讲解。
大家都知道BeanFactory是最底层的接口,规定了Bean工厂的基本行为。
public interface BeanFactory {
Object getBean(String name) throws BeansException;
boolean containsBean(String name);
boolean isSingleton(String name) throws NoSuchBeanDefinitionException;
boolean isPrototype(String name) throws NoSuchBeanDefinitionException;
Class<?> getType(String name) throws NoSuchBeanDefinitionException;
}
上面为其中一部分源码,我们经常使用的部门,有兴趣的可以自己官网下载,自己看看。主要定义Bean工厂的功能:获得Bean的代理实例、是否包含ByName的Bean(还有很多检查更全面的重载方法)、是否是单例还是原型、获得Bean定义类的类型等等。那我们看一下其结构图。
我们发现BeanFactory是最底层接口,它有三个子类(指接口),这三个子类都有什么作用呢?HierarchicalBeanFactory:表明层次关系(即继承关系);ListableBeanFactory:表明这些Bean是可以按不同的要求分成对应的集合;AutowireCapableBeanFactory:表明了Bean的自动装配的规则。大家可以看一下源码,一目了然。大家可以看到在Spring中四个最顶层的类:XmlBeanFactory、FileSystemXmlApplicationContext、ClassPathXmlApplicationContext、XmlWebApplicationContext,但是后者三个都是实现了ApplicationContext接口。那么,问题又来了:BeanFactory与ApplicationContext有什么区别呢?
public interface ApplicationContext extends EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory,
MessageSource, ApplicationEventPublisher, ResourcePatternResolver
大家是不是就一目了然了,他们有什么区别了。有兴趣的大家可以留言,说说他们具体的区别。
那言归正传,我们以ClassPathXmlApplicationContext类为例,我们在初始化时,第一步就是创建其实例,那我们直接看他的构造方法中干了什么?
public ClassPathXmlApplicationContext() { } public ClassPathXmlApplicationContext(ApplicationContext parent) { super(parent); } public ClassPathXmlApplicationContext(String configLocation) throws BeansException { this(new String[] {configLocation}, true, null); } public ClassPathXmlApplicationContext(String... configLocations) throws BeansException { this(configLocations, true, null); } public ClassPathXmlApplicationContext(String[] configLocations, @Nullable ApplicationContext parent) throws BeansException { this(configLocations, true, parent); } public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh) throws BeansException { this(configLocations, refresh, null); } public ClassPathXmlApplicationContext( String[] configLocations, boolean refresh, @Nullable ApplicationContext parent) throws BeansException { super(parent); setConfigLocations(configLocations); if (refresh) { refresh(); } }我们一般都是以这样的方式开始实例化:ClassPathXmlApplicationContext context = ClassPathXmlApplicationContext("framework-provider.xml"),我们最终发现其最终调用的就是上图中最后一个构造方法。它做了三件事情:1.super(parent),2.setConfigLocations(configLocations),3.refresh()。那我们就从这三件事情开始逐步分析:
- super(parent)
我们发现其正常实现该方法的是在AbstractApplicationContext这个类中。
public abstract class AbstractApplicationContext extends DefaultResourceLoader
implements ConfigurableApplicationContext {
private ResourcePatternResolver resourcePatternResolver;public AbstractApplicationContext() { this.resourcePatternResolver = getResourcePatternResolver(); } public AbstractApplicationContext(@Nullable ApplicationContext parent) { this(); setParent(parent); } protected ResourcePatternResolver getResourcePatternResolver() { return new PathMatchingResourcePatternResolver(this); } @Override public void setParent(@Nullable ApplicationContext parent) { this.parent = parent; if (parent != null) { Environment parentEnvironment = parent.getEnvironment(); if (parentEnvironment instanceof ConfigurableEnvironment) { getEnvironment().merge((ConfigurableEnvironment) parentEnvironment); } } }}
public class PathMatchingResourcePatternResolver implements ResourcePatternResolver {public PathMatchingResourcePatternResolver(ResourceLoader resourceLoader) { Assert.notNull(resourceLoader, "ResourceLoader must not be null"); this.resourceLoader = resourceLoader;
}} 可以看出来先this(),然后又调用了setParent(parent)方法,而this()里面又调用了getResourcePatternResolver()方法,而这个方法中又把自己给赋值进去了,而其构造方法中却是ResourceLoader这么个类,那为什么可以这样呢?那我们就要先看看AbstractApplicationContext继承的类和实现的接口,看看其结构图。

很明显我们发现AbstractApplicationContext继承了DefaultResourceLoader类,而DefaultResourceLoader又实现了ResourceLoader接口,看了之后有意思的吧,所以AbstractApplicationContext类不仅是一个IoC容器,还是一个资源加载器。
我们再看setParent(parent)方法,如果我们参数中的parent不为null,会配置好IoC层次关系。这里有兴趣的同学可以看看,其实没有太多的东西。
- setConfigLocations(configLocations)
其实这个方法进入到了ConfigurableApplicationContext类中,我们看看这个类一些重要的方法。
public interface ConfigurableApplicationContext extends ApplicationContext, Lifecycle, Closeable {
String CONFIG_LOCATION_DELIMITERS = ",; \t\n";
@Nullable private String[] configLocations;
public void setConfigLocation(String location) { setConfigLocations(StringUtils.tokenizeToStringArray(location, CONFIG_LOCATION_DELIMITERS)); }
public void setConfigLocations(@Nullable String... locations) { if (locations != null) { Assert.noNullElements(locations, "Config locations must not be null"); this.configLocations = new String[locations.length]; for (int i = 0; i < locations.length; i++) { this.configLocations[i] = resolvePath(locations[i]).trim(); } } else { this.configLocations = null; } }}
有意思了,我们其实在构造方法里写"abc.xml,cdf.xml" or "abc.xml","cdf.xml"等上述图片分隔符都是可以的,所以大家想加载多个配置文件时,可以写点别的花样。所以不难发现,该方法就是将传入的xml配置文件信息设置成对应的Bean资源路劲,放入到string[]数组中,即定位Bean资源。
- refresh()
这个方法才是重点,也是最为核心的部门,就算你不清楚其他的方法,其实问题也不大,因为前面的方法都是为这个方法做铺垫的(初始化资源加载器,定位Bean资源路径)。
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);
try {
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);
// Initialize message source for this context.
initMessageSource();
// Initialize event multicaster for this context.
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
onRefresh();
// Check for listener beans and register them.
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
finishRefresh();
}
catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
// Destroy already created singletons to avoid dangling resources.
destroyBeans();
// Reset 'active' flag.
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
}
finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
}
}
}
可以看到,refresh()方法中是用了synchronized同步锁的,为什么呢?我们都知道在spring中IoC容器只能有一个,即单例的,所以他为了保证其唯一性,用到了同步锁。首先看看prepaerRefresh()方法干了什么?看名字,我们可以猜出是准备刷新的意思,就是说为刷新之前做了一些准备,做了什么准备呢?直接看代码。
protected void prepareRefresh() {
this.startupDate = System.currentTimeMillis();
this.closed.set(false);
this.active.set(true);
if (logger.isInfoEnabled()) {
logger.info("Refreshing " + this);
}
// Initialize any placeholder property sources in the context environment
initPropertySources();
// Validate that all properties marked as required are resolvable
// see ConfigurablePropertyResolver#setRequiredProperties
getEnvironment().validateRequiredProperties();
// Allow for the collection of early ApplicationEvents,
// to be published once the multicaster is available...
this.earlyApplicationEvents = new LinkedHashSet<>();
}
准备工作:刷新时开始的时间,容器是否关闭属性设置“false”,容器是否活跃设置“true”,初始化一些属性资源等,这里也不细说了。obtainFreshBeanFactory():注释中说明的很清楚,就是告诉子类刷新其内部的Bean Factory,来看看其内部的代码。
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
refreshBeanFactory();
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
if (logger.isDebugEnabled()) {
logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
}
return beanFactory;
}
其实在refreshBeanFactory()方法并不在AbstractApplicationContext类中,而是运用了委派设计模式(其实在BeanFactory中大量用了该模式),在其子类AbstractRefreshableApplicationContext中实现了该方法。
@Override
protected final void refreshBeanFactory() throws BeansException {
if (hasBeanFactory()) {
destroyBeans();
closeBeanFactory();
}
try {
DefaultListableBeanFactory beanFactory = createBeanFactory();
beanFactory.setSerializationId(getId());
customizeBeanFactory(beanFactory);
loadBeanDefinitions(beanFactory);
synchronized (this.beanFactoryMonitor) {
this.beanFactory = beanFactory;
}
}
catch (IOException ex) {
throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
}
}
很明显,从这里就可以看出为什么我们总是说IoC容器是单例的,看上面的代码:首先它会判断当前上下文中是否已经有了该容器,如果有先销毁容器中的Bean,最后再关闭该容器。在关闭后,我们又创建了一个BeanFactory。所以这个createBeanFactory()方法我也就不多讲了,里面大家自己看看就可以了;后面又设置了其id属性,customizeBeanFactory(beanFactory)方法作用就是设置了一些容器启动时的一些参数(创建容器后初始化一些参数可以这么理解),loadBeanDefinitions(beanFactory)就是将Bean的定义类加载到容器里了。
@Override
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
// Create a new XmlBeanDefinitionReader for the given BeanFactory.
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
// Configure the bean definition reader with this context's
// resource loading environment.
beanDefinitionReader.setEnvironment(this.getEnvironment());
beanDefinitionReader.setResourceLoader(this);
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
// Allow a subclass to provide custom initialization of the reader,
// then proceed with actually loading the bean definitions.
initBeanDefinitionReader(beanDefinitionReader);
loadBeanDefinitions(beanDefinitionReader);
}
在该方法中首先我们是创建了一个Bean的读取器,但是它是通过回调将该读取器设置到容器中的,用来读取一些Bean的定义资源;并且将当前环境、资源加载器和资源解析器设置到读取器中;前面你可以说是读取器的一些初始化工作,那么initBeanDefinitionReader(beanDefiniReader)同样也是初始化读取器;loadBeanDefinitions(beanDefinitionReader)就是真正读取器加载Bean的方法了。
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
Resource[] configResources = getConfigResources();
if (configResources != null) {
reader.loadBeanDefinitions(configResources);
}
String[] configLocations = getConfigLocations();
if (configLocations != null) {
reader.loadBeanDefinitions(configLocations);
}
}
有没有发现一个很熟悉的地方,string[](configLocations)就是之前setConfigLocation()方法做的事,而Resource[]也是ClassPathXmlApplicationContext中的一个属性。
@Override
public int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException {
Assert.notNull(resources, "Resource array must not be null");
int counter = 0;
for (Resource resource : resources) {
counter += loadBeanDefinitions(resource);
}
return counter;
}
@Override
public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException {
return loadBeanDefinitions(location, null);
}
可以看出,其方法返回的是最后加载BeanDefinitions的数量,而真正加载的方法又是上图最下面的loadBeanDefinitions(location, null)。
public int loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources) throws BeanDefinitionStoreException {
ResourceLoader resourceLoader = getResourceLoader();
if (resourceLoader == null) {
throw new BeanDefinitionStoreException(
"Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");
}
if (resourceLoader instanceof ResourcePatternResolver) {
// Resource pattern matching available.
try {
Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
int loadCount = loadBeanDefinitions(resources);
if (actualResources != null) {
for (Resource resource : resources) {
actualResources.add(resource);
}
}
if (logger.isDebugEnabled()) {
logger.debug("Loaded " + loadCount + " bean definitions from location pattern [" + location + "]");
}
return loadCount;
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(
"Could not resolve bean definition resource pattern [" + location + "]", ex);
}
}
else {
// Can only load single resources by absolute URL.
Resource resource = resourceLoader.getResource(location);
int loadCount = loadBeanDefinitions(resource);
if (actualResources != null) {
actualResources.add(resource);
}
if (logger.isDebugEnabled()) {
logger.debug("Loaded " + loadCount + " bean definitions from location [" + location + "]");
}
return loadCount;
}
}
@Override @Nullable public ResourceLoader getResourceLoader() { return this.resourceLoader; }首先该方法拿到了一个资源加载器,然后将Bean的定义资源解析成多个资源文件,注意了,下面loadBeanDefinitions(resources)方法是调用的子类XmlBeanDefinitions中的方法。现在我们看看getResource(location)中的方法做了什么。
@Override
public Resource getResource(String location) {
Assert.notNull(location, "Location must not be null");
for (ProtocolResolver protocolResolver : this.protocolResolvers) {
Resource resource = protocolResolver.resolve(location, this);
if (resource != null) {
return resource;
}
}
if (location.startsWith("/")) {
return getResourceByPath(location);
}
else if (location.startsWith(CLASSPATH_URL_PREFIX)) {
return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
}
else {
try {
// Try to parse the location as a URL...
URL url = new URL(location);
return (ResourceUtils.isFileURL(url) ? new FileUrlResource(url) : new UrlResource(url));
}
catch (MalformedURLException ex) {
// No URL -> resolve as resource path.
return getResourceByPath(location);
}
}
}
public static final String CLASSPATH_URL_PREFIX = "classpath:";
其实该方法又是调用类DefaultResourceLoader类中的方法,可以看出,它会根据传入的“location”路径的方式,用不同的方式得到对应Bean的资源对象。这里又有一个问题,其实看代码的时候会有一个“classpath:”和“classpath*:”,那他们有什么区别呢?知道的朋友也可以底下留言。
前面我们讲到了loadBeanDefinitons(Resource resource),在XmlBeanDefinitionReader类中还有加载类的另一个过程。
@Override
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
return loadBeanDefinitions(new EncodedResource(resource));
}
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
Assert.notNull(encodedResource, "EncodedResource must not be null");
if (logger.isInfoEnabled()) {
logger.info("Loading XML bean definitions from " + encodedResource.getResource());
}
Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
if (currentResources == null) {
currentResources = new HashSet<>(4);
this.resourcesCurrentlyBeingLoaded.set(currentResources);
}
if (!currentResources.add(encodedResource)) {
throw new BeanDefinitionStoreException(
"Detected cyclic loading of " + encodedResource + " - check your import definitions!");
}
try {
InputStream inputStream = encodedResource.getResource().getInputStream();
try {
InputSource inputSource = new InputSource(inputStream);
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
}
finally {
inputStream.close();
}
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(
"IOException parsing XML document from " + encodedResource.getResource(), ex);
}
finally {
currentResources.remove(encodedResource);
if (currentResources.isEmpty()) {
this.resourcesCurrentlyBeingLoaded.remove();
}
}
}
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException { try { Document doc = doLoadDocument(inputSource, resource); return registerBeanDefinitions(doc, resource); } catch (BeanDefinitionStoreException ex) { throw ex; } catch (SAXParseException ex) { throw new XmlBeanDefinitionStoreException(resource.getDescription(), "Line " + ex.getLineNumber() + " in XML document from " + resource + " is invalid", ex); } catch (SAXException ex) { throw new XmlBeanDefinitionStoreException(resource.getDescription(), "XML document from " + resource + " is invalid", ex); } catch (ParserConfigurationException ex) { throw new BeanDefinitionStoreException(resource.getDescription(), "Parser configuration exception parsing XML from " + resource, ex); } catch (IOException ex) { throw new BeanDefinitionStoreException(resource.getDescription(), "IOException parsing XML document from " + resource, ex); } catch (Throwable ex) { throw new BeanDefinitionStoreException(resource.getDescription(), "Unexpected exception parsing XML document from " + resource, ex); } }
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception { return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler, getValidationModeForResource(resource), isNamespaceAware()); }这个方法其实就可以看出,我们在加载xml中Bean的资源时,是将其先转化为IO流的形式,而真正的读取过程其实在doLoadBeanDefinitions()方法中。哈哈,看到这里我们是不是熟悉了啊,我们经常解析xml文件时,不就是用的Document进行解析的嘛,我们看看doLoadDocument().loadDocument()方法,当然了还有最重要的registerBeanDefinitons()方法了(注册)。
@Override
public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {
DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
if (logger.isDebugEnabled()) {
logger.debug("Using JAXP provider [" + factory.getClass().getName() + "]");
}
DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
return builder.parse(inputSource);
}
protected DocumentBuilderFactory createDocumentBuilderFactory(int validationMode, boolean namespaceAware) throws ParserConfigurationException { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); factory.setNamespaceAware(namespaceAware); if (validationMode != XmlValidationModeDetector.VALIDATION_NONE) { factory.setValidating(true); if (validationMode == XmlValidationModeDetector.VALIDATION_XSD) { // Enforce namespace aware for XSD... factory.setNamespaceAware(true); try { factory.setAttribute(SCHEMA_LANGUAGE_ATTRIBUTE, XSD_SCHEMA_LANGUAGE); } catch (IllegalArgumentException ex) { ParserConfigurationException pcex = new ParserConfigurationException( "Unable to validate using XSD: Your JAXP provider [" + factory + "] does not support XML Schema. Are you running on Java 1.4 with Apache Crimson? " + "Upgrade to Apache Xerces (or Java 1.5) for full XSD support."); pcex.initCause(ex); throw pcex; } } } return factory; }
protected DocumentBuilder createDocumentBuilder(DocumentBuilderFactory factory, @Nullable EntityResolver entityResolver, @Nullable ErrorHandler errorHandler) throws ParserConfigurationException { DocumentBuilder docBuilder = factory.newDocumentBuilder(); if (entityResolver != null) { docBuilder.setEntityResolver(entityResolver); } if (errorHandler != null) { docBuilder.setErrorHandler(errorHandler); } return docBuilder; }上面的代码就简单的说明下,他首先创建了一个文档解析器DocumentBuilder,然后又设置一些解析XML的校验。我们看看最后注册时做了什么东西(registerBeanDefinitons())?
@Override
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
this.readerContext = readerContext;
logger.debug("Loading bean definitions");
Element root = doc.getDocumentElement();
doRegisterBeanDefinitions(root);
}
该方法就是获取了Document文件的根节点,然后进行注册动作。里面注册就不多说了,主要就是将节点信息生成对应的代理对象存到map中,如果有后续,后面会继续更新。