Spring框架原理及面试题详解

目录

  • Spring概述
    • 什么是Spring?
    • 你们项目为什么使用Spring框架?
    • Spring的组成部分
  • 依赖注入(DI)
    • 什么是依赖注入?
    • 依赖注入的方式有哪些?
  • 控制反转(IoC)
    • 什么是IoC
    • 依赖倒置原则、IoC、DI、IoC容器的关系
  • Spring Bean
    • 什么是Bean?
    • BeanFactory
    • ApplicationContext
    • BeanFactory和ApplicationContext的区别
    • Bean的作用域
    • Bean的生命周期
    • @Component和@Bean的区别
  • Spring AOP
    • 谈谈你对AOP的理解
    • AOP的三种织入方式
    • AOP中的重要概念
    • Advice的种类
    • AOP的实现
    • Spring中的代理模式的实现
    • Spring AOP的应用
  • Spring 事务
    • Spring管理事务的方式
    • 事务三要素是什么?
    • Spring 事务中有哪几种的事务传播行为?
    • 说说Spring事务的隔离级别
    • 事务注解的本质是什么?

Spring概述

什么是Spring?

Spring 是一款开源的轻量级 Java 开发框架,旨在提高开发人员的开发效率以及系统的可维护性。

你们项目为什么使用Spring框架?

这个问题我们直接回答其优点即可。

  • 轻量:Spring是轻量级框架,基本版本大约只有2MB。
  • 控制反转(IoC):Spring通过控制反转实现了松散耦合,使其具备高可维护性和拓展性。
  • 面向切面编程(AOP):Spring支持面向切面编程,能够很方便地进行日志记录,数据库访问等。
  • 容器:Spring包含并负责管理应用中对象的生命周期和配置,无需手动干预。
  • 事务管理:Spring提供一个持续的事务管理接口,可以扩展到上至本地事务下至全局事务(JTA)。
  • 异常处理:Spring 提供方便的API把具体技术相关的异常(比如由JDBC,Hibernate or JDO抛出的)转化为一致的unchecked 异常。

Spring的组成部分

Spring组成

重要模块如下:

  • Spring Core:核心类库,提供IoC服务
  • Spring Context:提供框架式的Bean访问方式,以及企业级功能(JNDI、定时任务等);
  • Spring Aspects:提供AspectJ 集成支持
  • Spring AOP:提供面向切面编程实现
  • Spring DAO:对JDBC的抽象,简化了数据访问异常的处理;
  • Spring ORM:对现有的ORM框架的支持;
  • Spring Web:提供了基本的面向Web的综合特性,如:Web基本功能,WebSockets等。
  • Spring MVC:提供面向Web应用的Model-View-Controller实现。
  • Spring Test:集成测试模块。(Spring团队提倡测试驱动开发,即TDD)

依赖注入(DI)

什么是依赖注入?

依赖注入,即Dependency Inversion,全称是“依赖注入到容器”,通俗地讲,容器是某个对象,如果把某个类放入这个容器中,通过“依赖注入”可以解析出这个类的实例。

依赖注入的方式有哪些?

依赖注入的方式:

  • 构造器注入:将被依赖对象通过构造函数的参数注入给依赖对象,并且在初始化对象的时候注入。

    • 优点: 对象初始化完成后便可获得可使用的对象。

    • 缺点: 当需要注入的对象很多时,构造器参数列表会变得很长,不够灵活。若有多种注入方式,每种方式只需注入指定几个依赖,那么就需要提供多个重载的构造函数,使得代码难以维护。

  • setter方法注入:IoC Service Provider通过调用成员变量提供的setter函数将被依赖对象注入给依赖类。

    • 优点: 灵活,可以有选择性地注入需要的对象。

    • 缺点: 由于依赖对象初始化完成后尚未注入被依赖对象,故还不能使用。

  • 接口注入:依赖类必须要实现指定的接口,然后实现该接口中的一个函数,该函数就是用于依赖注入,该函数的参数就是要注入的对象。

    • 优点:接口注入中,接口的名字、函数的名字都不重要,只要保证函数的参数是要注入的对象类型即可。

    • 缺点: 侵入性太强,破坏系统的封装性,不建议使用。

还有一种注入方式是注解注入,即使用@Autowired进行注入。严格地说,它不是一种新的注入方式,注解注入本质上还是Setter注入或构造函数注入。

控制反转(IoC)

什么是IoC

IoC(Inversion of Control)控制反转,是Spring Core最核心的部分,它是一种设计思想,不是具体的技术实现。IoC的思想就是将原本需要手动创建的对象交由Spring框架进行管理,Spring会根据容器的配置文件去创建实例和管理实例之间的依赖关系,对象与对象之间松散耦合,有利于功能的复用。简单来说,IoC让对象创建不用去new了,可以由Spring自动生产,并配合Java的反射机制,根据 配置文件在运行的时候动态去创建对象及管理对象,并调用对象方法。

在没有IoC之前,项目中的一个类可能依赖于很多其他不同的类,如果我们需要实例化这个类,我们需要搞清楚所有依赖类的构造函数,并逐一实例化,在添加或者修改类结构时,项目几乎无法维护。有了IoC之后,只需要配置好,并在需要的地方进行引用,这大大增加了可维护性。

依赖倒置原则、IoC、DI、IoC容器的关系

依赖倒置原则(Dependence Inversion Principle)是程序要依赖于抽象接口,不要依赖于具体实现。简单的说就是要求对抽象进行编程,不要对实现进行编程,这样就降低了客户与实现模块间的耦合。

关系如图所示:

关系图

Spring Bean

什么是Bean?

Spring bean是指被Spring IoC容器管理的对象。Bean是由Spring IoC容器进行实例化、组装和管理的对象。它是在程序运行时,通过反射机制生成的。

在使用时,我们需要编写配置文件告诉IoC容器帮助我们管理哪些对象,可以通过XML文件、注解或Java配置类进行配置。

BeanFactory

BeanFactory是Spring框架最底层的接口,它提供了IoC的配置机制,包含Bean的各种定义,便于实例化Bean。在实例化时,BeanFactory可用于建立Bean之间的依赖关系。

ApplicationContext

ApplicationContext,即应用上下文,继承自BeanFactory接口,它是Spring的一个更高级容器,提供了更丰富的功能:

  • 国际化(MessageSource)
  • 访问资源,如URL和文件(ResourceLoader)
  • 载入多个(有继承关系)上下文 ,使得每一个上下文都专注于一个特定的层次,比如应用的web层
  • 消息发送、响应机制(ApplicationEventPublisher)
  • AOP

BeanFactory和ApplicationContext的区别

  • 加载方式不同:BeanFactory采用的是延迟加载(懒加载)的方式来注入Bean的。在启动的时候不会实例化Bean,只有在使用到某个Bean,即调用getBean()时才会对该Bean进行实例化。ApplicationContext在启动时就会将所有Bean进行实例化。不过当Bean配置了lazy-init=true时,ApplicationContext也会延迟对该Bean的加载。

  • BeanFactory是Spring框架的基础设施,面向Spring,而ApplicationContext面向使用Spring框架的开发者,相比BeanFactory提供了更多面向实际应用的功能,几乎所有场合都能够直接使用ApplicationContext,而不是使用底层的BeanFactory。

Bean的作用域

Bean的作用域:

  • singleton:唯一的bean实例,与单例模式相同,Spring中的bean默认都是单例的。
  • prototype:每次请求都会创建一个新的bean实例。
  • request:为每个http请求都创建一个新的bean,该bean仅在当前http request内有效
  • session:每次来自新session的请求都创建一个新的bean,该bean仅在当前session内有效。
  • global-session : 全局 session 作用域,仅仅在基于 portlet 的 web 应用中才有意义,Spring5 已经没有了。

Bean的生命周期

Bean生命周期:

创建bean

  1. 实例化Bean:对于BeanFactory容器,当客户向容器请求一个尚未初始化的bean时,或初始化bean的时候需要注入另一个尚未初始化的依赖时,容器就会调用createBean进行实例化。对于ApplicationContext容器,当容器启动结束后,通过获取BeanDefinition对象中的信息,实例化所有的bean。
  2. 设置对象属性(依赖注入):实例化后的对象被封装在BeanWrapper对象中,Spring根据BeanDefinition中的信息以及通过BeanWrapper提供的设置属性的接口完成依赖注入。
  3. 处理Aware接口:在这一步,Spring会检测对象是否实现了xxxAware接口,并将相关的xxxAware实例注入给Bean:
    • 如果这个Bean已经实现了BeanNameAware接口,则调用它所实现的setBeanName(String beanId)方法,此处传递的是Spring配置文件中Bean的id值。
    • 如果这个Bean已经实现了BeanFactoryAware接口,会调用它所实现的setBeanFactory()方法,传递的是BeanFactory自身。
    • 如果这个Bean已经实现了ApplicationContextAware接口,会调用setApplicationContext(ApplicationContext)方法,传递的是ApplicationContext
  4. BeanPostProcessor:如果想对Bean进行一些自定义的处理,可以让Bean实现BeanPostProcessor接口,这样做会调用postProcessBeforeInitialization(Object obj, String s)方法。
  5. InitializingBean与init-method:如果Bean在Spring配置文件中配置了init-method属性,则会自动调用其配置的初始化方法。
  6. 如果这个Bean实现了BeanPostProcessor接口,将会调用postProcessAfterInitialization(Object obj, String s)方法。另外,由于这个方法是在Bean初始化结束时调用的,所以可以被应用于内存或缓存技术。

以上几个步骤执行完成后,Bean就已经被正确创建了,之后就可以使用这个Bean了。

销毁bean

  1. DisposableBean:若Bean实现了DisposableBean这个接口,会调用其实现的destroy()方法。
  2. destroy-method:若配置了destroy-method属性,则会调用其配置的销毁方法。

最后我通过一张图来总结Bean的生命周期。

Bean的生命周期

@Component和@Bean的区别

  • @Component作用于类,而@Bean作用于方法
  • @Component会自动扫描路径来自动装配Bean到Spring IoC中,也可以通过@ComponentScan自定义扫描路径。@Bean表示方法产生一个由Spring管理的bean。
  • @Bean@Component 的自定义性更强,许多地方我们只能通过 @Bean 来对bean进行注册。比如当我们引用第三方库中的类需要装配到 Spring容器时,则只能通过 @Bean来实现。

Spring AOP

谈谈你对AOP的理解

AOP(Aspect-Oriented Programming)面向切面编程,它能够将那些与业务无关,却为业务模块所共同调用的通用功能(例如事务处理、日志管理、权限控制等)封装起来,减少系统中的重复代码,降低模块间的耦合度,提高项目的拓展性和可维护性。

它的基本思想如下:AOP注重关注点分离,即不同的问题交给不同的部分去解决,目的是为了将业务功能的关注点和通用化功能的关注点进行分离。这些通用化功能(例如:事务处理、日志管理...)的代码实现就是所谓的切面(Aspect)。业务功能代码和切面代码分开后,架构将变得高内聚,低耦合。最后,切面需要最终被合并到业务中,确保功能的完整性,这就是织入(Weave)。

AOP的三种织入方式

  • 编译时织入:需要特殊的Java编译器,如AspectJ。
  • 类加载时织入:需要特殊的Java编译器,如AspectJ何AspectWerkz。
  • 运行时织入:Spring AOP采用的方式,通过动态代理的方式,实现简单。

AOP中的重要概念

AOP,面向切面编程,即Aspect Oriented Programming。

  • Aspect:切面,类似于Java中的类声明,是对系统中横跨多个类的关注点进行模块化封装
  • Joinpoint:连接点,程序执行过程中的一个“点”,例如:方法的执行或异常的处理。在Spring AOP中,一个连接点总是代表一个方法的执行。
  • Pointcut:切入点,即一组连接点的集合;
  • Advice:通知,定义了将会织入到 Joinpoint 的具体逻辑,通过@Before、@After、@Around等注解来区别在JointPoint之前、之后等位置执行代码
  • Weaving:织入,织入指的是将Advice连接到Pointcut指定的Joinpoint处的过程
  • Interceptor:拦截器,是一种实现Advice(通知)的方式;
  • Target:目标对象,即符合切入点所指定的条件被织入Advice的对象。

Advice的种类

  • 前置通知(Before)
  • 后置通知(AfterReturning)
  • 异常通知(AfterThrowing)
  • 最终通知(After)
  • 环绕通知(Around)

AOP的实现

前置知识:设计模式-代理模式,可查看我的这篇文章:https://www.yeliheng.com/articles/dc912a27

在Java中,AOP有两种基于动态代理的实现方式,分别是JDK Proxy和cglib。具体使用哪种方式生成,由AopProxyFactory根据AdvisedSupport对象的配置来决定。默认策略是:如果目标类是接口,则采用JDK Proxy来实现,否则使用cglib来生成代理。

JDK Proxy的核心是InvocationHandler接口和Proxy类,通过Java内部的反射机制实现。

cglib是通过修改字节码来实现代理的,底层基于ASM框架(ASM是一种字节码操作框架)。它以继承的方式动态生成目标类的代理,因此,如果某个类被final关键字修饰,则无法使用cglib来做动态代理。

反射机制在生成类的过程中比较高效,ASM在生成类之后的执行过程中比较高效。

Spring中的代理模式的实现

  • 真实实现类的逻辑包含在getBean()方法中。
  • getBean方法返回的实际上是Proxy的实例。
  • Proxy实例是Spring采用JDK Proxy或cglib动态生成的。

注:getBean()方法用于查找或实例化容器中的Bean,这样是为什么Spring AOP只能作用于Spring容器中的Bean的原因。对于不是使用IoC容器管理的对象,Spring AOP是无能为力的。

Spring AOP的应用

请查看我的这篇文章:Spring AOP的最佳实践:在你的系统中记录用户的操作日志

Spring 事务

Spring管理事务的方式

  • 编程式事务管理:在代码中通过编码的方式实现事务,例如通过TransactionTemplateTransactionManager手动管理事务,这种方式不推荐在项目中使用,实现复杂,会使得代码难以维护。
  • 声明式事务管理:此方式意味着你可以将事务管理和业务代码分离,只需要使用注解或在Xml配置文件中进行配置。在项目中经常基于AOP实现,使用@Transactional进行事务管理。

事务三要素是什么?

数据源:表示具体的事务性资源,是事务的真正处理者,如MySQL等。

事务管理器:像一个大管家,从整体上管理事务的处理过程,如打开、提交、回滚等。

事务应用和属性配置:像一个标识符,表明哪些方法要参与事务,如何参与事务,以及一些相关属性如隔离级别、超时时间等。

Spring 事务中有哪几种的事务传播行为?

事务传播行为是为了解决业务层方法之间互相调用的事务问题。当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。

Spring事务定义了以下7种传播行为:

  1. PROPAGATION_REQUIRED:默认的Spring事务传播级别,若当前存在事务,则加入该事务,若不存在事务,则新建一个事务。

  2. PAOPAGATION_REQUIRE_NEW:若当前没有事务,则新建一个事务。若当前存在事务,则新建一个事务,新老事务相互独立。外部事务抛出异常回滚不会影响内部事务的正常提交。

  3. PROPAGATION_NESTED:如果当前存在事务,则嵌套在当前事务中执行。如果当前没有事务,则新建一个事务,类似于PAOPAGATION_REQUIRE_NEW

  4. PROPAGATION_SUPPORTS:支持当前事务,若当前不存在事务,以非事务的方式执行。

  5. PROPAGATION_NOT_SUPPORTED:以非事务的方式执行,若当前存在事务,则把当前事务挂起。

  6. PROPAGATION_MANDATORY:强制事务执行,若当前不存在事务,则抛出异常。

  7. PROPAGATION_NEVER:以非事务的方式执行,如果当前存在事务,则抛出异常。

说说Spring事务的隔离级别

  • ISOLATION_DEFAULT:使用数据库默认的隔离级别。MySQL 默认采用的 REPEATABLE_READ 隔离级别,Oracle默认采用的READ_COMMITTED隔离级别。

  • ISOLATION_READ_UNCOMMITTED:这是事务最低的隔离级别,它允许读取该事务尚未提交的数据变更。此隔离级别可能会产生脏读、幻读或不可重复读

  • ISOLATION_READ_COMMITTED:保证一个事务修改的数据提交后才能够被另外一个事务读取。此隔离级别可以阻止脏读,但是幻读不可重复读仍有可能发生。

  • ISOLATION_REPEATABLE_READ:它除了保证一个事务不能读取另外一个事务未提交的数据外,还保证对同一字段多次读取结果都是一致的,除非数据是被自身事务所修改。此隔离级别可以阻止脏读不可重复读,但幻读仍有可能发生。

  • ISOLATION_SERIALIZABLE:最高的隔离级别,完全服从 ACID 的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能,开销巨大,使用场景较少。

事务注解的本质是什么?

@Transactional这个注解仅仅是一些(和事务相关的)元数据,在运行时被事务基础设施读取消费,并使用这些元数据来配置bean的事务行为。 大致来说具有两方面功能:

  • 表明该方法要参与事务。
  • 配置相关属性来定制事务的参与方式和运行行为。

声明式事务主要得益于Spring AOP。使用一个事务拦截器,在方法调用的前后/周围进行事务性通知(Advice),来驱动事务完成。@Transactional注解既可以标注在类上,也可以标注在方法上。当在类上时,默认应用到类里的所有方法。如果此时方法上也标注了,则方法上的优先级高,另外注意方法必须是public的。

面试JavaAOPSpringBoot
2025 © Yeliheng的技术小站 版权所有