Skip to content

问:在Spring框架中,Bean的生命周期是怎样的?

  1. 实例化:Spring容器启动的时候,会扫描并创建Bean的实例。
  2. 注入:给Bean的属性赋值了,一般用@Autowired注入其他依赖的Bean。
  3. Aware回调:如果这个Bean实现了一些Spring的Aware接口,Spring就会回调这些接口的方法,这样Bean就能拿到容器的信息了,比如上下文,比如BeanFactory。
  4. 前置处理:Spring会调用所有BeanPostProcessor的postProcessBeforeInitialization()方法。这个接口特别有用,比如我们可以在这里对Bean做一些自定义的修改,比如生成代理对象,或者给某些字段加密解密之类的。
  5. 初始化方法:一般使用@PostConstruct注解的方法,也可以实现初始化bean接口,还能通过配置init方法。
  6. 后置处理:Spring还会再走一遍BeanPostProcessor的postProcessAfterInitialization()方法。比如Spring AOP就是在这里给Bean生成代理对象的。这时候Bean才算完全准备好,可以被其他组件依赖了。
  7. 销毁的时候会先调用销毁前方法:一般使用@PreDestroy注解的方法,也可以实现销毁bean接口,或者配置销毁方法。我们一般会在这里关闭连接或者释放资源。

问:Spring如何处理循环依赖?

通过三级缓存机制:

  1. 一级缓存(singletonObjects):存完全初始化好的Bean。
  2. 二级缓存(earlySingletonObjects):存提前暴露的半成品Bean(已经实例化但还没填充属性)。
  3. 三级缓存(singletonFactories):存Bean的工厂对象,用来生成半成品Bean。 举个例子,比如现在A和B互相依赖:
  4. Spring先创建A这时候它还是个“半成品”,会被放进三级缓存。
  5. 接下来给A注入属性,发现它依赖B,于是去创建B。
  6. 创建B实例后,同样进入属性注入阶段,发现它需要A,于是从三级缓存里拿到之前那个半成品的A(虽然没初始化完,但对象已经存在了)。
  7. 接着B完成属性注入和初始化,变成成品,放进一级缓存。
  8. 最后回到A,注入已经完整的B,完成自己的初始化,也放进一级缓存。

问:如果是构造器注入导致的循环依赖呢?

这种情况只能调整代码,比如把其中一个Bean的注入方式改成setter方法注入。 或者用@Lazy延迟加载,这样Spring不会直接注入真实的bean,而是先注入一个代理对象。等真正调用bean的方法时,才会触发它的初始化。

问:Spring IoC容器的启动流程?

  1. 容器初始化准备:记录启动时间、初始化状态标志,检查环境配置(比如JVM参数、配置文件是否存在)。
  2. 获取BeanFactory:创建或刷新内部的BeanFactory(默认是DefaultListableBeanFactory),然后加载Bean的定义(比如解析XML或扫描注解)。
  3. 配置BeanFactory:给BeanFactory设置一些基础配置,比如类加载器、表达式解析器(StandardBeanExpressionResolver)、属性编辑器(PropertyEditorRegistrar)。
  4. 执行BeanFactory后置处理:调用所有BeanFactoryPostProcessor,允许修改Bean的定义信息。比如PropertySourcesPlaceholderConfigurer会替换占位符${...}为实际值。
  5. 注册BeanPostProcessor:找到所有BeanPostProcessor的Bean定义,提前实例化它们,并按优先级注册到BeanFactory中。这一步之后,这些后置处理器就能在后续Bean初始化时被调用了。
  6. 初始化消息源、事件广播器等:初始化国际化消息源(MessageSource)、应用事件广播器(ApplicationEventMulticaster)。如果用户没自定义,会用默认实现。
  7. 注册监听器:注册所有实现了ApplicationListener的Bean,监听容器事件(如ContextRefreshedEvent)。
  8. 实例化非懒加载的单例Bean:容器会遍历所有Bean定义,实例化所有非懒加载的单例Bean。
  9. 完成启动。

问:Spring的IoC是什么?

IoC全称是控制反转(Inversion of Control),它的核心思想是将对象的创建和依赖管理的控制权交给框架,而不是由开发者手动控制。比如以前我们通过new关键字直接创建对象,现在Spring容器负责创建这些对象并注入它们的依赖,这就是依赖注入(DI),而DI是IoC的一种实现方式。IoC让代码解耦。

问:Spring的AOP是什么?

AOP是面向切面编程(Aspect-Oriented Programming),它用来处理那些分散在代码各处的横切关注点,比如日志、事务、权限校验。避免代码冗余。 使用方法:

  1. 用注解@Around定义切点,通过表达式匹配需要拦截的方法(比如@Around("execution(* com.example.service..(..))"))
  2. 编写切面逻辑,比如在方法执行前后记录日志。
  3. 用注解@Aspect声明切面类。

问:AOP的实现原理是什么?

AOP核心是通过动态代理来实现的:

  1. JDK动态代理:基于接口的代理。如果目标类实现了至少一个接口,代理对象会实现相同的接口,并在调用方法时通过InvocationHandler来插入切面逻辑。
  2. CGLIB代理:基于子类继承的代理。如果目标类没有实现接口,Spring会用CGLIB库生成目标类的子类代理。这个子类会覆盖父类的方法,并在方法调用前后插入切面逻辑。

问:如何通过Spring实现声明式事务管理?(@Transactional)

主要是通过@Transactional注解来实现的。只需要在方法或类上加上这个注解,Spring就会在运行时为这个方法创建一个事务代理,自动处理事务的开启、提交或回滚。原理就是AOP。

问:@Transactional注解的失效场景有哪些?

  1. 同一个类里面内部方法调用:Spring事务基于AOP代理,内部调用绕过了代理对象,直接调用目标方法。
  2. 异常类型不匹配
  3. 方法不是public
  4. 数据库引擎不支持事务
  5. 手动捕获了异常但未抛出,也没标记回滚
  6. 多线程调用:事务绑定到线程上下文,子线程无法继承。
  7. 未启用事务管理:Spring Boot应用需确保主类有@EnableTransactionManagement

问:事务隔离级别及Spring中的配置方式?

  • 未提交读(Read Uncommitted):允许脏读,也就是可能读取到其他会话中未提交事务修改的数据
  • 提交读(Read Committed):只能读取到已经提交的数据。 Oracle 等多数数据库默认都是该级别(不 重复读)
  • 可重复读(Repeated Read):在同一个事务内的查询都是事务开始时刻一致的,Mysql 的InnoDB默 认级别。在 SQL 标准中,该隔离级别消除了不可重复读,但是还存在幻读(多个事务同时修改同一 条记录,事务之间不知道彼此存在,当事务提交之后,后面的事务修改的数据将会覆盖前事务,前 一个事务就像发生幻觉一样)
  • 可串行化(Serializable):完全串行化的读,每次读都需要获得表级共享锁,读写相互都会阻塞。

Spring框架提供了声明式(xml配置或注解配置)和编程式事务管理,可以配置不同的事务隔离级别。

问:Spring Boot自动配置的原理(@EnableAutoConfiguration与spring.factories)?

  1. 启动应用:当Spring Boot应用启动时,@SpringBootApplication注解中的@EnableAutoConfiguration被激活。
  2. 加载spring.factories:Spring Boot扫描所有JAR包中的META-INF/spring.factories文件,获取EnableAutoConfiguration键值对应的自动配置类列表。
  3. 条件评估:根据条件注解对每个自动配置类进行评估,决定是否加载。
  4. 配置生效:符合条件的自动配置类被加载,并根据配置类的内容自动配置应用程序。

通过这种机制,Spring Boot实现了智能化的自动配置,极大地简化了开发者的配置工作,提高了开发效率。

问:Spring boot中的监视器是什么?如何通过Actuator实现应用监控?

Spring boot actuator 是 spring 启动框架中的重要功能之一。Spring boot 监视器可帮助您访问生产 环境中正在运行的应用程序的当前状态。有几个指标必须在生产环境中进行检查和监控。即使一些 外部应用程序可能正在使用这些服务来向相关人员触发警报消息。监视器模块公开了一组可直接作 为 HTTP URL 访问的 REST 端点来检查状态。

问:如何优化Spring Boot应用的启动速度?

  1. 使用懒加载:延迟加载非必须立即使用的Bean,从而减少启动时的初始化时间。
  2. 创建扫描索引:在Spring 5版本中,使用@SpringBootApplication注解的index属性,可以在项目打包时生成META-INF/spring.components文件,包含所有需要扫描的组件类,加快启动时扫描速度。
  3. 减少自动配置:手动排除不必要的自动配置,可以减少启动时的扫描和配置时间。
  4. 优化依赖:检查项目的依赖关系,避免引入不必要的库,依赖树瘦身。
  5. 异步初始化:利用@Async注解异步初始化。

问:为什么选择Spring Boot而不是传统Spring MVC?

  1. 简化配置:Spring Boot通过自动配置机制,根据项目依赖自动配置应用程序,减少了手动配置的工作量。
  2. 提高开发效率:通过Spring Initializr可以快速生成项目骨架,专注于业务逻辑的实现。
  3. 便捷的部署和运维:内置了Tomcat、Jetty等服务器,可以直接打包成可执行的JAR文件,简化了部署流程。
  4. 便捷的运维:Actuator模块提供了丰富的监控和管理功能,方便监控应用状态。
  5. 微服务支持:天然支持微服务架构,结合Spring Cloud可以快速实现服务注册、配置中心等功能。

问:Spring Boot如何整合Tomcat?能否替换为Jetty?

Spring Boot整合Tomcat非常简单,因为Tomcat是Spring Boot默认的嵌入式Web服务器。通常情况下,只需要在项目的构建文件中添加Spring Boot的Web Starter依赖,Spring Boot就会自动配置并嵌入Tomcat服务器。

要替换为Jetty,在spring-boot-starter-web依赖中排除Tomcat,同时添加Jetty依赖即可。

问:Spring Boot如何实现热部署?

Spring Boot 通过 动态重载技术 实现热部署,无需重启服务即可使代码变更生效。

  • Spring DevTools:官方推荐,引入依赖并开启ide自动编译即可使用。
  • JRebel:企业级方案,商业工具。

问:MyBatis和Hibernate的主要区别是什么?为什么选择MyBatis?

MyBatis 和 Hibernate 是 Java 领域两大主流 ORM 框架,本质区别在于对 SQL 的控制权归属,这直接决定了它们的适用场景。

  • MyBatis: 开发者直接编写SQL语句,灵活性高。学习曲线相对较低。适合性能要求高、需要精细控制SQL的项目。
  • Hibernate:自动生成SQL,简化开发。学习曲线相对较高。适合需要快速开发、模型稳定,标准 CRUD 为主。

选择MyBatis,主要就是看重它的学习曲线相对较低,以及直接编写SQL语句,灵活性高。

问:如何优化MyBatis的批量插入性能?

批量插入:通过批量插入的方式 ExecutorType.BATCH + rewriteBatchedStatements=true,减少数据库连接和SQL执行次数,提高性能。

如果在数据量特别大。同时条件允许的情况下,可以将非唯一索引暂时禁用,然后进行批量插入,最后再启用,也会提高很多性能。

问:MyBatis的#{}${}区别及SQL注入防范?

  • 使用#{}表示占位符,MyBatis会自动对参数进行转义,防止SQL注入。
  • 使用${}表示动态SQL,直接拼接SQL,容易导致SQL注入。

页脚:版权前显示的信息