【Java 面试 八股文】框架篇

1. Spring框架中的单例bean是线程安全的吗?

Spring 中的 Bean 默认都是单例的。

候选人:
不是线程安全的。当多用户同时请求一个服务时,容器会给每个请求分配一个线程,这些线程会并发执行业务逻辑。如果处理逻辑中包含对单例状态的修改,比如修改单例的成员属性,就必须考虑线程同步问题。Spring框架本身并不对单例bean进行线程安全封装,线程安全和并发问题需要开发者自行处理。
通常在项目中使用的Spring bean是不可变状态(如Service类和DAO类),因此在某种程度上可以说Spring的单例bean是线程安全的。如果bean有多种状态(如ViewModel对象),就需要自行保证线程安全。最简单的解决办法是将单例bean的作用域由“singleton”变更为“prototype”。

2. 什么是AOP?什么是IoC

候选人:
AOP,即面向切面编程,在Spring中用于将那些与业务无关但对多个对象产生影响的公共行为和逻辑抽取出来,实现公共模块复用,降低耦合。常见的应用场景包括公共日志保存和事务处理。
任何一个系统都是由不同的组件组成的,每个组件负责一块特定的功能,当然会存在很多组件是跟业务无关的,例如日志、事务、权限等核心服务组件,这些核心服务组件经常融入到具体的业务逻辑中,如果我们为每一个具体业务逻辑操作都添加这样的代码,很明显代码冗余太多,因此我们需要将这些公共的代码逻辑抽象出来变成一个切面,然后注入到目标对象(具体业务)中去,AOP正是基于这样的一个思路实现的,通过动态代理的方式,将需要注入切面的对象进行代理,在进行调用的时候,将公共的逻辑直接添加进去,而不需要修改原有业务的逻辑代码,只需要在原来的业务逻辑基础之上做一些增强功能即可。

动态代理实现AOP可能引起的问题:在一个类内部,方法通过 this.method() 调用自身的另一个方法,绕过了代理对象,AOP 不生效。

IoC(Inverse of Control:控制反转) 是一种设计思想或者说是某种模式。这个设计思想就是 将原本在程序中手动创建对象的控制权交给第三方比如 IoC 容器
1、谁控制谁:在之前的编码过程中,都是需要什么对象自己去创建什么对象,有程序员自己来控制对象,而有了IOC容器之后,就会变成由IOC容器来控制对象
2、控制什么:在实现过程中所需要的对象及需要依赖的对象
3、什么是反转:在没有IOC容器之前我们都是在对象中主动去创建依赖的对象,这是正转的,而有了IOC之后,依赖的对象直接由IOC容器创建后注入到对象中,由主动创建变成了被动接受,这是反转
4、哪些方面被反转:依赖的对象

Bean 代指的就是那些被 IoC 容器所管理的对象

3. 你们项目中有没有使用到AOP?

候选人:
我们之前在后台管理系统中使用AOP来记录系统操作日志。主要思路是使用AOP的环绕通知和切点表达式,找到需要记录日志的方法,然后通过环绕通知的参数获取请求方法的参数,例如类信息、方法信息、注解、请求方式等,并将这些参数保存到数据库。

4. Spring中的事务是如何实现的?

候选人:
声明式事务。
Spring实现事务的本质是利用AOP完成的。它对方法前后进行拦截,在执行方法前开启事务,在执行完目标方法后根据执行情况提交或回滚事务。

5. Spring中事务失效的场景有哪些?

候选人:
事务管理在Spring Boot中通常是通过 @Transactional 注解来实现的。

  1. 如果方法内部捕获并处理了异常,没有将异常抛出,会导致事务失效。因此,处理异常后应该确保异常能够被抛出。
  2. 如果方法 抛出检查型异常(checked exception),并且没有在@Transactional注解上配置rollbackFor属性为Exception,那么异常发生时事务可能不会回滚。(原因:Spring 默认只会回滚非检查异常)
  3. 如果 事务注解的方法不是公开(public)修饰的,也可能导致事务失效。
  4. 同一个类中,A方法是非事务性方法,但是B方法是事务性方法,此时A调用B就会导致B的事务失效。

6. Spring的bean的生命周期?

候选人:
Spring中bean的生命周期包括以下步骤:

  1. 通过 BeanDefinition 获取bean的定义信息。
  2. 调用 构造函数 实例化bean。
  3. 进行bean的 依赖注入,例如通过setter方法或@Autowired注解。
  4. 处理实现了 Aware接口 的bean。
  5. 执行 BeanPostProcessor前置处理器
  6. 调用 初始化方法,如实现了 InitializingBean 接口或自定义的 init-method
  7. 执行 BeanPostProcessor后置处理器,可能在这里产生代理对象。
  8. 最后是 销毁bean
    在这里插入图片描述

7. Spring中的循环引用?

候选人:
循环依赖发生在两个或两个以上的bean互相持有对方,形成闭环。 Spring框架允许循环依赖存在,并通过三级缓存解决大部分循环依赖问题(set 方法形成的循环依赖):

  1. 一级缓存 singletonObjects:单例池,缓存已完成初始化的bean对象(已经经历了完整的生命周期)。
  2. 二级缓存 earlySingletonObects:缓存尚未完成生命周期的早期bean对象。
  3. 三级缓存 singletonFactories:缓存 ObjectFactory (对象工厂),用于创建bean对象。

在这里插入图片描述

8. 那具体解决流程清楚吗?

  1. 实例化A对象,并创建 ObjectFactory 存入三级缓存。
  2. A在初始化时需要B对象,开始B的创建逻辑。
  3. B实例化完成,也创建 ObjectFactory 存入三级缓存。
  4. B需要注入A,通过三级缓存获取 ObjectFactory 生成A对象,存入二级缓存。
  5. B通过二级缓存获得A对象后,B创建成功,存入一级缓存。
  6. A对象初始化时,由于B已创建完成,可以直接注入B,A创建成功存入一级缓存。
  7. 清除二级缓存中的临时对象A。

9. 构造方法出现了循环依赖怎么解决?

候选人:
三级缓存能够解决初始化过程中的循环依赖,不能解决构造函数中的循环依赖。
原因:由于构造函数是bean生命周期中最先执行的,Spring框架无法解决构造方法的循环依赖问题。
解决:可以使用 @Lazy 懒加载注解,延迟bean的创建直到实际需要时。

10. SpringMVC的执行流程?

HandlerMapping

  • 作用:HandlerMapping 负责将请求映射到处理器(Controller)。
  • 功能:根据请求的 URL、请求参数等信息,找到处理请求的 Controller。

HandlerAdapter

  • 作用:HandlerAdapter 负责调用处理器(Controller)来处理请求。
  • 功能:Controller 可能有不同的接口类型,HandlerAdapter 根据处理器的类型来选择合适的方法来调用处理器。

SpringMVC的执行流程包括以下步骤(版本1:视图版本,jsp):

  1. 用户发送请求到 前端控制器 DispatcherServlet (调度中心)。
  2. DispatcherServlet调用 处理器映射器 HandlerMapping 找到具体处理器。
  3. HandlerMapping返回处理器对象及拦截器(如果有)给DispatcherServlet。
  4. DispatcherServlet调用 处理器适配器 HandlerAdapter
  5. HandlerAdapter适配并调用 具体处理器 Handler/Controller
  6. Controller执行并返回 ModelAndView 对象。
  7. HandlerAdapter将ModelAndView返回给DispatcherServlet。
  8. DispatcherServlet传给 视图解析器 ViewResolver 进行视图解析。
  9. ViewResolver返回具体 视图 View 给DispatcherServlet。
  10. DispatcherServlet渲染视图并响应用户。
    在这里插入图片描述

SpringMVC的执行流程包括以下步骤(版本 2:前后端开发,接口开发):

  1. 用户发送请求到 前端控制器 DispatcherServlet (调度中心)。
  2. DispatcherServlet调用 处理器映射器 HandlerMapping 找到具体处理器。
  3. HandlerMapping返回处理器对象及拦截器(如果有)给DispatcherServlet。
  4. DispatcherServlet调用 处理器适配器 HandlerAdapter
  5. HandlerAdapter适配并调用 具体处理器 Handler/Controller
  6. 方法上添加了 @ResponseBody
  7. 通过 HttpMessageConverter 来返回结果转换为 JSON 并响应。
    在这里插入图片描述

11. Springboot自动配置原理?

候选人:
Spring Boot的自动配置原理基于 @SpringBootApplication 注解,它封装了 @SpringBootConfiguration@EnableAutoConfiguration@ComponentScan

@EnableAutoConfiguration 是实现自动化配置的核心注解,它通过 @Import 导入配置选择器,读取该项目和该项目引用的Jar包的 classpath 路径下 META-INF/spring.factories 文件中的类名,根据 条件注解所指定的条件 决定是否将配置类中的Bean导入到Spring容器中。

条件判断会有像 ConditionalOnClass 这样的注解,判断是否有对应的class文件,如果有则加载该类,把这个配置类的所有的Bean放入spring容器中。
在这里插入图片描述

12. Spring 的常见注解有哪些?

候选人:
Spring的常见注解包括:

  1. 声明Bean的注解:@Component、@Service、@Repository、@Controller。
  2. 依赖注入相关注解:@Autowired、@Qualifier、@Resource。
  3. 设置作用域的注解:@Scope。
  4. 配置相关注解:@Configuration、@ComponentScan、@Bean。
  5. AOP相关注解:@Aspect、@Before、@After、@Around、@Pointcut。
    在这里插入图片描述

13. SpringMVC常见的注解有哪些?

候选人:
SpringMVC的常见注解有:

  • @RequestMapping:映射请求路径。
  • @RequestBody:接收HTTP请求的JSON数据。
  • @RequestParam:指定请求参数名称。
  • @PathVariable:从请求路径中获取参数。
  • @ResponseBody:将Controller方法返回的对象转化为JSON。
  • @RequestHeader:获取请求头数据。
    在这里插入图片描述

14. Springboot常见注解有哪些?

候选人:
Spring Boot的常见注解包括:

  • @SpringBootApplication:由@SpringBootConfiguration、@EnableAutoConfiguration和@ComponentScan组成。
  • 其他注解如@RestController、@GetMapping、@PostMapping等,用于简化Spring MVC的配置。
    在这里插入图片描述

15. MyBatis执行流程?

MyBatis的执行流程如下:

  1. 读取MyBatis配置文件 mybatis-config.xml (加载运行环境和映射文件)。
  2. 构造会话工厂 SqlSessionFactory
  3. 会话工厂创建 SqlSession 对象(包含了执行SQL语句的所有方法)。
  4. 操作数据库的接口,Executor 执行器。
  5. Executor 执行方法中的 MappedStatement 参数(封装了映射信息)。
  6. 输入参数映射。
  7. 输出结果映射。

16. Mybatis是否支持延迟加载?

候选人:
MyBatis支持延迟加载,即在需要用到数据时才加载。可以通过配置文件中的lazyLoadingEnabled配置启用或禁用延迟加载。

17. 延迟加载的底层原理知道吗?

候选人:
延迟加载的底层原理主要使用CGLIB动态代理实现:

  1. 使用CGLIB创建目标对象的代理对象。
  2. 调用目标方法时,如果发现是null值,则执行SQL查询。
  3. 获取数据后,设置属性值并继续查询目标方法。

18. Mybatis的一级、二级缓存用过吗?

候选人:
MyBatis的一级缓存是基于PerpetualCache的HashMap本地缓存,作用域为Session,默认开启。二级缓存需要单独开启,作用域为Namespace或mapper,默认也是采用PerpetualCache,HashMap存储。

19. Mybatis的二级缓存什么时候会清理缓存中的数据?

候选人:
当作用域(一级缓存Session/二级缓存Namespaces)进行了新增、修改、删除操作后,默认该作用域下所有select中的缓存将被清空。

20. Java 反射

Java 反射(Reflection) 是 Java 提供的一种机制,它允许程序在运行时获取类的信息,并且可以动态地创建对象、调用方法、访问或修改字段,而不需要在编译时知道这些信息。

通过反射获取类的信息

Class<?> clazz = Class.forName("com.example.MyClass");  // 通过类名获取 Class 对象

反射的应用场景

✅ 1. 框架底层(Spring、MyBatis、Hibernate)
Spring 依赖注入(IoC) 就是通过反射创建 Bean 实例并赋值
MyBatis 动态代理 也是基于反射实现的

✅ 2. 运行时动态加载类(插件机制)
允许在运行时加载新的类,而不是在编译时固定
典型案例:Java SPI(Service Provider Interface) 机制

优点:
灵活:可以在运行时动态调用类、方法、字段
解耦:可以不依赖具体类(比如 JDBC 不需要直接依赖数据库驱动)

缺点:
性能开销:反射比直接调用慢(因为要检查安全性 + 解析元数据)
安全风险:可能破坏封装性(如修改私有变量)
代码可读性降低:反射代码比普通代码复杂

22. 动态代理

Java的动态代理是一种在运行时动态创建代理对象的机制,主要用于在不修改原始类的情况下对方法调用进行拦截和增强。

Java动态代理主要分为两种类型:

  • 基于接口的代理(JDK动态代理):这种类型的代理要求目标对象必须实现至少一个接口。Java动态代理会创建一个实现了相同接口的代理类,然后在运行时动态生成该类的实例。这种代理的实现核心是 java.lang.reflect.Proxy 类和java.lang.reflect.InvocationHandler接口。每一个动态代理类都必须实现InvocationHandler接口,并且每个代理类的实例都关联到一个handler。当通过代理对象调用一个方法时,这个方法的调用会被转发为由InvocationHandler接口的invoke(方法来进行调用。
  • 基于类的代理(CGLIB动态代理):CGLIB(CodeGeneration Library)是一个强大的高性能的代码生成库,它可以在运行时动态生成一个目标类的子类。CGLIB代理不需要目标类实现接口,而是通过继承的方式创建代理类。因此,如果目标对象没有实现任何接口,可以使用CGLIB来创建动态代理。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值