【面试资料】框架篇 之 Spring

〇. 前言

本文将介绍Spring框架相关的面试题,中间可能会穿插一些源码内容;但重点仍将会放在 高频面试题的讲解 上,并不会逐条的、详尽的剖析各部分的源码。

具体内容包括:Spring、SpringMVC、SpringBoot。

如果不深究,【bean】这个概念可以理解为【对象】,依照 Spring 项目中我们编写的各种 所创建出的对象,只不过这一切都是交由 Spring IOC 完成的,所以我们给他取一个名字叫做【bean】。

一. Spring 单例 bean 是线程安全的吗?

(1)首先,我们要搞清楚 Spring 的 单例 和 多例

单例:所有请求用 同一个对象 来处理;
多例:每个请求用一个 新的对象 来处理。

可以通过 @Scope 注解来设置,而 Spring 默认是单例模式;
在这里插入图片描述


(2)其次,我们要知道导致 线程安全问题的原因 到底是什么?

① 线程安全问题的 根源不同线程同一资源 同时进行操作,导致不同步。

② 那么在Java中,满足以上条件的资源 即:静态变量 与 成员变量(也称:static变量 与 类的属性),它们对于 类 的 一个实例对象 来说是 独一份 的。

③ 与之相对的,类的具体 方法内部定义的 局部变量 可以无视线程安全问题。


(3)至此,我们可以明确:Spring 的 单例bean 不是 线程安全的!

  • 单例bean 代表 IOC容器中 只有 一个实例对象
  • Spring中的类 可以自由定义所需的 静态变量 与 成员变量

(4)但是,对于 单例bean 来说,如果类的内部并 没有可修改的变量,那么 单例bean 也被称为 无状态bean,也是线程安全的,这是一种特殊情况。

在这里插入图片描述

  • 对于 UserController 来说,count 就是 可修改 的成员变量,此时线程不安全!
  • 如果忽略 count,userService 也是成员变量,但是它 不可修改,UserController 是 无状态 bean,所以此时线程安全!

(5)通过理解上面的内容,我们也可以得出 解决 Spring 线程安全问题 的方法

  • 剔除所有可修改变量,改变代码逻辑,使单例bean全部转为 无状态 bean;【 “同一资源” 彻底不存在】
  • 使用 多例 bean,为每个线程请求创建单独的对象,不存在争抢;【“同一资源” → “多份资源”】
  • 为 可修改的公共资源 加锁。【打破 “同步”】

二. Spring AOP

AOP(Aspect Orient Programming),面向切面编程。

作用:用于将那些与业务无关,但却对多个对象产生影响的 公共行为和逻辑,抽取并封装为一个可重用的模块,这个模块被命名为【切面 Aspect】,减少系统中的重复代码,降低模块间的耦合度,提升系统的可维护性。

常见的AOP应用场景:

  1. 权限验证拦截,与 Filter、Interceptor 类似,但有区别【点击查看】
  2. 记录 操作日志
  3. 缓存 处理
  4. Spring中内置的事务处理

三. Spring bean 的 生命周期

  1. 通过 BeanDefinition 获取bean的定义信息
  2. 调用 构造函数 实例化bean
  3. bean的 依赖注入
  4. 处理 Aware 接口(BeanNameAware、BeanFactoryAware、ApplicationContextAware)
  5. bean的后置处理器BeanPostProcessor#before - 前置
  6. 初始化 方法(InitializingBean、init-method)
  7. bean的后置处理器BeanPostProcessor#after - 后置
  8. 销毁 bean

在这里插入图片描述

解释:

BeanDefinition:Spring容器在实例化时,把 xml 中 < bean > 的信息封装成一个 BeanDefinition 对象,Spring 会根据 BeanDefinition 创建Bean 对象;

也就是说,BeanDefinition 中包含了创建 bean 所需的全部信息
在这里插入图片描述

构造函数:Spring 向 BeanDefinition中获取 bean 的 构造函数,实例化 bean

此时,我们完成了 bean 的 创建(实例化)!但是现在仍是 空对象


依赖注入:IOC 动态的向某个对象 提供 它所需要的 其他对象,也就是处理 @Autowired @Resource 等属性;

Aware接口:如果bean实现了一些 Aware 接口,那么此时要进行处理,例如 BeanName / BeanFactory / ApplicationContextAware…

BeanPostProcessor # before:如果bean实现了 BeanPostProcessor 接口,那么执行它的 postProcessBeforeInitialization() 方法;

初始化:如果bean实现了 InitializingBean 接口,那么此时需要处理;

BeanPostProcessor # after:和 ⑤ 差不多,此时postProcessAfterInitialization() 方法;这里通常和 AOP代理模式 等相关,是重要的类增强步骤;

此时,我们完成了 bean 的 初始化赋值!bean 已经可以被使用;


销毁:如果bean实现了 DisposableBean 接口,那么销毁 bean 时会调用它的 destory 方法。


四. Spring bean 的 循环依赖

循环依赖:也称 循环引用,两个或两个以上的 bean 互相持有,形成 闭环
在这里插入图片描述

Spring 允许循环依赖的使用,但是这容易产生 死循环
在这里插入图片描述

简单来说,A 在创建的时候需要注入 B,此时我们先去创建 B,但是 B 的创建又需要注入 A,死循环产生。

不过不用担心,Spring 已经提供了处理方法 —— 三级缓存
在这里插入图片描述
注意:三级缓存 是针对 【bean 初始化】 过程设计的,其他过程三级缓存并不生效。

1. 一级缓存

一级缓存 的 作用效果显然无法帮助解决循环依赖问题❌

A 和 B 在注入彼此时,并没有办法向一级缓存索要对方的 完整 bean 对象。

2. 二级缓存

二级缓存 可以解决 非代理对象 的循环依赖问题!
在这里插入图片描述

假设 A 不是 代理对象,即不需要处理 AOP 的问题;那么当发生循环依赖时,直接向二级缓存索要对方的 半成品 bean 对象就可以解决问题,不会影响既定功能的使用。

即使 B 是代理对象也没关系;

但是,假设 A 是代理对象,当 B 创建时索要的是 功能残缺的 A,那么肯定是不符合要求的❌

3. 三级缓存

三级缓存 中存储的是 某个bean对象的 ObjectFactory 对象工厂,作用是 创建该 bean 的 代理对象,比半成品对象更加完整。
在这里插入图片描述

三级缓存已经可以解决 绝大多数 循环依赖问题,但是有些 特例 仍需我们手动解决。

4. 构造方法 中的 循环依赖

在这里插入图片描述

@Autowired 注入,即使用set方法,这是在 bean 创建之后需要考虑的问题,也就是 bean 初始化阶段,三级缓存当然可以解决;

如果使用手写的 构造方法 注入,那么在 【bean 创建】 的过程中就已经出现了问题,根本无法到达 bean 初始化 的步骤,缓存自然不会生效。

解决方法很简单:
在这里插入图片描述
使用 @Lazy 延迟加载,告诉对象A:什么时候 真正需要 使用 B 对象,什么时候再注入,而不是 创建 / 实例化 的时候就把 B 注入进来。

五. SpringMVC 的 执行流程

1. 视图(JSP…)

在这里插入图片描述
视图阶段 的 MVC 工作流程比较复杂,主要因为需要解析 ModelAndView,下面具体解释一下每一步具体做了什么。

首先,整个过程中最核心的部分毫无疑问是 【DispatcherServlet 前端控制器】,执行对象的是各个【Controller方法 —— Handler】。

接下来,前端控制器要向 三个组件 进行访问:

  1. HandlerMapping处理器映射器 返回一个 处理器执行链,可以理解为 “方法名 + 拦截器”;保证能找到目标方法,且保证拦截器能够正常拦截。【找 方法
  2. HandlerAdaptor处理器适配器 接收到具体的方法名,随即去寻找该方法,带着参数发送请求,方法执行… 完毕后仍由HandlerAdaptor 接收结果返回值,返回一个ModelAndView给前端控制器。【得 结果
  3. ViewResolver视图解析器 接收到了逻辑视图ModelAndView,将其解析成 真正的视图 View对象,返回给前端控制器。【析 视图
  4. 最后前端控制器 渲染 视图,即 将模型数据 Model 填充到 View,最后把 View 响应给用户。【填 模型

2. 前后端分离(接口、异步…)

在这里插入图片描述
前后分离阶段 最显著的特点:将方法 执行结果 全部转为 JSON 并传递。

直接抛弃视图了 ModelAndView 和 View 的环节,当 处理器适配器 带着参数找到具体Handler,执行并生成结果之后,直接由 HttpMessageConverter转换器 转成 JSON,完成响应。【结果 转 JSON

想要完成这一切,前提是方法上要加 @ResponseBody,或Controller上加 @RestController

六. SpringBoot 的 自动装配原理

在这里插入图片描述

说到 SpringBoot 自动配置,核心注解就是 启动类的 @SpringBootApplication 注解,而该注解内部仍有主次,最核心的是 @EnableAutoConfiguration 注解

在这里插入图片描述

@EnableAutoConfiguration 通过 @Import 注解引入 配置选择器,它会加载一个 spring.factories 的文件到META-INF目录下,里面就拥有 100多种常用配置类,配置类当中定义的Bean会根据 条件注解 指定的条件来决定 是否 要将该配置类 导入 Spring容器。

七. 常见注解

下面分Spring、SpringMVC、SpringBoot三个部分介绍常用注解,其中只挑选重要的、易混淆的进行特别说明。

1. Spring

在这里插入图片描述
@Component: 标注一个类为 Spring容器 的 Bean;而在 控制层、服务层、持久层 分别有对应的、功能相同的 专属注解。

2. SpringMVC

在这里插入图片描述
@RequestBody:接JSON,转对象;@ResponseBody:对象转JSON并传递。

@RequestParam:接收基础类型 或 对象类型的请求参数;

👉@RequestParam 有三个配置:

  1. required:是否 必须,默认为 true,必要参数;
  2. defaultValue:参数的默认值;
  3. value:指定 参数名

3. SpringBoot

在这里插入图片描述
三个都是【自动装配原理】中提到的,@SpringBootApplication 下的三个注解。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值