Spring 框架重点解析
1. Spring 框架中的单例 bean 是线程安全的吗?
不是线程安全的
- Spring 框架中有一个 @Scope 注解,默认的值是 singleton,即单例的;
- 因为一般在 Spring 的 bean 对象都是无状态的(在生命周期中不被修改的,比如正经开发下, service 层的和 dao 层的),没有线程安全问题;
- 如果在 bean 中定义了可修改的成员变量,是要考虑线程安全问题的,可以使用多例 prototype 或者加锁来解决;
2. 什么是 AOP,你们项目中有没有使用到 AOP 呢?
什么是 AOP ?
- 面向切面编程,用户将与业务无直接相关,但却对多个对象产生影响的公共行为和逻辑,将这些抽取成公共模块进行复用,降低耦合度;
- 可以理解为 一组业务的效果上的"增强buff" ;
原理就是动态代理
- 即目标方法的执行时机和结果可以由代理决定和处理;
你们项目有没有使用到 AOP ?
- 如 统一处理(拦截器、异常处理器、响应处理器),记录接口访问日志,缓存处理,Spring 实现的事务;
- 记录接口访问日志核心就是,使用 Spring AOP 中的环绕通知 + 切点表达式(找到记录日志的方法),通过环绕通知的参数获取请求方法的参数,获得这些参数后,进行一些业务;
joinPoint.proceed();
代表,切入点执行,也就是目标方法的执行;
3. Spring 中的事务是如何实现的?
对于编程式事务,事务的维护还是有目标方法的业务进行,没用到 AOP,对业务代码入侵性大,项目中很少使用;
而对于声明式事务,通过对方法加注解 @Transactional 声明该方法是一个事务
- 其本质是通过 AOP 功能,对方法前后进行拦截,在执行方法之前开启事务,在执行完目标方法之后进行提交或者捕获到异常进行回滚事务;
4. Spring 中事务失效的场景有哪些?
- 异常捕获处理,自己业务中处理了异常,没有抛出,这就意味着这个业务被 Spring 认为是“没有问题,无需回滚”
- 解决:根据实际业务决定是否要手动抛出、手动回滚;
- 抛出检查异常(throws),Spring 默认认为事务抛出的检查异常要被调用者处理,故没有回滚;
- 解决:设置注解 @Transactional 的 rollbackFor 属性为 Exception.class,则即使是检查异常,也会回滚;
- 非 public 方法导致的事务失效,由于 Spring 声明式事务的原理是 Spring AOP,所以创建代理、获取参数、添加事务通知,都要求方法是 public;
- 解决:改为 public;
5. Spring 中 Bean 的生命周期(如何去管理和创建 Bean 实例的?)
推荐文章:【JavaEE】深入了解Spring中Bean的可见范围(作用域)以及前世今生(生命周期)_bean的全局变量-CSDN博客
-
通过 BeanDefinition 获取 Bean 的定义信息,调用构造方法创建 Bean(空);
-
Bean 对象中成员变量的依赖注入;
-
处理 Aware 接口(Bean Name Aware、Bean Factory Aware、ApplicationContextAware);
-
Bean 处理器 BeanPostProcessor - 前置;
-
初始化方法(InitializingBean、init-method);
-
Bean 处理器 BeanPostProcessor - 后置;
- 可以去加强 Bean 对象,例如使其成为代理对象,可以被动态代理;
-
销毁 Bean;
6. Spring 中的循环引用(循环依赖)
6.1 循环依赖是什么?
6.2 Spring 的三种缓存
6.3 一级缓存 + 二级缓存解决普通 Bean 对象的循环依赖
显然,循环依赖的主要原因就是两个对象都是半成品,所以单凭一级缓存是无法解决循环依赖问题的;
6.4 三种缓存一起解决加强 Bean 对象的循环依赖
由于原始对象是没有被特殊处理的,所以只凭借一二级缓存无法注入一些加强对象,如代理对象;
- 因为在 6.3 中,将半成品注入给 B,是因为地址后面不会变化,这样即使注入了半成品,也不会影响结果;
- 而代理对象地址和性质跟原对象是不同的;
这个时候可以加上三级缓存来解决问题;
6.5 构造方法出现了循环依赖
由于 Bean 的生命周期中构造函数是第一个执行的,Spring 框架并不能解决构造器函数的依赖注入,而我们可以通过“延迟加载”来解决问题;
6.6 回答
- 循环依赖:循环依赖也就是循环引用,也就是一个或一个以上的 Bean 互相注入的现象,形成了闭环。如 A 依赖 B,B 依赖 A;
- 循环依赖在 Spring 中是允许存在的,Spring 框架依据三级缓存已经解决了大部分的循环依赖;
- 一级缓存:单例池,存放完整的 Bean 对象;
- 二级缓存:缓存 Bean 半成品对象;
- 三级缓存:缓存的是 ObjectFactory,表示对象工厂,用来创建某个 Bean(间接存储 Bean 对象);
- 当然,Spring 无法解决构造方法出现了循环依赖的问题,但是我们可以通过 @Lazy 注解让某个 Bean “延迟加载”来解决问题;
7. Spring MVC 的执行流程知道吗?
7.1 JSP
7.2 前后端分离
AOP 也大概在 3、4、5 起代理作用,感兴趣可以去研究,不作为重点;
7.3 回答
Spring MVC 的执行流程是这个框架最核心的内容了,有两种:
- 比较老旧的 JSP;
- 比较主流的前后端分离的异步、接口开发;
对于 JSP :
- 用户发送请求到 前端控制器 DispatcherServlet;
- 前端控制器 DispatcherServlet 收到请求调用 处理器映射器 HandlerMapping;
- 处理器映射器 HandlerMapping 将 url 映射到对应的处理器,生成 处理器执行链 HandlerExecutionChain 返回给 前端控制器 DispatcherServlet;
- 前端控制器 DispatcherServlet 调用 处理器适配器 HandlerAdapter;
- 处理器适配器 HandlerAdapter 经过适配调用具体的处理器(Handler/Controller),进行目标方法的参数处理以及返回值处理,并将结果转化为逻辑视图数据 ModelAndView 返回给 前端控制器 DispatcherServlet;
- 前端控制器 DispatcherServlet 将 ModelAndView 传给 视图解析器 ViewReslover;
- 视图解析器 ViewReslover 将 ModelAndView 解析成真实的视图数据 View 返回给 前端控制器 DispatcherServlet;
- 前端控制器 DispatcherServlet 根据 View 进行渲染视图;
- 前端控制器 DispatcherServlet 将页面响应给用户;
对于前后端分离:
- 用户发送请求到 前端控制器 DispatcherServlet;
- 前端控制器 DispatcherServlet 收到请求调用 处理器映射器 HandlerMapping;
- 处理器映射器 HandlerMapping 将 url 映射到对应的处理器,生成 处理器执行链 HandlerExecutionChain 返回给 前端控制器 DispatcherServlet;
- 前端控制器 DispatcherServlet 调用 处理器适配器 HandlerAdapter;
- 处理器适配器 HandlerAdapter 经过适配调用具体的处理器(Handler/Controller),进行目标方法的参数处理以及返回值处理,将数据直接响应给用户;
- 方法上如果添加了 @ResponseBody,处理器适配器 HandlerAdapter 将目标方法的返回值转化为 JSON 字符串再响应给用户;
8. Spring Boot 自动配置原理
我们知道,Spring 中有不少的 Bean 是自带的和程序员自己写的,那么我们通过启动类,是怎么让这些 Bean 自动配置的呢?
就需要理解一下 Spring Boot 自动配置原理
8.1 @SpringBootApplication 注解
- @SpringBootConfiguration:声明一个配置类;
- @ComponentScan:组件扫描,扫描的包路径下的 Bean 存到 Spring 容器中;
- @EnableAutoConfiguration:Spring Boot 实现自动化配置的核心注解;
8.2 @EnableAutoConfiguration 注解
- @Import 注解导入了配置选择器,读取该项目的以及引用的 jar 包的包路径下的 META-INF/spring.factories 文件中的所有配置类;
- 根据一些条件判断的注解去判断是否把配置类中的 Bean 放入 Spring 容器;
8.3 回答
- 在 Spring Boot 项目中的引导类有一个注解 @SpringBootApplication,这个注解其实是对三个注解进行了封装,分别是:
- @SpringBootConfiguration:声明一个配置类;
- @ComponentScan:组件扫描,扫描的包路径下的 Bean 存到 Spring 容器中;
- @EnableAutoConfiguration:Spring Boot 实现自动化配置的核心注解;
- 其中,核心注解 @EnableAutoConfiguration,通过 @Import 注解导入了配置选择器,读取该项目的以及引用的 jar 包的包路径下的 META-INF/spring.factories 文件中的所有配置类;
- 会有一些条件判断的注解(@Conditional 开头),例如:
- @ConditionalOnClass 判断是否有对应的字节码文件,如果有字节码,就加载该类把这个配置类所有的 Bean 放入 Spring 容器中;
- @ConditionalOnMissingBean 加在 @Bean 标注的方法上,判断是否有对应的 Bean,如果对应的 Bean 不存在,则将这个 Bean 放入 Spring 容器;