摘要:本文围绕 SpringBoot、MyBatis 重点问题展开,涵盖AOP 、Spring 事务、循环依赖解决、SpringBoot 自动配置及常见注解,还有 MyBatis 执行流程、延迟加载等内容。

介绍一下AOP

Spring AOP是面向切面编程,通过动态代理在不修改源码的情况下,给目标方法添加额外功能。核心概念有目标对象,即被增强的原始对象;连接点,是程序执行中的特定点,比如方法执行;切入点,是要增强的连接点,通过表达式定义;通知,是增强的逻辑,有前置、后置、环绕、异常、最终通知;切面,是切入点和通知的组合;织入,是将切面应用到目标对象生成代理对象的过程。

在Spring AOP中,代理是实现AOP的方式,当目标对象实现接口时,默认用JDK动态代理,通过接口生成代理类;如果没有实现接口,就用CGLIB代理,通过继承目标类生成代理子类。


Spring AOP(面向切面编程)是 Spring 核心特性之一,核心目标是将与业务无关的通用逻辑(如日志、事务、权限校验)从业务代码中抽离,以 "切面" 的形式统一管理,实现代码解耦和复用。

在 AOP 中有以下几个核心概念

切面(Aspect):抽离的通用功能模块(比如日志切面、事务切面);

切点(Pointcut):指定切面织入到哪些业务方法(比如所有com.service包下的方法);

通知(Advice):切面的执行时机(前置通知 @Before、后置通知 @After等);

织入(Weaving):将切面应用到目标对象的过程(Spring 中是运行时织入)。

AOP 代理(AOP Proxy):Spring AOP 通过动态代理生成的、包含切面逻辑的代理对象,Spring AOP 的两种代理方式有JDK 动态代理和CGLIB 动态代理。

Spring AOP 的两种代理方式

动态代理是在运行时动态生成代理对象,而不是在编译时。它允许开发者在运行时指定要代理的接口和行为,从而实现在不修改源码的情况下增强方法的功能。

1. JDK 动态代理(原生方式)

要求目标对象必须实现至少一个接口,底层通过「接口代理」实现:

  • 运行时动态生成一个实现了目标对象所有接口的代理类(字节码);

  • 代理类的所有方法调用,都会转发到 InvocationHandler 接口的 invoke 方法,开发者在方法中实现增强逻辑,再调用目标方法。

JDK 动态代理在运行时,利用 Proxy.newProxyInstance() ,动态创建一个实现这些接口的代理类的实例。

当你调用代理对象的任何一个方法时,这个调用都会被转发到 InvocationHandler 接口的 invoke 方法中。在 invoke 方法里,我们就可以在调用原始方法(目标方法)之前或之后,加入我们自己的增强逻辑。

缺点:强制要求目标对象实现接口;只能代理接口中定义的方法,无法代理目标类的非接口方法


2. CGLIB 动态代理(第三方、基于子类)

CGLIB 不要求目标对象实现接口,底层通过「子类继承」实现:

  • 它在运行时,动态生成目标类的子类作为代理类;

  • 代理类重写目标类的非 final 方法,在重写方法中植入增强逻辑,再调用目标类的原始方法。

缺点:依赖第三方库;无法代理 final 方法 / 类;性能略低于 JDK 8 + 的动态代理。

Spring中的事务是如何实现的

候选人:

spring实现的事务本质就是aop完成,对方法前后进行拦截,在执行方法之前开启事务,在执行完目标方法之后根据执行情况提交或者回滚事务。

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

Spring Boot通过Spring框架的事务管理模块来支持事务操作。事务管理在Spring Boot中通常是通过 @Transactional 注解来实现的。事务可能会失效的一些常见情况包括:

  1. 未捕获异常: 如果一个事务方法中发生了未捕获的异常,并且异常未被处理或传播到事务边界之外,那么事务会失效,所有的数据库操作会回滚。
  2. 受检异常: 默认情况下,Spring对非受检异常(RuntimeException或其子类)进行回滚处理,这意味着当事务方法中抛出这些异常时,事务会回滚。
  3. 事务传播属性设置不当: 如果在多个事务之间存在事务嵌套,且事务传播属性配置不正确,可能导致事务失效。特别是在方法内部调用有 @Transactional 注解的方法时要特别注意。
  4. 多数据源的事务管理: 如果在使用多数据源时,事务管理没有正确配置或者存在多个 @Transactional 注解时,可能会导致事务失效。
  5. 跨方法调用事务问题: 如果一个事务方法内部调用另一个方法,而这个被调用的方法没有 @Transactional 注解,这种情况下外层事务可能会失效。
  6. 事务在非公开方法中失效: 如果 @Transactional 注解标注在私有方法上或者非 public 方法上,事务也会失效

候选人:

第一个,如果方法上异常捕获处理,自己处理了异常,没有抛出,就会导致事务失效,所以一般处理了异常以后,别忘了抛出去就行了

第二个,如果方法抛出检查异常,如果报错也会导致事务失效,最后在spring事务的注解上,就是@Transactional上配置rollbackFor属性为Exception,这样别管是什么异常,都会回滚事务

第三,我之前还遇到过一个,如果方法上不是public修饰的,也会导致事务失效

Spring中的循环引用



候选人:

嗯,好的,我来解释一下

循环依赖:循环依赖其实就是循环引用,也就是两个或两个以上的bean互相持有对方,最终形成闭环。比如A依赖于B,B依赖于A

循环依赖在spring中是允许存在,spring框架依据三级缓存已经解决了大部分的循环依赖

①一级缓存:单例池,缓存已经经历了完整的生命周期,已经初始化完成的bean对象

②二级缓存:缓存早期的bean对象(生命周期还没走完)

③三级缓存:缓存的是ObjectFactory,表示对象工厂,用来创建某个对象的

那具体解决流程清楚吗?




候选人:

第一,先实例A对象,同时会创建ObjectFactory对象存入三级缓存singletonFactories

第二,A在初始化的时候需要B对象,这个走B的创建的逻辑

第三,B实例化完成,也会创建ObjectFactory对象存入三级缓存singletonFactories

第四,B需要注入A,通过三级缓存中获取ObjectFactory来生成一个A的对象同时存入二级缓存,这个是有两种情况,一个是可能是A的普通对象,另外一个是A的代理对象,都可以让ObjectFactory来生产对应的对象,这也是三级缓存的关键

第五,B通过从通过二级缓存earlySingletonObjects 获得到A的对象后可以正常注入,B创建成功,存入一级缓存singletonObjects

第六,回到A对象初始化,因为B对象已经创建完成,则可以直接注入B,A创建成功存入一次缓存singletonObjects

第七,二级缓存中的临时对象A清除

怎么解决构造方法中循环依赖?

候选人:

由于bean的生命周期中构造函数是第一个执行的,spring框架并不能解决构造函数的的依赖注入,可以使用@Lazy懒加载,什么时候需要对象再进行bean对象的创建

SpringMVC的执行流程知道



候选人:

  1. 用户发送出请求到前端控制器DispatcherServlet,这是一个调度中心
  2. DispatcherServlet收到请求调用HandlerMapping(处理器映射器)
  3. HandlerMapping找到具体的处理器(可查找xml配置或注解配置),生成处理器对象及处理器拦截器(如果有),再一起返回给DispatcherServlet
  4. DispatcherServlet调用HandlerAdapter(处理器适配器)
  5. HandlerAdapter经过适配调用具体的处理器(Handler/Controller)
  6. Controller执行完成返回ModelAndView对象
  7. HandlerAdapter将Controller执行结果ModelAndView返回给DispatcherServlet
  8. DispatcherServlet将ModelAndView传给ViewReslover(视图解析器)
  9. ViewReslover解析后返回具体View(视图)
  10. DispatcherServlet根据View进行渲染视图(即将模型数据填充至视图中)
  11. DispatcherServlet响应用户

当然现在的开发,基本都是前后端分离的开发的,并没有视图这些,一般都是handler中使用Response直接结果返回

Springboot自动配置原理

候选人:

在Spring Boot项目中的引导类上有一个注解@SpringBootApplication,这个注解是对三个注解进行了封装,分别是:

  • @SpringBootConfiguration

  • @EnableAutoConfiguration

  • @ComponentScan

其中@EnableAutoConfiguration是实现自动化配置的核心注解。

该注解通过@Import注解导入对应的配置选择器。关键的是内部就是读取了该项目和该项目引用的Jar包的的classpath路径下META-INF/spring.factories文件中的所配置的类的全类名。

在这些配置类中所定义的Bean会根据条件注解所指定的条件来决定是否需要将其导入到Spring容器中。

一般条件判断会有像@ConditionalOnClass这样的注解,判断是否有对应的class文件,如果有则加载该类,把这个配置类的所有的Bean放入spring容器中使用。

Spring 的常见注解有哪些?

候选人:

第一类是:声明bean,有@Component、@Service、@Repository、@Controller

第二类是:依赖注入相关的,有@Autowired、@Qualifier、@Resourse

第三类是:设置作用域 @Scope

第四类是:spring配置相关的,比如@Configuration,@ComponentScan 和 @Bean

第五类是:跟aop相关做增强的注解 @Aspect,@Before,@After,@Around,@Pointcut

Springboot常见注解有哪些?

候选人:

Spring Boot的核心注解是@SpringBootApplication , 他由几个注解组成 :

  • @SpringBootConfiguration: 组合了- @Configuration注解,实现配置文件的功能;

  • @EnableAutoConfiguration:打开自动配置的功能,也可以关闭某个自动配置的选项

  • @ComponentScan:Spring组件扫描

MyBatis执行流程

候选人:

①读取MyBatis配置文件:mybatis-config.xml加载运行环境和映射文件

②构造会话工厂SqlSessionFactory,一个项目只需要一个,单例的,一般由spring进行管理

③会话工厂创建SqlSession对象,这里面就含了执行SQL语句的所有方法

④操作数据库的接口,Executor执行器,同时负责查询缓存的维护

⑤Executor接口的执行方法中有一个MappedStatement类型的参数,封装了映射信息

⑥输入参数映射

⑦输出结果映射

Mybatis是否支持延迟加载?

候选人:

支持的

延迟加载的意思是:就是在需要用到数据时才进行加载,不需要用到数据时就不加载数据。

Mybatis支持一对一关联对象和一对多关联集合对象的延迟加载

在Mybatis配置文件中,可以配置是否启用延迟加载lazyLoadingEnabled=true|false,默认是关闭的

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

候选人:

延迟加载在底层主要使用的CGLIB动态代理完成的

第一是,使用CGLIB创建目标对象的代理对象,这里的目标对象就是开启了延迟加载的mapper

第二个是当调用目标方法时,进入拦截器invoke方法,发现目标方法是null值,再执行sql查询

第三个是获取数据以后,调用set方法设置属性值,再继续查询目标方法,就有值了

Mybatis的一、二级缓存会吗?

候选人:

mybatis的一级缓存: 基于 PerpetualCache 的 HashMap 本地缓存,其存储作用域为 Session,当Session进行flush或close之后,该Session中的所有Cache就将清空,默认打开一级缓存

关于二级缓存需要单独开启

二级缓存是基于namespace和mapper的作用域起作用的,不是依赖于SQL session,默认也是采用 PerpetualCache,HashMap 存储。

如果想要开启二级缓存需要在全局配置文件和映射文件中开启配置才行。


感谢你的阅读!✿

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐