Spring面试总结
讲一下你对spring的理解
Spring 是一个轻量级的 Java 开发框架,它通过控制反转(IoC)和面向切面编程(AOP)等核心技术,为企业级应用提供了全面的解决方案。它整合了事务管理、Web 开发、数据访问等功能模块,具有模块化、非侵入性等特点。
核心特性:
-
控制反转与依赖注入:IoC,将对象的创建和依赖关系的管理从代码中移除,转由 Spring 容器负责。DI:IoC 的具体实现方式,通过构造器、Setter 方法或字段注入,将依赖对象传递给目标对象。
-
面向切面编程:Spring AOP 通过代理或字节码增强技术,将日志、事务等横切关注点模块化,定义切面(Aspect)、通知(Advice)、切入点(Pointcut)等概念,在不修改原有业务逻辑的前提下增强功能,实现了业务代码与系统服务的解耦,提升了代码的复用性和可维护性。
-
MVC分层框架:MVC 分层框架将应用分为 Model(数据与业务)、View(界面展示)、Controller(逻辑调度)三层,通过职责分离提升代码复用性与可维护性,广泛应用于 Spring MVC 等框架,是企业级开发的经典架构模式。
-
事务管理:Spring 提供声明式事务管理,通过 @Transactional 注解和 AOP 机制,在方法层面定义事务边界,支持传播行为(如 REQUIRED)和隔离级别配置,确保数据操作的原子性和一致性,使事务管理代码与业务逻辑分离,简化了企业级应用的数据持久化操作。
-
传播行为
传播行为 英文原名 核心特性 典型使用场景 REQUIRED 必须参与事务 若无事务则新建,有则加入 核心业务操作(如订单创建、资金转账) SUPPORTS 支持事务 存在事务时加入,否则非事务执行 只读查询操作(提升性能) MANDATORY 强制事务 必须在已有事务中执行,无则抛异常 依赖事务上下文的底层操作 REQUIRES_NEW 新建事务 挂起当前事务,新建独立事务执行 异步任务 / 日志记录(避免主事务影响) NOT_SUPPORTED 不支持事务 挂起当前事务,以非事务方式执行 缓存更新 / 非关键数据操作 NEVER 禁止事务 若存在事务则抛异常 纯计算逻辑(无数据持久化需求) NESTED 嵌套事务 基于保存点实现子事务,外层回滚影响内层,内层回滚不影响外层 分阶段操作(如支付前库存校验) -
隔离级别
隔离级别 脏读允许 不可重复读允许 幻读允许 数据库默认场景 性能消耗 READ_UNCOMMITTED 是 是 是 极少使用(数据一致性要求极低) 最低 READ_COMMITTED 否 是 是 Oracle 默认级别(读多写少场景) 较低 REPEATABLE_READ 否 否 是 MySQL 默认级别(默认推荐) 中等 SERIALIZABLE 否 否 否 金融 / 交易场景(强一致性要求) 最高 DEFAULT 由数据库决定 由数据库决定 由数据库决定 MySQL→REPEATABLE_READ Oracle→READ_COMMITTED 依赖数据库
-
Spring的核心思想说说你的理解
概念 | 核心思想 | 解决的问题 | 实现方式 | 典型应用场景 |
---|---|---|---|---|
IoC | 控制反转:将对象的创建和依赖关系的管理从代码转移到容器,由容器控制对象生命周期。 | 传统代码中对象硬编码依赖导致的耦合度高、可测试性差问题。 | 依赖查找(DL)、依赖注入(DI)。 | Spring 容器通过ApplicationContext 管理 Bean 的生命周期。 |
DI | 依赖注入:IoC 的具体实现方式,通过构造器、Setter 或字段注入依赖对象。 | 组件间依赖关系的手动管理繁琐,且依赖具体实现而非接口。 | 构造器注入、Setter 注入、注解注入(如@Autowired )。 | 服务层注入数据访问层组件:public UserService(UserRepository userRepository) {...} 。 |
AOP | 面向切面编程:将横切关注点(如日志、事务)模块化,通过代理或字节码增强织入目标代码。 | 业务逻辑与系统服务(如日志、安全)耦合导致代码冗余、可维护性差。 | 静态代理(编译时增强)、动态代理(运行时增强,如 JDK 动态代理、CGLIB)、字节码编织(如 AspectJ)、切面配置。 | 统一日志记录、事务管理、权限校验等。 |
Spring IoC和AOP 介绍一下
Spring IoC 的核心是将对象的创建与依赖管理从程序代码中剥离,交由容器统一管控。开发者无需手动 new 对象,只需通过注解或配置描述依赖关系,容器会在运行时自动构建对象图并注入依赖。这种方式实现了对象间的松耦合,使代码更易测试和维护,例如服务层可直接通过@Autowired
获取 DAO 实例,而无需关心其具体创建过程,是 Spring 框架简化开发的基础机制。
Spring AOP 通过分离 “业务逻辑” 与 “横切关注点”(如日志、事务),将重复逻辑封装为独立切面,利用动态代理、CGLIB代理等技术在运行时将切面逻辑织入目标方法。开发者通过@Aspect
定义切面,用@Pointcut
指定作用范围,@Before/@After
等注解控制逻辑执行时机,避免在业务代码中硬编码重复逻辑。例如在服务方法调用前后自动记录日志,既保证业务代码纯净,又实现了逻辑复用,是提升代码可维护性的关键技术。
-
JDK动态代理和CGLIB代理讲一下
-
JDK动态代理
JDK 动态代理是 Java 原生提供的基于接口的代理机制,通过
java.lang.reflect.Proxy
和InvocationHandler
在运行时动态生成代理类字节码。代理对象实现与目标对象相同的接口,方法调用通过反射转发至InvocationHandler
处理,从而实现对目标方法的增强。其优势在于无需额外依赖、符合面向接口编程原则,是 Spring AOP 的默认选择(当目标对象实现接口时),但只能代理接口方法,无法处理未实现接口的类。具体执行流程:代理对象的方法调用会先跳转至
InvocationHandler
的invoke
方法,再通过反射调用目标对象的实际方法 -
CGLIB代理
CGLIB 是基于继承的代理机制,通过 ASM 字节码库在运行时生成目标类的子类作为代理。代理类重写父类方法,并在调用时通过
MethodInterceptor
拦截,直接调用父类方法而非反射,因此性能略高。CGLIB 无需目标类实现接口,可代理未实现接口的类,但无法代理final
类或方法,适用于 Spring AOP 中未实现接口的 Bean(如 Controller),是 JDK 动态代理的补充方案。具体执行流程: 代理对象的方法调用会先进入
MethodInterceptor
的intercept
方法,再通过MethodProxy.invokeSuper
直接调用目标类的原始方法
-
Spring的aop介绍一下
概念以及思想在前几点已总结
- 核心术语详解:
术语 | 定义 | 示例 |
---|---|---|
切面(Aspect) | 封装横切逻辑的类,包含切点和通知的定义。 | @Aspect 注解标注的类,如 LogAspect 。 |
切点(Pointcut) | 定义横切逻辑作用的目标方法,通过表达式匹配方法签名。切点是连接点的一部分。 | execution(* com.service.*Service.*(..)) 匹配所有 Service 类的方法。 |
通知(Advice) | 横切逻辑的具体实现,分为 5 种类型,定义何时、如何增强目标方法。 | @Before 前置通知:在目标方法前执行日志记录。 |
连接点(Joinpoint) | 程序执行过程中的特定点(如方法调用、异常抛出),Spring AOP 仅支持方法调用。 | 调用 UserService.save() 方法时的连接点。 |
目标对象(Target Object) | 被代理的原始对象,即被增强的业务对象。 | UserService 实例。 |
AOP 代理(Proxy) | 由 Spring 生成的代理对象,用于拦截方法调用并应用切面逻辑。 | 通过 JDK 或 CGLIB 生成的代理对象,如 UserServiceProxy 。 |
-
通知类型
@Before
(前置通知):目标方法执行前调用。@After
(后置通知):目标方法正常执行完毕后调用(无论是否有返回值)。@AfterReturning
(返回后通知):目标方法成功返回后调用,可获取返回值。@AfterThrowing
(异常通知):目标方法抛出异常后调用,可获取异常信息。@Around
(环绕通知):包裹目标方法,可在方法前后执行逻辑,甚至控制方法是否执行(最强大的通知类型)。
-
通知类型执行顺序
- 无异常:
- 环绕通知前置逻辑
- 前置通知(@Before)
- 目标方法执行
- 后置通知(@After)
- 返回通知(@AfterReturning,仅在目标方法正常返回时执行)
- 环绕通知后置逻辑
- 抛出异常:
- 环绕通知前置逻辑
- 前置通知
- 目标方法执行(抛出异常)
- 异常通知(@AfterThrowing,替代返回通知执行)
- 后置通知(@After,无论是否异常都会执行)
- 环绕通知后置逻辑(若异常未被 @Around 捕获,可能不执行)
- 无异常:
IOC和AOP是通过什么机制来实现的?
-
Ioc核心实现技术
- 反射:Spring IOC容器利用Java的反射机制动态地加载类、创建对象实例及调用对象方法,反射允许在运行时检查类、方法、属性等信息,从而实现灵活的对象实例化和管理。
- 依赖注入:IOC的核心概念是依赖注入,即容器负责管理应用程序组件之间的依赖关系。Spring通过构造函数注入、属性注入或方法注入,将组件之间的依赖关系描述在配置文件中或使用注解。
- 工厂模式:Spring IOC容器通常采用工厂模式来管理对象的创建和生命周期。容器作为工厂负责实例化Bean并管理它们的生命周期,将Bean的实例化过程交给容器来管理。
- 容器实现:Spring IOC容器是实现IOC的核心,通常使用BeanFactory或ApplicationContext来管理Bean。BeanFactory是IOC容器的基本形式,提供基本的IOC功能;ApplicationContext是BeanFactory的扩展,并提供更多企业级功能。
-
AOP核心实现技术
Spring AOP的实现依赖于动态代理技术。动态代理是在运行时动态生成代理对象,而不是在编译时。它允
许开发者在运行时指定要代理的接口和行为,从而实现在不修改源码的情况下增强方法的功能。Spring AOP支持两种动态代理:
基于JDK的动态代理:使用java.lang.reflect.Proxy类和java.lang.reflect…InvocationHandler接口实现。这
种方式需要代理的类实现一个或多个接口。
**基于CGLIB的动态代理:**当被代理的类没有实现接口时,Spring会使用CGLIB.库生成一个被代理类的子
类作为代理。CGLIB(Code Generation Library)是一个第三方代码生成库,通过继承方式实现代理。
依赖倒置,依赖注入,控制反转分别是什么?
- 依赖倒置:依赖倒置是一种设计原则,要求程序设计中通过抽象(接口 / 抽象类)建立依赖关系,而非直接依赖具体实现类。例如,高层业务模块(如服务层)不直接调用低层数据访问模块(如 DAO)的具体实现,而是通过接口交互,使高层与低层解耦,便于代码扩展与维护,符合 “面向接口编程” 的理念。
- 依赖注入:依赖注入是控制反转(IOC)的具体实现方式,指在创建对象时,通过构造器、setter 方法或字段注解等方式,将其依赖的其他对象由外部(如容器)传入,而非由对象自己创建。例如,Spring 中
UserService
若依赖UserRepository
,无需在UserService
中手动 newUserRepository
,而是通过@Autowired
让容器将UserRepository
实例注入,实现对象间依赖关系的解耦。 - 控制反转:控制反转是一种设计理念,指传统开发中对象的创建与依赖管理由程序代码自身控制,而 IOC 模式下,控制权 “反转” 给容器(如 Spring 容器):容器根据配置自动创建对象、注入依赖并管理其生命周期,应用代码只需从容器获取对象即可。例如,开发者无需手动管理 Bean 的创建,而是通过
@Component
等注解让容器负责,体现了 “被动接受控制” 的反转逻辑,最终降低代码耦合度。
怎么实现依赖注入?
-
构造器注入:保证对象初始化时依赖已就绪
// 构造器注入示例 class UserService { private final UserDao userDao; // 通过构造器接收依赖对象(final修饰确保不可变) public UserService(UserDao userDao) { this.userDao = userDao; } }
-
Setter注入:灵活性高,但依赖可能未完全初始化
class UserService { private UserDao userDao; // 通过setter方法注入依赖(非必需时可设为null) public void setUserDao(UserDao userDao) { this.userDao = userDao; } }
-
字段注入:代码简洁但隐藏依赖关系,不推荐生产代码
// 字段注入示例(以Spring框架为例) class UserService { @Autowired // Spring注解实现字段注入 private UserDao userDao; // 直接声明依赖字段 }
动态代理和静态代理的区别
动态代理与静态代理的核心区别在于代理类的创建时机和灵活性:静态代理在编译期手动编写代理类,需与目标类实现相同接口或继承目标类,每个目标类对应独立代理类,扩展性较差;动态代理则在运行时通过反射(如 JDK 代理)或字节码生成技术(如 CGLIB)动态生成代理类,无需手动编写,可通过统一处理器处理多个目标类,适用于 AOP 等需要灵活增强的场景,相比静态代理更具扩展性与无侵入性。
spring是如何解决循环依赖的?
-
循环依赖在spring中有三种情况:
第一种:通过构造方法进行依赖注入时产生的循环依赖问题。
第二种:通过setter方法进行依赖注入且是在多例(原型)模式下产生的循环依赖问题。
第三种:通过setter方法进行依赖注入且是在单例模式下产生的循环依赖问题
-
只有第三种被解决了,详细方案步骤如下:
以 A→B→A 循环为例的详细流程
步骤 1:创建 A(暴露工厂到二级缓存)
1. 实例化 A(半成品,未填充依赖) 2. 将 A 的 ObjectFactory 放入二级缓存: singletonFactories.put("a", () -> getEarlyBeanReference("a", mbd, a)); 3. 开始填充 A 的依赖(发现需要 B)
步骤 2:创建 B(触发对 A 的循环依赖)
4. 实例化 B(半成品)
5. 将 B 的 ObjectFactory 放入二级缓存
6. 开始填充 B 的依赖(发现需要 A)
7. 调用 getSingleton("a") 查找 A:
- 一级缓存无 A
- 三级缓存无 A(早期引用尚未生成)
- 二级缓存有 A 的 ObjectFactory → 调用工厂生成早期引用 earlyA
- 将 earlyA 放入三级缓存 earlySingletonObjects
- 从二级缓存移除 A 的 ObjectFactory
8. 将 earlyA 注入 B
9. 完成 B 的初始化,将 B 放入一级缓存
步骤 3:继续完成 A 的初始化
10. B 已在一级缓存,将 B 注入 A
11. 完成 A 的初始化
12. 将 A 从三级缓存移至一级缓存
spring三级缓存的数据结构是什么?
- 一级缓存(Singleton Objects):这是一个Map类型的缓存,存储的是已经完全初始化好的bean,即完全准备好可以使用的bean实例。键是bean的名称,值是bean的实例。这个缓存在DefaultSingletonBeanRegistry类中的singletonObjects属性中。
- 二级缓存(Early Singleton Objects):这同样是一个Map类型的缓存,存储的是早期的bean引用,即已经实例化但还未完全初始化的bean。这些bean已经被实例化,但是可能还没有进行属性注入等操作。这个缓存在DefaultSingletonBeanRegistry类中的earlySingletonObjects属性中。
- 三级缓存(Singleton Factories):这也是一个Map类型的缓存,存储的是ObjectFactory对象,这些对象可以生成早期的bean引用。当一个bean正在创建过程中,如果它被其他bean依赖,那么这个正在创建的bean就会通过这个ObjectFactory来创建一个早期引用,从而解决循环依赖的问题。这个缓存在DefaultSingletonBeanRegistry类中的singletonFactories属性中。
spring框架中都用到了哪些设计模式
- 工厂设计模式:Spring使用工厂模式通过 BeanFactory、ApplicationContext 创建 bean 对象。
- 代理设计模式:Spring AOP 功能的实现。
- 单例设计模式:Spring 中的 Bean 默认都是单例的。
- 模板设计模式:Spring 中 jdbcTemplate、hibernateTemplate 等以 Template 结尾的对数据库操作的类,它们就使用到了模板模式。
- 包装器设计模式:我们的项目需要连接多个数据库,而且不同的客户在每次访问中根据需要会去访问不同的数据库。这种模式让我们可以根据客户的需求能够动态切换不同的数据源。
- 观察者模式:Spring 事件驱动模型就是观察者模式很经典的一个应用。
- 适配器模式:Spring AOP 的增强或通知(Advice)使用到了适配器模式、spring MVC 中也是用到了适配器模式适配Controller。
spring 核心容器常用注解有什么?
注解 | 作用 | 示例场景 |
---|---|---|
@Component | 声明一个组件(Bean),自动被组件扫描发现 | 通用组件类 |
@Repository | 声明数据访问层组件(DAO),提供持久层异常转换 | 数据库访问类 |
@Service | 声明业务逻辑层组件(Service) | 业务处理类 |
@Controller | 声明 Web 控制器组件(MVC) | Spring MVC 控制器 |
@RestController | @Controller + @ResponseBody 的组合,返回 JSON 数据 | RESTful API 接口 |
@Configuration | 声明配置类,替代 XML 配置文件 | 替代 applicationContext.xml |
@Bean | 在配置类中声明一个 Bean,替代 XML 中的<bean> 标签 | 自定义 Bean 创建逻辑 |
@Autowired | 自动注入依赖(按类型匹配) | 字段、构造器、setter 方法 |
@Qualifier | 配合@Autowired ,按名称指定注入的 Bean | 解决多实现类的歧义 |
@Resource | JSR-250 标准注解,按名称注入依赖(等价于@Autowired +@Qualifier ) | 替代@Autowired |
@Value | 注入外部配置属性(如application.properties 中的值) | 配置文件参数注入 |
@Scope | 指定 Bean 的作用域(如singleton 、prototype ) | 多例 Bean 配置 |
@Lazy | 延迟初始化 Bean(仅在首次使用时创建) | 资源消耗大的 Bean |
Spring的事务什么情况下会失效?
-
同一个类中方法内部调用:
场景:在类的内部使用
this.
调用被@Transactional
注解的方法。
原因:Spring 事务基于 AOP 代理实现,必须通过代理对象调用方法才能触发事务拦截。若使用this.
直接调用(如this.method()
),会绕过代理对象,导致事务不生效。@Service public class UserService { public void saveUser() { this.saveUserWithTransaction(); // 内部调用绕过代理,事务失效 } @Transactional public void saveUserWithTransaction() { // 数据库操作 } }
-
事务在非公开方法中失效:
场景:
@Transactional
注解应用在非public
修饰的方法上(如默认访问修饰符、private
)。
原因:Spring 框架的设计限制 —— 源码中会跳过非public
方法的事务拦截(TransactionAttributeSource
接口明确要求仅处理public
方法)。@Service public class OrderService { @Transactional void updateOrder() { // 非public方法,事务不生效 // 数据库操作 } }
-
事务传播属性设置不当:
场景:
@Transactional
的propagation
属性配置错误,导致方法脱离事务管理。
原因:不同传播属性决定了事务的行为,例如:Propagation.NOT_SUPPORTED
:挂起当前事务,在非事务环境中执行;Propagation.NEVER
:禁止事务,若存在事务则抛出异常。
@Service public class AccountService { @Transactional(propagation = Propagation.NOT_SUPPORTED) public void transferMoney() { // 即使调用方有事务,当前方法也会在非事务环境中执行 } }
-
异常类型不匹配:
场景:未正确处理异常类型,导致事务无法回滚(需注意:此场景可能被误认为 “事务失效”,但实际事务已生效,只是未触发回滚)。
原因:- 默认情况下,Spring 仅回滚
RuntimeException
或Error
类型的异常; - 若捕获异常后未重新抛出,或抛出的是检查异常(如
Exception
),事务会正常提交。
@Service public class TransactionService { @Transactional public void process() { try { // 数据库操作 throw new Exception("自定义检查异常"); // 非RuntimeException,默认不回滚 } catch (Exception e) { // 捕获异常且不抛出,事务正常提交 } } }
- 默认情况下,Spring 仅回滚
-
多数据源场景下事务管理器错误:
场景:在多数据源环境中,不同数据源的操作未被同一事务管理器管理。
原因:单数据源时事务管理器自动生效,但多数据源需手动指定transactionManager
。若未配置,跨数据源操作无法统一回滚(属于分布式事务问题)。
Spring的事务,使用this调用是否生效?
在 Spring 中使用this
调用事务方法时,事务会失效。这是因为 Spring 事务基于 AOP 代理实现,而this
引用指向的是目标对象本身,而非代理对象,导致 AOP 拦截器无法介入。
Bean的生命周期说一下?
- 实例化(Instantiation)
通过构造器或工厂方法创建 Bean 实例。 - 属性注入(Population)
填充 Bean 的依赖和属性(如@Autowired
)。 - Aware 接口回调
BeanNameAware.setBeanName()
:注入 Bean 名称。BeanFactoryAware.setBeanFactory()
:注入 BeanFactory。ApplicationContextAware.setApplicationContext()
:注入应用上下文。
- BeanPostProcessor 前置处理
所有BeanPostProcessor.postProcessBeforeInitialization()
方法被调用(全局处理)。 - 初始化方法调用
InitializingBean.afterPropertiesSet()
- 或
@PostConstruct
注解方法 - 或 XML 配置的
init-method
- BeanPostProcessor 后置处理
所有BeanPostProcessor.postProcessAfterInitialization()
方法被调用(全局处理)。 - Bean 就绪可用
Bean 可被应用程序使用。 - 销毁阶段
- 容器关闭时,调用
DisposableBean.destroy()
- 或
@PreDestroy
注解方法 - 或 XML 配置的
destroy-method
- 容器关闭时,调用
-
初始化方法顺序
BeanPostProcessor.postProcessBeforeInitialization
(全局前置)→@PostConstruct
→InitializingBean
→init-method
(优先级从高到低)→BeanPostProcessor.postProcessAfterInitialization
(全局后置)。 -
销毁方法顺序
@PreDestroy
→DisposableBean
→destroy-method
(优先级从高到低)。
Bean是否单例?
Spring 中的 Bean 默认都是单例的。每个Bean的实例只会被创建一次,并且会被存储在Spring容器的缓存中,以便在后续的请求中重复使用。这种单例模式可以提高应用程序的性能和内存效率。但是,Spring也支持将Bean设置为多例模式,即每次请求都会创建一个新的Bean实例。要将Bean设置为多例模式,可以在Bean定义中通过设置scope属性为"prototype"来实现。
Bean的单例和非单例,生命周期是否一样?
生命周期阶段 | 单例(Singleton) | 原型(Prototype) |
---|---|---|
实例创建时机 | 容器启动时(默认)或第一次被请求时(懒加载模式)创建唯一实例。 | 每次被请求时(如getBean() 调用或注入时)创建新实例。 |
实例数量 | 整个容器中仅存在 1 个实例,所有引用指向同一对象。 | 每次请求对应 1 个新实例,多次请求生成多个独立对象。 |
属性注入与初始化 | 容器负责完成属性注入(如@Autowired )、BeanNameAware 等接口回调、@PostConstruct 初始化方法等,仅执行一次。 | 容器同样负责属性注入和初始化操作,但每次创建新实例时都会重新执行(每次实例独立处理)。 |
容器管理范围 | 从创建到销毁全程由容器管理,生命周期与容器绑定。 | 容器仅负责创建和初始化,后续生命周期(如销毁)不由容器管理,需用户自行处理。 |
销毁阶段 | 容器关闭时触发销毁逻辑(如DisposableBean 接口的destroy() 、@PreDestroy 方法)。 | 容器不触发销毁逻辑,即使实现DisposableBean 或配置destroy-method 也不会执行。 |
与容器的生命周期绑定 | 完全绑定:容器启动则创建,容器关闭则销毁。 | 不绑定:实例创建后与容器脱离关系,容器关闭不影响已创建的原型实例。 |
适用场景 | 无状态组件(如 Service、Repository),复用实例以减少资源消耗。 | 有状态组件(如保存请求上下文的对象),避免多线程共享状态导致的安全问题。 |
Spring bean的作用域有哪些?
作用域 | 生命周期管理 | 实例数量 | 线程安全性 | 典型应用场景 |
---|---|---|---|---|
Singleton | 容器管理(创建与销毁) | 每个容器 1 个实例 | 需手动处理共享状态 | 无状态服务、工具类 |
Prototype | 用户管理(仅创建) | 每次请求 1 个新实例 | 天然安全(不共享) | 有状态组件、非线程安全对象 |
Request | HTTP 请求生命周期 | 每个请求 1 个实例 | 单线程环境 | 请求参数处理器、请求级缓存 |
Session | HTTP 会话生命周期 | 每个会话 1 个实例 | 会话内单线程 | 购物车、用户会话状态 |
Application | ServletContext 生命周期 | 每个应用 1 个实例 | 需线程安全 | 全局配置、应用级统计 |
WebSocket | WebSocket 会话生命周期 | 每个 WebSocket 会话 1 个实例 | 会话内单线程 | WebSocket 消息处理器 |
在Spring中,在bean加载/销毁前后,如果想实现某些逻辑,可以怎么做?
- 使用init-method和destroy-method
- 实现InitializingBean和DisposableBean接口,然后分别实现afterPropertiesSet和destroy方法。
- 使用@PostConstruct和@PreDestroy注解
- 使用@Bean注解的initMethod和destroyMethod属性
- 使用
BeanPostProcessor
(全局拦截),实现BeanPostProcessor
接口,重写postProcessBeforeInitialization
(初始化前)和postProcessAfterInitialization
(初始化后)
Spring给我们提供了很多扩展点,这些有了解吗?
一、Bean 生命周期扩展点
- BeanPostProcessor
允许在 Bean 初始化前后插入自定义逻辑,是 AOP 代理、属性加密等功能的底层实现机制。所有 Bean 初始化时都会触发,适合全局增强。 - BeanFactoryPostProcessor
在 Bean 定义加载后、实例化前修改 BeanDefinition,可动态调整 Bean 配置(如替换占位符、修改属性值),典型应用是PropertySourcesPlaceholderConfigurer
。 - FactoryBean
用于创建复杂 Bean 的工厂接口,可自定义对象的创建逻辑,Spring 内部如SqlSessionFactoryBean
即采用此模式,适合封装第三方组件。
二、MVC 扩展点
- WebMvcConfigurer
自定义 Spring MVC 配置,可注册拦截器、格式化器、视图解析器等,替代传统的web.xml
配置,实现细粒度的 MVC 定制。 - HandlerInterceptor
拦截 HTTP 请求和响应,可在请求处理前后执行逻辑(如登录验证、请求日志),支持对特定 URL 路径进行拦截。 - @ControllerAdvice 与 @ExceptionHandler
全局异常处理机制,通过@ControllerAdvice
定义全局处理器,结合@ExceptionHandler
统一处理 Controller 层抛出的异常,返回规范化的错误响应。
三、配置类扩展点
- @Configuration + @Bean
基于 Java 的配置方式,替代 XML 配置,可通过@Bean
方法自定义 Bean 的创建和初始化过程,支持依赖注入和条件加载。 - @Import 与 ImportSelector
用于导入其他配置类或动态选择配置类,实现模块化配置。ImportSelector
可基于条件返回需导入的类名数组,常用于自动配置。 - @Conditional 与 Condition
基于条件注册 Bean,可根据环境变量、类路径存在性等动态决定是否加载某个 Bean,是 Spring Boot 自动配置的核心机制。
MVC分层介绍一下
分层 | 核心职责 | 具体内容 | 特点 |
---|---|---|---|
Model(模型) | 管理应用数据和业务逻辑,是数据处理和业务决策的核心 | - 数据:实体类(如 User、Order)、临时计算数据(如统计结果) - 业务逻辑:数据验证(如手机号格式校验)、业务规则(如订单金额计算)、数据持久化(如数据库 CRUD) | 与 View、Controller 完全解耦,独立于界面和交互逻辑,稳定性高 |
View(视图) | 展示数据并与用户交互,是用户直接感知的界面 | - 数据展示:将 Model 传递的数据可视化(如网页表格、APP 列表) - 用户输入:接收用户操作(如表单提交、按钮点击)并传递给 Controller | 只关注 “如何展示”,不处理业务逻辑,可灵活替换(如从网页改为小程序界面) |
Controller(控制器) | 作为 Model 与 View 的桥梁,接收请求、协调业务处理并返回结果 | - 接收请求:获取 View 传递的用户输入(如表单参数、URL 参数) - 调度逻辑:调用 Model 层处理业务(如调用 Service 方法) - 响应处理:将 Model 处理结果传递给 View,决定展示哪个界面 | 不处理业务逻辑,仅负责 “调度”,降低 View 与 Model 的直接耦合 |
三层通过交互流程协作:用户操作 View → Controller 接收请求 → 调用 Model 处理业务 → Model 返回结果 → Controller 选择 View 展示结果。
了解SpringMVC的工作流程吗?
- 用户发送请求至前端控制器DispatcherServlet,DispatcherServlet收到请求调用处理器映射器HandlerMapping。
- 处理器映射器根据请求url找到具体的处理器,生成处理器执行链HandlerExecutionChain(包括处理器对象和处理器拦截器)一并返回给DispatcherServlet。
- DispatcherServlet根据处理器Handler获取处理器适配器HandlerAdapter执行HandlerAdapter处理一系列的操作,如:参数封装,数据格式转换,数据验证等操作,
- 执行处理器Handler(Controller,也叫页面控制器)。
- Handler执行完成返回ModelAndView。
- HandlerAdapter将Handler执行结果ModelAndView返回到DispatcherServlet。
- DispatcherServlet将ModelAndView传给ViewReslover视图解析器
- ViewReslover解析后返回具体View。
- DispatcherServlet对View进行渲染视图(即将模型数据model填充至视图中)。
- DispatcherServlet响应用户。
Handlermapping 和 Handleradapter有了解吗?
对比维度 | HandlerMapping(处理器映射器) | HandlerAdapter(处理器适配器) |
---|---|---|
核心功能 | 根据请求信息(URL、HTTP 方法、请求头等),找到处理请求的Handler(如@Controller 中的方法、接口实现类等),并封装拦截器链。 | 根据 Handler 的类型,适配并执行 Handler,处理参数绑定、数据转换、返回值处理等细节,统一调用逻辑。 |
处理阶段 | 位于请求处理的早期阶段,在DispatcherServlet 接收请求后首先被调用。 | 位于HandlerMapping 之后,在确定 Handler 后执行,负责调用 Handler 并返回结果。 |
输入信息 | HTTP 请求对象(HttpServletRequest ),包含 URL、参数、请求方法等。 | Handler 对象(如具体方法或接口实现类)、HTTP 请求 / 响应对象。 |
输出结果 | HandlerExecutionChain (包含匹配的 Handler 和拦截器链)。 | ModelAndView (视图与模型数据)或直接写入响应体(如@ResponseBody 标注的返回值)。 |
关键作用 | 解决 “找谁处理请求” 的问题,实现请求到处理逻辑的映射。 | 解决 “如何处理请求” 的问题,屏蔽不同 Handler 类型的调用差异,统一执行流程。 |
常见实现类 | RequestMappingHandlerMapping (处理注解式 Handler,如@RequestMapping 方法)、BeanNameUrlHandlerMapping (根据 Bean 名映射 URL)。 | RequestMappingHandlerAdapter (适配注解式 Handler)、SimpleControllerHandlerAdapter (适配Controller 接口实现类)。 |
设计意图 | 通过灵活的映射规则(如注解、配置),支持多样化的请求路由需求。 | 通过适配器模式,兼容不同类型的 Handler(注解式、接口式等),让DispatcherServlet 无需关心具体调用方式。 |
示例场景 | 当请求GET /users/1 时,找到UserController 中@GetMapping("/users/{id}") 标注的getUser 方法。 | 调用getUser 方法时,自动将 URL 中的id=1 转换为Long 类型参数,执行方法后将返回的视图名封装为ModelAndView 。 |
SpringBoot比Spring好在哪里
- 配置更简单:SpringBoot 通过 “自动配置” 机制,根据依赖自动生成默认配置,开发者仅需修改少量个性化配置(如端口、数据库连接)。
- 部署更便捷:SpringBoot 内嵌 Tomcat、Jetty 等服务器,可直接打包为可执行 JAR,通过
java -jar
一键启动,无需额外配置服务器。 - 依赖管理更高效:SpringBoot 提供 “Starter” 依赖(如
spring-boot-starter-web
),自动管理依赖版本,避免版本兼容问题。 - 开发效率更高:SpringBoot 开箱即用,默认集成常用功能(如日志、健康检查),且与 Spring 生态(如 Spring Cloud)无缝衔接,降低集成成本。
怎么理解SpringBoot中的约定大于配置
它通过预设合理默认值和标准化项目结构,让开发者用最少的配置快速启动项目,同时保留灵活扩展的能力。
**自动化配置:**Spring Boot 提供了大量的自动化配置,通过分析项目的依赖和环境,自动配置应用程序的行为。开发者无需显式地配置每个细节,大部分常用的配置都已经预设好了。例如,引入spring-boot-starter-web后,Spring Boot会自动配置内嵌Tomcat和Spring MVC,无需手动编写XML。
**默认配置:**Spring Boot 为诸多方面提供大量默认配置,如连接数据库、设置 Web 服务器、处理日志等。开发人员无需手动配置这些常见内容,框架已做好决策。例如,默认的日志配置可让应用程序快速输出日志信息,无需开发者额外繁琐配置日志级别、输出格式与位置等。
**约定的项目结构:**Spring Boot 提倡特定项目结构,通常主应用程序类(含 main 方法)置于根包,控制器类、服务类、数据访问类等分别放在相应子包,如com.example.demo.controller放控制器类,com.example.demo.service放服务类等。此约定使团队成员更易理解项目结构与组织,新成员加入项目时能快速定位各功能代码位置,提升协作效率。
SpringBoot自动装配原理是什么?
SpringBoot自动装配是基于约定大于配置的理念,通过@SpringBootApplication
注解中的@EnableAutoConfiguration
触发,扫描META-INF/spring.factories
中定义的自动配置类,利用条件注解(如@ConditionalOnClass
)判断是否满足配置条件,自动向Spring容器注册如数据源、MVC组件等Bean,同时允许用户通过自定义配置覆盖默认配置,从而显著减少Spring应用的样板代码,提升开发效率。
SpringBoot 过滤器和拦截器说一下?
对比项 | 过滤器(Filter) | 拦截器(Interceptor) |
---|---|---|
规范 / 框架 | Servlet 规范(容器层) | Spring MVC 框架(应用层) |
执行时机 | 在 Servlet 之前 / 之后 | 在 Controller 之前 / 之后、视图渲染前后 |
依赖注入 | 不支持(Filter 由 Servlet 容器管理) | 支持(Interceptor 由 Spring 容器管理) |
拦截范围 | 所有请求(如静态资源、Servlet 请求) | 仅拦截 Spring MVC 处理的请求(不包括静态资源) |
性能 | 更高(直接由 Servlet 容器调用) | 略低(需要经过 Spring MVC 的 DispatchServlet) |
使用场景 | 全局请求处理(如编码、日志、跨域) | 业务相关拦截(如登录校验、权限控制) |
过滤器(Filter)是 Servlet 规范中的组件,由 Servlet 容器管理,可拦截所有类型请求(包括静态资源、HTTP 请求等),在请求进入 Servlet 前或响应返回客户端前执行,支持全局处理(如编码设置、跨域处理),需通过 FilterRegistrationBean 或 @WebFilter 注册,不支持依赖注入,适用于容器级别的通用请求处理
拦截器(Interceptor)是 Spring MVC 框架的组件,由 Spring 容器管理,仅拦截 Spring MVC 处理的请求(不包括静态资源),在 Controller 调用前后、视图渲染前后执行,支持依赖注入(如注入 Service),通过 WebMvcConfigurer 注册,可针对特定 URL 进行细粒度控制(如登录校验、请求耗时统计),适用于业务相关的拦截逻辑。
Mybatis里的 # 和 $ 的区别?
Mybatis 在处理 #{} 时,会创建预编译的 SQL 语句,将 SQL 中的 #{} 替换为 ? 号,在执行 SQL 时会为预编译 SQL 中的占位符(?)赋值,调用 PreparedStatement 的 set 方法来赋值,预编译的 SQL 语句执行效率高,并且可以防止SQL 注入,提供更高的安全性,适合传递参数值。
Mybatis 在处理 ${} 时,只是创建普通的 SQL 语句,然后在执行 SQL 语句时 MyBatis 将参数直接拼入到 SQL 里,不能防止 SQL 注入,因为参数直接拼接到 SQL 语句中,如果参数未经过验证、过滤,可能会导致安全问题。
MybatisPlus和Mybatis的区别
CRUD操作:MybatisPlus通过继承BaseMapper接口,提供了一系列内置的快捷方法,使得CRUD操作更加简单,无需编写重复的SQL语句。
**代码生成器:**MybatisPlus提供了代码生成器功能,可以根据数据库表结构自动生成实体类、Mapper接口以及XML映射文件,减少了手动编写的工作量。
**通用方法封装:**MybatisPlus封装了许多常用的方法,如条件构造器、排序、分页查询等,简化了开发过程,提高了开发效率。
分页插件:MybatisPlus内置了分页插件,支持各种数据库的分页查询,开发者可以轻松实现分页功能,而在传统的MyBatis中,需要开发者自己手动实现分页逻辑
介绍一下服务熔断
服务熔断(Circuit Breaker)是分布式系统中的一种容错机制,用于防止级联故障和雪崩效应。当依赖的服务出现问题(如响应超时、频繁错误)时,熔断机制会暂时切断对该服务的调用,直接返回降级结果,避免资源浪费和故障扩散。
- 工作原理
- 关闭状态(Closed):
- 正常调用目标服务。
- 记录失败次数(或错误率),若超过阈值则转为打开状态。
- 打开状态(Open):
- 立即返回降级结果(如缓存数据、默认值)。
- 经过预设的冷却时间后,转为半开状态。
- 半开状态(Half-Open):
- 允许少量请求调用目标服务。
- 若请求成功,认为服务已恢复,熔断器关闭。
- 若请求失败,认为服务仍未恢复,熔断器重新打开。
- 关闭状态(Closed):
介绍一下服务降级
服务降级是分布式系统应对压力和故障的 “主动防御机制”,通过牺牲局部非核心功能,保障整体系统的稳定性和核心业务的可用性。它与服务熔断相辅相成(熔断常触发降级),共同构成微服务架构中的容错体系,是高可用系统设计的重要组成部分。
负载均衡有哪些算法?
1. 轮询算法
- 原理:按顺序轮流将请求分配给每个服务器,例如第 1 个请求给服务器 A,第 2 个给服务器 B,第 3 个再给服务器 A,循环往复。
2. 加权轮询算法
- 原理:为每个服务器分配权重(如性能高的服务器权重高),请求按权重比例分配。例如服务器 A 权重 3、服务器 B 权重 1,则 A 接收 3 次请求后,B 接收 1 次,循环执行。
3. 随机算法
- 原理:每次随机选择一个服务器处理请求,理论上概率均等。
4. 加权随机算法
- 原理:为服务器分配权重,权重高的服务器被随机选中的概率更高(如权重 3 的服务器被选中的概率是权重 1 的 3 倍)。
5. 最少连接数算法
- 原理:优先将请求分配给当前连接数最少的服务器,动态反映服务器负载。
6. 加权最少连接数算法
- 原理:在最少连接数基础上,结合权重调整 —— 服务器权重越高,允许的 “可接受连接数” 越多。例如,服务器 A 权重 2、当前连接数 4;服务器 B 权重 1、当前连接数 3,则 A 的 “有效连接数” 为 4/2=2,B 为 3/1=3,因此优先选择 A。
7.IP哈希算法
- 原理:****IP 哈希的原理是:对客户端的 IP 地址进行哈希计算得到一个数值,再将该数值与后端服务器的数量进行取模运算,最终的结果即为该客户端请求所分配到的服务器索引。
8. URL 哈希算法
- 原理:对请求的 URL 路径进行哈希计算,将相同 URL 的请求分配到同一服务器。
9. 最小响应时间算法
- 原理:优先选择响应时间最短的服务器(即处理请求最快的服务器)。
10.一致性哈希算法
- **原理:**先构建一个范围固定的哈希环(如 0 到 2³²-1),将所有服务器节点通过哈希函数映射到环上;当客户端请求到来时,同样对其 IP 或请求参数进行哈希计算并映射到环上,然后按顺时针方向在环上寻找距离该哈希值最近的服务器节点,作为请求的处理节点。
如何实现一直均衡给一个用户?
基于用户标识的哈希映射(结合一致性哈希优化):首先为每个用户分配唯一标识(如用户 ID、登录 Token、IP 地址等),对该标识进行哈希计算后,通过一致性哈希算法映射到服务器集群。一致性哈希会将服务器节点分布在哈希环上,用户请求的哈希值会顺时针匹配最近的服务器。
微服务常用的组件有哪些?
- **服务注册与发现:**在微服务架构中,服务实例的地址会动态变化(如扩缩容、故障重启),服务注册与发现通过注册中心统一管理服务的注册信息(如 IP、端口),并提供查询功能。调用方无需硬编码服务地址,而是从注册中心获取最新的可用实例列表,实现服务间的自动发现与调用,避免了人工维护地址的复杂性,是微服务 “去中心化” 的基石。
- **服务通信:**微服务间需通过网络进行协作,服务通信解决了 “如何调用” 的问题。同步通信(如 REST API、RPC)适用于实时性要求高的场景,调用方需等待响应;异步通信(如消息队列)通过中间件解耦服务,适用于削峰填谷、最终一致性场景,提升系统弹性。选择通信方式需根据业务场景权衡实时性与吞吐量。
- **负载均衡:**将请求均匀分配到多个服务实例,避免单点过载,提升系统可用性与吞吐量。客户端负载均衡(如 Ribbon)在调用方内部实现,根据本地维护的实例列表选择节点;服务端负载均衡(如 Nginx)在流量入口层统一分发。负载均衡算法(如轮询、随机、最少连接)决定请求路由策略,需结合服务性能与请求特性优化配置。
- **API网关:**作为微服务的统一入口,聚合所有服务的 API,对外提供统一接口,屏蔽内部服务细节。其核心功能包括路由分发(根据 URL 将请求转发至对应服务)、认证授权(校验用户身份与权限)、限流熔断(保护后端服务)、协议转换(如 HTTP 转 RPC)等,简化了客户端与微服务的交互,同时集中处理跨服务的共性逻辑。
- **配置中心:**集中管理微服务的配置信息(如数据库连接、环境参数),替代传统的本地配置文件。配置中心支持动态刷新(修改配置后无需重启服务)、版本控制、环境隔离(开发 / 测试 / 生产环境配置分离)、灰度发布等特性,解决了分布式系统中配置分散、难以同步的问题,尤其适用于频繁变更配置的场景。
- **服务熔断与限流:**熔断机制在依赖服务出现故障(如响应超时、频繁错误)时,自动切断调用,直接返回降级结果(如缓存数据、默认值),防止故障扩散引发级联崩溃;限流则通过限制请求速率(如每秒 1000 次),保护服务不被过载流量压垮。两者结合使用,确保系统在异常情况下仍能保持核心功能可用,提升整体容错能力。