👨🎓作者简介:一位大四、研0学生,正在努力准备大四暑假的实习
🌌上期文章:Java后端面试:MySQL面试篇(底层事务、SQL调优)
📚订阅专栏:Java后端面试
希望文章对你们有所帮助
框架篇高频面试(Spring、SpringMVC、SpringBoot、MyBatis)
- Spring
- 单例bean是线程安全的吗?
- AOP相关面试题
- Spring事务失效的场景
- bean的生命周期
- bean的循环依赖(循环引用)
- SpringMVC-执行流程
- 视图阶段(JSP)
- 前后端分离阶段
- SpringBoot-自动装配原理
- Spring框架常见注解(Spring、SpringMVC、SpringBoot)
- MyBatis
- 执行流程
- 延迟加载使用及原理
- 一级、二级缓存
Spring
单例bean是线程安全的吗?
通过@Scope
注解可以设置bean是单例的还是多例的,默认是单例的。
而单例bean线程不安全
。
Spring的bean中都是注入无状态的对象,也就是说无法修改,不会有线程安全问题。
但是如果在bean中定义了可修改的成员变量,是要考虑线程安全问题的,当然可以使用多例模式或者加锁来解决。
AOP相关面试题
AOP很重要,在Spring也算是一个难点吧,但是像IOC、AOP这种Spring核心,不会的话说不过去,这里就简单说说。
1、何为AOP?
AOP称为面向切面编程
,用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可重用的模块,这个模块被称为切面(Aspect),减少系统中的重复代码,降低了模块间的耦合度,同时提高了系统的可维护性。
2、常见的AOP使用场景?
1、记录操作日志(自行学习@Aspect、切点、环绕通知)
2、缓存处理
3、Spring中内置的事务处理
3、Spring中的事务是如何实现的?(一定要提到AOP!)
Spring支持编程式事务管理和声明式事务管理两种方式,主要使用的是
声明式事务
。
声明式事务管理是建立在AOP
上的。本质是通过AOP功能,对方法的前后进行拦截,将事务处理的功能编织到拦截的方法中,也就是在目标方法开始之前加入一个事务,在执行目标方法之后执行情况提交或者回滚事务。(@Transactional注解就是这样的,其底层的实现(开启事务、提交/回滚事务)就是AOP追加的)
Spring事务失效的场景
这种问题能够体现出我们对Spring框架的深入理解,以及复杂业务的编码经验。
有三种非常常见的事务失效的情况:
1、异常捕获处理
对于转账的业务,我们会给整个业务增加@Transactional注解,如果中途发生了异常我们就会回滚。
但是如果这个业务方法中使用了try…catch,中途发生异常以后,@Transactional将会失效。
原因:事务通知只有捉到了目标抛出的异常,才能进行后续的回滚处理,如果目标自己处理掉异常,事务通知将会无法知悉
解决方法:在catch块中添加throw new RuntimeException(E)
,将异常给抛出,不要处理。
2、抛出检查异常
依旧是转账业务,这次不用try…catch,而是在方法体使用throws来抛出
检查异常
(类似FileNotFoundException这种非运行异常),这种情况也会导致事务失效。
原因:Spring默认只会回滚非检查异常
。
解决方法:配置rollbackFor
属性:@Transactional(rollbackFor=Exception.class),这样事务回滚就不只是针对运行时异常了。
3、非public方法
如果没有用public修饰方法,就会导致事务失效。
原因:Spring为方法创建代理、添加事务通知,前提条件都是该方法是public的
解决:改为public方法
bean的生命周期
这个在业务开发过程中重要吗?可能真的不太重要,这就是个纯八股。但是面试官还是挺喜欢问的,可能是掌握生命周期能显得你更了解框架,方便调试和解决问题。
1、通过
BeanDefinition
获取bean的定义信息
2、调用构造函数实例化bean
3、bean的依赖注入
4、处理Aware接口
5、Bean的后置处理器BeanPostProcessor-前置
6、初始化方法
7、Bean的后置处理器BeanPostProcessor-后置
8、销毁bean
bean的循环依赖(循环引用)
如果是两个bean,成员变量分别用对方来注入,这就是循环依赖。
这种方式是会出现死循环的,对于A,先实例化bean,再初始化,初始化的过程中需要设置b属性,而b是B类型的对象,可是容器中不存在B对象,就会去实例化B的bean对象,再去初始化,初始化的时候需要设置a属性,a是A类型的对象,但是容器中也不存在A对象,最后造成了死循环。
Spring框架已经帮助我们解决了大部分的循环依赖问题,通过的是三级缓存:
一级缓存:单力池,缓存已经经历了完整声明周期,即已经初始化完成的bean对象
二级缓存:缓存早期的bean对象(生命周期还没走完)
三级缓存:缓存的是对象工厂ObjectFactory,用来创建某个对象的
整体流程非常的复杂,大家可以自行去看相关资料来理解,写起来很麻烦,但要记住一级缓存+二级缓存可以解决大部分的循环依赖问题
,但是解决不了存在代理对象时的问题,而一级+二级+三级可以解决代理对象的循环依赖问题
,过程需要理解。
这些都是发生在初始化的时候,而如果我们在声明周期的构造方法换发出现了循环依赖,也就是注入的方式是用构造函数。解决是使用@Lazy
进行懒加载,什么时候需要对象了再进行bean对象的创建就好了。
SpringMVC-执行流程
如果要问到SpringMVC,那么执行流程可以说是非常重要的。
但是其实开发分为了两个阶段,一个是视图阶段(老旧的JSP等)
,另外一个是前后端分离阶段(接口开发,异步)
,现在我们都是用的前后端分离的开发,但是视图阶段的开发也是面试可能会问到的,很繁琐,得记住。
视图阶段(JSP)
1、对于一个浏览器发起的请求,视图阶段的执行流程如下:
1、浏览器发起请求给
前端控制器DispatcherServlet
2、查询handler:前端控制器根据前端请求,去处理器映射器HandlerMapping
中查询对应的执行方法
3、返回处理器执行链
:因为对于一个请求,除了要找到这个路径对应的类名.方法名,还需要判断它是否会被拦截器拦截,因此处理器映射器
不是直接返回方法给前端控制器
,而是返回一个处理器执行链(处理器执行链可以暂且理解成方法handler+拦截器
)
4、请求执行handler:处理器执行链不会被拦截,那么前端控制器
就要开始执行方法handler,这个执行需要向处理器适配器
去请求,而不是直接到处理器中(适配器模式),用适配器有两个好处:
(1)可以处理请求的参数
(2)处理返回值
5、处理器适配器向处理器请求
6、处理器将响应返回给处理器适配器,这个响应就是ModelAndView
7、处理器适配器再将ModelAndView
返回给前端控制器
8、前端控制器返回视图解析器ViewResolver
,这个视图解析器的作用是将逻辑视图解析为真正的视图View
9、视图解析器将真正的视图View
返回给前端控制器
10、前端控制器将视图渲染
2、SpringMVC执行过程中重要的四个组件
1、前端控制器DispatcherServlet(调度中心,处理所有的请求)
2、处理器映射器HandlerMapping(通过路径,查询方法handler,返回处理器执行链)
3、处理器适配器HandlerAdaptor(执行handler、处理handler中的参数)
4、视图解析器ViewResolver(将逻辑视图ModelAndView解析为真正的视图View)
前后端分离阶段
现在的开发不太一样,没有ModelAndView了,而是返回的Json数据,流程将会简化为:
这时候处理器Handler的执行是变化了:
1、处理器Handler的方法上添加
@ResponseBody
注解
2、底层会通过一个转化器HttpMessageConverter
将返回结果转化为JSON
格式响应给前端
SpringBoot-自动装配原理
这是SpringBoot最高频的面试题了,也是框架的最核心思想。
我们的SpringBoot启动类中一定会有一个注解:@SpringBootApplication
,这个注解底层包含了三个部分:
@SpringBootConfiguration
:与@Configuration相同,用来声明当前是个配置类
@ComponentScan
:组件扫描,默认扫描当前引导类所在包及其子包
@EnableAutoConfiguration
:SpringBoot实现自动化配置的核心注解
其中,@EnableAutoConfiguration
是实现自动化配置的核心注解,该注解底层通过@Import
来导入对应的配置选择器。
其内部就是读取该项目和该项目引用的jar包的classpath路径下
META-INF/spring.factories
文件中的所配置的类的全类名。在这些配置类中所定义的Bean会根据条件注解所指定的条件来决定
是否需要将其导入到Spring容器中。
这个条件判断会有@ConditionalOnClass
这样的注解,判断是否有对应的class文件(字节码文件),若有则加载该类,把这个配置类中所有的Bean都放入Spring容器中使用。
Spring框架常见注解(Spring、SpringMVC、SpringBoot)
Spring的注解主要是用来进行bean的实例化以及依赖注入的,SpringMVC的注解主要是用来处理请求和响应的,而SpringBoot的注解尽量说一下跟自动装配有关系的,被问到不要混淆着讲。
1、Spring注解:
注解 | 说明 |
---|---|
@Componennt、@Controller、@Service、@Repository | 使用在类上,用于实例化bean |
@Autowired | 在字段上根据类型依赖注入 |
@Qualifier | 结合@Autowired一起使用,用于根据名称进行依赖注入 |
@Scope | 标注Bean的作用返回(单例or多例) |
@Configuration | 指定当前类是一个Spring配置类,当创建容器时会从该类上加载注解 |
@ComponentScan | 用于指定Spring在初始化容器时要扫描的包 |
@Bean | 用在方法上,标注该方法的返回值 会存到Spring容器中 |
@Import | 该注解导入的类会被Spring加载到IOC容器中 |
@Aspect、@Before、@After、@Around、@Pointcut | 用于AOP(切面、前置通知、后置通知、环绕通知、切入点表达式) |
2、SpringMVC注解:
注解 | 说明 |
---|---|
@RequestMapping(也有衍生的PostMapping、GetMapping等) | 用于映射请求路径,定义在类上或方法上,用在类上则该类中的所有方法都是以该地址作为父路径的 |
@RequestBody | 接受http请求的json数据,将json转化为java对象 |
@ResponseBody | 将controller方法返回的对象转化为json对象响应给客户端 |
@RequestParam | 指定请求参数的名称(和数据库字段名不一致时使用) |
@PathViriable | 从请求路径下获取请求参数/user/{id},传递给方法的形式参数(Restful风格) |
@RequestHeader | 获取指定的请求头数据 |
@RestController | @Controller+@ResponseBody |
3、SpringBoot注解:
注解 | 说明 |
---|---|
@SpringBootConfiguration | 配置文件 |
@EnableAutoConfiguration | 打开自动配置 |
@ComponentScan | Spring组件扫描 |
MyBatis
执行流程
1、读取MyBatis配置文件mybatis-config.xml,加载运行环境(数据库相关信息)和mapper映射文件
2、构造会话工厂SqlSessionFactory,会话工厂是全局唯一的
3、会话工厂创建SqlSession对象(包含了执行SQL语句的所有方法),每次操作都会创建出一个会话
4、Executor执行器是真正操作数据库的接口,同时负责了查询缓存的维护
5、Executor接口的执行方法中有一个MappedStatement类型的参数,封装了映射信息
6、输入参数的映射(将java类型转化为数据库处理的类型)
7、输出结果映射(将数据库操作的结果映射成java类型)
延迟加载使用及原理
1、延迟加载是需要用到数据的时候才会加载,不需要的时候就不会加载。
2、MyBatis是支持延迟加载的,但是默认是没有开启的。可以局部开启或者全局开启。
3、延迟加载的底层原理:
(1)使用
CGLIB
创建目标对象的代理对象
(2)当调用方法的时候,进入拦截器invoke方法,发现目标方法是null值,执行SQL查询
(3)获取数据后,调用set方法设置属性值,再继续查询目标方法,就有值了
一级、二级缓存
一级缓存:基于HashMap本地缓存,其存储作用域为SqlSession,当SqlSession进行flush或close之后,该Session中的Cache就被清空(默认开启)
二级缓存:基于namespace和mapper的作用域起作用的,不依赖于SqlSession,默认也是基于HashMap存储,需要单独开启(通过核心配置文件)