SSM框架

Spring基础

什么是Spring?Spring的优缺点?应用场景?

  • Spring 是一款开源的轻量级 Java 开发框架,旨在提升开发效率和系统的可维护性,其核心功能包括 依赖注入DI面向切面编程AOP

它的优点是:

  • 解耦。对象的创建和依赖关系交给 Spring IoC 容器管理,开发者不用自己去处理,很大程度上降低了系统的耦合度。
  • 支持 AOP。比如日志、事务这种通用功能,可以和业务逻辑分开写,增强了扩展性。
  • 生态完整。Spring 全家桶覆盖了数据访问、MVC、微服务等方方面面。

缺点的话也有:

  • 一个是配置比较多,虽然现在注解和 Spring Boot 缓解了不少;
  • 二是学习曲线相对陡,新人一开始会觉得东西太多;

至于应用场景,Spring 几乎覆盖了所有 JavaEE 开发场景。早些年常见的是 SSM 组合,现在更多是基于 Spring Boot 和 Spring Cloud 来做开发。

⭐️列举一些重要的Spring模块?

Spring 本身是一个大框架,它是由很多模块组成的。我简单列几个常见、核心的:

  • Spring Core:就是核心容器,提供 IoC 和依赖注入,这是 Spring 的基础。
  • Spring AOP:支持面向切面编程,用来处理日志、事务这种横切关注点。
  • Spring JDBC:对数据库操作做了封装,简化了我们和 JDBC 这些框架的整合。
  • Spring Web / Spring MVC:主要用来做 Web 开发,MVC 模式在企业应用里很常见。

如果再结合实际开发,现在我们用得更多的是 Spring BootSpring Cloud,它们是基于 Spring 的生态扩展,进一步简化了配置,支持微服务架构。

Spring IoC

⭐️⭐️谈谈你对Spring IoC(控制反转)的理解

  • IoC 就是控制反转,全称是 Inversion of Control,它其实是 Spring 的核心思想。
  • 传统写法里,我们需要自己在代码里去 new对象,手动控制它们的依赖关系;
  • 而有了 IoC之后,这个控制权就交给了 Spring 容器。容器会帮我们创建对象,并把依赖注入进去。
  • 这样可以减少类之间的耦合,提高系统的灵活性和可维护性。

什么是Spring Bean?

  • Bean 代指的就是那些被 IoC 容器所管理的对象。
  • Spring Bean 指的就是被 Spring IoC 容器管理的对象。只要交给容器去创建、初始化、销毁,它就是一个 Bean。

将一个类声明为 Bean 的注解有哪些?

在实际开发中,我们通常通过注解的方式把类交给 Spring 管理,常见的有:

  • @Component:最基础的注解,把类声明为 Bean;
  • @Service:通常用在业务逻辑层;
  • @Repository:用在数据访问层,比如 DAO;
  • @Controller:用在表现层,处理请求。

这几个其实都是 @Component 的衍生注解,只是语义更清晰,Spring 在不同层会有一些额外处理。

  • 方法上我们也可以用 @Bean 注解,一般是在配置类里显式声明一个 Bean。

⭐️@Component 和 @Bean 的区别是什么?

  • @Component 是放在 上的注解,Spring 会自动去扫描并把这个类交给容器管理。
  • 我们自己写的业务类、DAO 一般都用它或者它的衍生注解。
  • @Bean 是放在 方法 上的,方法返回的对象会被注册成 Bean。
  • @Bean 提供了更强的自定义性,比如当我们引用第三方库中的类需要装配到 Spring容器时,则只能通过 @Bean来实现。@Component 主要用于自动装配自定义类

注入 Bean 的注解有哪些?

常见的有几个:

  • @Autowired:最常用的注解,Spring 根据类型去自动装配依赖;
  • @Qualifier:通常和 @Autowired 搭配,用来指定注入哪个具体的 Bean;
  • @Resource:可以按名称或类型注入;
  • @Inject:和 @Autowired 类似,只不过是 Java 标准里的。

在项目里,最常见的组合还是 @Autowired + @Qualifier,这样既能自动装配,也能精确指定注入的 Bean。

img点击并拖拽以移动编辑

⭐️⭐️@Autowired 和 @Resource 的区别是什么?

这两个都能实现依赖注入,但有一些细节区别:

  • @Autowired 是 Spring 提供的注解,默认是按 类型 注入。如果同一个类型有多个 Bean,就需要配合 @Qualifier 来指定。它还支持 required=false,可以控制依赖是否必须注入。
  • @Resource 是 JDK 标准注解,Spring 也支持。它默认是按照 名称 来注入,如果没找到同名的 Bean,再按类型匹配。

img点击并拖拽以移动编辑

⭐️⭐️注入 Bean(依赖注入) 的方式有哪些?

常见有三种:

  1. 构造方法注入:通过构造函数把依赖传进来。
    1. 如果依赖关系是必须的且不可变的,更倾向于用 构造函数注入。
    2. 因为这样能保证对象在创建的时候,就一定具备它运行所需要的依赖,依赖关系也更清晰。
    3. 比如 Car 必须要有 Engine,没有发动机车就跑不起来,这种情况我就会用构造函数注入。
  2. Setter 方法注入:通过 set 方法把依赖传进去。
    1. 但如果依赖是可选的或者需要灵活控制的,更倾向于用 Setter 注入。它比较灵活,可以在对象创建之后再决定要不要注入。
    2. 比如 Car 里的 AirConditioner,有更好,没有车也能跑,这时候用 Setter 注入更合适。
  3. 字段注入:直接在字段上加注解,比如 @Autowired,这个最常见,写起来简单,但缺点是耦合度高。

依赖注入 DI (Dependency Injection)是Spring框架的核心设计模式,是指程序运行过程中,如果需要调用另一个对象协助时,无须在代码中创建被调用者,而是依赖于外部的注入

img点击并拖拽以移动编辑

IoC和DI的区别

  • IoC 是 控制反转,它更像是一种思想,指的是把对象的创建和依赖管理的控制权,从我们程序员手里“反转”给了容器。
  • DI 是 依赖注入,它是实现 IoC 的一种具体方式。也就是说,容器通过给对象 注入依赖 来实现控制反转。

⭐️⭐️⭐️Bean 的作用域有哪些?如何配置?

Spring中Bean的作用域决定了 Bean 实例的生命周期和创建策略,我通常把它分成两大类来记:一类是核心作用域,另一类是Web环境下的作用域。”

“首先,核心作用域主要有两个:”

  1. singleton(单例作用域)。这也是Spring默认的作用域。
  2. 整个 Spring 容器中只会有一个 Bean 实例。不管在多少个地方注入这个 Bean,拿到的都是同一个对象。
  3. 生命周期和 Spring 容器相同,容器启动时创建,容器销毁时销毁。
  4. 实际开发中,像 Service、Dao 这些业务组件基本都是单例的,因为单例既能节省内存,又能提高性能。
  5. prototype(原型或多例作用域)
  6. 这个和单例正好相反,它指的是我们每次从容器里获取这个Bean的时候,Spring都会为我们创建一个新的实例

然后,在Web开发环境下,还有三个非常常用的作用域:

  1. request(请求作用域)
    1. 表示在 Web 应用中,每个 HTTP 请求都会创建一个新的 Bean 实例,请求结束后 Bean 就被销毁。
  2. session(会话作用域)
  3. 表示在 Web 应用中,每个 HTTP 会话都会创建一个新的 Bean 实例,会话结束后 Bean 被销毁。
  4. 典型的使用场景是购物车、用户登录状态这些需要在整个会话期间保持的信息。
  5. application(应用作用域)
  6. 它的生命周期是整个Web应用的生命周期。
  7. 它有点像一个‘Web全局的单例’,在整个应用运行期间只会被创建一次。

“至于如何配置,现在在Spring Boot项目里非常简单,主要就是使用 @Scope 注解。”

img点击并拖拽以移动编辑

⭐️⭐️⭐️Spring框架中的单例Bean是线程安全的吗?

  • Spring 容器里单例 Bean 的确只会创建一个实例,但它并不会保证线程安全。
  • 因为 Spring 的单例只是作用域上的单例——也就是整个容器里只有一份实例对象,但如果这个 Bean 本身有可变的成员变量,被多个线程同时修改,就可能出现线程安全问题

所以,单例 Bean 是否线程安全,取决于它的设计:

  • 如果 Bean 是无状态的(比如只提供服务方法,不保存数据),那它就是线程安全的。
  • 如果 Bean 是有状态的(比如里面保存了用户请求的中间数据),那在并发场景下就不安全,需要考虑加锁、ThreadLocal 或者改成原型作用域来避免共享。

实际开发中,我们通常把 Service、Dao 这些 Bean 设计成无状态的,这样单例就不会有线程安全问题。

img点击并拖拽以移动编辑

img点击并拖拽以移动编辑

img点击并拖拽以移动编辑

⭐️⭐️⭐️Spring的Bean的生命周期?

  • Spring Bean的生命周期有实例化、属性赋值、初始化、使用、销毁这几个阶段。

具体步骤为:

首先是实例化阶段:

  • Spring 容器通过反射调用 Bean 的无参构造器,创建出Bean 的原始对象
  • 这时候只是分配了内存,Bean 里的成员变量、依赖的其他 Bean 都还没赋值。

接着是属性填充阶段:

  • 容器会根据配置(比如 @Autowired注解,或者 XML 里的),给上一步创建的对象注入依赖的 Bean 和具体属性值。

然后是最核心的初始化阶段:

  • 第一步,容器会检测 Bean 是否实现了 Aware 系列接口(比如 BeanNameAware、ApplicationContextAware),如果实现了就注入对应的容器资源,Bean 的名字、BeanFactory 或 ApplicationContext 等上下文信息。

  • 第二步执行 BeanPostProcessor 的前置处理方法

    • BeanPostProcessor 是 Spring 的 “Bean 后置处理器”,所有 Bean 初始化前都会都会走这一步,可用于统一修改 Bean 属性(如给所有 Bean 加日志拦截)。
  • 第三步执行开发者自定义的初始化逻辑
    有几种方式:

    • 使用注解@PostConstruct(推荐)
    • 要么实现 InitializingBean 接口重写 afterPropertiesSet 方法。
    • 要么通过 @Bean (initMethod=”xxx”) 指定初始化方法。
  • 第四步执行 BeanPostProcessor 的后置处理方法
    Spring AOP 生成代理对象就是在这一步完成的。到这里,Bean 就完全初始化好了,能正常使用了。

最后是销毁阶段

  • 只有单例 Bean(Spring 默认)会走这一步,原型 Bean 需要开发者自己管理。
  • 当容器关闭时,会先执行开发者定义的销毁逻辑
    • 要么实现 DisposableBean 接口重写 destroy 方法,
    • 要么通过 @Bean (destroyMethod=”xxx”) 指定销毁方法

简单说,整个生命周期就是从 “创建对象” 到 “注入依赖”,再到 “完善状态”,最后 “释放资源” 的过程,核心是通过接口和配置,让开发者能灵活控制 Bean 的关键节点。

img点击并拖拽以移动编辑

img点击并拖拽以移动编辑

BeanDefinition的作用?其中的懒加载是什么?

  • BeanDefinition 其实就是 Spring 里描述 Bean 的元信息的,里面包含了创建 Bean 需要的所有信息,像 Bean 的类型、作用域,还有是否懒加载这些。
  • 而懒加载是它里面的一个配置,意思是如果设置了懒加载,这个 Bean 就不会在容器启动时马上创建,而是等到第一次被用到的时候才会实例化。

Spring AOP

⭐️⭐️什么是AOP?有什么用?

  • AOP 就是面向切面编程。简单说,就是把那些和业务无关但很多地方都要用的逻辑,比如日志记录、事务管理这些,抽出来做成一个 “切面” 模块
  • 这样做的话,业务代码里就不用重复写这些东西了,既减少了重复代码,也降低了模块之间的依赖,后续改起来也方便。像我们平时记录接口调用日志、公共字段填充、管理数据库事务,很多时候都是用 AOP 来实现的。

苍穹公共字段处理

image-20250915150345890

你们项目中有没有使用到AOP?

👉 第一个是接口调用日志

  • 在没有 AOP 的时候,比如一个用户登录接口或者订单提交接口,我都得在方法里手动写 log.info("请求参数:{}", param),还要记录调用时间、返回结果,代码里到处都是日志逻辑,业务很乱。
  • 但是有了 AOP,我就能写一个日志切面,拦截所有 Controller 的方法,在方法执行前自动获取请求参数、接口名,方法执行后再记录返回结果和耗时。这样日志逻辑完全跟业务解耦,代码也简洁很多。

👉 第二个是事务管理

  • 像下单这种场景,要先扣库存,再创建订单,这两个操作必须同时成功或者同时失败。没用 AOP 的时候,就得手动写 beginTransactioncommitrollback 这些事务管理代码,而且每个地方都要写一遍。
  • 现在只要在方法上加一个 @Transactional 注解就行,Spring 的 AOP 会在方法执行前自动开启事务,成功就提交,抛异常就回滚。这样我们就可以只专注写业务逻辑,不用管事务的细节。

所以我觉得 AOP 的好处就是,把这些 “跟业务没关系但又必须做” 的事,集中到一个地方管理,既减少重复代码,也让业务代码更干净,后续维护也方便。

img点击并拖拽以移动编辑

AOP 常见的通知类型有哪些?

  • Before(前置通知):目标对象的方法调用之前触发,比如接口调用前的权限校验;
  • After (后置通知):目标对象的方法调用之后触发(不管成功或失败),比如方法结束后的资源清理;
  • AfterReturning(返回通知):目标对象的方法调用完成且有返回值才触发,比如记录接口的返回数据;
  • AfterThrowing(异常通知):目标对象的方法抛异常时触发,比如捕获异常并记录错误日志;
  • Around (环绕通知):能包裹整个目标方法,前后都能操作,还能控制方法是否执行,比如统计接口耗时。

img点击并拖拽以移动编辑

多个切面的执行顺序如何控制?

  • 一种是用 @Order 注解,给切面类加上这个注解,值越小数字越小,切面优先级越高,执行越早。比如 @Order (1) 就比 @Order (2) 先执行。
  • 另一种是让切面类实现 Ordered 接口,重写 getOrder 方法返回数字,原理和 @Order 一样,数字小的先执行。
  • 如果都没设置,默认按类名的字母顺序来,不过实际开发里一般都会显式指定顺序,避免混乱。
img

事务

Spring中的事务是如何实现的?

  • Spring实现事务的本质是利用AOP完成的。
  • 它对方法前后进行拦截,在执行方法前开启事务,在执行完目标方法后根据执行情况提交或回滚事务。

⭐️⭐️Spring 管理事务的方式有几种?

  • 一种是声明式事务,这是实际开发里用得最多的。就是通过注解或者 XML 配置来定义事务,不用写代码手动控制。比如在方法上加 @Transactional 注解,Spring 就会自动管理事务的开启、提交和回滚,特别方便。
  • 另一种是编程式事务,比如用 TransactionTemplate或者直接操作PlatformTransactionManager接口手动管理事务的开启、提交和回滚,适用于更复杂的事务控制场景。

现在主流都是用声明式事务,尤其是注解方式,简洁又清晰。

img点击并拖拽以移动编辑

@Transactional注解有什么用?项目中是如何使用的?

  • @Transactional 的核心作用是让 Spring 自动管理事务,我们就不用自己写开启、提交、回滚的代码了,底层其实是通过 AOP 来实现的。

  • 它能保证一组数据库操作要么都成功,要么都失败,避免数据不一致。

  • 它有几个常用属性,比如
    propagation:控制事务的传播行为。

    isolation:设置事务的隔离级别。

    rollbackFor / noRollbackFor:指定哪些异常要回滚或不回滚。
    readOnly ,默认值为 false。设为 true 时表示只读事务,只读事务不允许执行写操作,以提高性能。

  • 我们项目里主要在 Service 层的业务方法上用。
    • 比如之前做过一个付费阅读的优惠券订单功能,创建订单时先扣减优惠券库存,再生成订单记录,这两步必须同时成功或失败。不能出现库存扣了但订单没创建成功的情况。
    • 所以就在 createOrder 方法上加了 @Transactional,还指定了 rollbackFor = Exception.class,确保任何异常都能触发回滚,避免出现库存扣了但订单没生成的问题

img点击并拖拽以移动编辑

@Transactional(rollbackFor = Exception.class)注解了解吗?

  • rollbackFor:用来指定哪些异常类型发生时事务需要回滚。
  • 默认情况下,Spring 只会回滚 RuntimeException (运行时异常) 或者 Error 时才会回滚事务,而不会回滚像 IOException 这种 Checked Exception(受检查异常)。
  • 加上 rollbackFor = Exception.class 后,就表示不管是检查异常还是非检查异常,只要方法抛出了 Exception 及其子类的异常,事务都会回滚。
  • 项目里常用这个来覆盖默认行为,比如在一些涉及写操作的方法里,确保任何异常情况下都能回滚,避免数据不一致。

Spring事务传播行为有哪些?

Spring的事务传播行为,简单来说,就是定义了当一个已经有事务的方法(我们称之为外部方法),去调用另一个也可能需要事务的方法(内部方法)时,这个内部方法的事务应该如何运行的规则。

  • REQUIRED(默认):如果当前有事务,就加入;没有就新建一个。比如调用有事务的方法 A,再调用 B,B 会用 A 的事务。
  • REQUIRES_NEW:不管当前有没有事务,都新建一个。比如方法 A 有事务,调用 B(用这个),A 和 B 的事务独立,B 回滚不影响 A。
  • MANDATORY:必须在已有事务里运行,否则报错。适合强制依赖上级事务的场景。
  • SUPPORTS:有事务就加入,没有就以非事务方式运行。一般用在查询方法。
  • NEVER:必须在非事务环境运行,有事务就报错。

img

Spring 事务中的隔离级别有哪些?

  • Spring事务的隔离级别,其实就是直接对应了数据库ACID特性里‘I’(Isolation)的实现标准。它主要是为了解决在多个事务并发执行时,可能出现的脏读、不可重复读和幻读这三大问题。共 5 种
  • 为了解决这些问题,SQL标准定义了四种隔离级别,Spring也完全支持,Spring 通过 @Transactional 注解中的 isolation 属性来设置事务的隔离级别。级别从低到高分别是:
    • 读未提交(READ UNCOMMITTED):
      • 这是最低的级别,基本上什么问题都解决不了,会产生脏读、不可重复读和幻读。
      • 因为它允许一个事务读取另一个事务还没提交的数据。性能最好,但数据一致性最差,实际项目中基本不用。
    • 读已提交(READ COMMITTED):
      • 这个级别解决了脏读。它保证一个事务只能读到其他事务已经提交的数据。
      • 这是大多数数据库,比如Oracle和PostgreSQL的默认隔离级别。
      • 但它无法解决不可重复读和幻读。
    • 可重复读(REPEATABLE READ):
      • 这个级别解决了脏读和不可重复读
      • 事务在执行期间会锁定查询的数据行,确保该数据在事务完成前不会被其他事务修改。
      • 这是MySQL InnoDB引擎的默认隔离级别
      • 它通过MVCC(多版本并发控制)机制,理论上解决了不可重复读,但在某些情况下仍然可能出现幻读问题。
    • 串行化(SERIALIZABLE):
      • 这是最高的隔离级别,通过强制事务串行执行(一个接一个排队),解决了所有问题(脏读、不可重复读和幻读)。
      • 数据绝对一致,但因为并发能力急剧下降,性能开销最大,所以也只有在对数据一致性要求极高的特殊场景下才会使用。
  • 除了这四个,Spring还提供了一个特殊的隔离级别:
    • DEFAULT
      • 这个是@Transactional注解的默认值。它本身不指定任何隔离级别,而是使用底层数据库自己配置的默认隔离级别
      • 比如,如果我的数据库是MySQL,那么DEFAULT就等同于REPEATABLE_READ;如果是Oracle,就等同于READ_COMMITTED

img

⭐️⭐️Spring中事务失效的场景有哪些?

  • 方法内部调用:就是一个在类里面,一个没有加 @Transactional 注解的A方法,去调用了同一个类里面加了 @Transactional 注解的B方法(this.B())。这种情况下,B方法的事务是不会生效的。
    • 原因还是和动态代理有关。当我们调用A方法时,我们是通过普通的 this 引用来调用的,而不是通过Spring生成的代理对象。所以,这个调用就不会被AOP的事务切面拦截到,事务自然也就无法开启
  • 异常类型不匹配:默认情况下,Spring 只会在运行时异常(RuntimeException)和 Error 时回滚,如果抛的是受检异常(比如 cheaked Exception),事务不会回滚,除非显式配置 rollbackFor
    • 解决:配置rollbackFor属性,@Transactional(rollbackFor=Exception.class)
  • 方法不是 public:Spring 的事务是基于代理的,只能对 public 方法生效,如果加了@Transactional 注解的方法是 private 的,那么事务就会失效。
    • 动态代理的原理是为目标对象生成一个代理对象,在代理对象中增强方法。但代理对象是无法继承和重写 private 方法的,所以也就无法为私有方法添加事务管理的逻辑。
  • 数据库本身不支持事务:比如对 MySQL 用了 MyISAM 引擎,它不支持事务,再怎么加注解都没用。
  • 事务传播配置不当:比如,一个方法A调用了另一个方法B,B方法的事务传播行为被设置成了 PROPAGATION_NOT_SUPPORTED(不支持事务)或者 PROPAGATION_NEVER(从不以事务方式运行)。
    • 那么当A的事务传播到B时,B方法就会以非事务的方式运行,或者直接抛出异常,导致事务失效。

img

img点击并拖拽以移动

Spring的循环依赖

⭐️⭐️为什么会发生循环引用?Spring中的循环引用(依赖)如何解决?

  • 简单来说,循环依赖就是两个或多个Bean之间相互依赖,形成了一个闭环。最简单的情况就是,A对象依赖了B对象,同时B对象又依赖了A对象。
  • 当Spring容器在创建这些Bean的时候,就会遇到一个难题:在创建A的时候,发现它需要B,于是Spring就跑去创建B;但是在创建B的时候,又发现它需要A,可这个时候A自己也还没创建完。如果处理不好,程序就会陷入死循环,最终导致栈溢出。

对于这个问题,Spring框架提供了一套很巧妙的机制来解决,但这套机制只对单例(Singleton)作用域的、并且是基于setter或者字段注入的Bean有效。

Spring解决循环依赖的核心,并通过==三级缓存==解决大部分循环依赖问题:

Spring在创建单例Bean的时候,不是等这个Bean完全创建好之后才把它对外暴露,而是遵循一个“提前暴露”的原则。它内部有三个Map结构的缓存:

  • singletonObjects(一级缓存):存放完全初始化好的单例 Bean。
  • earlySingletonObjects(二级缓存):存放还没初始化完成,但已经实例化的 Bean(早期对象)。
  • singletonFactories(三级缓存):存放创建 Bean 的工厂对象(ObjectFactory),主要用于解决 AOP 代理的场景。

img

解决循环依赖的具体流程清楚吗?⭐️⭐️⭐️

整个过程大概是这样:

  1. Spring创建A,先实例化(调用构造函数),但还没注入属性。然后把一个能够获取A的“工厂”放到三级缓存里。
  2. 接着,Spring准备给A注入属性,发现A依赖B,于是就去创建B。
  3. Spring创建B,也先实例化。然后发现B又依赖A。
  4. 这时,Spring就去缓存里找A。它会依次检查一级、二级缓存,最后在三级缓存里找到了创建A的工厂。
  5. 通过这个工厂,Spring获取到了一个“半成品”的A(虽然A的属性还没注入,但对象引用已经有了),并把这个半成品的A放到二级缓存里,然后从三级缓存里删除A的工厂。
  6. B拿到了A的引用,顺利完成了自己的创建和初始化,然后被放入一级缓存。
  7. B创建好了,Spring就可以回来继续完成第一步里A的创建,把B注入给A,然后A也顺利完成了初始化,最后被放入一级缓存。

通过这种方式,Spring就巧妙地打破了循环。

但是,就像我前面提到的,这个机制是有局限性的。它无法解决构造函数注入(Constructor Injection)的循环依赖。因为构造函数注入要求在对象实例化的时候,它==所依赖的Bean就必须已经准备好了==,这无法满足“提前暴露”的条件,容器根本拿不到半成品对象,所以Spring遇到这种情况会直接抛出异常。

同样,对于原型(Prototype)作用域的Bean,Spring也不会去解决它们的循环依赖问题,因为原型Bean每次都是新创建的,没有缓存的必要。

以上就是我对Spring循环依赖的理解。

img

img

构造方法出现了循环依赖怎么解决?

  • 构造函数是bean生命周期中最先执行的,Spring框架无法解决构造方法的循环依赖问题
  • 代码层面重构:把构造器里的依赖改成 Setter 注入,或者字段注入,这样 Spring 就能通过提前暴露 Bean 来解决。
  • **使用 @Lazy**:在其中一个依赖上加上懒加载,让 Spring 延迟注入,这样可以绕开构造器阶段的死锁。
  • 总结一下就是:构造器循环依赖 Spring 无法自动处理,一般要通过重构代码或懒加载来解决

img

SpringMVC

Spring, SpringMVC, SpringBoot的区别

  • Spring 是提供了核心能力和零件的基础框架。它提供了整个Java后端开发最核心的功能,比如 **IoC(控制反转)*和*AOP(面向切面编程)。
  • SpringMVC 是Spring框架中用于Web开发的一个模块
  • Spring Boot 是一个简化了Spring和Spring MVC开发的脚手架和自动化工具,让我们可以快速搭建独立运行的、生产级别的Spring应用。

SpringMVC包含哪些组件?

img

⭐️⭐️⭐️SpringMVC(收到请求时)的执行流程?(工作原理)

Spring MVC 的执行流程是 Spring 框架中重要概念,它展示了请求是如何从客户端到达服务器,并被处理的。

  • Spring MVC 的整个工作流程,核心就在于一个前端控制器,叫做 DispatcherServlet。我们可以把它想象成一个公司的“总调度”或者“前台”,所有的外部请求都会先到达它这里,然后由它来统一协调处理。

它的执行流程大概是下面这样的:

  • 请求进入:所有HTTP请求先到达DispatcherServlet,它是整个流程的入口和总调度。
  • 找处理器:DispatcherServlet通过HandlerMapping,根据请求URL找到对应的Controller方法(含拦截器信息)。
  • 执行处理器:DispatcherServlet通过HandlerAdapter,统一适配并调用找到的Controller方法,执行业务逻辑。
  • 处理返回:Controller执行后返回结果(可能是ModelAndView,也可能是数据对象)。
    • - 若返回ModelAndView:DispatcherServlet调用ViewResolver解析视图名,找到具体视图并渲染模型数据。
    • - 若加@ResponseBody:直接通过消息转换器将数据转为JSON等格式,跳过视图解析,直接响应。
  • 完成响应:最终结果返回给客户端。

整个过程通过DispatcherServlet协调HandlerMapping、HandlerAdapter、ViewResolver等组件,实现请求处理与响应的统一调度。

img

Spring Boot

Spring Boot 支持哪些内嵌 Web 容器?默认用的是哪个?

  • Spring Boot 支持三种内嵌 Web 容器:Tomcat、Jetty 和 Undertow。
  • 默认情况下,Spring Boot 使用 Tomcat 作为内嵌服务器,但你也可以根据项目需求选择其他容器。
    例如,在高并发场景下,Tomcat 可能不如 Undertow 更适合表现。

什么是Spring Boot Start?

  • Spring Boot 的 Starter 本质上就是一组依赖的封装,帮我们快速引入某个功能模块。

常见的 Spring Boot Starter 包括:

  • 比如我想做 Web 项目,以前要手动引入 SpringMVC、Jackson、Tomcat 相关的依赖,还得自己配置。但现在只要加一个spring-boot-starter-web自动配置 Spring MVC 和内嵌的 Tomcat 服务器。
  • spring-boot-starter-test:用于单元测试和集成测试,集成了 JUnit 和其他测试工具。
  • spring-boot-starter-redis:用于集成 Redis,简化缓存、消息队列等功能的配置和使用

⭐️⭐️⭐️Springboot自动配置原理?

Spring Boot的自动配置原理基于启动类上加的**@SpringBootApplication**注解,它封装了

  • @SpringBootConfiguration:声明当前类是一个配置类
  • @ComponentScan:用来组件扫描,扫描启动类所在包及其子包下所有被@Component (@Repository,@Service,@Controller)注解声明的类。
  • @EnableAutoConfiguration:它就是开启自动配置的总开关。

它的工作流程可以概括为:

  • 首先,它通过 @Import 注解导入了一个 AutoConfigurationImportSelector 选择器。
  • 这个选择器会去扫描我们项目所有依赖路径下的 META-INF/spring.factories 文件。
  • spring.factories 文件里,定义了大量可能需要用到的自动配置类。
  • 最后,SpringBoot会把这些配置类加载进来,但并不是全部都让它们生效。它会使用一系列的 @Conditional 条件注解来逐一判断。
  • 只有满足了特定条件的配置类,才会真正地被解析,将其中的Bean注入到Spring容器里。

所以,整个过程就是:通过@EnableAutoConfiguration加载spring.factories中定义的大量备选配置,再通过@Conditional注解按需筛选,最终把我们需要的Bean自动装配好。

img

注解

  • 通过注解消除臃肿代码img

  • @RestController=@Controller+@ResponseBody,表示这是个控制器 bean,并且是将函数的返回值直接填入 HTTP 响应体中,是 REST 风格的控制器。

  • @Scope:声明 Spring Bean 的作用域

  • @Configuration :用于标记一个配置类,@Configuration 是 @Component 的特化注解,表示该类专门用于配置 Spring 容器,而不仅仅是一个普通的组件类。

  • 处理常见的 HTTP 请求类型: GET查 POST增 PUT改 DELETE删
    @GetMapping(“users”)=@RequestMapping(value=”/users”,method=RequestMethod.GET)
    @PostMapping(“users”)=@RequestMapping(value=”/users”,method=RequestMethod.POST)
    @PutMapping(“/users/{userId}”)=@RequestMapping(value=”/users/{userId}”,method=RequestMethod.PUT)
    @DeleteMapping(“/users/{userId}”)=@RequestMapping(value=”/users/{userId}”,method=RequestMethod.DELETE)

  • @PathVariable用于获取路径参数,**@RequestParam**用于获取查询参数
    如果我们请求的 url 是:/klasses/123456/teachers?type=web
    那么我们服务获取到的数据就是:klassId=123456,type=web

    1
    2
    3
    4
    5
    6
    @GetMapping("/klasses/{klassId}/teachers")
    public List<Teacher> getKlassRelatedTeachers(
    @PathVariable("klassId") Long klassId,
    @RequestParam(value = "type", required = false) String type ) {
    ...
    }

全局处理 Controller 层异常

  • 在 Spring 中处理 Controller 层异常,核心方案是用全局异常处理器
  • Spring为此提供了一套非常优雅的机制,主要就是利用 @RestControllerAdvice@ExceptionHandler 这两个注解来配合实现。具体实现和优势可以这样理解:
    • 用**@RestControllerAdvice**标记一个类作为全局异常处理类(它是@ControllerAdvice+@ResponseBody的组合,确保返回 JSON 格式)。
    • 在该类中,用**@ExceptionHandler(异常类型.class)**注解方法,指定该方法处理哪种异常(如NullPointerException、自定义BusinessException等)。
  • 这种方式能统一返回格式,避免重复编写 try-catch,提高系统的健壮性和可维护性。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// GlobalExceptionHandler.java
@RestControllerAdvice
public class GlobalExceptionHandler {
// 处理所有类型的异常
@ExceptionHandler(value = Exception.class)
public ResponseEntity<String> handleException(Exception e) {
return new ResponseEntity<>("An error occurred: " + e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
}

// 处理自定义业务异常
@ExceptionHandler(BusinessException.class)
public Result handleBusinessException(BusinessException e) {
return Result.fail(e.getCode(), e.getMessage());
}

// 处理系统异常(如空指针)
@ExceptionHandler(NullPointerException.class)
public Result handleSystemException(Exception e) {
log.error("系统异常", e); // 记录日志
return Result.fail(500, "系统繁忙,请稍后再试");
}
}

⭐️Spring 的常见注解有哪些?

Spring最核心的就是IoC和AOP,所以它的注解也主要围绕着Bean的管理切面展开。

  • 声明Bean的注解
    • @Component: 这是一个通用的最基础的组件注解,表明这个类需要被Spring扫描管理
    • @Service: 通常用在业务逻辑层(Service层)。
    • @Repository: 通常用在数据访问层(DAO层)。
    • @Controller: 通常用在控制层(Web层),下面会细说。
    • @Configuration: 声明这是一个Java配置类,可以用来替代以前的XML配置。
  • 注入Bean的注解
    • @Autowired: 这是我们用得最多的,Spring会按类型自动把Bean注入进来
    • @Resource: 这是Java EE规范的注解,默认按名称(ByName)注入。
    • @Qualifier: 当同一个类型有多个Bean时,配合@Autowired使用,通过名称来指定到底要注入哪一个。
  • 配置相关的注解
    • @Bean: 用在@Configuration类的方法上,表示这个方法的返回值是一个需要被Spring管理的Bean。
    • @Scope: 用来定义Bean的作用域,最常见的是singleton(单例)和prototype(原型)。
    • @Value: 用来从配置文件(比如application.properties)中读取值并注入到类的属性里

⭐️SpringMVC常见的注解有哪些?

  • 路由和请求处理
    • @RestController: 这是一个组合注解,相当于@Controller + @ResponseBody。现在前后端分离开发中,我们基本上都用这个注解,表示这个类所有方法的返回值都会被直接序列化成JSON格式。
    • @RequestMapping: 这是最核心的路由注解,用来映射URL路径到具体的处理方法上。
    • @GetMapping, @PostMapping, @PutMapping, @DeleteMapping: 这些是@RequestMapping的细化版本,让代码意图更清晰。
  • 参数绑定
    • @RequestParam: 用来获取URL查询参数(?key=value)。
    • @PathVariable: 用来获取路径中的参数(比如 /users/{id} 中的 id)。
    • @RequestBody: 用来接收POST或PUT请求中的JSON数据,并自动反序列化成Java对象。
  • 响应处理
    • @ResponseBody: 标记在方法上,表示方法的返回值直接作为HTTP响应体,通常是JSON。
    • @ExceptionHandler: 用于全局异常处理。

⭐️Springboot常见注解有哪些?

Spring Boot的核心是简化配置快速开发,所以它的注解主要是围绕自动配置启动

  • 核心注解
    • @SpringBootApplication: 这是Spring Boot应用的“启动器”注解,它是一个组合注解,包含了@SpringBootConfiguration, @EnableAutoConfiguration@ComponentScan。基本上所有Spring Boot项目都有它。
  • 自动配置与条件装配
    • @EnableAutoConfiguration: 明确地开启自动配置,虽然@SpringBootApplication已经包含了它。
    • @ConditionalOnClass, @ConditionalOnMissingBean 等:这些是条件注解,是实现自动配置的关键,用来判断某个Bean是否需要被装配。
  • 配置属性绑定
    • @ConfigurationProperties: 这是一个非常强大的注解,可以把配置文件中一批相关的属性,自动绑定到一个Java类的字段上,比一个个用@Value要方便得多。

总的来说,

  • Spring的注解负责Bean的生老病死
  • Spring MVC的注解负责接收请求和返回响应
  • 而Spring Boot的注解则负责简化和自动化整个配置流程

Mybatis

#{} 和 ${} 的区别是什么?⭐️⭐️

  • #{}:用于防止 SQL 注入,它会将参数值作为参数传递给 SQL 语句,MyBatis 会自动进行类型处理并转义输入值。
  • ${}:直接将参数值拼接到 SQL 语句中,不做任何转义或参数化处理,可能会导致 SQL 注入的风险。

模糊查询like语句该怎么写?

  • name like ‘%${name}%’ SQL注入风险 name like ‘%#{name}%’ ?不允许在%%里

img点击并拖拽以移动编辑

  • MYSQL拼接函数 CONCAT(‘%’, 关键字 , ‘%’)
  • CONCAT(‘%’, #{name}, ‘%’)

实体类属性名和表中字段名不一样 ,怎么办?

  • 起别名
    img
  • 开启驼峰命名
    img
  • 手动结果映射:使用 @Results 注解手动指定映射关系:
1
2
3
4
5
6
@Select("SELECT user_name, age FROM users WHERE user_name = #{userName}")
@Results({
@Result(property = "userName", column = "user_name"),
@Result(property = "age", column = "age")
})
User findByUserName(@Param("userName") String userName);

MyBatis执行流程?

MyBatis的执行流程如下:

  1. 读取MyBatis配置文件mybatis-config.xml。
  2. 构造会话工厂SqlSessionFactory。
  3. 会话工厂创建对象SqlSession。
  4. 操作数据库的接口,Executor执行器。
  5. Executor执行方法中的MappedStatement参数。
  6. 输入参数映射。
  7. 输出结果映射。

img

img

Mybatis是否支持延迟加载?

  • MyBatis支持延迟加载,即在需要用到数据时才加载。
  • 可以通过配置文件中的 lazyLoadingEnabled = true|false,配置启用或禁用延迟加载,默认是关闭的。

延迟加载的底层原理知道吗?

延迟加载的底层原理主要使用CGLIB动态代理实现:

  1. 使用CGLIB创建目标对象的代理对象。
  2. 调用目标方法时,进入拦截器invoke方法,如果发现目标方法是null值 则执行SQL查询
  3. 获取数据后,调用set方法设置属性值,再继续查询目标方法,就有值了

img

Mybatis的一级、二级缓存用过吗?

  • MyBatis的一级缓存是基于PerpetualCache的HashMap本地缓存,作用域为Session,默认开启。
  • 二级缓存需要单独开启,作用域为Namespace或mapper,默认也是采用PerpetualCache,HashMap存储。

Mybatis的二级缓存什么时候会清理缓存中的数据?

当作用域(一级缓存Session/二级缓存Namespaces)进行了新增、修改、删除操作后,默认该作用域下所有select中的缓存将被清空。


SSM框架
https://blog.xirui.work/posts/766433e0.html
作者
xirui
发布于
2025年5月17日
许可协议