学习优秀框架的源码, 是提升个人技术水平必不可少的一个环节. 如果只是停留在知道怎么用, 但是不懂其中的来龙去脉, 在技术的道路上注定走不长远. 最近, 学习了一段时间的 spring 源码, 现在整理出来, 以便日后温故知新.
IoC 容器是 spring 最核心的模块之一, 是整个 spring 体系的基石, spring 其他模块中, 都需要用到 IoC 容器的功能. spring 框架为我们提供了多种 IoC 容器, DefaultableBeanFact
ory,FileSystemXmlApplicationContext,ClassPathXmlApplicationContext,XmlwebApplicationContext 等. 虽然我们平时很少在项目中使用这种硬编码的方式来获取 IoC 容器, 继而获取 IoC 容器中的 bean, 但是研究这些 IoC 容器的源码, 对我们理解 IoC 容器的原理还是很有必要的. BeanFactory 这个接口是 spring 所有 IoC 容器最上层的接口, getBean() 这个方法就是在这个接口中定义的, 下面是其中定义的方法:
- public interface BeanFactory {
- 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;
- boolean containsBean(String name);
- boolean isSingleton(String name) throws NoSuchBeanDefinitionException;
- boolean isPrototype(String name) throws NoSuchBeanDefinitionException;
- boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException;
- boolean isTypeMatch(String name, Class<?> typeToMatch) throws NoSuchBeanDefinitionException;
- Class<?> getType(String name) throws NoSuchBeanDefinitionException;
- String[] getAliases(String name);
- }
可以看到其中定义了获取 bean 的多种方式, 和各种对 bean 的判断, 以及获取 bean 的类型和别名的方法. 这个接口是 spring 框架 IoC 容器的入口. 下面以 FileSystemXmlApplicatio
nContext 为例, 深入源码探究 IoC 容器的实现原理. IoC 容器的初始化过程分为三个阶段: 定位, 载入和注册. 接下来一一进行分析, 先从 xml 的定位开始.
相信我们大家都使用以下代码获取过 IoC 容器, 获取 IoC 容器之后, 我们就可以得到想要的 bean, 然后进行操作了:
1 FileSystemXmlApplicationContext context = new FileSystemXmlApplicationContext("bean.xml");
进入 FileSystemXmlApplicationContext 这个类, 发现它定义了各种构造器, 但最终都会调用下面这个构造器:
- public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
- throws BeansException {
- super(parent);
- setConfigLocations(configLocations);
- if (refresh) {
- refresh();
- }
- }
在分析它的流程之前, 有必要给一下它的 UML 图, 上面标注了它的继承体系结构:
FileSystemXmlApplicationContext 的构造器中有个重要的方法 refresh(), 这是 IoC 容器的启动方法, 在它的父类 AbstractXmlApplicationContext 中有实现, 其代码如下:
- @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();
- }
- }
- }
进入 obtainFreshBeanFactory() 方法, 其代码如下:
- protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
- refreshBeanFactory();
- ConfigurableListableBeanFactory beanFactory = getBeanFactory();
- if (logger.isDebugEnabled()) {
- logger.debug("Bean factory for" + getDisplayName() + ":" + beanFactory);
- }
- return beanFactory;
- }
继续跟, 进入 refreshBeanFactory() 方法, 在父类 AbstractRefreshableApplicationContext 中有实现, 其代码如下:
- @Override
- protected final void refreshBeanFactory() throws BeansException {
- if (hasBeanFactory()) {
- destroyBeans();
- closeBeanFactory();
- }
- try {
- // 创建 DefaultListableBeanFactory
- DefaultListableBeanFactory beanFactory = createBeanFactory();
- // 指定序列化的 id, 所以, 如果需要反序列化这个 BeanFactory, 则可以直接根据这个 id 来进行反序列化
- beanFactory.setSerializationId(getId());
- // 定制化
- customizeBeanFactory(beanFactory);
- // 初始化 DocumentReader, 读取 xml
- loadBeanDefinitions(beanFactory);
- synchronized (this.beanFactoryMonitor) {
- this.beanFactory = beanFactory;
- }
- }
- catch (IOException ex) {
- throw new ApplicationContextException("I/O error parsing bean definition source for" + getDisplayName(), ex);
- }
- }
这段代码可以看到:
1, 首先, 创建了一个 DefaultListableBeanFactory 的 IoC 容器;
2, 对容器进行了一些设置;
3, 调用 loadBeanDefinitions() 方法对 xml 文件进行定位和加载.
所以, 进入 loadBeanDefinitions() 方法继续探索, 在类 AbstractXmlApplicationContext 中有实现, 它是 FileSystemXmlApplicationContext 的父类, 其代码如下:
- 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);
- }
这个方法中, 使用 XmlBeanDefinitionReader 类来加载 xml 文件, 最后经过一系列的设置, 调用了 loadBeanDefinitions(beanDefinitionReader) 这个方法, 进入:
- 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);
- }
- }
跟到这里, 到底是走哪个方法呢? 我们再回过头看一下, FileSystemXmlApplicationContext 的那个构造器, 其中有个 setConfigLocations(configLocations) 方法, 通过这个方法将我们配置的 xml 文件的路径设置进来了, 跟代码, 发现它调用的是父类的方法, 并将路径赋给了 AbstractRefreshableConfigApplicationContext 类中的 configLocations 成员变量, 而 getConfigLocations() 方法也是 AbstractRefreshableConfigApplicationContext 类中的, 它正好获取了 configLocations 的值, 所以 configLocations 一定不为 null, 上面方法应该走下面的 loadBeanDefinitions() 方法. 跟进, 其代码如下:
- public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException {
- Assert.notNull(locations, "Location array must not be null");
- int counter = 0;
- for (String location : locations) {
- counter += loadBeanDefinitions(location);
- }
- return counter;
- }
- public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException {
- return loadBeanDefinitions(location, null);
- }
- public int loadBeanDefinitions(String location, 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;
- }
- }
这里先得到一个 ResourceLoader 对象. 在类 AbstractXmlApplicationContext 中的 loadBeanDefinitions() 方法中有 beanDefinitionReader.setResourceLoader(this) 这段代码, 而
DefaultListableBeanFactory 又是继承了 DefaultResourceLoader 的, 所以, 这里的 resourceLoader 对象是 DefaultResourceLoader 类型的, 所以走下面的逻辑. 首先, 获取一个 resource
对象, getResource 方法在 DefaultResourceLoader 中有实现, 其代码如下:
- 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 new UrlResource(url);
- }
- catch (MalformedURLException ex) {
- // No URL -> resolve as resource path.
- // 如果都不是, 则使用子类重写的方法, 例如子类 FileSystemXMLApplicationContext 中就重写了这个方法
- return getResourceByPath(location);
- }
- }
- }
根据不同的情况, 生成一个 ResourceLoader 对象, 这样就完成了对配置的 xml 文件的定位.
经过这么一长条的跟踪, 终于完成了 xml 资源的定位工作. spring 的继承体系特别深, 刚开始的时候感觉特别绕, 但是多跟着代码跟几遍, 基本就清晰了, 关键是要有耐心. 在上面的分析中, 我们发现, spring 使用了很多的模板方法, 比如 getResource 方法, 还有就是单一职责原则, 每个类很清晰, 每个方法中都是一个一个方法的调用, 而不是代码的堆砌, 这点是值得我们平时好好学习和运用的.
来源: https://www.cnblogs.com/helei123/p/11073373.html