Spring IoC(控制反转)

Spring IoC(控制反转)是Spring框架的核心功能之一,以下是其核心知识点总结:

一、IoC基础概念

  1. 控制反转(IoC)

    • 核心思想:将对象的创建和依赖管理交给容器,而非手动编码。
    • 优势:降低代码耦合度,提高灵活性和可维护性。
  2. 依赖注入(DI)

    • IoC的具体实现方式,通过容器向对象注入依赖。

二、核心接口与容器

  1. BeanFactory

    • IoC容器的基础接口,提供基本的Bean管理功能(如getBean())。
    • 延迟加载:Bean在第一次使用时创建。
  2. ApplicationContext

    • BeanFactory的高级实现,扩展了功能:
      • 支持国际化(MessageSource)、事件监听(ApplicationEventPublisher)、资源加载(ResourcePatternResolver)等。
    • 常见实现类:
      • ClassPathXmlApplicationContext(从类路径加载XML配置)。
      • AnnotationConfigApplicationContext(基于注解配置)。

三、Bean的定义与管理

  1. BeanDefinition

    • 描述Bean的元数据(类名、作用域、依赖等)。
    • 来源:XML配置、注解(如@Component)、Java配置类(@Bean)。
  2. Bean的作用域

    • singleton(默认,单例)。
    • prototype(原型,每次获取新实例)。
    • request/session(Web环境中请求/会话级)。
  3. 依赖注入方式

    • 构造函数注入:通过构造方法注入依赖(推荐,确保不可变性)。
    • Setter方法注入:通过Setter方法注入。
    • 字段注入:使用@Autowired直接注入字段(不推荐,不利于测试)。

四、Bean的生命周期

  1. 初始化流程

    • 实例化Bean → 填充属性 → 调用@PostConstruct注解方法 → 执行InitializingBean接口的afterPropertiesSet() → 自定义初始化方法(XML中init-method@Bean(initMethod=))。
  2. 销毁流程

    • 调用@PreDestroy注解方法 → 执行DisposableBean接口的destroy() → 自定义销毁方法(XML中destroy-method@Bean(destroyMethod=))。

五、高级特性

  1. 自动装配(Autowiring)

    • @Autowired:按类型自动装配。
    • @Qualifier:指定名称装配。
    • @Resource:JSR-250标准,按名称或类型装配。
  2. AOP(面向切面编程)

    • 与IoC结合,通过代理模式为Bean添加横切逻辑(如日志、事务)。
  3. 条件化Bean

    • @Conditional:根据条件动态注册Bean(如环境、类是否存在)。

六、配置方式

  1. XML配置

    • 使用<bean>标签定义Bean和依赖关系。
  2. 注解配置

    • @Component系列(@Service@Repository等):标记Bean。
    • @Configuration + @Bean:替代XML定义Bean。
  3. Java配置类

    • 通过@Configuration类中的@Bean方法显式定义Bean。

七、最佳实践

  1. 优先使用构造函数注入,确保依赖不可变。
  2. 避免过度依赖注解,适当结合XML或Java配置提高可读性。
  3. 理解循环依赖问题及解决方案(如延迟注入@Lazy)。
  4. 合理使用作用域,避免内存泄漏(如prototype Bean未被容器管理)。

通过掌握以上知识点,结合源码调试和项目实践,可深入理解Spring IoC的设计思想与应用场景。 Spring 中 IoC 容器主要通过以下方面实现:

核心接口及类

  • BeanFactory :IoC 容器的基本实现,是 Spring 内部使用的接口 ,定义了基本的 IoC 容器规范,如包含 getBean() 等基本方法来获取 Bean 。它就像一个 “发动机”,负责实例化 Bean ,建立 Bean 之间的依赖关系,以及控制 Bean 的生命周期 。例如,通过它可以从容器中获取一个已定义好的用户服务 Bean 实例。
  • ApplicationContext :BeanFactory 的子接口 ,是更高级的 IoC 容器实现,面向 Spring 的使用者 。它继承了多个接口,具备更多功能:
    • 继承 EnvironmentCapable 可访问配置环境;继承 ListableBeanFactory 能列出容器中所有 Bean 定义等信息 。
    • 继承 ResourcePatternResolver 可加载资源文件;继承 MessageSource 能实现国际化;继承 ApplicationEventPublisher 可注册监听器实现事件监听机制 。比如在一个多语言的项目中,利用其国际化功能根据用户地区显示对应语言的提示信息 。常见的实现类有:
      • ClassPathXmlApplicationContext :从类路径中加载 XML 格式的配置文件创建 IoC 容器对象 。
      • FileSystemXmlApplicationContext :通过文件系统路径读取 XML 格式的配置文件创建 IoC 容器对象 。
      • AnnotationConfigApplicationContext :用于加载基于注解(如 @Configuration@Bean 等)的配置来创建 IoC 容器 。

Bean 定义与管理

  • BeanDefinition :用于描述 Bean 的定义 ,Spring 容器启动时,会将 XML 配置、Java 配置类或注解里的 Bean 定义解析成 BeanDefinition 。它记录了 Bean 的类名、作用域(如单例 singleton 、原型 prototype 等 )、是否懒加载等元数据信息 。比如在 XML 配置文件中定义 <bean id="userService" class="com.example.UserService" scope="singleton"/> ,就会被解析成相应的 BeanDefinition
  • BeanDefinitionRegistry :提供了向 IoC 容器注册 BeanDefinition 对象的方法 ,Spring 容器通过它将解析后的 BeanDefinition 注册到容器中进行管理 。

依赖注入实现方式

  • 构造函数注入 :通过类的构造函数传入依赖对象 。例如:
public class UserService {
    private final UserRepository userRepository;
    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
}

在配置文件或使用注解配置时,Spring 会根据构造函数参数找到对应的 BeanDefinition ,实例化依赖对象并注入 。

  • Setter 方法注入 :利用类的 Setter 方法设置依赖对象 。如下示例:
public class UserService {
    private UserRepository userRepository;
    public void setUserRepository(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
}

Spring 会调用对应的 Setter 方法来完成依赖注入 。

  • 字段注入 :使用注解(如 @Autowired )直接在类的字段上注入依赖对象 ,例如:
public class UserService {
    @Autowired
    private UserRepository userRepository;
}

不过这种方式不利于测试和实现依赖不可变,一般不推荐 。

容器初始化与刷新

  • 初始化过程 :以 ApplicationContext 为例,创建容器实例时(如 new ClassPathXmlApplicationContext("applicationContext.xml") ) ,会经历一系列步骤 。首先定位配置文件资源 ,然后解析配置文件内容,将其中的 Bean 定义加载并注册到容器中 。
  • refresh() 方法 :容器创建好后会调用 refresh() 方法 ,该方法定义了整个 Spring 上下文加载的流程 ,包括调用工厂后处理器处理各类 Bean 标签 、扫描 Bean 文件并解析成一个个 BeanDefinition ;注册 Bean 后置处理器;初始化事件广播器;注册事件监听器;实例化所有已注册但未被实例化的非懒加载 Bean ;以及完成一些初始化生命周期处理器等操作 ,为 IOC 容器以及 Bean 的生命周期管理提供条件 。
Spring 文章被收录于专栏

Spring 生态是以 Spring Framework 为核心,衍生出的一系列相互关联、功能互补的技术和工具集合,用于简化企业级应用开发,覆盖从单体应用到分布式微服务、从 Web 开发到数据处理等诸多场景。

全部评论

相关推荐

(自我介绍,略)#面试常问题系列#我:介绍项目,提到一些设计的改动就是你这边有提到状态的这弱一致性,那你这边怎么去理解弱一致性和强一致性?你有对这一块有了解吗?我理解,我这边看你还有使用&nbsp;naocs,充当的部分是什么?是配置中心还是做那个服务发现这一块的能力?想问你的就是对于&nbsp;Narcos&nbsp;的&nbsp;AP&nbsp;和&nbsp;CP&nbsp;是怎么去配置的?然后你是怎么考虑这个&nbsp;AP&nbsp;和&nbsp;CP&nbsp;的?嗯,对这一块有了解吗?那如果我不用&nbsp;MQ&nbsp;的话,能不能实现这一套呢?就是我完全靠,比如说就只靠DB,我不需要&nbsp;MQ&nbsp;这样的一个中间件能不能实现同样的效果呢?(分布式事务)定时任务的频率你是怎么考虑的呢?如果是多节点呢?怎么考虑定时任务的同步协作那如果不想用分布式的引擎调度呢?(回答分布式锁)在设计这个分布式锁的时候会考虑到哪些东西?你这边对于定时任务的线程池的配置是怎么样的?如果数据量上来了之后,你定时任务超出了那个执行的时长的话,你是怎么考虑这个情况的呢Redis&nbsp;常用的一些数据类型有哪些呢?比如&nbsp;Redis&nbsp;里面我要存一些对象列表,然后这些对象里面有一些属性,然后我需要对这些对象列表做分页、做搜索、做筛选。有这三个需求,&nbsp;Redis&nbsp;能满足我,哪一个数据类型能满足我?或者是它这个数据类型的&nbsp;API&nbsp;能不能满足这一点?如果不满足的话,有没有其他的解决方案的你这一块提到了lua脚本的话,那如果这个&nbsp;Lua&nbsp;脚本很复杂的情况下,你是怎么做调试的边的话,我看你这边对&nbsp;Java&nbsp;并发编程这一块了解的比较多,你可以讲一下对于&nbsp;Java&nbsp;内存模型的一些理解吗?这边的理解我想我希更希望的是你能表达出这个内存模型,比如说我们都知道计算机硬件,对吧?都知道有CPU,对吧?有内存,多个线程会对这一块数据做进行操作的时候,为什么会有并发的问题?然后我们怎么从那个计算机的硬件角度上去理解它这样整体的一个过程?然后我们又是通过什么样的方式来解决这个问题的?然后我们再去了解这个过程,就是它是通过什么特性或者通过什么协议来保证的,然后或者是它这个协议其实际上原则上没有保证这一点,又是通过什么手段去解决这个问题的?你简单讲一下这边就是提及到两点,一个是&nbsp;synchronize&nbsp;的关键字,对吧?嗯,然后还有一个是&nbsp;retrend&nbsp;lock&nbsp;这一些对应的一些API,对吧?嗯,是的,然后你刚刚也提到&nbsp;volatile&nbsp;这一块的一个关键字,它只能保证内存的数据的一个可见性。那你这边,嗯,能深入聊一下,就是对于&nbsp;volatile&nbsp;这一块,它关键字实际上是怎么保证这个内存数据是可见的呢(答了字节码层面的标记变量)&nbsp;return&nbsp;lock&nbsp;这一块,它你知道它背后它是怎么支持的吗?跟那个&nbsp;synchronize&nbsp;的区别是什么呢你理解的&nbsp;return&nbsp;的log,它实际上维护的是一个状态数据,这一块状态数据是在哪里存储的呢?使用&nbsp;spring&nbsp;事务过程中的时候是怎么使用它的?然后使用它的时候需要注意哪些方面?这些方面会有哪些隐患?说到了使用事务的时候会有你这个就是一开始定好的分布式锁的力度和事务所谓的力度并不一致的情况。那我如果想问一下,我在一个事务方法里面,它可能存在一些异步的一些代码,我可能就是比如说有些代码会去考虑到并行的情况,我想去提速的话,我需会遇到哪些问题?或者说这个问题能不能解决掉?其实想问的一点就是你理解的这一块的事物的数据,如果我们是用声明式的注解去做的时候,这一块事物的数据它是怎么存储的?您知道了解这一块吗?(懵逼,面试官解释了一下:它其实是跟着线程变量来的)就是你在使用这个事务过程中的时候,假如我们这边不用&nbsp;d&nbsp;b&nbsp;的事物去做了,然后那我能不能就是相当于我自己去实现套你这一套的事物的一个提交回滚?然后我可能不是放,不是用&nbsp;DB&nbsp;的那个statement,&nbsp;prepare&nbsp;statement&nbsp;去做这一块事情的话,有没有其他的方式去做呢?比如说我把这个数据,就是这些事物的一些数据,因为我们这个东西肯定是存这个这些DB、&nbsp;MySQL&nbsp;或者&nbsp;PG&nbsp;它们引擎里面,它自己会去存这样的一个事务数据,对吧?它有对应的事务记录,那这个事物的记录你如果我把它迁移到,比如说就假设是一个开放性,等于,就比如说我放到&nbsp;Redis&nbsp;里面,那我在&nbsp;Redis&nbsp;里面应该怎么存?然后我怎么去用这一块的一个就是这一块的事务记录我是怎么交互的呢?比如说我想在&nbsp;Java&nbsp;里我不用&nbsp;Sprint&nbsp;事务了,我想自己实现一套AOP,然后这一块我该怎么去实现呢?(完全懵逼)我们实际上我们是有一些业务库和一些用专门用来做查询的一些库,我们是会分开的,就是写库和查库,我们实际上可能并不是统一到数据源,那我们是怎么保证这一块去做的呢?就是你会选择去做实时的时候,双写还是离线的方式去做,那离线去做的时候你又怎么去控制这一块的一个定时任务的频率的?对实时和离线这一块的选择会考虑哪方面的一些事情呢?就是为何选择实施?为什么要选择离线?你有想过这一点吗?(依旧懵逼)----------------------------------------------------------------------彻彻底底被拷打得体无完肤,最服气的一次,面试官人很好,答错时会给出一些解答和考察方向,打断的时候也很有礼貌
查看22道真题和解析 面试常问题系列
点赞 评论 收藏
分享
评论
点赞
1
分享

创作者周榜

更多
牛客网
牛客企业服务