Spring面试题
# 1. Spring框架中的单例bean是线程安全的吗?
不是线程安全的。当多用户同时请求一个服务时,容器会给每个请求分配一个线程,这些线程会并发执行业务逻辑。如果处理逻辑中包含对单例状态的修改,比如修改单例的成员属性,就必须考虑线程同步问题。Spring框架本身并不对单例bean进行线程安全封装,线程安全和并发问题需要开发者自行处理。
通常在项目中使用的Spring bean是不可变状态(如Service类和DAO类),因此在某种程度上可以说Spring的单例bean是线程安全的。如果bean有多种状态(如View Model对象),就需要自行保证线程安全。最简单的解决办法是将单例bean的作用域由“singleton”变更为“prototype”。
# 2. 什么是AOP?
AOP,即面向切面编程,在Spring中用于将那些与业务无关但对多个对象产生影响的公共行为和逻辑抽取出来,实现公共模块复用,降低耦合。常见的应用场景包括公共日志保存和事务处理。
# 3. 你们项目中有没有使用到AOP?
我们之前在后台管理系统中使用AOP来记录系统操作日志。主要思路是使用AOP的环绕通知和切点表达式,找到需要记录日志的方法,然后通过环绕通知的参数获取请求方法的参数,例如类信息、方法信息、注解、请求方式等,并将这些参数保存到数据库。
# 4. Spring中的事务是如何实现的?
Spring实现事务的本质是利用AOP完成的。它对方法前后进行拦截,在执行方法前开启事务,在执行完目标方法后根据执行情况提交或回滚事务。
# 5. Spring中事务失效的场景有哪些?
在项目中,我遇到过几种导致事务失效的场景:
- 如果方法内部捕获并处理了异常,没有将异常抛出,会导致事务失效。因此,处理异常后应该确保异常能够被抛出。
- 如果方法抛出非RuntimeException/Error,即检查型异常(checked exception),并且没有在
@Transactional
注解上配置rollbackFor
属性为Exception
,那么异常发生时事务可能不会回滚。 - 如果事务注解的方法不是公开(public)修饰的,也可能导致事务失效。
- 自调用:如果在类内部方法直接调用带@Transaction的方法,会绕过AOP代理,导致事务失效
# 6. Spring的bean的生命周期?
Spring的Bean生命周期主要分为四大步
实例化
- 通过反射去推断构造函数进行实例化
- 实例工厂、静态工厂
属性赋值
- 解析自动装配
- 循环依赖
初始化
- 调用XXXAware回调方法
- 调用初始化生命周期回调(三种)
- 如果bean实现aop 创建动态代理
销毁
- 在spring容器关闭时进行调用
- 调用销毁生命周期回调
Spring中bean的生命周期包括以下步骤:
- 通过
BeanDefinition
获取bean的定义信息。 - 调用构造函数实例化bean。
- 进行bean的依赖注入,例如通过setter方法或
@Autowired
注解。 - 处理实现了
Aware
接口的bean。 - 执行
BeanPostProcessor
的前置处理器。 - 调用初始化方法,如实现了
InitializingBean
接口或自定义的init-method
。 - 执行
BeanPostProcessor
的后置处理器,可能在这里产生代理对象。 - 最后是销毁bean。
# 7. Spring中的循环引用?
循环依赖发生在两个或两个以上的bean互相持有对方,形成闭环。Spring框架允许循环依赖存在,并通过三级缓存解决大部分循环依赖问题:
- 一级缓存:单例池,缓存已完成初始化的bean对象。
- 二级缓存:缓存尚未完成生命周期的早期bean对象。
- 三级缓存:缓存
ObjectFactory
,用于创建bean对象。
# 8. 那具体解决流程清楚吗?
解决循环依赖的流程如下:
- 实例化A对象,并创建
ObjectFactory
存入三级缓存。 - A在初始化时需要B对象,开始B的创建逻辑。
- B实例化完成,也创建
ObjectFactory
存入三级缓存。 - B需要注入A,通过三级缓存获取
ObjectFactory
生成A对象,存入二级缓存。 - B通过二级缓存获得A对象后,B创建成功,存入一级缓存。
- A对象初始化时,由于B已创建完成,可以直接注入B,A创建成功存入一级缓存。
- 清除二级缓存中的临时对象A。
# 9. 构造方法出现了循环依赖怎么解决?
由于构造函数是bean生命周期中最先执行的,Spring框架无法解决构造方法的循环依赖问题。可以使用@Lazy
懒加载注解,延迟bean的创建直到实际需要时。
# 10. SpringMVC的执行流程?
SpringMVC的执行流程包括以下步骤:
- 用户发送请求到前端控制器
DispatcherServlet
。 Dispatcher``Servlet
调用HandlerMapping
找到具体处理器。HandlerMapping
返回处理器对象及拦截器(如果有)给DispatcherServlet
。DispatcherServlet
调用HandlerAdapter
。HandlerAdapter
适配并调用具体处理器(Controller)。- Controller执行并返回
ModelAndView
对象。 HandlerAdapter
将ModelAndView
返回给DispatcherServlet
。DispatcherServlet
传给ViewResolver
进行视图解析。ViewResolver
返回具体视图给DispatcherServlet
。DispatcherServlet
渲染视图并响应用户。
# 11. Springboot自动配置原理?
Spring Boot的自动配置原理基于@SpringBootApplication
注解,它封装了@SpringBootConfiguration
、@EnableAutoConfiguration
和@ComponentScan
。@EnableAutoConfiguration
是核心,它通过@Import
导入配置选择器,读取META-INF/spring.factories
文件中的类名,根据条件注解决定是否将配置类中的Bean导入到Spring容器中。
# 12. Spring 的常见注解有哪些?
Spring的常见注解包括:
- 声明Bean的注解:
@Component
、@Service
、@Repository
、@Controller
。 - 依赖注入相关注解:
@Autowired
、@``Qualifier
、@Resource
。 - 设置作用域的注解:
@Scope
。 - 配置相关注解:
@Configuration
、@ComponentScan
、@Bean
。 - AOP相关注解:
@Aspect
、@Before
、@After
、@Around
、@Pointcut
。
# 13. SpringMVC常见的注解有哪些?
SpringMVC的常见注解有:
@RequestMapping
:映射请求路径。@RequestBody
:接收HTTP请求的JSON数据。@RequestParam
:指定请求参数名称。@PathVariable
:从请求路径中获取参数。@ResponseBody
:将Controller方法返回的对象转化为JSON。@RequestHeader
:获取请求头数据。@PostMapping
、@GetMapping
等。
# 14. Springboot常见注解有哪些?
Spring Boot的常见注解包括:
@SpringBootApplication
:由@SpringBootConfiguration
、@EnableAutoConfiguration
和@ComponentScan
组成。- 其他注解如
@RestController
、@GetMapping
、@PostMapping
等,用于简化Spring MVC的配置。
# 15. MyBatis执行流程?
MyBatis的执行流程如下:
- 读取MyBatis配置文件
mybatis-config.xml
。 - 构造会话工厂
SqlSessionFactory
。 - 会话工厂创建
SqlSession
对象。 - 操作数据库的接口,
Executor
执行器。 Executor
执行方法中的MappedStatement
参数。- 输入参数映射。
- 输出结果映射。
# 16. Mybatis是否支持延迟加载?
MyBatis支持延迟加载,即在需要用到数据时才加载。可以通过配置文件中的lazyLoadingEnabled
配置启用或禁用延迟加载。
# 17. 延迟加载的底层原理知道吗?
延迟加载的底层原理主要使用CGLIB动态代理实现:
- 使用CGLIB创建目标对象的代理对象。
- 调用目标方法时,如果发现是null值,则执行SQL查询。
- 获取数据后,设置属性值并继续查询目标方法。
# 18. Mybatis的一级、二级缓存用过吗?
MyBatis的一级缓存是基于Perpetual``Cache
的HashMap本地缓存,作用域为Session,默认开启。二级缓存需要单独开启,作用域为Namespace或mapper,默认也是采用PerpetualCache
,HashMap存储。
# 19. Mybatis的二级缓存什么时候会清理缓存中的数据?
当作用域(一级缓存Session/二级缓存Namespaces)进行了新增、修改、删除操作后,默认该作用域下所有select中的缓存将被清空。
# 推荐使用构造函数注入
依赖注入的三种方式
- 使用
@Autowired
注解:将@Autowired
注解直接标记在字段上,Spring会自动注入依赖。 - Setter注入:使用
@Autowired
注解标记Setter方法,Spring会自动调用Setter方法注入依赖。 - 构造函数注入:如果有构造函数,Spring会自动调用构造函数并注入所需的依赖。
为什么推荐使用构造函数注入?
不可变性(Immutability)
- 构造函数注入允许将依赖字段声明为
final
,确保依赖在对象创建后不可变。
- 构造函数注入允许将依赖字段声明为
确保依赖项不为null
构造函数注入强制要求在创建对象时提供所有依赖,如果依赖项不存在,编译器会报错
避免循环依赖
构造函数注入可以帮助发现循环依赖问题,因为Spring在启动时会检查构造函数注入的循环依赖。可以通过
@Lazy
解决符合单一职责原则
构造函数注入强制要求类的依赖在构造函数中明确声明,有助于遵循单一职责原则(SRP)。
使用构造函数时,当一个类只有一个构造函数时,Spring 会自动选择它进行注入,无需额外注解。如果有多个构造函数,需要使用 @Autowired
或 @Primary
明确指定。
如果需要添加注入的依赖,还需要修改构造函数,这时可以使用lombok工具的RequiredArgsConstructor,会自动注入被final修饰的字段
# @Autowired和@Resource的区别
@Autowired是Spring提供的注解,默认根据类型注入,
@Autowired有一个属性required,默认为true,表示强制要求bean实例的注入,在项目启动时,如果在IOC容器中没有对应类型的Bean就会报错,如果不想要自动注入,也可以设置为false。
但IOC容器中有多个相同类型的Bean,由于@Autowired注解时根据类型注入bean的,所以会报错,可以用@Primary、@Qualifier注解解决,
- @Primary是指定主要的bean,当有多个相同类型的bean时,会优先使用声明了@Primary注解的bean。
- 而使用@Qualifier可以根据bean的名字去装配
@Resource是JDK提供的注解,Spring提供了对这个注解的支持,
@Resource可以通过属性name和type指定注入bean的方式,使用name可以根据bean的名字注入,type可以根据类型注入,如果都不指定,默认是根据名字注入,如果没有匹配成功,再通过类型注入,如果两个都没有匹配到的话,就会报错。
总结:
- @Autowired是spring提供的注解。@Resource是jdk提供的注解,只是spring提供了对这个注解的支持。
- @Autowired默认根据类型注入bean,如果有多个相同类型的bean,可以通过@Primary、@Qualifier注解来解决。而@Resource可以指定通过名字或者类型来注入bean。默认是根据bean的名字注入,如果没有匹配到,再根据类型注入。
# Spring通知有哪些类型?
# 解释基于XML Schema方式的切面实现
# 解释基于注解的切面实现
# Spring AOP的动态代理JDK和CGLIB的区别
Spring AOP使用的动态代理,它基于动态代理来实现。默认地,如果使用接口的,用 JDK 提供的动态代理实现,如果没有接口,使用 CGLIB 实现。(面试题): /class com.sun.proxy.$Proxy19 是jdk代理所产生的一个动态代理类,当被代理的类实现了接口会默认使用jdk代理 实现了接口的bean不能用实现类的class对象来获取bean,只能通过其接口的class对象来获取bean(因为实现类不在IOC容器中,被代理拦截了,代理了实现类的代理放在IOC容器中,该代理和实现类实现了共同接口)或者实现类名字(需要用接口接收)
IUserService bean = ioc.getBean(IUserService.class): IUserService bean = (IUserService) ioc.getBean("userServiceImpl");
//class cn.tulingxueyuan.service.impl.UserServiceImplSSEnhancerBySpringCGLIBS8f281cf64
cgIib代理所所产生的一个动态代理类,当被代理的类没有实现接口就会使用cglib代理