一切福田,不離方寸,從心而覓,感無不通。

Java面试准备

  • 基础
    • Spring相关
      1. Spring, Spring MVC, SpringBoot是什么关系?
        Spring 包含了多个功能模块,Spring MVC是其中一个模块,专门处理Web请求。Spring Boot 只是简化了配置,如果需要构建 MVC 架构的 Web 程序,还是需要使用 Spring MVC 作为 MVC 框架,只是说 Spring Boot 简化了 Spring MVC 的很多配置,真正做到开箱即用。
      2. 谈一谈对Spring IoC的理解
        IoC(Inversion of Control:控制反转) 将原本在程序中手动创建对象的控制权,交由 Spring 框架来管理。为什么叫控制反转?
        控制:指的是对象创建(实例化、管理)的权力
        反转:控制权交给外部环境(Spring 框架、IoC 容器)
      3. @Component 和 @Bean 的区别?
        @Component 注解作用于类,而@Bean注解作用于方法。
        当我们引用第三方库中的类需要装配到 Spring容器时,则只能通过 @Bean来实现。
      4. @Autowired 和 @Resource 的区别?
        @Autowired 属于 Spring 内置的注解,默认的注入方式为byType(根据类型进行匹配),也就是说会优先根据接口类型去匹配并注入 Bean (接口的实现类)。 当一个接口存在多个实现类的话,byType这种方式就无法正确注入对象了,因为这个时候 Spring 会同时找到多个满足条件的选择,默认情况下它自己不知道选择哪一个。这种情况下,注入方式会变为 byName(根据名称进行匹配),这个名称通常就是类名(首字母小写)。
        通过 @Qualifier 注解可以来显式指定名称而不是依赖变量的名称。@Resource属于 JDK 提供的注解,默认注入方式为 byName。如果无法通过名称匹配到对应的 Bean 的话,注入方式会变为byType。
        @Resource 有两个比较常用的属性:name(名称)、type(类型)。如果仅指定 name 属性则注入方式为byName,如果仅指定type属性则注入方式为byType,如果同时指定name 和type属性(不建议这么做)则注入方式为byType+byName。
      5. 注入Bean的方法有哪些?
        构造函数注入:通过类的构造函数来注入依赖项。
        Setter 注入:通过类的 Setter 方法来注入依赖项。
        Field(字段) 注入:直接在类的字段上使用注解(如 @Autowired 或 @Resource)来注入依赖项。
      6. 为什么Spring 官方推荐构造函数注入?
        依赖完整性:确保所有必需依赖在对象创建时就被注入,避免了空指针异常的风险。
        不可变性:有助于创建不可变对象,提高了线程安全性。
        初始化保证:组件在使用前已完全初始化,减少了潜在的错误。
        测试便利性:在单元测试中,可以直接通过构造函数传入模拟的依赖项,而不必依赖 Spring 容器进行注入。
      7. Bean 的作用域有哪些?
        singleton : IoC 容器中只有唯一的 bean 实例。Spring 中的 bean 默认都是单例的,是对单例设计模式的应用。
        prototype : 每次获取都会创建一个新的 bean 实例。也就是说,连续 getBean() 两次,得到的是不同的 Bean 实例。
        request : 每一次 HTTP 请求都会产生一个新的 bean,该 bean 仅在当前 HTTP request 内有效。
        session : 每一个 HTTP Session 会产生一个新的 bean,该 bean 仅在当前 HTTP session 内有效。
      8. Bean 是线程安全的吗?
        Spring 框架中的 Bean 是否线程安全,取决于其作用域和状态。几乎所有场景的 Bean 作用域都是使用默认的singleton ,重点关注 singleton 作用域即可。prototype 作用域下,每次获取都会创建一个新的 bean 实例,不存在资源竞争问题,所以不存在线程安全问题。singleton 作用域下,IoC 容器中只有唯一的 bean 实例,可能会存在资源竞争问题(取决于 Bean 是否有状态)。如果这个 bean 是有状态的话,那就存在线程安全问题(有状态 Bean 是指包含可变的成员变量的对象)。
      9. Bean 的生命周期了解吗?
        fb2a33ef0b4e4e4f8421231f01f13392
        Bean的生命周期从Spring容器启动开始,首先根据配置或注解扫描获取Bean的定义信息,随后通过反射实例化对象并填充属性,完成依赖注入。如果Bean实现了诸如BeanNameAware等Aware接口,容器会在此阶段回调相关方法使其感知自身信息。接下来,BeanPostProcessor的postProcessBeforeInitialization方法被调用,执行初始化前的自定义逻辑,例如处理@PostConstruct注解的方法。随后容器触发初始化回调,包括InitializingBean接口的afterPropertiesSet方法或通过XML、注解定义的初始化方法。BeanPostProcessor的postProcessAfterInitialization在此之后执行,常见于生成AOP代理对象等增强处理。此时Bean已就绪,可被应用程序使用。当容器关闭时,销毁流程启动,依次执行@PreDestroy注解的方法、DisposableBean接口的destroy方法或配置的销毁方法,最终完成Bean的资源释放与生命周期终结。
      10. 如何解决 Spring 中的循环依赖问题?Spring通过三个缓存层级解决单例Bean的循环依赖问题:
        缓存名称 描述
        singletonObjects 一级缓存:存放完全初始化好的Bean(成品对象)。
        earlySingletonObjects 二级缓存:存放早期暴露的Bean(已实例化但未填充属性,未初始化)。
        singletonFactories 三级缓存:存放Bean的工厂对象(ObjectFactory),用于生成早期引用。

        以A依赖B,B依赖A为例:

        1. 创建Bean A
          • 实例化A:调用A的构造函数创建对象(此时对象未填充属性,未初始化)。
          • 将A的工厂对象放入三级缓存(singletonFactories),用于后续生成早期引用。
          • 填充A的属性:发现需要注入B。
        2. 创建Bean B
          • 实例化B:调用B的构造函数创建对象。
          • 将B的工厂对象放入三级缓存。
          • 填充B的属性:发现需要注入A。
        3. 解决B对A的依赖
          • 从三级缓存中获取A的工厂对象(singletonFactories),生成A的早期引用(通过getEarlyBeanReference方法)。这一步是整合了第一步那个实例化但是没在任何缓存里的A对象,给他变成了半成品代理对象(如果A有被代理的话)
          • 将A的早期引用存入二级缓存(earlySingletonObjects),并从三级缓存中删除A的工厂。
          • 将A的早期引用注入到B中,完成B的属性填充和初始化。
          • 将初始化后的B存入一级缓存(singletonObjects)。
        4. 完成A的创建
          • 从一级缓存中获取已初始化的B,注入到A中。
          • 完成A的属性填充和初始化。
          • 将A存入一级缓存,并从二级缓存中删除A的早期引用。

        我直接在实例化的时候判断A有没有代理,如果有的话,我直接把代理对象放到二级缓存里不行吗?这样就不用三级缓存了

        代理对象应该在bean 实例化→属性填充→初始化 做完之后才去生成的(bean的生命周期),假设没有出现循环依赖,bean能通过正常的生命周期生成代理,我们直接在bean没完成初始化前就生成代理对象了,就打乱了bean的生命周期了。

        通过三级缓存,可以推迟bean的早期引用暴露,也就是说,要不要提前生成代理对象这个事情,推迟到循环依赖真正发生的时候。如果真发生了循环依赖,B才会调用getEarlyBeanReference方法生成A的代理,如果没循环依赖的话,在二级缓存正常放填充好属性的A对象的,就不用提前把A的代理放二级缓存了。

        注意点:

        • 仅支持单例Bean的循环依赖
          原型(Prototype)作用域的Bean无法通过缓存解决循环依赖,Spring会直接抛出异常。
        • 构造器注入无法解决循环依赖
          如果循环依赖通过构造函数参数注入(而非Setter方法或字段注入),Spring无法提前暴露对象,会抛出BeanCurrentlyInCreationException。

        解释:如果两个Bean都是原型模式的话,那么创建A1需要创建一个B1,创建B1的时候要创建一个A2,创建A2又要创建一个B2,创建B2又要创建一个A3,创建A3又要创建一个B3,循环依赖就没办法解决了。

        如果A和B的依赖都是通过构造器注入,那连一个半成品对象都创建不出来,也没办法解决循环依赖

    •  MyBatis
  • Spring Cloud/Alibaba/Dubbo
  • JVM调优
  • 多线程
    • 多线程并发
  • Netty/NIO
  • AI编程
    • Cursor
    • Cladue
  • Redis
    • 集群
    • 缓存策略
    • 穿透/雪崩解决方案
  • Kafka
  • Elasticsearch
  • MySQL
    • 分库分表
    • 读写分离
    • 性能优化
  • PostgresSQL
  • RabbitMQ
  • RocketMQ
  • Zookeeper
  • Etcd
  • Docker
  • Kubernetes
  • 服务网格/lstio
  • 性能调优
    • JVM
    • GC
    • SQL
    • 网格
  • 全链路监控
    • Metrics
    • Tracing
    • Logging
    • Prometheus
    • SkyWalking
    • ELK
  • 安全漏洞
    • OWASP Top 10
  • 加密认证
    • OAuth2.0
    • JWT
  • 高并发
    • 分布式事务
    • 分布式锁
    • 高并发
  • 物联网
    • mqtt
  • Vue2/3
  • React
  • 其他
    • 反射