栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

当我们在聊 IoC 的时候,我们在聊什么?(上篇)

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

当我们在聊 IoC 的时候,我们在聊什么?(上篇)

当我们在聊 IoC 的时候,我们在聊什么?

首先要明白一个点 IoC 究竟是什么?

大多数的资料都会告诉你:

IoC 是 Inversion of Control 的缩写,意为控制反转。控制反转 这四个字真是好难理解。当时作为初学者的我直接就蒙了。

满脑子就四个字: 我不理解

IoC 控制反转通常的解释是这样的:将对象的创建和生命周期交给由 Spring 进行控制。

那究竟为什么叫反转?

即一般来说我们创建一个对象都是通过 new 关键字来新建一个对象,是由我们来控制对象的创建,而 IoC 是将这个 new 的过程交给 Spring 来进行,也就是说创建对象的过程由 Spring 代替我们来做了,也可以理解为是一种代理。所以说是创建对象的控制权力反转了。

ok 到了这里,我们似乎有些理解 IoC 到底说的是什么了,但是我仍然对 IoC 的实现一无所知,他怎么做到控制翻转呢,这里究竟做了什么?

常常可能会听到这样一个词: IoC 容器

这个容器指什么?我们思考这样一个流程,Spring 在进行控制反转时他需要做什么。

  1. 读取配置文件或者注解
  2. 扫描配置中的类文件确定哪些类需要自动装配
  3. 将用户设置的需要自动装配的类初始化放到一个容器中
  4. 当用户使用时,将已经装配好的类直接从容器中取出使用

IoC 容器 如此称呼,其实就是因为它会存储我们自动装配的类,容器这两个词似乎还是有些抽象,其实在 Spring 中的实现它就是 Map ,当然这里不会是简简单单的一个 Map ,真正的实现会有一些其他的处理比如 循环依赖 等问题。

所以 IoC 的好处是什么?

  1. 资源集中管理,可配置、易于配置。
  2. 各个类之间解耦,各自修改不受影响。

这里再提一点,跟随 IoC 一起出现的往往还有一个词 dependency injection 简称 **DI ** 依赖注入。

什么区别? 其实来说 依赖注入 是 实现 ,控制翻转 是 理念。也就是说 Spring 容器会在创建 bean 的时候将依赖注入到类中,如我们使用了 @Autowired 或者 @Resource 等注解的地方。

大家可以参考 Spring 核心技术官方文档

一、聊聊 IoC 的流程

在细聊 IoC 的整个初始化流程之前,我们需要一个主线,一个由头。

就像上文所说,Spring 的 IoC 容器将所有我们 配置好的类 进行了统一的管理和初始化,而我们只需要拿来就用即可。

那什么叫 配置好的类 ?

即通过 xml 配置 ,或者通过 @注解 的方式,或者通过 基于 Java 的配置 所标识出的相关类对象。而这些配置称之为 metadata 元数据。

而由 Spring IoC 容器统一管理的类对象,称之为 bean。

下图简单描述了 Spring 是如何进行工作的,而这个 “由头” 就是所说的 metadata 。

1. 配置元数据

基于 xml 的配置 :Spring 最基本的配置方式




      
        
    

    
        
    

    


基于 @注解 的配置 :Spring 2.5 版本引入了基于注解配置的支持




    


通过 @Autowired @Resource @Value 等注解

public class MovieRecommender {

    private final CustomerPreferenceDao customerPreferenceDao;

    @Autowired
    private MovieCatalog movieCatalog;

    @Autowired
    public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
        this.customerPreferenceDao = customerPreferenceDao;
    }

    // ...
}

基于 Java 的配置 从 Spring 3.0 开始,你可以使用 java 定义你项目之外的 bean,这类方式基于 @Configuration @Bean @import @DependsOn 这些注解

@Configuration
public class AppConfig {

    @Bean
    public MyService myService() {
        return new MyServiceImpl();
    }
}
2. BeanDefinition bean 定义

Spring IoC 容器管理一个或者多个 bean,那么这些 bean 是事通过给容器配置的 metadata 元数据来创建的,因为支持了多种元数据的配置方式,所以对于 bean 的最终表现形式需要作出最终的统一,以方便后续的创建和使用,所以 Spring IoC 通过读取元数据配置,最终会转换为 bean 定义,其表示为 BeanDefinition 对象,该对象包含了 bean 的所有基本信息,比如:

  • 包限定类名,通常是定义的 bean 的实际实现类。
  • bean 的行为配置,即 bean 在容器中的行为方式,如 范围、生命周期、回调等。
  • bean 工作时所需要引用的其他 bean,这些 bean 也被称作 协作者 或者 依赖。
  • 一些其他的设置使得以新的方式创建类,如池的大小,或者用来管理 bean 的连接池的连接数。

然而,从 metadata 到 BeanDefinition 在到可以使用的 Bean ,这其中在 Spring IoC 容器中经历了一个复杂的过程,下面让我们一起看看,实例化一个容器到底做了什么。

3. 实例化一个容器

在 Spring 中 IoC 的基础包是 org.springframework.beans 和 org.springframework.context 。

而我们常提到的 BeanFactory 是生产 Bean 的工厂,其使用了 简单工厂的设计模式 当然 BeanFactory 在设计上是接口,会有不同的实现类,其提供了一种能够管理任何类型对象的高级配置能力。

通常来说我们开发中更常见的是 ApplicationContext 其实现了 BeanFactory 并提供了更加方便的功能。

  • 更容易与 Sping AOP 的特性集成
  • 用于国际化的消息资源处理
  • 事件发布
  • 应用层特定上下文,如用于 web 项目的 WebApplicationContext

可以这样理解:BeanFactory 就是我们的容器,而 ApplicationContext 是对于 BeanFactory 的增强,我们在日常开发中一般使用的就是 ApplicationContext ,很少情况会直接操作 BeanFactory

public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
    MyService myService = ctx.getBean(MyService.class);
    myService.doStuff();
}

简单剖析一下 ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class); 做了什么

public AnnotationConfigApplicationContext(Class... componentClasses) {
  // 调用无参构造函数,初始化 DefaultListableBeanFactory bean 工厂以及 AnnotatedBeanDefinitionReader bean 定义读取器 和 ClassPathBeanDefinitionScanner bean 定义扫描器
	this();
	register(componentClasses);
	refresh();
}

下面的剖析我这边不会深入到源码,而是直接告诉你这里做了什么,感兴趣的同学可以自己进入到源码中进行验证。

3.1 this()

AnnotationConfigApplicationContext 的无参构造函数。

  1. 调用父类 GenericApplicationContext 的无参构造函数,初始化 DefaultListableBeanFactory 内部属性 bean 工厂

  2. 初始化 AnnotatedBeanDefinitionReader 该过程会调用 AnnotationConfigUtils.registerAnnotationConfigProcessors 方法注册 一些系统固有的 BeanDefinition 比如 ConfigurationClassPostProcessor AutowiredAnnotationBeanPostProcessor 等,这些类都实现了 Spring 自身提供的多个拓展点如 BeanPostProcessor BeanFactoryAware 等,这些拓展点会在整个 IoC 容器创建流程的合适位置(这个合适位置在这里先不展开聊)进行执行。

  3. 初始化一个 ClassPathBeanDefinitionScanner 类路径 BeanDefinition 扫描器,其用来扫描类路径下候选的 bean ,使用给定的注册表注册这些 bean 的 definition,这里这个 “注册表” registry 其实就是 AnnotationConfigApplicationContext 其实现了 BeanDefinitionRegistry 接口,而本质上的注册就是 DefaultListableBeanFactory 实现的 registerBeanDefinition 方法,将对应的 bean 定义统一管理到了 ConcurrentHashMap beanDefinitionMap 当中。

扫描器在你构造函数传入的是包路径时会进行扫描如使用的是下面的构造函数:

	public AnnotationConfigApplicationContext(String... basePackages) {
		this();
    // 扫描传入 basePackages 路径下的所有类
		scan(basePackages);
		refresh();
	}

也许你懂了,也许你没懂,不如看看源码,相信你会有更深的理解。

3.2 register(componentClasses) or scan(basePackages);

register(componentClasses) 在这之中,注册了用户在声明 AnnotationConfigApplicationContext 所传入的类列表,如实例代码中的AppConfig.class ,同样最终调用了 DefaultListableBeanFactory 的注册方法,放入了 beanDefinitionMap

或者对你传入的包路径下的所有符合条件的类进行了扫描装载。

3.3 refresh()

重点逻辑在 refresh() 方法中,这里调用的是抽象父类 AbstractApplicationContext 的方法

@Override
	public void refresh() throws BeansException, IllegalStateException {
		synchronized (this.startupShutdownMonitor) {
			StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");

			// 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);

				StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
				// Invoke factory processors registered as beans in the context.
				invokeBeanFactoryPostProcessors(beanFactory);

				// Register bean processors that intercept bean creation.
				registerBeanPostProcessors(beanFactory);
				beanPostProcess.end();

				// 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();
				contextRefresh.end();
			}
		}
	}

可以说,refash() 方法是 Spring IoC 容器的最最核心的方法了,在这个方法中将实例化 Bean 并掌控整个 IoC 加载的生命周期,可以看得这里包含了很多的方法调用,那对于 refash 方法这里放到下篇进行讲解,这里最后做一个总结

总结

IoC 作为一个容器,帮助我们创建实例化类,管理类与类直接的依赖关系和生命周期,通过读取我们配置的元数据将类转换标准的 bean 定义集中放到 Map 中,再通过这些 BeanDefinition 成产我们最终使用的 Bean ,同时 Spring IoC 在整个加载的过程中提供了很多的扩展点,各种各样的 PostProcessor、Aware,这些扩展点使得 Spring 可以进行灵活的扩展和插拔,并且其内部的设计也是依附这些扩展点进行实现的,包括类的加载,Aop 等。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/439472.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号