Skip to content

Spring面试题

Updated: at 04:12 PM

1. Spring是什么?

1.1. 请说一下Spring为企业级开发带来的好处?

  1. 非侵入式:不强制要求实现Spring框架中的接口或者继承Spring框架中的类
  2. IOC容器:IOC容器帮助应用程序管理对象以及对象之间的依赖关系
  3. AOP:将所有横切关注功能封装到切面,通过配置的方式将横切关注的功能动态添加到目标代码,实现了业务逻辑和系统服务之间的分离
  4. 事务管理:Spring可以集成多种常用的持久层技术,并且提供了声明式事务管理,可以直接使用@Transactional注解完成事务的管理
  5. MVC:SpringMVC为web表示层提供了更好的解决方案

1.2. @Controller和@RestController的区别?

@RestController相当于@ResponseBody + @Controller

@ResponseBody的作用是将返回值结果序列化为JSON格式作为HTTP的返回值,如果不适用该注解,则Spring会返回一个视图

2. IOC

2.1. Spring中的Bean的作用域有哪些?

共有五种,可以通过@scope注解来设定

  1. singleton:<1>Bean以单例存在,IOC容器中只会存在一个共享的Bean实例对象 <2>可以设置lazy-init = true来实现懒加载(默认bean是启动时refresh()初始化的,而lazy-init的bean是context.getBean()初始化的 <3>适用于无状态的bean

  2. prototype:<1>prototype是原型类型,其在启动的时候不会实例化,而当我们获取bean的时候才会去创建一个对象,并且每次获取的都不是同一个对象 <2>适用于有状态的bean

    还有三种是在MVC的WebApplicationContext

  3. request:每一次HTTP请求会创建了一个bean,该bean仅在当前HTTP request内有效,当请求结束了该对象的生命周期也就结束了

  4. session:每个用户共享一个session bean,生命周期同http session,request.getSession()

  5. global session:所有用户共享一个global session bean,生命周期同http session,需要线程安全防护。

2.2. 什么是IOC和DI,并简要说明DI是如何实现的?

2.2.1. IOC

IOC是指的控制反转。<1>IOC意味设计好的对象交给IOC容器 <2>IOC容器还可以帮我们查找并且注入依赖,对象由主动变为被动,所以叫做反转 <3>IOC一个重点是系统运行的时候,动态的向某个对象提供它所需要的其他对象,这一点是通过依赖注入实现的。

2.2.2. DI

DI指的是依赖注入,依赖注入有两种方式实现:

  1. 使用XML手动注入:<1>通过配置property属性实现调用setter方法(底层是反射)来进行注入 <2>通过constructor-arg元素实现带参数构造器的注入
  2. 使用注解的自动注入(自动装配,见下面)

2.3. 什么是Spring中的自动装配?自动装配的方式有哪些?

Spring的自动装配就是Spring帮助我们在上下文自动查找,并自动给bean装配与其相关的属性

Spring中自动装配的方式有以下几种:

  1. byType:使用@Autowired先根据bean的type查找,如果有多个再根据bean的name查找
  2. byName:使用@Resource现根据bean的name查找,如果多个再根据bean的type查找
  3. 构造器装配:在类构造器上使用@Autowired注解,Spring会自动将构造函数的参数装配进来
  4. autodect:如果只有一个有参构造器,spring会自动注入,无需实现

2.4. Spring中的BeanFactory和ApplicationContext的区别是什么?

  1. BeanFactory:Spring中最底层的接口,只提供了最基本的容器的功能比如实例化bean和getBean,ApplicationContext则实现了BeanFactory接口,在beanFactory的基础上提供了很多扩展的功能,比如对bean的生命周期的管理。从refresh()函数就能看出来它有对整个bean生命周期的管理,比如beanPostProcessor以及AOP。
  2. 两者在装载bean上也有区别,beanFactory在启动的时候不会去实例化bean,只能从容器中getBean的时候才会去实例化。而ApplicationContext启动的时候就会去扫描对应的bean并且去实例化,但是可以为bean配置lazy-init进行延迟初始化。

2.5. Spring中的BeanFactory和FactoryBean的区别?

  1. beanFactory是接口,提供了IOC最基本的接口如创建bean实例和获取bean
  2. FactoryBean也是接口,但是是一个bean,通过创建getObject()方法可以灵活的生产和创建新的对象。本质上是给Bean的创建增加了简单的工厂模式和装饰模式。

DEMO:

@Component
public class FirstFactoryBean implements FactoryBean<TargetObject> {
    @Override
    public TargetObject getObject() throws Exception {
        TargetObject targetObject = new TargetObject();
        targetObject.setA(3);
        return targetObject;
    }
}
TargetObject factoryBean = (TargetObject) context.getBean("firstFactoryBean");
System.out.println(factoryBean.getA());
// 3

2.6. @Autowired和@Resource的区别是什么?

  1. <1> @Autowired注解是默认按照byType依赖对象,如果多个同类型才会去byName。<2>如果想用byName,可以配合@Qualifier来使用
@Autowired()
@Qualifier("demoB")
private DemoB demoB;
  1. <1>@Resource注解默认按照byName来自动注入,只有当多个name相同的才会使用byType的方式进行注入

2.7. Spring中@Component和@Bean的区别?

  1. Component注解表明⼀个类将作为组件类,并告诉Spring要为这个类创建Bean
  2. Bean常用于方法中,并且告诉Spring要为该返回的类创建Bean

2.8. Spring中如何区分不同的Bean,生产环境中要测试的Bean和开发环境不同怎么办?

  1. @Autowired的时候使用@Qualifier来标注不同的bean
  2. 使用@Primary配合@Component或者@Bean,优先加载@Primary的Bean(区分开发环境和生产环境)

2.9. Spring Bean的生命周期

  1. 构造器创建Bean
  2. 给Bean对象设置相关的属性(set方法)
  3. 调用bean的后置处理器的before方法
  4. 调用bean的初始化(init-method/post-construct接口的init())
  5. 调用bean的后置处理器
  6. bean对象创建完成
  7. bean对象的销毁(配置destroy()方法)

2.10. Spring IOC容器的启动过程

  1. 起点是new ClassPathXmlApplicationContext,传入xml配置文件的地址
  2. 执行核心方法refresh()
  3. 创建BeanFactory,创建一个DefaultListableBeanFactory,用于存放后续所有的bean。在这里通过扫描xml获得bean的定义并进行beanDefinition的加载,解析然后放入Map中
  4. 初始化BeanFactory工厂,添加一些特殊的后置处理器比如ApplicationContextAwareProcessor,为了让bean能够感知ApplicationContext,需要在后置处理器中设置其ApplicationContext。
  5. 调用BeanFactoryPostProcessors,在实例化bean之前再对beanDefinition做一些自定义修改
  6. 注册一些后置处理器,以及注册一些监听器等资源
  7. 实例化所有的非懒加载的单例对象,首先判断有没有设置内置的值处理器,比如处理xml属性中一些特殊${}之类的处理器,没有使用默认的EmbbedResolver。然后把所有的BeanDefinitionNames一个一个遍历,判断是不是单例的,是不是懒加载的,并且还不能是FactoryBean(单独处理),如果都不是则进入getBean()。这里面先检查是否在缓存Map中存在了,三层缓存用来解决循环依赖问题。(对于非单例对象不予解决循环依赖,直接抛出异常)

3. AOP

3.1. 什么是Spring中的AOP?

将那些与业务无关却为业务模块所共用调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度

3.2. AOP的实现原理?

  1. Spring AOP的实现原理是动态代理,动态代理有两种方式,一种是JDK代理另一种是cglib动态代理
  2. JDK动态代理通过反射来接收被代理的类,并且要求被代理的类必须实现一个接口,如果目标类没有实现接口,那么Spring AOP会选择使用cglib来动态代理。

3.3. 如何理解AOP中的连接点,切点,增强,引介,织入,切面?

a. 从额外逻辑的角度看

1. 横切关注点:分散在各个模块中的共同问题,如日志管理
2. 切面:横切关注点的实现。一般切面由切点和通知,(引介)来实现
 	1. 切点:指的是在哪个方法增加额外的逻辑。使用@PointCut定义切点,或者使用execution(* com.example.orderservice.*.*(..))表达式来匹配切点。
 	2. 通知:通知方法指的是在调用目标方法前后执行的额外逻辑
 	3. 引介:引介可以动态的让某个业务类实现原本没有被实现的接口

b. 从核心逻辑的角度看

  1. 增强:就是你想要增强的功能,比如 安全,事务,日志等
  2. 连接点:spring允许你使用通知的地方,例如方法的执行,异常的处理

c. 织入:织入是将增强添加到目标类具体连接点上的过程

img

4. 事务

4.1. @Transactional注解的添加位置?

  1. 接口实现类或者接口实现类的方法上,而不是接口
  2. 当添加到接口实现类中,表明接口实现类下的所有方法都被@Transactional修饰,当添加到某个方法上时,则只有该方法被修饰
  3. 需要注意的是,只有访问权限为public的方法才起作用

4.2. @Transcational什么时候触发回滚?

  1. @Transcational注解只有在抛出运行时异常或者错误的时候才会触发事务的回滚
  2. 常见的非运行时异常不会触发事务的回滚,但是我们平常做的业务处理的时候,需要捕获异常,所以可以手动抛出异常

4.3. Spring的事务传播机制?

  1. REQUIRED(默认):如果当前已经开启了事务,则复用当前事务,否则新建事务,适合增删改
  2. REQUIRED_NEW:不论当前是否开启事务,都新建一个事务,之前的事务会被挂起,适合log
  3. SUPPORTS:如果当前有事务,复用当前事务,如果当前没有事务,就以非事务方式运行,适合查
  4. NOT_SUPPORTS:以非事务方式执行,如果当前存在事务,就把当前事务挂起
  5. MANDATORY:支持当前事务,如果没有事务就抛出异常
  6. NEVER:非事务方式运行,如果存在事务就抛出异常
  7. NESTED:如果有活动事务,则运⾏在⼀个嵌套的事务中,如果没有活动事务,则新建一个事务。如果外部事务回滚,则里面的事务也回滚。否则如果只有里面的事务回滚,那么外层的事务不回滚

4.4. Spring的事务隔离级别?

  1. ioslation_default:默认的事务隔离级别,使用数据库默认的隔离级别
  2. ioslation_read_uncommited:读未提交
  3. ioslation_read_commited:读已提交
  4. ioslation_repeatable_read:可重复读
  5. ioslation_serializable:可串行化

4.5. @Transcational注解实现的原理?

  1. 事务开始的时候通过AOP机制,生成一个代理机制
  2. 声明的@Transcational的目标方法被拦截器拦截之后,会在目标方法开始执行之前创建并加入事务(autocommit = false),并执行目标方法的逻辑,然后根据执行情况是否异常来决定是否需要提交或者回滚

4.6. Spring支持的事务管理类型有哪些?

  1. Spring支持编程式事务管理和声明式事务管理,使用声明式事务可以解耦合,编程式事务可以更准确的控制事务
  2. 事务分为全局事务和局部事务,全局事务由应⽤服务器管理,局部事务和底层采⽤的持久化⽅案有关,这些事务的⽗接⼝都是PlatformTransactionManager。

5. 循环依赖/三级缓存

5.1. Spring是如何解决循环依赖的?

5.1.1. 哪些循环依赖是可以解决的?哪些是不可以解决的?

  1. 基于构造器的单例循环依赖不可以解决,出现循环依赖会抛异常
  2. 基于设置值setter的单例循环依赖是可以解决的
  3. 原型对象的循环依赖不可以解决

5.1.2. Spring的三级缓存机制

一级缓存(singletonObjects)

bean实例化完毕,属性注入完毕,初始化完毕后的单例放入一级缓存

二级缓存(earlySingletonObjects)

bean实例化完成,并从三级缓存拿出后创建代理对象放入二级缓存

三级缓存(singletonFactories)

bean实例化完成就放到三级缓存中

5.1.3. Spring如何解决循环依赖的?

使用了三级缓存解决循环依赖,在doGetBean的方法中,依次从第一、第二、第三级缓存中先尝试获取Bean。

img

A依赖B,B依赖A。创建A,在A实例化后放入三级缓存,然后注入属性,注入的时候发现依赖B,就去创建B,完成B的实例化后放入三级缓存,在进行B的属性注入的时候发现依赖A。在doGetBean(A)的时候发现三级缓存已经存在A的工厂,从中得到A的代理对象放入二级缓存并删除三级缓存。接着继续完成B的初始化,创建完后放入一级缓存。A再完成初始化放入一级缓存。

5.2. 为什么三级缓存?

假如只有二级缓存,由于Spring的历史原因会把属性注入和后置处理器两个过程分开。那么一开始放入二级缓存的是普通bean对象,经过AOP后放入二级缓存的是代理对象,把原来的普通bean对象覆盖。这样假如A同时循环依赖B和C,B和C在属性注入doGetBean(A)的时候可能会分别拿到普通bean和代理bean,造成错误。

其实如果不考虑Spring规范原则原因,规定放入二级缓存的都是代理对象就可以了。

6. Spring MVC

6.1. 什么是Spring MVC?简单介绍一下你对Spring MVC的理解?

SpringMVC是一个基于MVC设计模式实现的请求驱动类型的轻量级web框架,通过Model、View、Controller分离,将Web层进行职责解耦,把复杂的web应用分成清晰的几个部分,简化开发。

6.2. Spring MVC的流程

img

  1. request请求发送到DispatcherServlet,DispatcherServlet去查看RequestMapping得到Handle执行链(一个Handler处理器即controller,多个Handler拦截器)。

  2. DispatcherServlet用Handler请求HandlerAdpater去执行特定的Controller方法,得到ModelAndView返回给DispatcherServlet

  3. DispatcherServlet请求View Resolver解析ModelAndView并返回View,由DispatcherServlet将数据填充到视图,并放入response中返回给前端

6.3. Spring MVC的主要组件?

  1. DispatcherServlet:接收请求,响应结果。相当于转发器,减少了各个组件的偶合

  2. RequestMapping:根据请求的URL生成handlerChain

  3. HandlerAdapter:根据Handler执行具体的Controller方法

  4. View Resolver:解析视图,根据视图逻辑名解析出真正的视图

6.4. Spring MVC是如何进行全局异常处理的?

6.4.1. 实现Spring的异常处理接口HandlerExceptionResolver

实现HandlerExceptionResolver接口,并重写resolveException方法

6.4.2. @ExceptionHandler注解

@ControllerAdvice增强Handler,@ExceptionHandler注解声明异常处理具体类型

7. SpringBoot

7.1. SpringBoot自动装配的原理

  1. 通过@EnableAutoConfiguration注解,SpringBoot 在启动时会去依赖的starter包中寻找 resources/META-INF/spring.factories 文件,然后根据文件中配置的Jar包去扫描项目所依赖的Jar包,这类似于 Java 的 SPI 机制。
  2. 根据 spring.factories配置加载AutoConfigure类。
  3. 根据 @Conditional注解的条件,进行自动配置并将Bean注入Spring Context 上下文当中。也可以使用@ImportAutoConfiguration({MyServiceAutoConfiguration.class}) 指定自动配置哪些类。

7.2. SpringBootApplication注解

  1. @SpringBootConfiguration注解:继承了@Configuration,表明当前是java config类,会被自动加载到IOC容器中
  2. @EnableAutoConfiguration注解:自动装配
  3. @ComponentScan注解:扫描路径设置,扫描默认包或指定包下⾯的符合条件的组件并且加载,⽐如被 标注了Component注解的或者被标注了Controller注解的