建议先看最后的总结再细读全文
由上一篇文章得知,从 bean 的装配到获取,分别用到了三大组件:
- 资源抽象 Resource
- 工厂 DefaultListableBeanFactory
- 配置信息读取器 BeanDefinitionReader
public class SpringDemo {
public static void main(String[] args) {
// 1.指定加载的资源文件
Resource resource = new ClassPathResource("spring.xml");
// 2.创建管理bean的工厂
DefaultListableBeanFactory defaultListableBeanFactory = new DefaultListableBeanFactory();
// 3.资源读取器,把读取的到信息装配到 defaultListableBeanFactory 里面,工厂再对 bean 进行管理
BeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(defaultListableBeanFactory);
// 4.读取装配文件 xml 里面的信息
beanDefinitionReader.loadBeanDefinitions(resource);
// 5.获取 bean
Student student = defaultListableBeanFactory.getBean("student", Student.class);
System.out.println(student.getName());
System.out.println(student.getAge());
}
}
1. ClassPathResource
接下来谈谈spring是如何通过 ClassPathResource
把资源加载进来。
首先来看看源码:
private final String path;
@Nullable
private ClassLoader classLoader;
@Nullable
private Class<?> clazz;
public ClassPathResource(String path) {
// 继续调用来下面的方法
this(path, (ClassLoader) null);
}
public ClassPathResource(String path, @Nullable ClassLoader classLoader) {
// 断言
Assert.notNull(path, "Path must not be null");
// 判断这个路径是否为一个合法的路径,并解析成spring能识别的标准路径
String pathToUse = StringUtils.cleanPath(path);
if (pathToUse.startsWith("/")) {
pathToUse = pathToUse.substring(1);
}
// 已经是一个带解析并且能识别的path
this.path = pathToUse;
this.classLoader = (classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader());
}
public ClassPathResource(String path, @Nullable Class<?> clazz) {
Assert.notNull(path, "Path must not be null");
this.path = StringUtils.cleanPath(path);
this.clazz = clazz;
}
从源码可以看出,ClassPathResource 可以通过给定 class 或者给定的 classloader来进行资源的加载。ClassPathResource 的 构造方法主要是判断加载资源的路径是否有误,然后由默认的线程上下文类加载器(在运行期间,可以动态地去改变类加载器加载地方式)加载资源。如果传过来的classloader
是空的,则:
public static ClassLoader getDefaultClassLoader() {
ClassLoader cl = null;
try {
cl = Thread.currentThread().getContextClassLoader();
}
catch (Throwable ex) {
// Cannot access thread context ClassLoader - falling back...
}
if (cl == null) {
// No thread context class loader -> use class loader of this class.
cl = ClassUtils.class.getClassLoader();
if (cl == null) {
// getClassLoader() returning null indicates the bootstrap ClassLoader
try {
cl = ClassLoader.getSystemClassLoader();
}
catch (Throwable ex) {
// Cannot access system ClassLoader - oh well, maybe the caller can live with null...
}
}
}
return cl;
}
从作者的源码和注释中可以看出,首先判断当前线程的上下文类加载器是否存在,若不存在,则使用当前 ClassUtils
的 classloader,cl还是为空则说明当前的 cl 是用到了 bootstrap classloader,如果一个类是通过bootstrap classloader 载入的,那我们通过这个类去获得classloader的话,有些jdk的实现是会返回一个null的。所以,如果以上都获取不到 classloader,最终会由 SystemClassLoader 系统类加载器 来进行加载。
2. DefaultListableBeanFactory
DefaultListableBeanFactory 是 BeanFactory 的一个默认实现类,它继承了AbstractAutowireCapableBeanFactory,实现了ConfigurableListableBeanFactory, BeanDefinitionRegistry。
DefaultListableBeanFactory
构造函数源码分析
public DefaultListableBeanFactory() {
super();
}
这里的无参构造函数调用了父类的方法,继续分析:
public AbstractAutowireCapableBeanFactory() {
super();
ignoreDependencyInterface(BeanNameAware.class);
ignoreDependencyInterface(BeanFactoryAware.class);
ignoreDependencyInterface(BeanClassLoaderAware.class);
}
可以看到创建了一个 AbstractAutowireCapableBeanFactory ,它 的无参构造方法除了继续调用了父类的方法之外,忽略掉了3个class文件,为了在依赖注入的时候不应该用这三种类型来进行依赖的注入。继续往上跟:
public AbstractBeanFactory() {
}
只是创建了一个 AbstractBeanFactory 实例。
3. BeanDefinitionReader
实现类 XmlBeanDefinitionReader
实现类XmlBeanDefinitionReader
构造方法源码分析:
public XmlBeanDefinitionReader(BeanDefinitionRegistry registry) {
super(registry);
}
把工厂一起传给了父类,继续往上跟:
protected AbstractBeanDefinitionReader(BeanDefinitionRegistry registry) {
Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
this.registry = registry;
// Determine ResourceLoader to use.
if (this.registry instanceof ResourceLoader) {
this.resourceLoader = (ResourceLoader) this.registry;
}
else {
this.resourceLoader = new PathMatchingResourcePatternResolver();
}
// Inherit Environment if possible
if (this.registry instanceof EnvironmentCapable) {
this.environment = ((EnvironmentCapable) this.registry).getEnvironment();
}
else {
this.environment = new StandardEnvironment();
}
}
这里可以看到通过 ResourceLoader
去加载资源文件并继承它的环境,判断是否实现了ResourceLoader,若没有,则:
public PathMatchingResourcePatternResolver() {
this.resourceLoader = new DefaultResourceLoader();
}
@Override
@Nullable
public ClassLoader getClassLoader() {
return (this.classLoader != null ? this.classLoader : ClassUtils.getDefaultClassLoader());
}
又回到了上面所说获取类加载器的方法,是一种典型的回退思想。这算是为工厂装配bean准备环境的一步。
- EnvironmentCapable:如果可以的话 registry 把环境也继承了,如果没有则创建一个StandardEnvironment。
4. XmlBeanDefinitionReader.loadBeanDefinitions()
对 xml 文件信息整体的解析,将解析出来的xml信息装配成一个bean,并且把bean存放到工厂当中。看看 loadBeanDefinitions
做了哪些操作:
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
return loadBeanDefinitions(new EncodedResource(resource));
}
首先把 resource
传给 EncodedResource
,让 EncodedResource
封装成一个真正带编码或者字符集的资源。且encoding 和 charset 是互斥关系:
public EncodedResource(Resource resource) {
this(resource, null, null);
}
public EncodedResource(Resource resource, @Nullable String encoding) {
this(resource, encoding, null);
}
public EncodedResource(Resource resource, @Nullable Charset charset) {
this(resource, null, charset);
}
private EncodedResource(Resource resource, @Nullable String encoding, @Nullable Charset charset) {
super();
Assert.notNull(resource, "Resource must not be null");
this.resource = resource;
this.encoding = encoding;
this.charset = charset;
}
然后再调用重载的 loadBeanDefinitions
:
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
Assert.notNull(encodedResource, "EncodedResource must not be null");
if (logger.isTraceEnabled()) {
logger.trace("Loading XML bean definitions from " + encodedResource);
}
Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
// 防止循环依赖,因为set集合不可重复的原因,若集合中存在 encodedResource A,再次add A 的时候则会抛出异常
if (!currentResources.add(encodedResource)) {
throw new BeanDefinitionStoreException(
"Detected cyclic loading of " + encodedResource + " - check your import definitions!");
}
// 获取 encodedResource 里面 resource 的输入流
try (InputStream inputStream = encodedResource.getResource().getInputStream()) {
// InputSource 用字节流创建一个新的输入源,是 org.xml.sax 提供的一个对象,并不是 spring 内部的
InputSource inputSource = new InputSource(inputStream);
// 这里我们传入的是 null
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
// doLoadBeanDefinitions 方法在下文做出分析,实际是从指定的XML文件加载bean定义的方法
return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(
"IOException parsing XML document from " + encodedResource.getResource(), ex);
}
finally {
currentResources.remove(encodedResource);
if (currentResources.isEmpty()) {
// resourcesCurrentlyBeingLoaded 是 threadlocal 变量,remove 防止内存泄漏
this.resourcesCurrentlyBeingLoaded.remove();
}
}
}
-
doLoadBeanDefinitions
:
// 实际从指定的XML文件加载 bean 定义的方法
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {
try {
// 通过doLoadDocument加载inputSource资源,返回一个 doc 对象
Document doc = doLoadDocument(inputSource, resource);
// 注册给定DOM文档中包含的bean定义,完成对 xml 的解析
int count = registerBeanDefinitions(doc, resource);
if (logger.isDebugEnabled()) {
logger.debug("Loaded " + count + " bean definitions from " + resource);
}
return count;
}
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);
}
}
可以看出 doLoadBeanDefinitions
是对 spring 装配 bean 的主要实现。下一章看看Spring是如何装配bean的。
5. 总结
通过上面的分析得知,spring 想要把 bean 注册到工厂,然后再从工厂中获取(IoC容器思想)具体需要以下操作(文中采取的是xml方式注入,和注解方式注入实质上原理一样):
- 通过
ClassPathResource
把 xml 配置文件加载进来,加载方式有两种:- 通过默认的线程上下文类加载器(SystemClassLoader)
- 通过给定的 class 对象
- 定义一个Bean工厂
DefaultListableBeanFactory
用来装配和管理Bean的所有信息,指定注入的相关方式,但这时和资源文件还没有任何交互 - 定义一个资源读取器
BeanDefinitionReader
,调用其实现类XmlBeanDefinitionReader
把读取到的beans信息装配到DefaultListableBeanFactory
,这时已经准备好了装配bean的加载器和环境并和bean工厂关联起来。 - 通过调用
XmlBeanDefinitionReader.loadBeanDefinitions()
方法把加载进来xml资源解析并装配成bean,一同注册到DefaultListableBeanFactory
。此过程中,底层还有很多操作,可以概括为先把 xml 转成 inputSource 流资源,再转化成相应的 doc 对象,最终在DefaultBeanDefinitionDocumentReader
类中通过doRegisterBeanDefinitions
方法,递归遍历doc对象的所有节点和元素从而完成bean定义
的注册。
至此操作已全部完毕,下一章将会继续分析spring装配bean的详细过程。