简单了解IoC

IoC

什么是IoC?

IoC(Inversion of Control),即控制反转,这是一种设计思想,在Spring指将对象的控制权交给Spring容器,由容器来实现对象的创建、管理,程序员只需要从容器获取想要的对象就可以了。这里引申一个词叫DI(Dependency Injection),即依赖注入,他是IoC的一种具体实现方式,类似于Map和HashMap的关系,指对象创建时的属性赋值交给Spring容器来做。

还有一种理解是把IoC理解为Spring容器,底层是一个Map,key是beanName,value是bean。

为什么需要IoC?

  • 不用手动new对象,将对象创建和业务解耦

  • 无需关注对象创建的过程,只管使用就行

IoC怎么使用?

声明bean

xml文件

@Component及其衍生注解

@Configuration和@Bean

获取bean

beanFactory.getBean(String beanName)

applicationContext.getBean(String beanName)

属性注入注解方式

@Autowired、@Value

IoC底层实现?

IoC把对象控制权反转给Spring框架,那么我们就来关注一下Spring怎么实现bean的创建、管理等,再详细一些,bean从实例化 -> 属性赋值 -> 初始化 -> 使用 -> 销毁,这叫做bean的生命周期,Spring管理的bean主要走的就是这么个流程。

什么是属性赋值前面已经解释过了,实例化和初始化是怎么回事呢?

  • 实例化实际上就是bean的创建,给bean在内存中分配空间;

  • 初始化回调各种Aware接口、回调各种初始化方法、生成AOP代理对象也在该阶段进行,该阶段主要是完成初始化回调。

基本概念

接下来会介绍底层源码,在此之前会给出一些基本概念,以便更加清晰地了解源码。

  • BeanFactory:bean工厂,用来生成bean,故bean的实例化、属性赋值、初始化都在此进行

  • BeanDefinition:bean的定义信息,你想让BeanFactory生产bean,那么自然要有张图纸,这张图纸就是BeanDefinition

  • BeanPostProcessor:bean的增强器,用来执行一些增强方法

  • BeanFactoryPostProcessor:beanFactory的增强器,用来执行一些增强方法

bean生命周期详解

refresh ()的第11步finishBeanFactoryInitialization(beanFactory);这里会进行bean的生命周期

通过getBean(beanName);进行单例bean的初始化,这里有对工厂bean的特殊处理TODO

getBean顾名思义就是获取bean,引出两个问题,去哪里获取?获取不到怎么办?

  • Spring里面大量使用缓存,这里存储bean的缓存特指Map,通过beanName为key可以获取到对应的bean;

  • 如果本身缓存不存在bean,那么需要去创建bean并将其放到缓存中去。

走从缓存中拿的逻辑

getSingleton(String beanName)会尝试从缓存中获取bean,值得一提的是这里有三个缓存,俗称三级缓存,为什么要使用三级缓存,等到后面再说。

走bean创建的逻辑doCreateBean

getSingleton, ObjectFactory<?> singletonFactory)内部会调用createBean()去创建bean,并且创建后把bean放到缓存中去

接下来就是bean的生命周期了:

  • 实例化:一般是反射调用无参构造器创建对象,instanceWrapper = createBeanInstance(beanName, mbd, args);

  • 属性赋值:populateBean(beanName, mbd, instanceWrapper);

  • 初始化:对bean进行一些额外操作,exposedObject = initializeBean(beanName, exposedObject, mbd);

  • 销毁:

实例化详解

前面提到实例化时通过构造器实现,不通过直接new对象的方式,我们该如何创建对象呢?Spring用到了反射去实例化bean,要用反射自然少不了bean对应的Class对象,通过内存中的Class对象我们才能够获取到类的信息(一个对象的模板)从而创建对象。

  • 获取bean的Class对象

    • Class<?> beanClass = resolveBeanClass(mbd, beanName);

  • 还可能用工厂去创建bean

  • 可能这里会用有参构造器,如果你有编写有参构造器的话

  • 然后到是无参构造器构造

    • return instantiateBean(beanName, mbd);

    • constructorToUse = clazz.getDeclaredConstructor();

3种实例化方式

除了构造器之外可以使用工厂方法去进行bean的实例化,以下列出他们各自的优缺点

  • 构造器:反射使用类构造器

    • 优点:使用方便,简单明了

    • 缺点:如果创建逻辑复杂,无法定制化配置

  • 静态工厂:调用一个静态的方法

    • 优点:可以在创建bean的时候获取到一些静态资源

    • 缺点:需要提前写好,无法拓展

  • 普通工厂:调用一个普通的方法

    • 优点:适用于逻辑复杂的创建,如依赖注入或状态管理,如某个依赖需要通过入参判断注入哪个类

    • 缺点:相比构造器方式需要多写一个工厂类

总的来说,如果没有特别复杂的创建逻辑需求,直接用构造器创建就好。

策略模式

值得一提的是这里用到了策略模式

Object beanInstance = getInstantiationStrategy().instantiate(mbd, beanName, this);

下面这张图是继承关系图:顶级策略接口InstantiationStrategy

image-20240620223742711

默认JDK的无参构造器就是在SimpleInstantiationStrategy类中实现,如果实现CGLIB的方式实例化,在CglibSubclassingInstantiationStrategy有内部类CglibSubclassCreator通过instantiate(@Nullable Constructor<?> ctor, Object... args)实现CGLIB的一个实例化操作

属性赋值详解

对象已经在内存中存在了,但是它的属性还是空的,我们需要对其进行属性赋值,populateBean(beanName, mbd, instanceWrapper);,可以思考几个问题,值存放在哪里?如何赋值?

这些值是存放在BeanDefinition的成员变量里面的MutablePropertyValues propertyValues,通过这个就可以获取到bean的属性,在xml中是以<property>标签表示,如果你需要用注解的方式实现,可以自定义注解,搭配自定义BeanFactoryPostProcessor

 @Target(ElementType.FIELD)
 @Retention(RetentionPolicy.RUNTIME)
 @Documented
 public @interface MyIntegerValue {
 ​
     int value() default 0;
 }
 ​
 @Component
 public class MyValueBeanPostProcessor implements BeanDefinitionRegistryPostProcessor {
 ​
     @Override
     public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
         try {
             BeanDefinition beanDefinition =  registry.getBeanDefinition("person");
             String beanClassName = beanDefinition.getBeanClassName();
             Class<?> beanClass = Class.forName(beanClassName);
             Field field = beanClass.getDeclaredField("num");
             MyIntegerValue annotation = field.getAnnotation(MyIntegerValue.class);
             int value = annotation.value();
             beanDefinition.getPropertyValues().getPropertyValueList().add(new               PropertyValue(field.getName(), value));
         } catch (NoSuchFieldException | ClassNotFoundException e) {
             throw new RuntimeException(e);
         }
     }
 }

如何赋值?

setPropertyValues(PropertyValues pvs)setPropertyValue(PropertyValue pv)

 protected void setPropertyValue(PropertyTokenHolder tokens, PropertyValue pv) throws BeansException {
     if (tokens.keys != null) {
         // 设置数组、List、Map,暂时TODO
        processKeyedProperty(tokens, pv);
     }
     else {
         // 设置普通值,就是调用反射,method.invoke(obj, arg)
        processLocalProperty(tokens, pv);
     }
 }

对于@Autowired的注解,通过InstantiationAwareBeanPostProcessor的postProcessProperties()方法进行赋值

 if (hasInstantiationAwareBeanPostProcessors()) {
     if (pvs == null) {
        pvs = mbd.getPropertyValues();
     }
     for (InstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().instantiationAware) {
        // 对所有需要依赖检查的属性进行后处理
        PropertyValues pvsToUse = bp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName);
        if (pvsToUse == null) {
           return;
        }
        pvs = pvsToUse;
     }
 }

初始化详解

属性赋值之后,我们还需要对其进行初始化,可以调用初始化方法实现一些逻辑,例如资源分配或者初始化,如Spring Boot的自动配置类,exposedObject = initializeBean(beanName, exposedObject, mbd);

调用用户自己实现的初始化方法invokeInitMethods(String beanName, Object bean, @Nullable RootBeanDefinition mbd)

  1. 先调用 @PostConstructor 注解,在BeanPostProcessor前置增强执行

  2. 再调用实现 InitializingBean 接口的回调方法 afterPropertiesSet()

  3. 最后调用xml文件的 init-method 方法

初始化增强

在初始化前后,会对bean进行增强

前置增强

一般是一些Aware回调方法执行,如ApplicationContextAware的回调方法的执行,调用BeanPostProcessor增强processor.postProcessBeforeInitialization(result, beanName);

后置增强

这里会和AOP有关了,调用BeanPostProcessor增强,processor.postProcessAfterInitialization(result, beanName)

为什么使用三级缓存?
循环依赖

这个三级缓存其实和属性赋值那一步相关,缓存的是bean,那我们属性赋值的时候有可能是给基本属性赋值、也有可能是给引用属性赋值,在给引用属性赋值的时候我们会调用getBean(),是不是很熟悉,即创建bean的时候会调用的,从某种角度来看很像递归,肯会出现一直调用导致SOF,在Spring表现的就是会出现循环依赖问题,那我们就需要一个终止条件,即用缓存代替创建bean

具体是哪三级缓存?
 /** Cache of singleton objects: bean name to bean instance. */
 // 一级缓存,beanName -> 实例化并且初始化的成品
 private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
 ​
 /** Cache of singleton factories: bean name to ObjectFactory. */
 // 三级缓存,beanName -> ObjectFactory
 private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
 ​
 /** Cache of early singleton objects: bean name to bean instance. */
 // 二级缓存,beanName -> 实例化但是未初始化的成品
 private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);

为什么用了三级缓存就没有循环依赖?

先看循环依赖的原因:假设有A,B两个类,他们互相依赖

1.实例化A

2.给A的b属性赋值

3.实例化B

4.给B的a属性赋值

5.重复第一步

那用二级缓存能否解决循环依赖呢?

1.实例化A

2.给A的b属性赋值

3.实例化B

4.给B的a属性赋值(注意,此时的a就不会再走到第五步去实例化了,而是从二级缓存中获取完成品A)

5.B的属性赋值完成,接下来回到第二步,即将A的属性赋值完成,自此循环依赖解决

那为什么是三级缓存呢?

想一想如果只有二级缓存,再加上AOP代理,此时B.a是半成品A,而A是个代理对象,这两个不相同,即b.getA() != A a

而A是个单例对象,这就很奇怪了吧。

那你可能又会想那实例化的时候我直接先把代理对象创建出来不久好了吗,不就一样了吗,但是objectFactory每次调用getObject()返回的都是新的代理对象,所以如果某个代理类C被当作很多个类的属性,那么这些类的这个引用属性C还是不相同的,所以我们需要三级缓存,有了二级就不用找三级,即代理对象只会被生产一次

<think>嗯,用户想了解IOC控制反转的原理和实现,以及在Spring框架中的作用。首先,我需要回顾一下IOC的基本概念。根据引用中的内容,IOC的核心是依赖注入、容器和配置文件或注解。那用户可能不太清楚这些术语的具体含义,需要逐一解释。比如,依赖注入是什么,容器在这里扮演什么角色,配置文件又是如何使用的? 接下来,原理部分。用户可能想知道IOC是如何工作的,底层机制是什么。引用里提到了xml解析、工厂模式和反射,这些需要展开说明。比如,传统的对象创建是直接new出来的,而IOC通过工厂模式将对象的创建交给容器,容器通过读取配置文件,利用反射机制动态生成对象,从而降低耦合度。 然后,实现部分。Spring框架中的IOC容器有两种实现:BeanFactory和ApplicationContext。需要解释两者的区别,以及它们如何使用配置元数据,比如XML或注解。用户可能对具体的代码实现感兴趣,比如如何定义Bean,如何注入依赖,这里可以举一个简单的例子,比如通过@Component和@Autowired注解来实现依赖注入。 在Spring框架中的作用,用户可能想知道使用IOC带来的好处,比如解耦、提高可维护性和测试性。这里需要结合引用中的内容,比如控制反转如何将对象的创建和调用交给Spring管理,从而降低耦合度。同时,可以提到依赖注入的不同方式,构造器注入和setter注入,以及它们的应用场景。 用户可能还会对IOC和DI的关系有疑问,需要澄清这两个概念的联系和区别。引用中提到IOC的实现依赖于DI,而DI是实现IOC的一种手段,所以需要明确说明这一点。此外,用户可能对底层原理中的xml解析和反射机制不太熟悉,需要简单解释这些技术是如何支持IOC的。 最后,相关问题部分需要考虑用户可能进一步想了解的内容。比如IOC和DI的区别,Spring中的Bean生命周期,如何选择不同的配置方式(XML vs 注解),以及IOC的优缺点。这些问题可以帮助用户深入学习和应用IOC,解决实际开发中的问题。 总结一下,需要将IOC的概念、原理、实现、在Spring中的作用分点解释清楚,结合引用中的信息,使用具体的例子和术语说明,确保用户能够逐步理解并应用这些知识。</think>### IOC控制反转的原理与实现及在Spring框架中的作用 #### 一、IOC控制反转的核心概念 1. **定义** IOC(Inversion of Control)即“控制反转”,其核心思想是将对象的创建和调用过程从代码中剥离,交由容器统一管理,从而降低代码耦合度[^4]。例如,传统方式通过`new`直接创建对象,而IOC由容器通过依赖注入(DI)动态分配对象[^3]。 2. **核心组成** - **依赖注入(DI)**:通过构造器、Setter方法或接口注入对象的依赖关系[^1]。 - **容器**:负责管理对象的生命周期和依赖关系(如Spring的`BeanFactory`)[^3]。 - **配置元数据**:通过XML、注解或Java代码定义对象及其依赖关系。 #### 二、IOC的实现原理 1. **底层技术** - **XML解析**:容器读取XML配置文件,解析Bean的定义和依赖关系[^3]。 - **工厂模式**:通过工厂类集中创建对象,而非直接在代码中实例化[^3]。 - **反射机制**:动态加载类并创建对象实例,例如`Class.forName()`。 2. **流程示例** 传统方式:`UserService userService = new UserServiceImpl();` IOC方式: ```xml <!-- XML配置 --> <bean id="userService" class="com.example.UserServiceImpl"/> ``` ```java // 容器通过反射创建对象并注入依赖 ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); UserService userService = context.getBean("userService"); ``` #### 三、Spring框架中IOC的作用 1. **解耦与可维护性** 将对象的创建逻辑与业务代码分离,修改实现类时无需调整调用方代码[^2]。例如: ```java @Service public class OrderService { @Autowired // 由Spring注入依赖 private PaymentService paymentService; } ``` 2. **依赖注入方式** - **构造器注入**:强制依赖初始化,避免空指针异常。 - **Setter注入**:适合可选依赖,灵活性高。 - **注解注入**:通过`@Autowired`自动装配,简化配置[^1]。 3. **应用场景** - 多环境配置切换(如开发、测试、生产环境)。 - 管理第三方库(如数据库连接池)。 - 支持AOP(面向切面编程)实现日志、事务等功能。 #### 四、IOC与DI的关系 - **IOC是设计思想**,强调控制权的转移。 - **DI是实现手段**,通过注入依赖具体落实IOC。 例如:Spring通过`@Component`定义Bean,`@Autowired`实现依赖注入。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值