Spring面试题

1.Spring原理

  1. IoC(控制反转)和 DI(依赖注入):
    • IoC(Inversion of Control):IoC是一种设计思想,它将程序的控制权交给容器或框架,由容器来负责对象的创建和管理。Spring的IoC容器,即ApplicationContext,负责管理Bean的生命周期。
    • DI(Dependency Injection):DI是IoC的一种实现方式,它通过注入的方式将一个对象的依赖关系交给容器管理,而不是在对象内部直接创建依赖对象。这样做提高了组件的可重用性和可测试性。
  2. AOP(面向切面编程):
    • AOP(Aspect-Oriented Programming):AOP是一种编程范式,它允许将横切关注点(如日志、安全、事务管理等)从业务逻辑中剥离出来,使得系统关注点的变化不影响业务逻辑。在Spring中,AOP通过代理机制实现,常见的代理方式有基于JDK动态代理和基于CGLIB的代理。
  3. Bean的生命周期和作用域:
    • Bean的生命周期:Bean的生命周期包括实例化、初始化、使用和销毁四个阶段。Spring容器负责Bean的实例化和初始化,而销毁阶段则由容器负责。你可以提及InitializingBeanDisposableBean接口以及@PostConstruct@PreDestroy注解来控制Bean的初始化和销毁操作。
    • Bean的作用域:Spring定义了多种Bean的作用域,包括singleton(单例,默认)、prototype(原型)、request(每个HTTP请求一个实例)、session(每个HTTP Session一个实例)和global session(全局HTTP Session一个实例)等。
  4. Spring的事务管理:
    • 声明式事务管理:通过使用@Transactional注解或XML配置来声明事务,使得事务的控制更加方便。Spring提供了对编程式事务管理的支持,可以通过PlatformTransactionManager接口进行编程式事务控制。
  5. Spring的数据访问与集成:
    • 数据访问:Spring提供了JdbcTemplate等模板类,简化了JDBC的操作,同时支持ORM框架(如Hibernate、MyBatis)的集成,提供了对持久层的支持。
    • 集成其他技术:Spring能够与其他技术(如消息队列、缓存、搜索引擎等)进行集成,提供了丰富的集成方案。
  6. Spring框架的核心组件:
    • ApplicationContext:Spring的IoC容器,负责管理Bean的生命周期。
    • BeanFactory:是IoC容器的基础接口,提供了基本的IoC功能。
    • BeanPostProcessor:在Bean初始化前后执行一些操作。
    • BeanDefinition:定义了Bean的配置信息。

2.什么是控制反转?什么是依赖注入

​ 控制反转(IoC,Inversion of Control)和依赖注入(DI,Dependency Injection)是面向对象编程中两个重要的概念,它们是实现松耦合(Loose Coupling)的关键。

控制反转(IoC):

控制反转是一种设计原则,它将程序的控制权从程序代码本身转移到了外部容器或框架。在传统的程序设计中,程序代码通常负责对象的创建、依赖关系的管理以及资源的释放。而在IoC中,这些职责被反转,由外部容器负责。IoC使得系统更加灵活,易于扩展和维护。

在IoC中,对象的创建和管理由IoC容器负责。Spring的ApplicationContext就是一个经典的IoC容器。它负责实例化Bean、注入Bean之间的依赖关系、管理Bean的生命周期等。开发者只需定义好Bean和它们之间的关系,由容器负责具体的实例化和管理。

依赖注入(DI):

依赖注入是IoC的一种具体实现方式。在依赖注入中,组件的依赖关系由外部容器在组件被创建时注入。依赖关系通常以构造函数、Setter方法或接口注入的方式实现。

假设有一个类UserService依赖于UserRepository,传统的做法是在UserService类内部创建UserRepository的实例。但在依赖注入中,UserService不再负责创建UserRepository,而是通过构造函数或Setter方法接受一个UserRepository的实例。容器在创建UserService时,将UserRepository的实例注入到UserService中。

public class UserService {
private final UserRepository userRepository;

// 通过构造函数注入依赖
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}

// 或者通过Setter方法注入依赖
// public void setUserRepository(UserRepository userRepository) {
// this.userRepository = userRepository;
// }
}

依赖注入使得对象之间的关系更加灵活,容易被替换和测试。Spring框架提供了多种依赖注入的方式,包括构造函数注入、Setter方法注入、接口注入等。

3.Spring的生命周期

​ Spring框架中的Bean(Java对象)的生命周期由Spring容器来管理,包括Bean的创建、初始化、使用和销毁等阶段。以下是Spring Bean的生命周期:

  1. 实例化(Instantiation): 当Spring容器接收到Bean的定义后,它会通过构造函数或工厂方法来实例化Bean对象。
  2. 设定Bean的属性(Population of Properties): Spring容器将配置文件或注解中定义的属性值或引用注入到Bean中。
  3. Bean的初始化(Bean Initialization): 如果Bean实现了InitializingBean接口,Spring将调用其afterPropertiesSet方法进行初始化。另外,如果在配置文件中使用了init-method属性,指定了初始化方法,Spring容器会在属性设置完成后调用这个指定的方法。
  4. Bean的使用(Bean is ready to use): 此时,Bean已经可以被应用程序使用了。
  5. Bean的销毁(Bean Destruction): 如果Bean实现了DisposableBean接口,Spring容器在Bean不再需要时调用其destroy方法进行销毁。或者,如果在配置文件中使用了destroy-method属性,指定了销毁方法,Spring容器在需要销毁Bean时调用这个指定的方法。

在整个生命周期中,Spring容器负责管理Bean的创建、依赖注入、初始化和销毁等工作,确保Bean在应用程序中的正确运作。

4.Spring事务

​ Spring框架提供了丰富的事务管理功能,支持编程式事务和声明式事务两种方式。事务是用来保持数据库的一致性和完整性的机制,当一组相关的操作要么全部成功,要么全部失败。

在Spring中,你可以使用注解或XML配置来声明式地管理事务。以下是一些关于Spring事务的关键概念:

  1. 事务管理器(Transaction Manager): 事务管理器负责实际管理事务。Spring支持各种事务管理器,如DataSourceTransactionManager(针对关系型数据库)、JtaTransactionManager(用于分布式事务)等。

  2. 事务定义(Transaction Definition): 事务定义定义了事务的隔离级别、传播行为、超时等属性。Spring使用org.springframework.transaction.TransactionDefinition接口来表示事务定义。

  3. 事务传播行为(Transaction Propagation): 事务传播行为定义了在方法调用中的事务如何传播。例如,一个方法A调用另一个方法B,B是否应该加入A的事务。Spring定义了多种传播行为,例如REQUIREDREQUIRES_NEWNESTED等。

  4. 隔离级别(Isolation Level): 隔离级别定义了多个事务并发执行时,彼此之间的可见性。Spring支持不同的隔离级别,包括DEFAULT(使用数据库默认隔离级别)、READ_UNCOMMITTED(允许读取未提交的数据)、READ_COMMITTED(只能读取已提交的数据)、REPEATABLE_READ(可重复读取)、SERIALIZABLE(串行化)等。

  5. 声明式事务管理: 通过注解或XML配置,你可以在方法上声明事务的属性。例如,使用@Transactional注解来标识一个方法应该在事务中执行。

    @Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.READ_COMMITTED)
    public void someTransactionalMethod() {
    // 事务性操作
    }
  6. 编程式事务管理: 在代码中通过编程的方式控制事务。Spring提供了TransactionTemplate来简化编程式事务管理。

    transactionTemplate.execute(new TransactionCallbackWithoutResult() {
    @Override
    protected void doInTransactionWithoutResult(TransactionStatus status) {
    // 事务性操作
    }
    });

通过合适的配置,Spring事务管理器可以确保事务的正确性和一致性,保障了数据操作的可靠性。

5.AOP是什么?AOP代理方式有哪些?

​ AOP(Aspect-Oriented Programming)是一种编程范式,它允许你定义横切关注点(cross-cutting concerns),如日志记录、性能监控、事务管理等,并将这些关注点模块化,然后将它们自动应用到应用程序的特定部分,而无需修改这些部分的代码。AOP的目的是提高代码的模块性,降低耦合度,使代码更易于维护和扩展。

在AOP中,关注点(Aspect)是一个模块化的、可重用的模块,它包含了通知(Advice)和切点(Pointcut)两个主要概念。通知是关注点的具体行为,例如在方法执行前后执行的操作,而切点是指在何处应用通知的定义。

AOP代理方式主要有两种:

  1. 基于代理的AOP(Proxy-Based AOP): 这是最常见的AOP代理方式。在基于代理的AOP中,AOP框架创建一个目标对象(被代理的对象)的代理对象,并将通知织入到代理对象的方法调用中。Spring的AOP默认采用基于代理的AOP实现。基于代理的AOP主要有两种代理方式:
    • JDK动态代理:如果目标对象实现了接口,Spring将使用JDK动态代理来创建代理对象。
    • CGLIB代理:如果目标对象没有实现接口,Spring将使用CGLIB库来创建代理对象。
  2. 基于字节码的AOP(AspectJ AOP): 基于字节码的AOP通过直接在类的字节码上织入切面,因此不需要代理对象。这种方式更为强大和灵活,但也更复杂,通常需要使用特定的编译器或者在运行时进行字节码操纵。AspectJ是一个常用的基于字节码的AOP框架。

6.Spring的Bean有哪些创建方式?

​ 在Spring框架中,有多种方式可以创建Bean:

  1. XML配置方式:在XML配置文件中使用<bean>标签定义Bean的配置信息。

    <bean id="myBean" class="com.example.MyBean"/>
  2. 注解方式:使用注解标记类,然后在配置类中使用@ComponentScan或者@Component等注解进行扫描和定义Bean。

    @Component
    public class MyBean {
    // ...
    }
  3. Java配置方式:通过Java类配置Bean,使用@Configuration@Bean注解。

    @Configuration
    public class AppConfig {
    @Bean
    public MyBean myBean() {
    return new MyBean();
    }
    }
  4. 工厂方法方式:使用工厂方法创建Bean,即在XML配置文件或者Java配置类中定义工厂方法。

    <bean id="myBean" class="com.example.MyBeanFactory" factory-method="createInstance"/>
    public class MyBeanFactory {
    public static MyBean createInstance() {
    return new MyBean();
    }
    }
  5. 实例工厂方法方式:使用实例工厂方法创建Bean,需要定义一个实例工厂,然后在XML配置文件或Java配置类中引用该工厂。

    <bean id="myBean" factory-bean="myBeanFactory" factory-method="createInstance"/>
    public class MyBeanFactory {
    public MyBean createInstance() {
    return new MyBean();
    }
    }

以上是常见的Spring Bean创建方式,选择适合项目需求的方式可以提高代码的灵活性和可维护性。

7.Spring的Bean作用域有哪些?

在Spring框架中,Bean的作用域(Scope)定义了Bean实例的生命周期范围。Spring支持以下几种Bean的作用域:

  1. Singleton(单例):在整个Spring容器中,只存在一个Bean实例。无论有多少个Bean的定义,Spring容器都只会创建一个Bean实例,并在需要时返回给每个请求。

    <bean id="myBean" class="com.example.MyBean" scope="singleton"/>
  2. Prototype(原型):每次请求Bean时,容器都会创建一个新的Bean实例。每个请求都会得到一个全新的Bean对象。

    <bean id="myBean" class="com.example.MyBean" scope="prototype"/>
  3. Request(请求):在一次HTTP请求中,容器会返回同一个Bean实例。该作用域仅在使用Spring Web应用时有效。

    <bean id="myBean" class="com.example.MyBean" scope="request"/>
  4. Session(会话):在一个HTTP会话中,容器会返回同一个Bean实例。该作用域同样只在Spring Web应用时有效。

    <bean id="myBean" class="com.example.MyBean" scope="session"/>
  5. Global Session(全局会话):在一个全局HTTP会话中,容器会返回同一个Bean实例。该作用域通常用于Portlet应用环境。

    <bean id="myBean" class="com.example.MyBean" scope="globalSession"/>
  6. Application(应用):在整个Web应用中,容器会返回同一个Bean实例。

    <bean id="myBean" class="com.example.MyBean" scope="application"/>

这些作用域允许你控制Bean实例的生命周期和共享程度,根据需求选择合适的作用域可以提高系统的性能和资源利用率。

8.Spring如何解决Bean循环依赖问题?

​ 在Spring中,Bean的循环依赖是指两个或多个Bean相互依赖,形成一个循环引用的关系。Spring容器默认是不支持循环依赖的,但是它提供了一种机制来处理部分循环依赖情况。

Spring使用了三级缓存解决Bean的循环依赖问题:

  1. SingletonObjects缓存:Spring容器创建Bean时,会将正在创建的Bean放入SingletonObjects缓存中,标记为正在创建中。
  2. EarlySingletonObjects缓存:当发现循环依赖时,Spring会将早期暴露的Bean放入EarlySingletonObjects缓存中,标记为早期暴露的Bean。
  3. SingletonFactories缓存:当Bean创建完成后,会放入SingletonFactories缓存中,表示该Bean可以被其他Bean引用。

Spring容器在创建Bean时,会先从SingletonObjects缓存中查找是否存在Bean的实例。如果存在,直接返回。如果不存在,会先创建一个ObjectFactory,用于生成Bean的实例。然后,将该ObjectFactory放入SingletonFactories缓存中。接着,Spring会递归地创建Bean的依赖关系。如果在创建依赖关系的过程中,发现了循环依赖,Spring会从EarlySingletonObjects缓存中获取早期暴露的Bean,而不是直接创建新的实例。这样,就避免了循环依赖的问题。

需要注意的是,虽然Spring提供了这种机制来处理部分循环依赖情况,但是过多的循环依赖可能会导致系统设计存在问题,因此在设计时,尽量避免复杂的循环依赖关系。