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 Boot 和 Spring 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。


⭐️⭐️@Autowired 和 @Resource 的区别是什么?
这两个都能实现依赖注入,但有一些细节区别:
- @Autowired 是 Spring 提供的注解,默认是按 类型 注入。如果同一个类型有多个 Bean,就需要配合 @Qualifier 来指定。它还支持
required=false,可以控制依赖是否必须注入。- @Resource 是 JDK 标准注解,Spring 也支持。它默认是按照 名称 来注入,如果没找到同名的 Bean,再按类型匹配。


⭐️⭐️注入 Bean(依赖注入) 的方式有哪些?
常见有三种:
- 构造方法注入:通过构造函数把依赖传进来。
- 如果依赖关系是必须的且不可变的,更倾向于用 构造函数注入。
- 因为这样能保证对象在创建的时候,就一定具备它运行所需要的依赖,依赖关系也更清晰。
- 比如
Car必须要有Engine,没有发动机车就跑不起来,这种情况我就会用构造函数注入。- Setter 方法注入:通过 set 方法把依赖传进去。
- 但如果依赖是可选的或者需要灵活控制的,更倾向于用 Setter 注入。它比较灵活,可以在对象创建之后再决定要不要注入。
- 比如
Car里的AirConditioner,有更好,没有车也能跑,这时候用 Setter 注入更合适。- 字段注入:直接在字段上加注解,比如 @Autowired,这个最常见,写起来简单,但缺点是耦合度高。
依赖注入 DI (Dependency Injection)是Spring框架的核心设计模式,是指程序运行过程中,如果需要调用另一个对象协助时,无须在代码中创建被调用者,而是依赖于外部的注入


IoC和DI的区别
- IoC 是 控制反转,它更像是一种思想,指的是把对象的创建和依赖管理的控制权,从我们程序员手里“反转”给了容器。
- DI 是 依赖注入,它是实现 IoC 的一种具体方式。也就是说,容器通过给对象 注入依赖 来实现控制反转。
⭐️⭐️⭐️Bean 的作用域有哪些?如何配置?
Spring中Bean的作用域决定了 Bean 实例的生命周期和创建策略,我通常把它分成两大类来记:一类是核心作用域,另一类是Web环境下的作用域。”
“首先,核心作用域主要有两个:”
singleton(单例作用域)。这也是Spring默认的作用域。- 整个 Spring 容器中只会有一个 Bean 实例。不管在多少个地方注入这个 Bean,拿到的都是同一个对象。
- 生命周期和 Spring 容器相同,容器启动时创建,容器销毁时销毁。
- 实际开发中,像 Service、Dao 这些业务组件基本都是单例的,因为单例既能节省内存,又能提高性能。
prototype(原型或多例作用域)。- 这个和单例正好相反,它指的是我们每次从容器里获取这个Bean的时候,Spring都会为我们创建一个新的实例。
然后,在Web开发环境下,还有三个非常常用的作用域:
request(请求作用域)。
- 表示在 Web 应用中,每个 HTTP 请求都会创建一个新的 Bean 实例,请求结束后 Bean 就被销毁。
session(会话作用域)。- 表示在 Web 应用中,每个 HTTP 会话都会创建一个新的 Bean 实例,会话结束后 Bean 被销毁。
- 典型的使用场景是购物车、用户登录状态这些需要在整个会话期间保持的信息。
application(应用作用域)。- 它的生命周期是整个Web应用的生命周期。
- 它有点像一个‘Web全局的单例’,在整个应用运行期间只会被创建一次。
“至于如何配置,现在在Spring Boot项目里非常简单,主要就是使用
@Scope注解。”


⭐️⭐️⭐️Spring框架中的单例Bean是线程安全的吗?
- Spring 容器里单例 Bean 的确只会创建一个实例,但它并不会保证线程安全。
- 因为 Spring 的单例只是作用域上的单例——也就是整个容器里只有一份实例对象,但如果这个 Bean 本身有可变的成员变量,被多个线程同时修改,就可能出现线程安全问题
所以,单例 Bean 是否线程安全,取决于它的设计:
- 如果 Bean 是无状态的(比如只提供服务方法,不保存数据),那它就是线程安全的。
- 如果 Bean 是有状态的(比如里面保存了用户请求的中间数据),那在并发场景下就不安全,需要考虑加锁、ThreadLocal 或者改成原型作用域来避免共享。
实际开发中,我们通常把 Service、Dao 这些 Bean 设计成无状态的,这样单例就不会有线程安全问题。






⭐️⭐️⭐️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 的关键节点。




BeanDefinition的作用?其中的懒加载是什么?
- BeanDefinition 其实就是 Spring 里描述 Bean 的元信息的,里面包含了创建 Bean 需要的所有信息,像 Bean 的类型、作用域,还有是否懒加载这些。
- 而懒加载是它里面的一个配置,意思是如果设置了懒加载,这个 Bean 就不会在容器启动时马上创建,而是等到第一次被用到的时候才会实例化。
Spring AOP
⭐️⭐️什么是AOP?有什么用?
- AOP 就是面向切面编程。简单说,就是把那些和业务无关但很多地方都要用的逻辑,比如日志记录、事务管理这些,抽出来做成一个 “切面” 模块
- 这样做的话,业务代码里就不用重复写这些东西了,既减少了重复代码,也降低了模块之间的依赖,后续改起来也方便。像我们平时记录接口调用日志、公共字段填充、管理数据库事务,很多时候都是用 AOP 来实现的。
苍穹公共字段处理

你们项目中有没有使用到AOP?
👉 第一个是接口调用日志:
- 在没有 AOP 的时候,比如一个用户登录接口或者订单提交接口,我都得在方法里手动写
log.info("请求参数:{}", param),还要记录调用时间、返回结果,代码里到处都是日志逻辑,业务很乱。- 但是有了 AOP,我就能写一个日志切面,拦截所有 Controller 的方法,在方法执行前自动获取请求参数、接口名,方法执行后再记录返回结果和耗时。这样日志逻辑完全跟业务解耦,代码也简洁很多。
👉 第二个是事务管理:
- 像下单这种场景,要先扣库存,再创建订单,这两个操作必须同时成功或者同时失败。没用 AOP 的时候,就得手动写
beginTransaction、commit、rollback这些事务管理代码,而且每个地方都要写一遍。- 现在只要在方法上加一个
@Transactional注解就行,Spring 的 AOP 会在方法执行前自动开启事务,成功就提交,抛异常就回滚。这样我们就可以只专注写业务逻辑,不用管事务的细节。所以我觉得 AOP 的好处就是,把这些 “跟业务没关系但又必须做” 的事,集中到一个地方管理,既减少重复代码,也让业务代码更干净,后续维护也方便。


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


多个切面的执行顺序如何控制?
- 一种是用 @Order 注解,给切面类加上这个注解,值越小数字越小,切面优先级越高,执行越早。比如 @Order (1) 就比 @Order (2) 先执行。
- 另一种是让切面类实现 Ordered 接口,重写 getOrder 方法返回数字,原理和 @Order 一样,数字小的先执行。
- 如果都没设置,默认按类名的字母顺序来,不过实际开发里一般都会显式指定顺序,避免混乱。
事务
Spring中的事务是如何实现的?
- Spring实现事务的本质是利用AOP完成的。
- 它对方法前后进行拦截,在执行方法前开启事务,在执行完目标方法后根据执行情况提交或回滚事务。
⭐️⭐️Spring 管理事务的方式有几种?
- 一种是声明式事务,这是实际开发里用得最多的。就是通过注解或者 XML 配置来定义事务,不用写代码手动控制。比如在方法上加 @Transactional 注解,Spring 就会自动管理事务的开启、提交和回滚,特别方便。
- 另一种是编程式事务,比如用 TransactionTemplate或者直接操作PlatformTransactionManager接口手动管理事务的开启、提交和回滚,适用于更复杂的事务控制场景。
现在主流都是用声明式事务,尤其是注解方式,简洁又清晰。


@Transactional注解有什么用?项目中是如何使用的?
@Transactional的核心作用是让 Spring 自动管理事务,我们就不用自己写开启、提交、回滚的代码了,底层其实是通过 AOP 来实现的。它能保证一组数据库操作要么都成功,要么都失败,避免数据不一致。
它有几个常用属性,比如
propagation:控制事务的传播行为。isolation:设置事务的隔离级别。
rollbackFor / noRollbackFor:指定哪些异常要回滚或不回滚。
readOnly ,默认值为 false。设为 true 时表示只读事务,只读事务不允许执行写操作,以提高性能。
- 我们项目里主要在 Service 层的业务方法上用。
- 比如之前做过一个付费阅读的优惠券订单功能,创建订单时要先扣减优惠券库存,再生成订单记录,这两步必须同时成功或失败。不能出现库存扣了但订单没创建成功的情况。
- 所以就在 createOrder 方法上加了 @Transactional,还指定了 rollbackFor = Exception.class,确保任何异常都能触发回滚,避免出现库存扣了但订单没生成的问题


@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:必须在非事务环境运行,有事务就报错。

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。

⭐️⭐️Spring中事务失效的场景有哪些?
- 方法内部调用:就是一个在类里面,一个没有加
@Transactional注解的A方法,去调用了同一个类里面加了@Transactional注解的B方法(this.B())。这种情况下,B方法的事务是不会生效的。
- 原因还是和动态代理有关。当我们调用A方法时,我们是通过普通的
this引用来调用的,而不是通过Spring生成的代理对象。所以,这个调用就不会被AOP的事务切面拦截到,事务自然也就无法开启- 异常类型不匹配:默认情况下,Spring 只会在运行时异常(
RuntimeException)和Error时回滚,如果抛的是受检异常(比如 cheakedException),事务不会回滚,除非显式配置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方法就会以非事务的方式运行,或者直接抛出异常,导致事务失效。


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 代理的场景。

解决循环依赖的具体流程清楚吗?⭐️⭐️⭐️
整个过程大概是这样:
- Spring创建A,先实例化(调用构造函数),但还没注入属性。然后把一个能够获取A的“工厂”放到三级缓存里。
- 接着,Spring准备给A注入属性,发现A依赖B,于是就去创建B。
- Spring创建B,也先实例化。然后发现B又依赖A。
- 这时,Spring就去缓存里找A。它会依次检查一级、二级缓存,最后在三级缓存里找到了创建A的工厂。
- 通过这个工厂,Spring获取到了一个“半成品”的A(虽然A的属性还没注入,但对象引用已经有了),并把这个半成品的A放到二级缓存里,然后从三级缓存里删除A的工厂。
- B拿到了A的引用,顺利完成了自己的创建和初始化,然后被放入一级缓存。
- B创建好了,Spring就可以回来继续完成第一步里A的创建,把B注入给A,然后A也顺利完成了初始化,最后被放入一级缓存。
通过这种方式,Spring就巧妙地打破了循环。
但是,就像我前面提到的,这个机制是有局限性的。它无法解决构造函数注入(Constructor Injection)的循环依赖。因为构造函数注入要求在对象实例化的时候,它==所依赖的Bean就必须已经准备好了==,这无法满足“提前暴露”的条件,容器根本拿不到半成品对象,所以Spring遇到这种情况会直接抛出异常。
同样,对于原型(Prototype)作用域的Bean,Spring也不会去解决它们的循环依赖问题,因为原型Bean每次都是新创建的,没有缓存的必要。
以上就是我对Spring循环依赖的理解。


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

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

⭐️⭐️⭐️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等组件,实现请求处理与响应的统一调度。

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自动装配好。

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

@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=web1
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 | |
⭐️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}%’ ?不允许在%%里
编辑
- MYSQL拼接函数 CONCAT(‘%’, 关键字 , ‘%’)
- CONCAT(‘%’, #{name}, ‘%’)
实体类属性名和表中字段名不一样 ,怎么办?
- 起别名
- 开启驼峰命名
- 手动结果映射:使用 @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的执行流程如下:
- 读取MyBatis配置文件mybatis-config.xml。
- 构造会话工厂SqlSessionFactory。
- 会话工厂创建对象SqlSession。
- 操作数据库的接口,Executor执行器。
- Executor执行方法中的MappedStatement参数。
- 输入参数映射。
- 输出结果映射。


Mybatis是否支持延迟加载?
- MyBatis支持延迟加载,即在需要用到数据时才加载。
- 可以通过配置文件中的 lazyLoadingEnabled = true|false,配置启用或禁用延迟加载,默认是关闭的。
延迟加载的底层原理知道吗?
延迟加载的底层原理主要使用CGLIB动态代理实现:
- 使用CGLIB创建目标对象的代理对象。
- 调用目标方法时,进入拦截器invoke方法,如果发现目标方法是null值 则执行SQL查询
- 获取数据后,调用set方法设置属性值,再继续查询目标方法,就有值了

Mybatis的一级、二级缓存用过吗?
- MyBatis的一级缓存是基于PerpetualCache的HashMap本地缓存,作用域为Session,默认开启。
- 二级缓存需要单独开启,作用域为Namespace或mapper,默认也是采用PerpetualCache,HashMap存储。
Mybatis的二级缓存什么时候会清理缓存中的数据?
当作用域(一级缓存Session/二级缓存Namespaces)进行了新增、修改、删除操作后,默认该作用域下所有select中的缓存将被清空。


