博客记录-day103-Spring面试题

129 阅读17分钟

一、小林-Spring面试题

1、spring是如何解决循环依赖的?

循环依赖指的是两个类中的属性相互依赖对方:例如 A 类中有 B 属性,B 类中有 A属性,从而形成了一个依赖闭环,如下图。

img

循环依赖问题在Spring中主要有三种情况:

  • 第一种:通过构造方法进行依赖注入时产生的循环依赖问题。
  • 第二种:通过setter方法进行依赖注入且是在多例(原型)模式下产生的循环依赖问题。
  • 第三种:通过setter方法进行依赖注入且是在单例模式下产生的循环依赖问题。

只有【第三种方式】的循环依赖问题被 Spring 解决了,其他两种方式在遇到循环依赖问题时,Spring都会产生异常。

Spring 解决单例模式下的setter循环依赖问题的主要方式是通过三级缓存解决循环依赖。三级缓存指的是 Spring 在创建 Bean 的过程中,通过三级缓存来缓存正在创建的 Bean,以及已经创建完成的 Bean 实例。具体步骤如下:

  • 实例化 Bean:Spring 在实例化 Bean 时,会先创建一个空的 Bean 对象,并将其放入一级缓存中。
  • 属性赋值:Spring 开始对 Bean 进行属性赋值,如果发现循环依赖,会将当前 Bean 对象提前暴露给后续需要依赖的 Bean(通过提前暴露的方式解决循环依赖)。
  • 初始化 Bean:完成属性赋值后,Spring 将 Bean 进行初始化,并将其放入二级缓存中。
  • 注入依赖:Spring 继续对 Bean 进行依赖注入,如果发现循环依赖,会从二级缓存中获取已经完成初始化的 Bean 实例。

通过三级缓存的机制,Spring 能够在处理循环依赖时,确保及时暴露正在创建的 Bean 对象,并能够正确地注入已经初始化的 Bean 实例,从而解决循环依赖问题,保证应用程序的正常运行。

2、spring三级缓存的数据结构是什么?

都是 Map类型的缓存,比如Map {k:name; v:bean}。

  1. 一级缓存(Singleton Objects) :这是一个Map类型的缓存,存储的是已经完全初始化好的bean,即完全准备好可以使用的bean实例。键是bean的名称,值是bean的实例。这个缓存在DefaultSingletonBeanRegistry类中的singletonObjects属性中。
  2. 二级缓存(Early Singleton Objects) :这同样是一个Map类型的缓存,存储的是早期的bean引用,即已经实例化但还未完全初始化的bean。这些bean已经被实例化,但是可能还没有进行属性注入等操作。这个缓存在DefaultSingletonBeanRegistry类中的earlySingletonObjects属性中。
  3. 三级缓存(Singleton Factories) :这也是一个Map类型的缓存,存储的是ObjectFactory对象,这些对象可以生成早期的bean引用。当一个bean正在创建过程中,如果它被其他bean依赖,那么这个正在创建的bean就会通过这个ObjectFactory来创建一个早期引用,从而解决循环依赖的问题。这个缓存在DefaultSingletonBeanRegistry类中的singletonFactories属性中。

3、spring框架中都用到了哪些设计模式

  • 工厂设计模式 : Spring使用工厂模式通过 BeanFactory、ApplicationContext 创建 bean 对象。
  • 代理设计模式 : Spring AOP 功能的实现。
  • 单例设计模式 : Spring 中的 Bean 默认都是单例的。
  • 模板方法模式 : Spring 中 jdbcTemplate、hibernateTemplate 等以 Template 结尾的对数据库操作的类,它们就使用到了模板模式。
  • 包装器设计模式 : 我们的项目需要连接多个数据库,而且不同的客户在每次访问中根据需要会去访问不同的数据库。这种模式让我们可以根据客户的需求能够动态切换不同的数据源。
  • 观察者模式:  Spring 事件驱动模型就是观察者模式很经典的一个应用。
  • 适配器模式 :Spring AOP 的增强或通知(Advice)使用到了适配器模式、spring MVC 中也是用到了适配器模式适配Controller。

4、spring 常用注解有什么?

@Autowired 注解

@Autowired:主要用于自动装配bean。当Spring容器 (opens new window)中存在与要注入的属性类型匹配的bean时,它会自动将bean注入到属性中。就跟我们new 对象一样。

用法很简单,如下示例代码:

@Component
public class MyService {
}

@Component
public class MyController {

    @Autowired
    private MyService myService;

}

在上面的示例代码中,MyController类中的myService属性被@Autowired注解标记,Spring会自动将MyService类型的bean注入到myService属性中。

@Component

这个注解用于标记一个类作为Spring的bean。当一个类被@Component注解标记时,Spring会将其实例化为一个bean,并将其添加到Spring容器中。在上面讲解@Autowired的时候也看到了,示例代码:

@Component
public class MyComponent {
}

在上面的示例代码中,MyComponent类被@Component注解标记,Spring会将其实例化为一个bean,并将其添加到Spring容器中。

@Configuration

@Configuration,注解用于标记一个类作为Spring的配置类。配置类可以包含@Bean注解的方法,用于定义和配置bean,作为全局配置。示例代码:

@Configuration
public class MyConfiguration {

    @Bean
    public MyBean myBean() {
        return new MyBean();
    }

}

@Bean

@Bean注解用于标记一个方法作为Spring的bean工厂方法。当一个方法被@Bean注解标记时,Spring会将该方法的返回值作为一个bean,并将其添加到Spring容器中,如果自定义配置,经常用到这个注解。

@Configuration
public class MyConfiguration {

    @Bean
    public MyBean myBean() {
        return new MyBean();

    }

}

@Service

@Service,这个注解用于标记一个类作为服务层的组件。它是@Component注解的特例,用于标记服务层的bean,一般标记在业务service的实现类。

@Service
public class MyServiceImpl {

}

@Repository

@Repository注解用于标记一个类作为数据访问层的组件。它也是@Component注解的特例,用于标记数据访问层的bean。这个注解很容易被忽略,导致数据库无法访问。

@Repository
public class MyRepository {

}

在上面的示例代码中,MyRepository类被@Repository注解标记,Spring会将其实例化为一个bean,并将其添加到Spring容器中。

@Controller

@Controller注解用于标记一个类作为控制层的组件。它也是@Component注解的特例,用于标记控制层的bean。这是MVC结构的另一个部分,加在控制层

@Controller
public class MyController {

}

在上面的示例代码中,MyController类被@Controller注解标记,Spring会将其实例化为一个bean,并将其添加到Spring容器中。

5、Spring的事务什么情况下会失效?

Spring Boot通过Spring框架的事务管理模块来支持事务操作。事务管理在Spring Boot中通常是通过 @Transactional 注解来实现的。事务可能会失效的一些常见情况包括:

  1. 未捕获异常: 如果一个事务方法中发生了未捕获的异常,并且异常未被处理或传播到事务边界之外,那么事务会失效,所有的数据库操作会回滚。
  2. 非受检异常: 默认情况下,Spring对非受检异常(RuntimeException或其子类)进行回滚处理,这意味着当事务方法中抛出这些异常时,事务会回滚。
  3. 事务传播属性设置不当: 如果在多个事务之间存在事务嵌套,且事务传播属性配置不正确,可能导致事务失效。特别是在方法内部调用有 @Transactional 注解的方法时要特别注意。
  4. 多数据源的事务管理: 如果在使用多数据源时,事务管理没有正确配置或者存在多个 @Transactional 注解时,可能会导致事务失效。
  5. 跨方法调用事务问题: 如果一个事务方法内部调用另一个方法,而这个被调用的方法没有 @Transactional 注解,这种情况下外层事务可能会失效。
  6. 事务在非公开方法中失效: 如果 @Transactional 注解标注在私有方法上或者非 public 方法上,事务也会失效。

6、Spring的事务,使用this调用是否生效?

不能生效。

因为Spring事务是通过代理对象来控制的,只有通过代理对象的方法调用才会应用事务管理的相关规则。当使用this直接调用时,是绕过了Spring的代理机制,因此不会应用事务设置。

7、Bean的生命周期说一下?

img

  1. Spring启动,查找并加载需要被Spring管理的bean,进行Bean的实例化
  2. Bean实例化后对将Bean的引入和值注入到Bean的属性中
  3. 如果Bean实现了BeanNameAware接口的话,Spring将Bean的Id传递给setBeanName()方法
  4. 如果Bean实现了BeanFactoryAware接口的话,Spring将调用setBeanFactory()方法,将BeanFactory容器实例传入
  5. 如果Bean实现了ApplicationContextAware接口的话,Spring将调用Bean的setApplicationContext()方法,将bean所在应用上下文引用传入进来。
  6. 如果Bean实现了BeanPostProcessor接口,Spring就将调用他们的postProcessBeforeInitialization()方法。
  7. 如果Bean 实现了InitializingBean接口,Spring将调用他们的afterPropertiesSet()方法。类似的,如果bean使用init-method声明了初始化方法,该方法也会被调用
  8. 如果Bean 实现了BeanPostProcessor接口,Spring就将调用他们的postProcessAfterInitialization()方法。
  9. 此时,Bean已经准备就绪,可以被应用程序使用了。他们将一直驻留在应用上下文中,直到应用上下文被销毁。
  10. 如果bean实现了DisposableBean接口,Spring将调用它的destory()接口方法,同样,如果bean使用了destory-method 声明销毁方法,该方法也会被调用。

8、Bean是否单例?

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

就是说,每个Bean的实例只会被创建一次,并且会被存储在Spring容器的缓存中,以便在后续的请求中重复使用。这种单例模式可以提高应用程序的性能和内存效率。

但是,Spring也支持将Bean设置为多例模式,即每次请求都会创建一个新的Bean实例。要将Bean设置为多例模式,可以在Bean定义中通过设置scope属性为"prototype"来实现。

需要注意的是,虽然Spring的默认行为是将Bean设置为单例模式,但在一些情况下,使用多例模式是更为合适的,例如在创建状态不可变的Bean或有状态Bean时。此外,需要注意的是,如果Bean单例是有状态的,那么在使用时需要考虑线程安全性问题。

9、Bean的单例和非单例,生命周期是否一样

不一样的,Spring Bean 的生命周期完全由 IoC 容器控制。Spring 只帮我们管理单例模式 Bean 的完整生命周期,对于 prototype 的 Bean,Spring 在创建好交给使用者之后,则不会再管理后续的生命周期。

具体区别如下:

阶段单例(Singleton)非单例(如Prototype)
创建时机容器启动时创建(或首次请求时,取决于配置)。每次请求时创建新实例。
初始化流程完整执行生命周期流程(属性注入、Aware接口、初始化方法等)。每次创建新实例时都会完整执行生命周期流程(仅到初始化完成)。
销毁时机容器关闭时销毁,触发DisposableBeandestroy-method容器不管理销毁,需由调用者自行释放资源(Spring不跟踪实例)。
内存占用单实例常驻内存,高效但需注意线程安全。每次请求生成新实例,内存开销较大,需手动管理资源释放。
适用场景无状态服务(如Service、DAO层)。有状态对象(如用户会话、临时计算对象)。

10、Spring bean的作用域有哪些?

Spring框架中的Bean作用域(Scope)定义了Bean的生命周期和可见性。不同的作用域影响着Spring容器如何管理这些Bean的实例,包括它们如何被创建、如何被销毁以及它们是否可以被多个用户共享。

Spring支持几种不同的作用域,以满足不同的应用场景需求。以下是一些主要的Bean作用域:

  • Singleton(单例) :在整个应用程序中只存在一个 Bean 实例。默认作用域,Spring 容器中只会创建一个 Bean 实例,并在容器的整个生命周期中共享该实例。
  • Prototype(原型) :每次请求时都会创建一个新的 Bean 实例。次从容器中获取该 Bean 时都会创建一个新实例,适用于状态非常瞬时的 Bean。
  • Request(请求) :每个 HTTP 请求都会创建一个新的 Bean 实例。仅在 Spring Web 应用程序中有效,每个 HTTP 请求都会创建一个新的 Bean 实例,适用于 Web 应用中需求局部性的 Bean。
  • Session(会话) :Session 范围内只会创建一个 Bean 实例。该 Bean 实例在用户会话范围内共享,仅在 Spring Web 应用程序中有效,适用于与用户会话相关的 Bean。
  • Application:当前 ServletContext 中只存在一个 Bean 实例。仅在 Spring Web 应用程序中有效,该 Bean 实例在整个 ServletContext 范围内共享,适用于应用程序范围内共享的 Bean。
  • WebSocket(Web套接字) :在 WebSocket 范围内只存在一个 Bean 实例。仅在支持 WebSocket 的应用程序中有效,该 Bean 实例在 WebSocket 会话范围内共享,适用于 WebSocket 会话范围内共享的 Bean。
  • Custom scopes(自定义作用域) :Spring 允许开发者定义自定义的作用域,通过实现 Scope 接口来创建新的 Bean 作用域。

11、Spring容器里存的是什么?

在Spring容器中,存储的主要是Bean对象

Bean是Spring框架中的基本组件,用于表示应用程序中的各种对象。当应用程序启动时,Spring容器会根据配置文件或注解的方式创建和管理这些Bean对象。Spring容器会负责创建、初始化、注入依赖以及销毁Bean对象。

12、在Spring中,在bean加载/销毁前后,如果想实现某些逻辑,可以怎么做

在Spring框架中,如果你希望在Bean加载(即实例化、属性赋值、初始化等过程完成后)或销毁前后执行某些逻辑,你可以使用Spring的生命周期回调接口或注解。这些接口和注解允许你定义在Bean生命周期的关键点执行的代码。

使用init-method和destroy-method

在XML配置中,你可以通过init-method和destroy-method属性来指定Bean初始化后和销毁前需要调用的方法。

<bean id="myBean" class="com.example.MyBeanClass"  
      init-method="init" destroy-method="destroy"/>

然后,在你的Bean类中实现这些方法:

public class MyBeanClass {  
  
    public void init() {  
        // 初始化逻辑  
    }  
  
    public void destroy() {  
        // 销毁逻辑  
    }  
}

实现InitializingBean和DisposableBean接口

你的Bean类可以实现org.springframework.beans.factory.InitializingBean和org.springframework.beans.factory.DisposableBean接口,并分别实现afterPropertiesSet和destroy方法。

import org.springframework.beans.factory.DisposableBean;  
import org.springframework.beans.factory.InitializingBean;  
  
public class MyBeanClass implements InitializingBean, DisposableBean {  
  
    @Override  
    public void afterPropertiesSet() throws Exception {  
        // 初始化逻辑  
    }  
  
    @Override  
    public void destroy() throws Exception {  
        // 销毁逻辑  
    }  
}

使用@PostConstruct和@PreDestroy注解

import javax.annotation.PostConstruct;  
import javax.annotation.PreDestroy;  
  
public class MyBeanClass {  
  
    @PostConstruct  
    public void init() {  
        // 初始化逻辑  
    }  
  
    @PreDestroy  
    public void destroy() {  
        // 销毁逻辑  
    }  
}

使用@Bean注解的initMethod和destroyMethod属性

在基于Java的配置中,你还可以在@Bean注解中指定initMethod和destroyMethod属性。

@Configuration  
public class AppConfig {  
  
    @Bean(initMethod = "init", destroyMethod = "destroy")  
    public MyBeanClass myBean() {  
        return new MyBeanClass();  
    }  
}

13、Bean注入和xml注入最终得到了相同的效果,它们在底层是怎样做的

在Spring框架中,基于注解的Bean注入(如@Autowired@Resource)和基于XML的依赖注入虽然在配置方式上不同,但在底层最终都通过Spring容器的统一机制实现依赖注入。它们的核心流程可以归纳为以下步骤:

阶段注解注入XML注入
配置解析通过注解处理器扫描类路径,解析@Component@Autowired等注解。解析XML文件中的<bean><property><constructor-arg>标签。
生成BeanDefinition将注解信息转换为AnnotatedBeanDefinition将XML配置转换为GenericBeanDefinition
依赖注入AutowiredAnnotationBeanPostProcessor等后处理器处理。在BeanDefinition中直接记录属性或构造器参数,由容器直接注入。
最终结果生成完整的Bean实例,完成依赖注入。生成完整的Bean实例,完成依赖注入。

XML 注入

使用 XML 文件进行 Bean 注入时,Spring 在启动时会读取 XML 配置文件,以下是其底层步骤:

  • Bean 定义解析:Spring 容器通过 XmlBeanDefinitionReader 类解析 XML 配置文件,读取其中的 <bean> 标签以获取 Bean 的定义信息。

  • 注册 Bean 定义:解析后的 Bean 信息被注册到 BeanDefinitionRegistry(如 DefaultListableBeanFactory)中,包括 Bean 的类、作用域、依赖关系、初始化和销毁方法等。

  • 实例化和依赖注入:当应用程序请求某个 Bean 时,Spring 容器会根据已经注册的 Bean 定义:

    • 首先,使用反射机制创建该 Bean 的实例。
    • 然后,根据 Bean 定义中的配置,通过 setter 方法、构造函数或方法注入所需的依赖 Bean。

注解注入

使用注解进行 Bean 注入时,Spring 的处理过程如下:

  • 类路径扫描:当 Spring 容器启动时,它首先会进行类路径扫描,查找带有特定注解(如 @Component@Service@Repository 和 @Controller)的类。
  • 注册 Bean 定义:找到的类会被注册到 BeanDefinitionRegistry 中,Spring 容器将为其生成 Bean 定义信息。这通常通过 AnnotatedBeanDefinitionReader 类来实现。
  • 依赖注入:与 XML 注入类似,Spring 在实例化 Bean 时,也会检查字段上是否有 @Autowired@Inject 或 @Resource 注解。如果有,Spring 会根据注解的信息进行依赖注入。

尽管使用的方式不同,但 XML 注入和注解注入在底层的实现机制是相似的,主要体现在以下几个方面:

  1. BeanDefinition:无论是 XML 还是注解,最终都会生成 BeanDefinition 对象,并存储在同一个 BeanDefinitionRegistry 中。

  2. 后处理器

    • Spring 提供了多个 Bean 后处理器(如 AutowiredAnnotationBeanPostProcessor),用于处理注解(如 @Autowired)的依赖注入。
    • 对于 XML,Spring 也有相应的后处理器来处理 XML 配置的依赖注入。
  3. 依赖查找:在依赖注入时,Spring 容器会通过 ApplicationContext 中的 BeanFactory 方法来查找和注入依赖,无论是通过 XML 还是注解,都会调用类似的查找方法。

14、Spring给我们提供了很多扩展点,这些有了解吗?

Spring框架提供了许多扩展点,使得开发者可以根据需求定制和扩展Spring的功能。以下是一些常用的扩展点:

  1. BeanFactoryPostProcessor:允许在Spring容器实例化bean之前修改bean的定义。常用于修改bean属性或改变bean的作用域。
  2. BeanPostProcessor:可以在bean实例化、配置以及初始化之后对其进行额外处理。常用于代理bean、修改bean属性等。
  3. PropertySource:用于定义不同的属性源,如文件、数据库等,以便在Spring应用中使用。
  4. ImportSelector和ImportBeanDefinitionRegistrar:用于根据条件动态注册bean定义,实现配置类的模块化。
  5. Spring MVC中的HandlerInterceptor:用于拦截处理请求,可以在请求处理前、处理中和处理后执行特定逻辑。
  6. Spring MVC中的ControllerAdvice:用于全局处理控制器的异常、数据绑定和数据校验。
  7. Spring Boot的自动配置:通过创建自定义的自动配置类,可以实现对框架和第三方库的自动配置。
  8. 自定义注解:创建自定义注解,用于实现特定功能或约定,如权限控制、日志记录等