Spring面试题

1.介绍一下Spring

关于spring的话,我们平时做项目一直都在用,不管是使用SSH还是SSM,都可以整合。spring里面主要的就三点,也就是核心思想,IOC控制反转DI依赖注入AOP切面编程
我先来说说IOC吧,IOC就是Spring里的控制反转,把类的控制权呢交给spring来管理,我们在使用的时候,在Spring的配置文件中配置好bean标签,以及类的全路径,如果有参数,然后在配置上相应的参数。这样的话,spring就会给我们通过反射的机制实例化这个类,同时放到spring容器当中去。
我们在使用的时候,需要结合DI依赖注入使用,把我们想使用的类注入到需要的地方就可以,依赖注入的方式有构造器注入,get,set注入还有注解注入。我们现在都使用@Autowired或者@Resource注解的方式注入。
然后就是AOP切面编程,他可以在不改变源代码的情况下对代码功能的一个增强。我们在配置文件中配置好切点,然后去实现切面的逻辑就可以实现代码增强,这个代码增强,包括在切点的执行前,执行中,执行后都可以进行增强逻辑处理,不用改变源代码,这块在我们项目中一般用于权限认证、日志、事物处理这几个地方。
记录操作日志核心:使用aop中的环绕通知+切点表达式(找到要记录日志的方法),通过环绕通知的参数获取请求方法的参数(类、方法、注解、请求方式等),获取到这些参数以后,保存到数据库
spring事务是如何实现
其本质是通过AOP功能,对方法前后进行拦截,在执行方法之前开启事务,在执行完目标方法之后根据执行情况提交或者回滚事务。
spring事务失效的场景
异常捕获处理:原因,事务通知只有捉到了目标抛出的异常,才能进行后续的回滚处理,如果目标自己处理掉异常,事务通知无法知悉。解决,在catch块添加throw new RuntimeException(e)抛出。
抛出检查异常,原因,Spring 默认只会回滚非检查异常,解决,配置rollbackFor属性为Exception
非public方法导致的事务失效,原因,Spring 为方法创建代理、添加事务通知、前提条件都是该方法是 public 的
解决,改为public

2.AOP的实现原理

这块呢,我看过spring的源码,底层就是动态代理来实现的,所谓的动态代理就是说AOP框架不会去修改字节码,而是每次运行时在内存中临时为方法生成一个AOP对象,这个AOP对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法。
Spring AOP中的动态代理主要有两种实现方式,JDK动态代理和CGLIB动态代理

  • JDK动态代理只提供接口的代理,不支持类的代理。核心是InvocationHandler接口和Proxy类,InvocationHandler通过invoke()方法反射来调用目标类中的代码,动态地将横切逻辑和业务编织在一起;接着,Proxy利用InvocationHandler动态创建一个符合接口的实例,生成目标类的代理对象。
  • 如果代理类没有实现InvocationHandler接口,那么Spring会选择使用CGLIB来动态代理目标类。CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成指定类的一个子类对象,并覆盖其中特定方法并添加增强代码,从而实现AOP。CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。不过在我们的业务场景中没有代理过的final的类,基本上都代理的controller层实现权限以及日志,还有就是service层实现事物统一管理。

3.详细介绍下IOC容器

Spring 提供了两种 IoC 容器,分别为 BeanFactory 和 ApplicationContext
BeanFactory 是基础类型的 IoC 容器,提供了完整的 IoC 服务支持。简单来说,BeanFactory 就是一个管理 Bean 的工厂,它主要负责初始化各种 Bean,并调用它们的生命周期方法。
ApplicationContext 是 BeanFactory 的子接口,也被称为应用上下文。它不仅提供了 BeanFactory 的所有功能,还添加了对 i18n(国际化)、资源访问、事件传播等方面的良好支持。
它俩的主要区别在于,如果 Bean 的某一个属性没有注入,则使用 BeanFacotry 加载后,在第一次调用 getBean() 方法时会抛出异常,但是呢ApplicationContext 会在初始化时自检,这样有利于检查所依赖的属性是否注入。
因此,在实际开发中,通常都选择使用 ApplicationContext。

4.@Autowired 和 @Resource的区别

@Autowired 默认是按照类型注入的,如果这个类型没找到,会根据名称去注入,当一个接口存在多个实现类的情况下,如果在用的时候需要指定名称,可以加注解@Qualifier(“指定名称的类”)。@Autowired 是 Spring 提供的注解,@Autowired 支持在构造函数、方法、字段和参数上使用。

@Resource注解也可以从容器中注入bean,默认是按照名称注入的,如果这个名称的没找到,就会按照类型去找,也可以在注解里直接指定名称@Resource(name=“类的名称”)。@Resource 是 JDK 提供的注解,@Resource 主要用于字段和方法上的注入,不支持在构造函数或参数上使用。

5.bean的生命周期

生命周期这块无非就是从创建到销毁的过程。
spring容器可以管理 singleton 作用域 Bean 的生命周期,在此作用域下,Spring 能够精确地知道该 Bean 何时被创建,何时初始化完成,以及何时被销毁。
而对于 prototype 作用域的 Bean,Spring 只负责创建,当容器创建了 Bean 的实例后,Bean 的实例就交给客户端代码管理,Spring 容器将不再跟踪其生命周期。每次客户端请求 prototype 作用域的 Bean 时,Spring 容器都会创建一个新的实例,并且不会管那些被配置成 prototype 作用域的 Bean 的生命周期。
整体来说就4个步骤:实例化bean,属性赋值,初始化bean,销毁bean

  • 首先就是实例化bean,容器通过获取BeanDefinition对象中的信息进行实例化
  • 然后呢就是属性赋值,利用依赖注入完成 Bean 中所有属性值的配置注入
  • 接着就是初始化bean,如果在配置文件中通过 init-method 属性指定了初始化方法,则调用该初始化方法。
  • 最后就是销毁bean,和init-method一样,通过给destroy-method指定函数,就可以在bean销毁前执行指定的逻辑

6.bean的作用域

Spring 容器中的 bean 可以分为 5 个范围:

(1)singleton:单例模式,使用 singleton 定义的 Bean 在 Spring 容器中只有一个实例,这也是 Bean 默认的作用域。 controller、service、dao层基本都是singleton的

(2)prototype:原型模式,每次通过 Spring 容器获取 prototype 定义的 Bean 时,容器都将创建一个新的 Bean 实例。

(3)request:在一次 HTTP 请求中,容器会返回该 Bean 的同一个实例。而对不同的 HTTP 请求,会返回不同的实例,该作用域仅在当前 HTTP Request 内有效。

(4)session:在一次 HTTP Session 中,容器会返回该 Bean 的同一个实例。而对不同的 HTTP 请求,会返回不同的实例,该作用域仅在当前 HTTP Session 内有效。

(5)global-session:全局作用域,在一个全局的 HTTP Session 中,容器会返回该 Bean 的同一个实例。

7.事物的传播特性

事务的传播特性发生在事务方法与非事物方法之间相互调用的时候,在事务管理过程中,传播行为可以控制是否需要创建事务以及如何创建事务。
在这里插入图片描述

8.事物的隔离级别

Spring 事务的本质其实就是数据库对事务的支持,没有数据库的事务支持,spring是无法提供事务功能的。真正的数据库层的事务提交和回滚是通过 binlog实现的。隔离级别有四种:

  • Read uncommitted (读未提交):读未提交,允许另外一个事务可以看到这个事务未提交的数据,最低级别,任何情况都无法保证。
  • Read committed (读已提交):保证一个事务修改的数据提交后才能被另一事务读取,而且能看到该事务对已有记录的更新,可避免脏读的发生
  • Repeatable read (可重复读):保证一个事务修改的数据提交后才能被另一事务读取,但是不能看到该事务对已有记录的更新,可避免脏读、不可重复读的发生。
  • Serializable (串行化):一个事务在执行的过程中完全看不到其他事务对数据库所做的更新,可避免脏读、不可重复读、幻读的发生。

9.spring中都用了哪些设计模式

(1)工厂模式:BeanFactory就是简单工厂模式的体现,用来创建对象的实例;
(2)单例模式:Bean默认为单例模式。
(3)代理模式:Spring的AOP功能用到了JDK的动态代理和CGLIB字节码生成技术;
(4)模板方法:用来解决代码重复的问题。比如. RestTemplate, JmsTemplate, JpaTemplate。
(5)观察者模式:定义对象键一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知被制动更新,如Spring中listener的实现–ApplicationListener。

10.spring中如何处理bean在线程并发时线程安全问题

在一般情况下,只有无状态的 Bean 才可以在多线程环境下共享,在 Spring 中,绝大部分 Bean 都可以声明为 singleton 作用域,因为 Spring 对一些 Bean 中非线程安全状态采用 ThreadLocal 进行处理,解决线程安全问题。
ThreadLocal 和线程同步机制都是为了解决
多线程中相同变量的访问冲突问题
。同步机制采用了“时间换空间”的方式,仅提供一份变量,不同的线程在访问前需要获取锁,没获得锁的线程则需要排队。而 ThreadLocal 采用了“空间换时间”的方式。
ThreadLocal 会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。ThreadLocal 提供了线程安全的共享对象,在编写多线程代码时,可以把不安全的变量封装进 ThreadLocal。
我们项目中的拦截器里就有这样的逻辑,在我们微服务中,网关进行登录以及鉴权操作,具体的微服务中需要用到token去解析用户信息,我们就在拦截器的preHandler里定义了threadlocal,通过token解析出user的信息,后续controller以及service使用的时候,直接从threadlocal中取出用户信息的,在拦截器的afterCompletion方法中清理threadlocal中的变量,避免变量堆积消耗内存。

11.Spring常用的注解

@Autowired:用于自动装配bean,可以标注在成员变量、方法以及构造函数上,Spring容器在启动时会根据类型或名称自动注入匹配的bean。
@Qualifier:用于指定注入bean的名称,当有多个相同类型的bean时,通过@Qualifier注解可以消除歧义,确保注入正确的bean。
@Resource:直接按照bean的id进行注入,它可以独立使用,用于替代传统的setter注入方式。
@Component、@Repository、@Service、@Controller:这些注解用于实例化对象,分别标注不同的层,如组件层、数据访问层、业务逻辑层和控制器层。
@RequestMapping:用于映射HTTP请求到具体的方法上,可以应用于类和方法级别,允许开发人员定义处理传入请求的URL模式。
@RequestBody:用于获取请求体的内容,并将请求体的内容转换为JavaBean,常用于接收JSON格式的数据。
@CrossOrigin:用于开启跨域请求,允许其他域的请求访问该controller,解决跨域访问问题。
@Configuration: 用于标识一个类为配置类,通常与@Bean注解一起使用,用于替代xml配置文件。
@Aspect: 用于定义切面,结合其他注解如@Before、@After等实现AOP功能。
@Transactional: 用于声明事务,通常用在Service层的方法上。

12.如何在spring启动时执行一些操作

可以通过实现ApplicationRunner或者CommandLineRunner接口,在Spring Boot应用程序启动后执行特定操作。另外,你也可以使用@PostConstruct注解,在Spring Bean初始化后立即执行特定操作。此外,Spring Boot还提供了事件机制,你可以使用ApplicationListener接口或者@EventListener注解来监听应用程序的不同阶段,并在触发事件时执行相应的操作。

如果你需要处理复杂的命令行参数,建议使用ApplicationRunner;如果只需要简单地处理命令行参数,可以使用CommandLineRunner。通常情况下,根据实际需求选择合适的接口来实现即可。@PostConstruct注解适用于在Spring Bean初始化后立即执行一些必要的操作,例如初始化资源、建立连接、加载配置等。

13、Spring如何解决循环依赖的问题

在 Spring 框架中,循环依赖指的是两个或多个 bean 互相依赖,形成了一个循环关系。Spring 通过多种机制来解决循环依赖问题,主要有以下几种方式:

  1. 单例模式下的构造器注入

    • Spring 无法处理通过构造器注入的循环依赖。如果两个或多个 bean 通过构造器互相依赖,Spring 会抛出 BeanCurrentlyInCreationException 异常。这是因为构造器注入要求所有依赖在对象创建时就要满足,但在循环依赖的情况下,这无法实现。
  2. 单例模式下的属性注入

    • Spring 能够处理通过属性注入的循环依赖。Spring 的单例 bean 默认是通过 ApplicationContext 的一级缓存(单例缓存)和二级缓存(提前暴露的单例对象)来解决循环依赖的。

详细说明:

属性注入的循环依赖

假设有两个类 AB,它们互相依赖:

public class A {
    private B b;

    public void setB(B b) {
        this.b = b;
    }
}

public class B {
    private A a;

    public void setA(A a) {
        this.a = a;
    }
}

在 Spring 配置中:

<bean id="a" class="com.example.A">
    <property name="b" ref="b"/>
</bean>

<bean id="b" class="com.example.B">
    <property name="a" ref="a"/>
</bean>
Spring 如何解决
  1. 创建 Bean A

    • Spring 首先创建 A 的实例,但 A 需要 B,此时 B 还没有创建。
    • Spring 将 A 的实例放入一个早期引用的缓存中(第三级缓存)。
  2. 创建 Bean B

    • Spring 开始创建 B 的实例,并发现 B 需要 A
    • Spring 从早期引用缓存中获取 A 的实例,并注入到 B 中。
  3. 完成初始化

    • B 创建完成后,B 被注入到 A 中,完成 A 的初始化。

三级缓存

Spring 解决循环依赖的关键是三级缓存机制:

  1. 一级缓存:单例池,用于存放完全初始化好的单例 bean。
  2. 二级缓存:提前暴露的单例对象,用于存放尚未完全初始化的 bean 实例。
  3. 三级缓存:工厂缓存,用于存放 bean 实例的工厂,以便在循环依赖时能够获取到提前曝光的 bean。

示例

以下是通过属性注入解决循环依赖的示例:

@Configuration
public class AppConfig {
    @Bean
    public A a() {
        return new A();
    }

    @Bean
    public B b() {
        return new B();
    }

    @Bean
    public CommandLineRunner commandLineRunner(ApplicationContext ctx) {
        return args -> {
            A a = ctx.getBean(A.class);
            B b = ctx.getBean(B.class);

            a.setB(b);
            b.setA(a);

            System.out.println("A -> B: " + a.getB());
            System.out.println("B -> A: " + b.getA());
        };
    }
}

其他解决方案

  1. @Lazy 注解
    • 在 Spring 配置中,可以使用 @Lazy 注解来解决循环依赖问题。@Lazy 注解表示在需要使用时才进行初始化。
   public class A {
       private B b;

      @Autowired
       @Lazy
       public void setB(B b) {
           this.b = b;
       }
   }

   public class B {
       private A a;

       @Autowired
       @Lazy
       public void setA(A a) {
           this.a = a;
       }
   }
   ```

2. **使用 `@PostConstruct` 和 `@PreDestroy`**-Spring 管理的 bean 中,可以使用 `@PostConstruct` 注解的方法来初始化依赖关系,解决循环依赖。

   ```java
   public class A {
       private B b;

       @Autowired
       public void setB(B b) {
           this.b = b;
       }

       @PostConstruct
       public void init() {
           // 在这里初始化依赖
       }
   }

   public class B {
       private A a;

       @Autowired
       public void setA(A a) {
           this.a = a;
       }

       @PostConstruct
       public void init() {
           // 在这里初始化依赖
       }
   }
   ```

3. **使用构造器注入解决循环依赖**- 尽量避免使用构造器注入来解决循环依赖,因为构造器注入无法被 Spring 自动解决。可以考虑使用其他设计模式来重构代码,避免循环依赖。
### 总结
Spring 通过三级缓存机制(单例缓存、提前暴露的单例对象、工厂缓存)解决属性注入情况下的循环依赖。通过合理的设计和使用注解(如 `@Lazy`、`@PostConstruct`)可以进一步优化和解决循环依赖问题。尽量避免构造器注入中的循环依赖,使用属性注入或重构代码来解决。
## 14、spring事物如何回滚
在涉及到数据库操作时,事务的回滚机制能够确保数据的一致性。Spring 提供了多种方式来管理事务,包括编程式事务管理和声明式事务管理。
### 声明式事务管理
这是 Spring 提供的最常用的事务管理方式,使用注解或 XML 配置来声明事务。
#### 使用 @Transactional 注解
`@Transactional` 注解可以应用在类或者方法上,用来声明该类或方法是一个事务的边界。在事务过程中,如果遇到任何运行时异常(unchecked exception)或者 `Error`,事务将会回滚。
```java
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class UserService {

   @Autowired
   private UserRepository userRepository;

   @Transactional
   public void createUser(User user) {
       userRepository.save(user);
       // 其他业务逻辑
       // 如果这里抛出一个运行时异常,事务将会回滚
       if (someCondition) {
           throw new RuntimeException("Something went wrong");
       }
   }
}
回滚规则

默认情况下,@Transactional 注解会对 RuntimeExceptionError 进行回滚,对 Checked Exception(非运行时异常)不会回滚。如果你希望对 Checked Exception 也进行回滚,可以使用 rollbackFor 属性:

@Transactional(rollbackFor = Exception.class)
public void createUser(User user) {
    userRepository.save(user);
    // 其他业务逻辑
    if (someCondition) {
        throw new Exception("Checked Exception");
    }
}

同样的,可以使用 noRollbackFor 属性来指定某些异常不触发回滚:

@Transactional(noRollbackFor = RuntimeException.class)
public void createUser(User user) {
    userRepository.save(user);
    // 其他业务逻辑
    if (someCondition) {
        throw new RuntimeException("Runtime Exception");
    }
}

编程式事务管理

除了声明式事务管理,Spring 还提供了编程式事务管理。这种方式允许你在代码中手动管理事务边界。

使用 TransactionTemplate

TransactionTemplate 是 Spring 提供的一个编程式事务管理工具类。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;

@Service
public class UserService {

    private final TransactionTemplate transactionTemplate;

    @Autowired
    public UserService(PlatformTransactionManager transactionManager) {
        this.transactionTemplate = new TransactionTemplate(transactionManager);
    }

    public void createUser(User user) {
        transactionTemplate.execute(new TransactionCallbackWithoutResult() {
            @Override
            protected void doInTransactionWithoutResult(TransactionStatus status) {
                try {
                    userRepository.save(user);
                    // 其他业务逻辑
                    if (someCondition) {
                        throw new RuntimeException("Something went wrong");
                    }
                } catch (Exception e) {
                    status.setRollbackOnly();
                    throw e;
                }
            }
        });
    }
}

常见问题

  1. 事务传播行为

    • REQUIRED(默认):如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
    • REQUIRES_NEW:创建一个新的事务,如果当前存在事务,则挂起当前事务。
    • NESTED:如果当前存在事务,则在嵌套事务中执行;如果当前没有事务,则与 REQUIRED 类似。
  2. 事务隔离级别

    • READ_UNCOMMITTEDREAD_COMMITTEDREPEATABLE_READSERIALIZABLE
  3. 事务超时

    • 可以通过 timeout 属性来设置事务超时时间。
@Transactional(timeout = 5)  // 事务超时时间为5秒
public void createUser(User user) {
    userRepository.save(user);
    // 其他业务逻辑
}
  1. 只读事务
    • 可以通过 readOnly 属性来设置事务为只读模式,以优化查询性能。
@Transactional(readOnly = true)
public User getUser(Long id) {
    return userRepository.findById(id).orElse(null);
}

总结

Spring 提供了多种方式来管理事务,通过 @Transactional 注解实现声明式事务管理是最常用和推荐的方式。理解和合理使用事务传播行为、隔离级别、超时和回滚规则,可以确保应用程序的数据一致性和性能。编程式事务管理提供了更细粒度的控制,但在大多数情况下,声明式事务管理已经能够满足需求。

  • 19
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值