面试-Spring
Spring
1.对Spring的理解
Spring框架核心特征包括:
IOC容器: Spring 的 IOC 容器负责对象的创建、初始化和依赖注入,将对象之间的依赖关系交由容器管理,而不是由程序主动创建对象,从而实现控制反转,降低代码耦合度,提高系统的可维护性和可扩展性。开发者只要定义bean及其依赖关系; (控制反转(IOC,Inversion of Control) 是一种设计思想,指的是将对象的创建和对象之间依赖关系的控制权,从程序本身反转交给容器来管理;)
**AOP: **Spring AOP 通过面向切面编程的方式,将日志、事务、权限控制等与业务无关的横切关注点从业务逻辑中分离出来,在不修改原有业务代码的情况下,对方法进行统一增强,提高代码复用性和系统的清晰度。
事务管理: Spring 提供了统一的事务管理机制,支持声明式事务和编程式事务,开发者通常只需使用 @Transactional 注解即可完成事务控制,从而保证数据操作的原子性、一致性、隔离性和持久性,简化了事务开发。
MVC框架: Spring MVC 是基于 Servlet API构建的的 Web 框架,采用了模型-视图-控制器(MVC)架构;支持灵活的URL到页面控制器的映射,以及多种视图技术;
2.对Spring的核心思想的理解
Spring 核心思想对比表(IOC / DI / AOP)
| 核心思想 | 解决的问题 | 实现手段 | 典型应用场景 |
|---|---|---|---|
| IOC(控制反转) | 对象由代码自行创建,组件耦合度高,难以维护和测试 | 由 Spring IOC 容器统一创建和管理 Bean,通过配置或注解完成对象装配 | 动态替换数据库实现, 服务组装 |
| DI(依赖注入) | 类之间依赖关系写死,修改或替换实现成本高 | 通过构造器注入、Setter 注入、字段注入(@Autowired)将依赖注入对象 |
Service 注入 DAO,Controller 注入 Service |
| AOP(面向切面编程) | 日志、事务、权限等代码与业务逻辑混杂,重复且难维护 | 基于代理机制(JDK 动态代理 / CGLIB),通过切点和通知增强方法 | 事务管理、日志记录、权限校验、性能监控 |
Spring通过这三个核心思想,实现了轻量级,高内聚,低耦合的企业级应用开发框架;
3.介绍Spring IOC和AOP;
Spring IOC和AOP的区别:
IOC: 是控制反转的意思,是一种创建和获取对象的技术思想,依赖注入(DI)是实现这种技术的一种方式; 不使用传统的new对象,而是通过IOC容器来实例化对象,通过IOC可以大大降低对象之间的耦合;
AOP: 是面向切面编程的意思,将那些与业务无关的逻辑封装起来,减少代码的重复和耦合,Spring AOP是基于动态代理实现的,如果要代理的对象,实现了某个接口,那么Spring AOP会使用JDK Proxy,去创建代理对象,对于没有实现接口的对象,就无法使用JDK Proxy创建的代理对象去代理,这时Spring AOP就会使用Cglib生成一个被代理对象的子类来作为代理;
4.详细介绍Spring中的AOP;
AOP最小的单位是切面;
在面向切面编程中,核心业务(登录,注册,增删改查等)和周边功能(日志,事务管理等)可能是独立开发的,不是耦合的,这时就需要AOP来将它们”编织”在一起;
AOP能将哪些与业务无关,却为业务模块所共同调用的逻辑封装起来,减少代码重复和模块耦合度;
AOP中的概念:
AspectJ: 切面,只是一个概念,没有具体的类或接口与之对应,是join point,advice和pointcut的一个统称;
Join Point: 连接点,指程序执行过程中的一个点,在Spring AOP中仅支持方法级别的连接点
Advice: 通知,即定义的一个切面中的横切逻辑;有Around,Before,After三种类型
Pointcut: 切点,用于匹配连接点,一个AspectJ包含哪些Join Point需要Pointcut进行筛选
Introduction: 引介,让一个切面可以声明被通知的对象实现任何他们没有真正实现的两个额外的接口
Weaving: 织入,指的是将切面(Aspect)中定义的增强逻辑(Advice)按照切点(Pointcut)的规则,应用到目标对象的指定方法上的过程,本质上就是把“横切关注点”与“业务代码”结合起来的过程。
AOP proxy: AOP代理,指在AOP实现框架中,实现切面协议的对象;在Spring AOP中有两种代理,分别是JDK动态代理和CGLIB动态代理
Target object: 目标对象,就是被代理的对象
相关举例:
1️⃣ 目标接口(Target 的接口)
1 | public interface UserService { |
2️⃣ 目标对象(Target Object)
1 |
|
- Target Object:
UserServiceImpl - Join Point:
login()方法的执行(Spring AOP 仅支持方法级别)
3️⃣ 引介接口(Introduction 接口)
1 | public interface Monitorable { |
4️⃣ 引介接口的默认实现
1 | public class MonitorableImpl implements Monitorable { |
5️⃣ 切面(Aspect)——核心
1 |
|
- Aspect:
LogAspect(概念集合) - Advice:Before / Around / After
- Pointcut:
execution(...) - Introduction:
@DeclareParents
6️⃣ 启动类(开启 AOP)
1 |
|
7️⃣ 测试代码(触发 AOP Proxy & Weaving)
1 |
|
8️⃣ 运行结果(体现 Weaving)
1 | 【Before】方法即将执行 |
5.IOC和AOP是通过什么机制来实现的?
Spring IOC实现机制:
反射: Spring 在启动阶段通过 Java 反射机制读取 Bean 的类信息、构造方法、字段和方法,并在运行期动态创建对象实例、调用构造器和为属性赋值,这是 IOC 容器能够“不依赖具体实现类”创建和管理对象的基础。
依赖注入: Spring 通过依赖注入将对象所依赖的其他对象在运行期注入到目标对象中,常见方式包括构造器注入、Setter 注入和字段注入(@Autowired),从而避免在类内部硬编码依赖关系,实现组件之间的解耦。
设计模式-工厂模式: Spring IOC 容器本质上是一个Bean 工厂,通过工厂模式统一负责对象的创建、初始化和销毁,屏蔽对象创建细节,使应用程序只依赖接口而非具体实现,提高系统的可维护性和扩展性。
容器实现: Spring 通过 BeanFactory 和 ApplicationContext 两个核心容器接口实现 IOC,其中 ApplicationContext 在 BeanFactory 基础上增加了其他企业级特性,是实际开发中最常用的 IOC 容器实现。
Spring AOP实现机制:
动态代理是在运行时动态生成代理对象,而不是编译时;它允许开发者在运行时指定要代理的接口和方法;
基于JDK的动态代理: JDK 动态代理基于Proxy类和InvocationHandler接口实现;这种方式需要代理的类实现一个或多个接口;
**基于CGLIB的动态代理: **当目标类没有实现接口或显式指定使用 CGLIB 时,Spring 会采用 CGLIB 动态代理,通过生成目标类的子类并重写方法来实现增强,其底层基于字节码技术,但无法代理 final 类或 final 方法。
6.依赖倒置,依赖注入,控制反转分别是什么?
控制反转: 在使用框架前程序员控制整个流程执行,使用后,框架来控制;流程控制权从程序员”反转”给了框架;
依赖注入: 是一种具体的编码技巧; 不通过new创建对象,而是将依赖的类对象在外部创建好,注入给类使用;
依赖倒置: 跟控制反转有点类似,主要是指导框架层面的设计;高层模块不依赖底层模块,共同依赖同一个抽象;抽象不依赖具体实现细节,具体实现细节依赖抽象;
7.依赖注入的实现方式:
**1.构造器注入:**通过构造方法注入依赖,依赖在对象创建时即被确定,能够保证依赖完整性,便于测试,也是 Spring 官方推荐的注入方式。
1 |
|
2.Setter注入: 通过 Setter 方法注入依赖,依赖可以在对象创建后再设置,适合可选依赖,但不利于保证对象完整性。
1 |
|
**3.字段注入:**直接在成员变量上注入依赖,代码简洁,但不利于单元测试,也违背依赖显式声明原则,一般不推荐在大型项目中使用。
1 |
|
8.设计一个Spring IOC需要从哪些方面考虑
1.Bean的生命周期管理; 2.依赖注入; 3.Bean的作用域; 4.AOP功能的支持; 5.异常处理; 6.配置文件加载;
9.AOP在Spring中的应用
1.事务管理: Spring的声明式事务就是基于AOP实现的;只需要在方法上标注@Transaction, Spring就会通过AOP在方法执行前开启事务,执行后根据是否有异常来决定提交或者回滚;
2.日志记录: 可以通过AOP来把日志逻辑抽离出来,定义成切面,然后通过@before,@after等来执行;
3.权限校验: 可以定义一个切面通过@before来在方法执行前校验用户登录状态和校验权限;
10.动态代理是什么?
Java的动态代理是一种在运行时动态创建代理对象的机制,主要用于在不修改原始类的情况下对方法调用经行拦截和增强;
分为: 1.基于接口的代理(JDK动态代理); 2.基于类的代理(CBLIB动态代理);
11.动态代理和静态代理的区别
代理是一种常用的设计模式,目的是: 为其他对象提供一个代理以控制对某个对象的访问,将两个类解耦;代理类和委托类都要实现相同的接口,因为代理真正调用的是委托类的方法;
区别:
1.静态代理: 由程序员创建或由特定工具创建,在代码编译时就确定了被代理的类是一个静态代理; 静态代理通常只代理一个类;
2.动态代理: 在代码运行期间,运用反射机制动态创建生成;动态代理代理的是一个接口下的多个实现类;
12.能使用静态代理的方式实现AOP吗?
可以但是基本没人用;
1.代码爆炸: 静态代理需要为每一个目标类手写对应的代理类,当系统中目标类和方法数量增多时,代理类数量会急剧膨胀,造成大量重复代码,维护成本极高。
2.僵化: 静态代理在编译期就已经确定代理关系和增强逻辑,一旦需要新增、修改或删除横切逻辑,就必须修改并重新编译代理类,灵活性极差,难以适应需求变化。
3.无法动态筛选: 静态代理无法像 AOP 那样通过切点表达式动态选择需要增强的方法,只能对固定的类或方法进行增强,无法实现按规则、按条件的统一增强。
13.AOP实现有哪些注解?
@Aspect: 用于定义切面,标注在切面类上;
@Pointcut: 定义切点,标注在方法上,用于指定连接点;
@Before: 在方法执行之前执行通知;
@After: 在方法执行之后执行通知;
@Around: 在方法执行前后都执行通知;
@AfterReturning: 在方法执行后返回结果后执行通知;
@AfterThrowing: 方法抛出异常后执行通知;
示例:
1 |
|
14.什么是反射?有哪些使用场景?
1.Spring框架的依赖注入和反转控制;
在Spring中可以通过XML配置文件或者基于注解的方式声明组件之间的依赖关系;当应用程旭启动时,Spring会扫描这些配置或者注解,然后通过反射来实例化Bean,并根据配置自动装配他们的依赖;
2.动态代理的实现;
在需要对现有类的方法调用进行拦截,记录日志,权限控制或是事务管理等场景中,反射结合动态代理技术被广泛应用;
15.Spring是如何解决循环依赖的?
**循环依赖(Circular Dependency)*指的是多个 Bean 之间*相互依赖,形成闭环。
1 | A 依赖 B |
如果直接按依赖顺序创建,会出现谁也创建不了的问题。
Spring 通过三级缓存机制,在 Bean 实例化后、初始化前提前暴露半成品对象,从而解决单例 Bean 的 setter 循环依赖,同时兼容 AOP 代理。构造器注入和 prototype 作用域的循环依赖无法解决。
三级缓存:
1️⃣ 一级缓存:singletonObjects
1 | Map<String, Object> singletonObjects |
- 存放:完整 Bean
- 状态:实例化 + 属性注入 + 初始化完成
- 是否可直接使用:✅ 可以
- 来源:Bean 完成创建后放入
📌 这是最终对外可用的 Bean 池
2️⃣ 二级缓存:earlySingletonObjects
1 | Map<String, Object> earlySingletonObjects |
- 存放:提前暴露的 Bean(半成品)
- 状态:实例化完成,但未初始化
- 是否可能是代理对象:✅ 可能
- 来源:从三级缓存中创建后放入
📌 专门用于解决循环依赖
3️⃣ 三级缓存:singletonFactories(核心)
1 | Map<String, ObjectFactory<?>> singletonFactories |
存放:ObjectFactory
ObjectFactory 能干什么?
1
Object getObject();
👉 用来 延迟创建 Bean 或代理对象
📌 三级缓存不存 Bean,本质是“Bean 的生成器”
场景:A ↔ B 循环依赖
① 创建 A
- new A()
- 放入 三级缓存(ObjectFactory)
② A 需要 B
- 开始创建 B
- new B()
- B 放入三级缓存
③ B 需要 A
- A 正在创建中
- 调用
getSingleton(beanName, allowEarlyReference=true)
查找顺序👇
1 | 一级缓存 singletonObjects ❌ |
④ 从三级缓存拿 A
1 | Object earlyBean = singletonFactory.getObject(); |
- 可能创建 代理对象
- 放入 二级缓存
- 删除三级缓存中 A
⑤ B 注入 A 成功
- B 完成创建
- 进入一级缓存
⑥ A 注入 B
- A 完成初始化
- 进入一级缓存
- 清空二、三级缓存
三级缓存的设计精髓:
三级缓存:负责在实例化后立即暴露对象的生成能力,兼顾AOP代理的提前生成;
二级缓存: 临时存储已确定的早期引用,避免重复生成代理;
一级缓存: 最终交付完整bean;
整个机制通过中断初始化流程,逆向注入半成品,延迟代理生成三大策略,将循环依赖的死结转换为有序的接力协作;
16.Spring三级缓存的数据结构是什么?
都是Map类型的缓存;
17.Spring框架中都用到了哪些设计模式?
工厂设计模式:Spring使用工厂模式通过BeanFactory, ApplicationContext创建Bean对象;
代理设计模式:Spring AOP功能的实现;
单例设计模式:Spring中Bean默认都是单例的;
模板方法模式: Spring中jdbcTemplate, hibernateTemplate 等以Template结尾的对数据库操作的类,他们就使用到了模板模式;
包装器设计模式: 项目需要连接多个数据库,而且不同的客户在每次访问中根据需要会去访问不同的数据库;这种模式让我们可以根据客户的需求动态的切换不同的数据源;
观察者模式: Spring事件驱动模型就是观察者模式的一个经典应用;
适配器模式: Spring AOP的增强或通知使用到了适配器模式,Spring MVC中也是用到了适配器模式适配Controller;
18.Spring常用注解有什么?
1. @Component
- 作用:把普通类交给 Spring 容器管理
- 本质:所有组件注解的父注解
2. 语义化组件注解(本质都是 @Component)
| 注解 | 使用场景 |
|---|---|
@Controller |
控制层 |
@Service |
业务层 |
@Repository |
持久层(DAO) |
3.@Autowired
用于自动装配Bean,按照类型注入
4.@Resource
用于自动装配Bean,按照名称注入
5. @Value
- 注入普通值 / 配置文件中的值
1 | @Value("${server.port}") |
6. @Scope
- 定义 Bean 作用域
| 作用域 | 说明 |
|---|---|
| singleton | 默认 |
| prototype | 多例 |
| request / session | Web 环境 |
7.@Configuration
- 声明配置类(Full 模式)
- 保证
@Bean方法单例
8. @Bean
- 将方法返回值注册为 Bean
1 |
|
19.Spring的事务什么情况下会失效?
Spring Boot通过Spring框架的事务管理模块来支持事务操作;事务管理在Spring Boot中通常是通过@Transaction注解来实现的;
事务失效的情况包括:
1.未捕获异常: 如果一个事务方法中发生了未捕获的异常,并且异常未被处理或者传播到事务边界之外,那么事务会失效,所有数据库操作会回滚;
2.非受检异常: 默认情况下,Spring对非受检异常(RuntimeException及其子类)会进行回滚处理;
3.事务传播属性设置不当: 如果多个事务之间存在事务嵌套,且事务传播属性配置不正确,可能导致事务失效; 特别是在方法内部调用@Transactional注解的方法
4.多数据源的事务管理: 如果在使用多个数据源时,事务管理没有正确配置或者存在多个@Transactional注解时,可能导致事务失效
5.跨方法调用事务问题: 如果一个事务方法内部调用了另一个方法,而这个被调用的方法没有@Transactional注解,这种情况外层事务可能会失效
6.事务在非公开方法中失效: 如果@Transactional注解标注在私有方法或非public方法上,事务也会失效;
20.Spring的事务,使用this调用是否生效?
不能生效;
因为Spring事务是通过代理对象来控制的,只有通过代理对象的方法调用才会应用事务管理的相关规则;当使用this直接调用时,是绕过了Spring的代理机制,因此不会应用事务设置;
21.Bean的生命周期
Spring中Bean默认是单例的,可以通过@scope注解设置为多例
- 实例化阶段 (Instantiating)
容器通过反射机制,根据 Bean 定义(BeanDefinition)调用构造函数或工厂方法,在堆内存中创建 Bean 的实例对象。
- 属性赋值阶段 (Populate)
实例化完成后,容器会将配置中指定的属性值和依赖的 Bean 注入到该对象中(如 @Autowired 或 XML 配置的 property)。
- 初始化阶段 (Initialization)
这是生命周期中最复杂的部分,Spring 提供了一系列回调接口供开发者干预:
- Aware 接口回调:如果 Bean 实现了
BeanNameAware、BeanFactoryAware等接口,容器会将对应的容器信息注入给 Bean。 - BeanPostProcessor 前置处理:执行所有注册的
BeanPostProcessor的postProcessBeforeInitialization方法。 - 初始化方法执行:
- 如果 Bean 实现了
InitializingBean接口,执行afterPropertiesSet()方法。 - 执行自定义的
init-method(通过@Bean(initMethod=...)或 XML 指定)。
- 如果 Bean 实现了
- BeanPostProcessor 后置处理:执行
postProcessAfterInitialization方法。注意:AOP 代理对象通常在此阶段生成。
- 销毁阶段 (Destruction)
当容器关闭时,会执行销毁逻辑:
- 如果 Bean 实现了
DisposableBean接口,执行destroy()方法。 - 执行自定义的
destroy-method。
22.Bean的单例和非单例,生命周期是否一致?
1.生命周期步骤的差异
| 阶段 | 单例 Bean (Singleton) | 非单例 Bean (Prototype) |
|---|---|---|
| 创建时机 | 容器启动初始化时(默认) | 每次调用 getBean() 时 |
| 实例化/属性赋值 | 由容器完成 | 每次请求时由容器完成 |
| 初始化回调 | 执行(如 @PostConstruct) |
执行(如 @PostConstruct) |
| 销毁回调 | 执行(容器关闭时) | 不执行 |
| 容器后续管理 | 容器一直持有引用,直到关闭 | 容器交给请求者后不再管理 |
2.核心区别解析
销毁阶段(最关键的区别)
- 单例 Bean:Spring 容器负责其整个生命周期,包括销毁。当容器关闭时,会触发
@PreDestroy或DisposableBean.destroy()。 - 非单例 Bean:Spring 容器在完成初始化并交给调用者后,就不再记录该实例的引用。因此,容器关闭时不会调用其销毁方法。如果你在 Prototype Bean 中持有了需要释放的资源(如数据库连接、Socket),必须由客户端代码手动进行清理
23.Spring Bean的作用域有哪些?
Spring框架中的Bean作用域(Scope)定义了Bean的生命周期和可见性;
Bean作用域:
**1.Singleton(单例):**在整个应用程序中只存在一个Bean实例;(默认作用域) ,Spring容器只会创建一个Bean实例,并在容器整个生命周期共享这个实例;
**2.Prototype(原型):**每次请求时都会创建一个Bean实例, 每次从容器中取获取该Bean时都会创建一个实例,适合于瞬时的Bean;
3.Request(请求): 每次HTTP请求时都会创建一个Bean实例,仅在Spring Web应用程序中有效; 适用于Web局限型的Bean;
4.Session(会话): Session范围内只会创建一个Bean实例; 该Bean在会话范围内共享,仅在Spring Web应用程序中有效; 适用于会话相关的Bean;
5.Application: 当前ServletContext中只存在一个Bean实例; 仅在Spring Web应用程序中有效; 该Bean实例在整个ServletContext范围内共享,适用于应用程序范围内的Bean;
6.webSocket(Web套接字): 在WebSocket范围内只存在一个Bean实例; 仅在WebSocket应用程序中有效; 该Bean实例在整个WebSocket会话范围内共享,适用于WebSocket范围内共享的Bean;
7.Custom Scopes(自定义作用域): Spring允许开发者自定义作用域,通过Scope接口来创建新的Bean作用域;
在Spring配置文件中,可以通过
1 | <bean id="myBean" class="com.example.MyBeanClass" scope="singleton"/> |
在Spring Boot中可以通过@Scope注解来指定Bean的作用域:
1 | @Scope("prototype") |
注解: ServletContext 是什么?
ServletContext 是 Servlet 规范中的核心对象,也叫应用上下文,代表整个 Web 应用的全局环境。
简单说:一个 Web 应用对应一个 ServletContext,所有 Servlet、JSP 都共享这个对象里的资源和数据。
24.Spring容器中存的是什么?
存储的主要是Bean对象;
Bean是Spring框架中的基本组件,用于表示应用程序中的各种对象;当应用程序启动时,Spring容器会根据配置文件或注解的方式创建和管理这写Bean对象;Spring容器会负责创建,初始化,注入依赖和销毁Bean对象;
25.在Spring中,在Bean加载/销毁前后,如果想实现某些逻辑,可以怎么做?
可以使用Spring的生命周期回调接口或注解;
1.使用init-method和destory-method
在xml配置中指定相关属性来指定Bean初始化和销毁前后需要调用的方法
1 | <bean id="myBean" class="com.example.MyBeanClass" init-method="init" destory-method="destsory"/> |
然后在Bean类中实现这写方法:
1 | public class MyBeanClass{ |
2.实现InitializingBean和DisposableBean接口
Bean类可以实现两个接口,并分别实现afterPropertiesSet和destory方法;
1 | public class MyBeanClass implements InitializingBean ,DisposableBean{ |
3.使用@PostConstruct和@PreDestory注解
1 | public class MyBeanClass{ |
4.使用@Bean注解的initMethod和destroyMethod属性
在基于java的配置中,可以在@Bean注解中指定initMethod和destroyMethod属性
1 |
|
5. BeanPostProcessor(最重要 ⭐)
作用范围:所有 Bean(全局)
(1)初始化前执行逻辑
1 | postProcessBeforeInitialization(Object bean, String beanName) |
(2)初始化后执行逻辑
1 | postProcessAfterInitialization(Object bean, String beanName) |
示例
1 | @Component |
用途
- AOP 代理创建(核心!)
- Bean 增强
- 日志、监控、埋点
26.Spring提供的扩展点
扩展点指 Spring 在 Bean 创建、初始化、使用、销毁过程中,预留的可插入自定义逻辑的接口/机制。
Spring框架提供了许多扩展点,使得开发者可以根据需求定制和扩展Spring的功能
**1.BeanFactoryPostProcesser:**允许在Spring容器实例化bean之前修改bean的定义;常用于修改bean的属性或改变bean的作用域;
2.BeanPostProcessor:可以在bean实例化,配置以及初始化之后对其进行额外处理;常用于代理bean,修改bean属性等;
3.PropertySource: 用于定义不用的属性源,如文件,数据库等;以便在spring应用中使用;
4.ImportSelector和ImportBeanDefinitionRegistrar: 用于根据条件动态注册bean定义,实现配置类的模块化;
5.SpringMVC中的HandlerInterceptor: 用于拦截处理请求,可以在请求处理前,中,后指定特定逻辑;
6.SpringMVC中的ControllerAdvice: 用于处理全局处理控制器的异常,数据绑定和数据校验;
7.Spring Boot的自动配置: 通过创建自定义的自动配置类,可以实现对框架和第三方库的自动配置;
8.自定义注解: 创建自定义注解,用于实现特定功能或约定,如控制权限,日志记录等;
SpringMVC
1.MVC分层
MVC全名是Model View Controller,是Model(模型)-View(视图)-Controller(控制器)的缩写, 用一种业务逻辑,数据,界面显示分离的方法组织代码,将业务逻辑聚集到一个部件里面,在改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑;
视图:为用户提供使用界面,与用户直接交互;
模型: 代表一个存取数据的对象或JAVA POJO(简单java对象) ; 它可以带有逻辑,主要用于承载数据,并对用户提交请求进行计算的模块;模型可以分为两类: 1.数据承载Bean 2.业务处理Bean ; 数据承载Bean是指实体类(如: User类),专门为用户承载业务数据的;而业务处理Bean则是指Service或Dao对象,专门用于处理用户提交请求的;
控制器: 用于将用户请求转发给响应的模块进行处理,并根据模块的计算结果向用户提供相应响应;它使视图与模型分离;
流程:
1.用户通过View页面向服务器端提出请求,可以是表单请求,超链接请求,AJAX请求等;
2.服务端Controller控制器接收到请求后对请求进行解析,找到对应的Model,对用户请求进行Model处理;
3.将处理结果在交给Controller(控制器只是起到了承上启下的作用);
4.根据处理结果找到要作为向客户端发回的响应View页面,页面经渲染后发送给客户端;
2.SpringMVC的处理流程
1 | 浏览器 |
二、详细执行流程(按顺序)
1. 请求进入 DispatcherServlet(前端控制器)
- 所有请求都会先到 DispatcherServlet
- 这是 Spring MVC 的“总指挥”
作用
- 统一接收请求
- 不处理业务,只做调度
2. HandlerMapping 查找处理器
- DispatcherServlet 调用 HandlerMapping
- 根据 URL + HTTP Method 找到对应的 Controller 方法
常见实现
RequestMappingHandlerMapping(@RequestMapping / @GetMapping)
返回内容
- Handler(Controller 方法)
- 以及 拦截器链(HandlerInterceptor)
3 .执行拦截器 preHandle(如果有)
1 | boolean preHandle(...) |
典型用途
- 登录校验
- 权限校验
- 请求日志
如果返回 false,请求被直接拦截
4. HandlerAdapter 调用 Controller
📌 为什么需要 HandlerAdapter?
因为 Controller 形式多样,需要“适配器”统一调用方式
📌 常见实现
RequestMappingHandlerAdapter
📌 执行内容
- 参数解析(@RequestParam / @PathVariable)
- 数据绑定
- 调用 Controller 方法
5. Controller 处理业务逻辑
1 | @GetMapping("/user/{id}") |
📌 返回值类型
ModelAndViewString(视图名)void- @ResponseBody / ResponseEntity
6. 处理返回值(是否需要视图)
情况一:返回视图(传统 MVC)
- DispatcherServlet 得到
ModelAndView - 进入视图解析流程
情况二:返回数据(REST)
1 | @ResponseBody |
📌 执行:
- HttpMessageConverter
- 对象 → JSON
- 直接写入 Response
- ❌ 不走 ViewResolver
7. ViewResolver 解析视图名
- 将逻辑视图名解析成物理视图
1 | "user" → /WEB-INF/jsp/user.jsp |
📌 常见 ViewResolver
- InternalResourceViewResolver
- ThymeleafViewResolver
8. View 渲染页面
- 将 Model 数据填充到 View
- 生成 HTML
- 写回 Response
9. 执行拦截器 postHandle / afterCompletion
1 | postHandle() |
📌 用途
- 日志
- 资源清理
- 异常处理
10. 响应返回浏览器
3.Handlermapping和Handleradapter
HandlerMapping:
作用: 根据请求信息,找到能够处理该请求的 Handler(Controller 方法)
功能: 根据请求的URL,请求参数等信息,找到处理请求的Controller
类型: Spring提供了多种HandlerMapping实现,如:RequestMappingHandlerMapping,BeanNameUrlHandlerMapping等;
工作流程: 根据请求信息确定要请求的处理器(Controller);HandlerMapping可以根据URL,请求参数等规则确定对应的处理器;
HandlerAdapter:
作用: 负责调用处理器(Controller)来处理请求
功能: 处理器(Controller)可能有不同的接口类型(Controller接口,HttpRequestHandler接口等),HandlerAdapter根据处理器的类型来选择合适的方法来调用处理器
类型: Spring提供了多个HandlerAdapter实现,如:RequestMappingHandlerAdapter,HttpRequestHandlerAdapter等;
工作流程: 根据处理器的接口类型,选择相应的HandlerAdapter来调用处理器;
SpringBoot
1.SpringBoot的好处
简化开发: SpringBoot通过提供一系列的开箱即用的组件和自动配置,简化了项目的配置和开发过程,开发人员可以更专注于业务逻辑的是实现;
快速启动: SpringBoot提供了快速启动的应用程序启动方式,可通过内嵌的tomcat,jetty或undertow等容器快速启动应用程序,无需额外的部署步骤,方便快捷;
自动化配置: SpringBoot通过自动配置功能,根据项目中的依赖关系和约定俗成的规则来配置应用程序,减少了配置的复杂性,是开发者更容易实现应用的最佳实践;
2.SpringBoot比Spring好在哪?
1.提供了自动化配置,大大简化了项目的配置过程
2.提供了快速的项目启动器,通过引入不同的starter,可以快速集成常用的框架和库,极大提高了开发效率
3.默认集成了多种内嵌服务器,无需额外配置,即可将应用打包成可执行的JAR文件,方便部署和运行
3.SpringBoot用到了哪些设计模式?
代理模式: Spring的AOP通过动态代理实现方法级别的切面增强,有静态和动态两种代理方式,实现动态代理方式
**策略模式: **SpringAOP支持JDK和CGlib两种动态代理实现方式,通过策略接口和不同策略类,运行时动态选择,其创建一般通过工厂方法实现
**装饰器模式: **Spring用TransactionAwareCacheDecorator解决缓存与数据库事务问题增强对事务的支持
**单例模式: **Spring Bean默认是单例模式,通过单例注册表(如:HashMap)实现
**简单工厂模式: **Spring中的BeanFactory是简单工厂模式的体现,通过工厂类方法获取Bean实现
**工厂方法模式: **Spring中的FactoryBean体现工厂方法模式,为不同产品提供不同工厂
**观察者模式: **Spring观察者模式包含Event(事件),Listener(监听者),Publisher(发送者),通过定义事件,监听器和发送者实现,观察者注册在ApplicationContext中,消息发送由ApplicationEventMulicaster完成
**模板模式: **Spring Bean的创建过程涉及模板模式,体现扩展性,类似Callback回调实现方式
**适配器模式: **SpringMVC中针对不同方式定义的Controller,利用适配器模式同意函数定义,定义了统一接口HandlerAdapter及对应适配器类;
4.SpringBoot中的约定大于配置
约定大于配置是SpringBoot的核心设计理念,它通过预设合理的默认行为和项目规范,大幅减少开发者需要手动配置的步骤,从而提升开发效率和项目标准化程度;
**自动化配置: **SpringBoot提供了大量的自动化配置,通过分析项目的依赖和环境,自动配置应用程序的行为;开发者无需显式的配置每个细节,大部分常用的配置都已经预设好了;例如引入spring-boot-starter-web后,SpringBoot会自动配置内嵌tomcat和SpringMVC,无需手动编写XML;
**默认配置: **SpringBoot为诸多方面提供大量默认配置,如数据库连接,设置Web服务器,处理日志等;开发人员无需手动配置这些常见内容,框架已经做好决策;
**约定的项目结构: **SpringBoot提倡特定项目结构,通常主应用程序类(含main方法)置于根包,控制器类,服务类,数据访问类等分别放在对应子包;如com.example.controller放控制器类,com.example.service放服务类;此约定是团队成员更容易理解项目结构与组织,新成员加入项目时更容易定位各功能代码位置;
5.SpringBoot的项目结构
1 | project-name |
开放接口层:可直接封装 Service 方法暴露成 RPC 接口;通过 Web 封装成 http 接口;进行网关安全控制、流量控制等。
终端显示层:各个端的模板渲染并执行显示的层。当前主要是 velocity 渲染,JS 渲染,JSP 渲染,移动端展示等。
Web 层:主要是对访问控制进行转发,各类基本参数校验,或者不复用的业务简单处理等。
Service 层:相对具体的业务逻辑服务层。
Manager 层:通用业务处理层,它有如下特征:
1) 对第三方平台封装的层,预处理返回结果及转化异常信息;
2) 对 Service 层通用能力的下沉,如缓存方案、中间件通用处理;
3) 与 DAO 层交互,对多个 DAO 的组合复用。
DAO 层:数据访问层,与底层 MySQL、Oracle、Hbase 等进行数据交互。
外部接口或第三方平台:包括其它部门 RPC 开放接口,基础平台,其它公司的 HTTP 接口。
分层领域模型规约:
DO(Data Object):与数据库表结构一一对应,通过 DAO 层向上传输数据源对象。
DTO(Data Transfer Object:数据传输对象,Service 或 Manager 向外传输的对象。
**BO(Business Object)**业务对象。由 Service 层输出的封装业务逻辑的对象。
AO(Application Object):应用对象。在 Web 层与 Service 层之间抽象的复用对象模型,极为贴近展示层,复用度不高。
VO(View Object):显示层对象,通常是 Web 向模板渲染引擎层传输的对象。
Query:数据查询对象,各层接收上层的查询请求。注意超过 2 个参数的查询封装,禁止使用 Map 类来传输。
6.SpringBoot的自动装配原理
Spring Boot 的自动装配,本质是:
在启动时,根据 classpath 中的依赖和当前容器环境,
通过一系列 @Conditional 条件判断,
自动向 Spring 容器中注册合适的 Bean。
二、自动装配从哪“开始”?—— 启动入口
一切都从这个注解开始
1 | @SpringBootApplication |
它是一个组合注解,展开后是:
1 | @SpringBootApplication 源码拆解(核心) |
自动装配的“发动机”就是:@EnableAutoConfiguration
@Target(ElementType.TYPE)
该注解用于指定当前注解的作用范围,ElementType.TYPE 表示该注解只能作用在类、接口或枚举等类型上,确保 @SpringBootApplication 只能标注在启动类这种类型声明上,而不能用于方法或字段。
@Retention(RetentionPolicy.RUNTIME)
该注解用于指定注解的生命周期,RUNTIME 表示注解在运行时仍然存在,并且可以通过反射机制读取,这是 Spring 框架能够在启动过程中解析并处理 @SpringBootApplication 及其组合注解的前提条件。
@Documented
该注解表示被标注的注解会出现在生成的 Javadoc 文档中,本身不影响程序运行逻辑,主要用于增强代码的可读性和规范性,方便开发者在文档中了解类所使用的注解信息。
@Inherited
该注解表示当一个类使用了该注解时,其子类在没有显式声明的情况下可以继承该注解,主要用于类级别注解的继承机制,在一定程度上支持配置的复用。
@SpringBootConfiguration
该注解是 @Configuration 的派生注解,与@Configuration作用几乎相同,用于标识当前类是 Spring Boot 应用的主配置类,表明该类可以作为 Bean 定义的来源,通常只标注在启动类上,用于区分普通配置类和 Spring Boot 的核心配置入口。
@EnableAutoConfiguration
该注解用于开启 Spring Boot 的自动装配功能,在应用启动时通过导入自动配置选择器,读取自动配置元数据,并结合条件注解根据当前环境和依赖情况动态加载合适的自动配置类,从而自动完成大量基础 Bean 的注册。
@ComponentScan
该注解用于开启组件扫描机制,默认以启动类所在包为扫描路径,递归扫描其子包中被组件类注解标注的类,并将其注册到 Spring 容器中,从而实现业务组件的自动发现和管理。
三、@EnableAutoConfiguration 做了什么?
1. 它本身并不干活
1 | @Import(AutoConfigurationImportSelector.class) |
关键点:
真正逻辑在 AutoConfigurationImportSelector
2.AutoConfigurationImportSelector 干了啥?
它的核心职责只有一句话:
把“符合条件的自动配置类”批量导入 Spring 容器
流程简化为 👇
1 | Spring 启动 |
**扫描类路径: **在应用程序启动时AutoConfigurationImportSelector会扫描类路径上的META-INF/spring.factories文件,这个文件包含了各种Spring配置和扩展的定义;在这里,他会查找所有实现了AutoConfiguration接口的类,具体的实现为getCandidateConfigurations方法
**条件判断: **对于每一个发现的自动配置类,AutoConfigurationImportSelector会使用条件判断机制(通过@ConditionalOnXXX注解)来确定是否满足导入条件;这些条件可以是配置属性,类是否存在,Bean是否存在等等;
**根据条件导入自动配置类: **满足条件的自动配置类将被导入到应用程序的上下文中;意味者它们被实例化并应用与应用程序的配置;
四、自动配置类从哪来?
关键文件(面试必答)
1 | META-INF/spring.factories (Spring Boot 2.x) |
以 Spring Boot 2.x 为例:
1 | org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ |
这里列的是:
所有可能的自动配置类(不是都会生效)
7.常用的几个启动器(Starter)
**spring-boot-starter:**最常用的起步依赖之一,包含了SpringMVC和Tomcat嵌入式服务器,用于快速构建Web应用程序
**spring-boot-starter-security:**提供了Spring Security的基本配置,帮助开发者快速实现应用的安全性,包括认证和授权功能;
mybatis-spring-boot-starter: 用于简化Spring Boot应用集成MyBatis的过程;自动配置了MyBatis相关组件,包括SqlSessionFactory, MapperScannerConfigurer等,使得开发者能够快速地开始使用MyBatis进行数据库操作;
spring-boot-starter-data-jpa或spring-boot-starter-jdbc: Spring Boot 提供的数据访问启动器,用于快速集成数据库访问能力。它通过引入一组约定好的依赖,并结合自动装配机制,在项目启动时根据数据源配置自动创建 DataSource、事务管理器以及常用的数据访问组件,其中 JPA 启动器会自动配置 EntityManager 和 Repository 接口实现,而 JDBC 启动器则自动配置 JdbcTemplate,从而简化数据库操作相关的配置,实现开箱即用。
spring-boot-starter-data-redis: 用于集成Redis缓存和数据存储服务; 包含了与Redis交互所需的客户端(默认是Jedis客户端也可配置为Lettuce),在应用启动时根据配置自动创建 RedisConnectionFactory、RedisTemplate 以及相关的序列化组件,以及Spring Data Redis的支持,使得Redis使用非常便捷; 但需要在配置文件中设置Reids服务器的连接详情;
**spring-boot-starter-test:**包含了单元测试和集成测试所需的库,如JUnit,Spring Test,AssertJ等,便于进行测试驱动开发(TDD);
8.自己写一个SpringBoot Starter
1.创建Maven项目
首先创建一个Maven项目并在pom.xml添加starter parent和一些必要依赖
1 | <project> |
2.添加自动配置
在META-INF/spring.factories中添加自动配置的元数据;例如:
1 | org.springframework.boot.autoconfigure.EnableAutoConfiguration = com.example.starter.HelloAutoConfiguration |
然后创建HelloAutoConfiguration类
1 |
|
3.创建配置属性类
使用@ConfigurationProperties注解来绑定配置文件中的属性
1 |
|
4.创建服务和控制器
创建一个服务类和服务实现类,以及一个控制器来展示starter的功能
1 |
|
5.发布Starter
将Starter发布到Maven仓库,无论是私有还是公有,如Nexus或Maven Central
6.使用Starter
在主应用的pom.xml中添加starter依赖,然后再application.yml或application.properties中配置相关属性
1 | hello: |
9.SpringBoot中常用的注解
包含spring常用注解;
SpringMVC中常用注解:
**@RequestMapping:**用于映射HTTP请求路径到Controller的处理方法;
@GetMapping,@PostMapping,@PutMapping,@DeleteMapping: 简化对应的请求;
同时多了
@SpringBootApplication: 用于标注主应用程序类,标识一个SpringBoot应用程序的入口点,同时启用自动配置和组件扫描;
@RestController: 结合@Controller和@ResponseBody,返回RESTful风格的数据;
10.SpringBoot怎么开启事务?
在服务层的方法上加@Transactional注解即可;
开启事务管理(通常可省略)加上@EnableTransactionManagement 注解;
1 |
|
Spring Boot 默认已经开启,加不加都可以(面试要说明这一点)
1 |
|
11.SpringBoot怎么做到导入就可以直接使用的?
依赖于 自动配置,起步依赖和条件注解等;
**起步依赖: ** 开发者只需在项目中添加一个起步依赖,Maven或Gradle就会自动下载并管理相关联的所有依赖,避免手动添加; 例如spring-boot-starter-web包含了Spring Web MVC,tomcat等构建web应用所需的核心依赖;
**自动配置: ** 自动配置是 SpringBoot 的核心机制,它会在项目启动时根据当前类路径中的依赖情况,自动加载对应的配置类并创建相关 Bean,省去手动配置步骤,其原理是通过 @EnableAutoConfiguration 加载自动配置类,并根据默认规则为应用提供合理的初始配置,从而实现“零配置”快速开发。
**条件注解: **条件注解用于控制自动配置是否生效,SpringBoot 会根据特定条件判断是否创建 Bean,例如当类路径中存在某个类、容器中不存在某个 Bean、或配置文件满足某个属性时才进行装配,常见注解如 @ConditionalOnClass、@ConditionalOnMissingBean、@ConditionalOnProperty,通过这些条件判断机制,既保证了自动配置的灵活性,又避免了 Bean 冲突问题。
12.SpringBoot中的过滤器和拦截器
在SpringBoot中,过滤器(Filter)和拦截器(Interceptor)是用于处理请求和响应的两种不同机制;
| 对比维度 | 过滤器(Filter) | 拦截器(Interceptor) |
|---|---|---|
| 规范 / 框架 | 属于 Servlet 规范,由 Web 容器提供实现 | 属于 SpringMVC 框架,由 Spring 提供实现 |
| 作用范围 | 作用于所有进入容器的请求和响应 | 只作用于 SpringMVC 控制器(Controller)请求 |
| 执行顺序 | 请求进入容器后、进入 Servlet 之前执行 | 进入 Controller 方法前后执行 |
| 依赖注入支持 | 无法直接注入Spring Bean(需间接获取) | 支持自动注入Spring Bean |
| 触发时机 | doFilter()在请求前/响应后被调用 | preHandle,postHandle,afterCompletion分阶段触发 |
| 适用场景 | 全局处理(编码处理、日志记录、权限校验等) | 业务逻辑相关的处理(权限,参数校验等) |
过滤器比拦截器先执行;
过滤器通过实现javax.servlet.Filter接口,并重写其中的init,doFilter和destroy方法来完成相应逻辑;
拦截器通过实现servlet.HandlerInterceptor接口,并重写其中的preHandle,postHandle和afterCompletion 方法来完成相应逻辑;
区别:
**所属规范: **过滤器是Java Servlet规范的一部分,拦截器是Spring框架提供的机制
**执行顺序: **过滤器在请求进入Servlet容器后,在到达目标Servlet或控制器之前执行;拦截器在请求到达控制器之后, 在控制器方法执行前后进行;
**适用范围: **过滤器可以对所有类型的请求进行过滤,包括静态资源请求; 拦截器只对SpringMVC控制器的请求拦截;
**功能特性: **过滤器主要用于对请求和响应进行预处理和后处理,如字符编码处理,请求日志记录等; 拦截器可以更细粒度的控制控制器方法的执行,如权限校验和性能监控等;
Mybatis
1.与jdbc相比Mybatis优点有什么?
1.灵活的 SQL 控制,SQL写在XML里,解除sql与程序代码的耦合,便于统一管理;提供XML标签,支持动态SQL;
2.相比jdbc可以减少很多重复代码;
3.很好的与各种数据库兼容,Mybatis用jdbc来连接数据库,只要jdbc支持的数据库,Mybatis也支持;
4.能够与Spring很好的集成,开发效率高;
5.提供映射标签,支持对象与数据库的ORM字段关系映射;提供对象关系映射标签,支持对象关系组件维护;
ORM(Object-Relational Mapping) 是一种编程技术,用于在面向对象的编程语言和关系型数据库之间建立映射关系。它将数据库中的表、行、列等关系型结构映射为程序中的对象、属性、关系等面向对象的概念。
2.Mybatis的优点
Mybatis在SQL灵活性,动态SQL支持,结果集映射和与Spring整合方面表现卓越;
SQL灵活性:
1 | <select id="findUsersWithStats" resultMap="userWithStatsMap"> |
动态sql: 使用
1 | <!-- 2.1 多条件查询 --> |
结果集映射: 自动映射和自定义映射结合,自动将查询结果字段名与对象属性名匹配(如驼峰转换);
1 | <!-- 3.3 混合映射 --> |
与Spring整合: 插件拓展机制,可编写插件拦截SQL执行过程,实现分页,性能监控,SQL改写等通用逻辑;
1 |
|
通过@MapperScan快速扫描Mapper接口,结合Spring事务管理,配置简洁高效;
1 |
|
3.JDBC连接数据库的步骤
导入JDBC驱动包
将数据库厂商提供的驱动包(如 MySQL 的mysql-connector-j)加入项目依赖中。注册驱动
通过加载驱动类,让JVM识别数据库驱动程序。使用Class.forName(“com.mysql.cj.jdbc.Driver”);
(现在多数驱动可自动注册)获取数据库连接(Connection)
使用DriverManager.getConnection()方法,通过数据库URL、用户名、密码建立连接。创建执行对象(Statement / PreparedStatement)
通过连接对象创建SQL执行对象,用于发送SQL语句。执行SQL语句
调用执行方法:
executeQuery()—— 查询操作executeUpdate()—— 增删改操作execute()—— 通用执行
处理结果集(ResultSet)
如果是查询操作,需要遍历结果集,读取数据。释放资源
关闭ResultSet、Statement、Connection,避免资源浪费。
示例:
1 | import java.sql.Connection; |
4.原生的Mybatis查询步骤
1.配置MyBatis: 在配置文件中配置数据源,MyBatis的Mapper文件位置等信息;
**2.创建实体类:**创建与数据库表对应的实体类,字段名和类型需要与数据库表保持一致;
1 | public class User{ |
3.编写SQL映射文件: 在resource目录下创建XML文件,定义SQL语句;
1 | 创建userMapper.xml |
4.编写DAO接口: 创建DAO接口,定义查询方法;
1 | public interface UserMapper{ |
5.在xml文件中编写具体的SQL查询语句
**6.调用查询方法: **在服务层或控制层中调用DAO接口中的方法进行查询
1 | //在Service层中调用 |
5.MyBatis中 # 和 $ 的区别
Mybatis在处理#{}时,会创建预编译的SQL语句,将SQL中的#{}替换为**?号**,在执行SQL时会预编译SQL中的占位符 (?) 赋值,调用PreparedStatement中的set方法来赋值,预编译的SQL居于执行效率更高,并且可以防止SQL注入,提供更高的安全性,适合传递参数值;
Mybatis在处理${}时,只是创建普通的SQL语句,然后在执行SQL语句时MyBatis将参数直接拼入到SQL中,不能防止SQL注入,因为参数直接拼接到SQL语句中,如果参数未经过验证,过滤,可能导致安全问题;
6.MybatisPlus和Mybatis的区别
7.Mybatis运用的常见设计模式
SpringCloud
<看小林>