Java后端面试:框架篇高频面试(Spring、SpringMVC、SpringBoot、MyBatis)

👨‍🎓作者简介:一位大四、研0学生,正在努力准备大四暑假的实习
🌌上期文章:Java后端面试:MySQL面试篇(底层事务、SQL调优)
📚订阅专栏:Java后端面试
希望文章对你们有所帮助

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打开自动配置
@ComponentScanSpring组件扫描

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存储,需要单独开启(通过核心配置文件)

  • 22
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

布布要成为最负责的男人

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值