您的当前位置:首页正文

Spring面试题——第五篇

2024-11-23 来源:个人技术集锦

1. Spring的优点

  • 轻量级和非侵入性:不需要引入大量的依赖和配置。
  • 面向切面编程:Spring提供了强大的面向切面编程,允许用户定义横切关注点,并将其与核心业务逻辑分离,提高了灵活性。
  • 依赖注入(DI)和控制反转:Spring的核心容器是IOC,他实现了依赖注入模式,通过配置文件或者注解来管理对象之间的依赖关系,降低了耦合度,提高了代码的可维护性和可测试性。
  • 拥有大量的生态和活跃的社区。

2. Spring IOC容器初始化过程

一共有四个阶段,分别是启动、Bean定义注册、实例化和依赖注入、初始化。

  • 配置加载:加载配置文件或者配置类,IOC容器首先需要加载应用程序的配置信息,这些配置信息可以是XML配置文件、Java配置类或者注解配置等方式。
  • 创建容器:Spring创建IOC容器(BeanFactory、ApplicationContext),准备加载和管理Bean。
  1. Bean定义注册阶段
  • 解析和注册:BeanDefinitionReader读取解析配置中的Bean定义,并将其注册到容器中,形成BeanDefinition对象。
  1. 实例化和依赖注入
  • 实例化:根据BeanDefinition创建Bean实例
  • 依赖注入:根据BeanDefinition中的依赖关系,可以通过构造函数注入、Setter注入或者字段注入、将依赖注册到Bean中。
  1. 初始化
  • BeanPostProcessor:这些处理器会在Bean初始化生命周期中加入定义的处理逻辑,postProcessBeforeInitialization和postProcessAfterInitialization
  • Aware接口调用:如果Bean实现了Aware接口(如BeanNameAware、BeanFactoryAware),Spring会回调这些接口,传递容器相关信息。
  • 初始化方法调用:调用Bean的初始化方法(如通过@PostConstruct注解标注的方法,或者实现InitializingBean接口的bean会被调用afterPropertiesSet的方法)。

3. Spring Bean注册到容器有哪些方式

Spring Bean注册到容器的方式有以下几种

  • 基于XML的配置:使用XML文件配置Bean,并定义Bean的依赖关系
  • 基于@Component注解及其衍生注解:@Component、@Service、@Controller、@Repository
  • 基于@Configuration(声明配置类)、@Bean(注解定义Bean)注解
  • 基于@Import注解:@Import可以将普通类导入到Spring容器中,这些类会自动被注册为Bean。

4. @Qualifier注解作用

@Qualifier注解在Spring的主要作用是用于在依赖注入时消除歧义,当一个类型有多个实现时,@Qualifier注解可以指定需要注入哪一个具体的Bean。

例如,当Service有多个实现类的时候,可以通过@Qualifier指定名称选择对应的实现Bean。

@Component
public class Client {

    private final Service service;

    @Autowired
    public Client(@Qualifier("serviceImpl1") Service service) {
        this.service = service;
    }

    public void doSomething() {
        service.serve();
    }
}

5. @Bean和@Component有什么区别

@Bean和@Component 都是用于定义spring容器中的Bean的注解,但是,他们的使用场景和方式有所不同:

  • @Bean注解通常用于Java配置类的方法上,以声明一个Bean并将其添加到Spring容器中,用于显示声明。
  • @Component注解用于类级别,将该类标记为Spring容器中的一个组件,自动检测并注册为Bean,用于自动扫描和注入。
特性@Bean@Component
使用位置方法级别(在@Configuration类中)类级别
扫描机制不支持自动扫描,需要手动注册支持自动扫描,通过@ComponentScan自动发现
主要用途用于配置第三方库或者复杂对象用于自动发现并注册自定义类
常见场景手动配置复杂对象、第三方库类自定义服务、DAO层、控制器等类的自动注册
灵活性更灵活,适合复杂初始化自动化更强,适合类的简单注册

扩展
@Bean注解用于显示声明spring容器管理的Bean,通常用于以下场景

  • 手动创建复杂的对象:需要进行复杂的初始化过程,或者需要传递参数给构造函数的对象
  • 如果某个类不是自己开发,且无法添加spring注解时,可以通过@Bean来手动注册。

@Component使用场景
@Component注解用于类级别的扫描和注入,spring会自动发现和管理这些类,他是spring中实现自动扫描Bean的基础。
@Component 是一个通用的注解,还有一些特定用途的衍生注解

  • @Service
  • @Repository
  • @Controller

6. spring事务在什么情况下会失效

一般而言,失效的情况都是用了声明式事务即@Transactional注解,如果使用了这个注解那么在以下几种情况下会导致事务失效

  • rollbackFor没设置对,比如默认没有任何设置(只有发生了RuntimeException或者Error才能回滚),则方法内抛出IOException则不会回滚。需要设置@Transactional(rollbackFor=Exception.class).
  • 异常被捕获了,比如代码抛错,但是被catch了,这样事务无法正常获取到错误,因此不会回滚。
  • 同一个类中的方法调用,因此事务是基于动态代理实现的,同类的方法调用不会走代理方法,因此,事务自然就失效了。
  • @Transactional应用在非public修饰的方法上,spring事务管理器判断非公共方法则不应用事务。
  • @Transactional应用在final和static方法上,因为aop默认是cglib代理,无法对final方法子类化。static是静态方法,属于类,不属于实例对象,无法被代理。
  • propagation传播机制错误
  • 多线程环境,因为@Transactional是基于ThreadLocal存储上下文的,多线程情况下每个线程都有自己的上下文,那么就无法保持事务同步。
  • 用的是MySQL的MyISAM引擎,这个引擎本身不支持事务。

7. Spring启动过程

  1. 加载配置文件,初始化容器:spring启动时首先读取配置文件,包括配置数据库连接、事务管理、AOP配置等。
  2. 实例化容器:spring根据配置文件中的信息创建容器ApplicationContext,在容器启动阶段实例化BeanFactory,并加载容器中的BeanDefinitions。
  3. 解析BeanDefinitions:spring容器会解析配置文件中的BeanDefinitions,即声明的Bean元数据,包括Bean的作用域,依赖关系等信息。
  4. 实例化Bean:spring根据BeanDefinitions实例化Bean对象,将其放入容器管理。
  5. 注入依赖:spring进行依赖注入,将Bean之间的依赖关系进行注入,包括构造函数注入、属性注入等。
  6. 处理Bean生命周期初始化方法
  • spring调用Bean初始化方法,对Bean进行初始化
  • 如果Bean实现了InitializingBean接口,spring会调用其afterPropertiesSet方法。
  1. 处理BeanPostProcessors:容器定义了很多BeanPostProcessor,处理其中的自定义逻辑,例如PostProcessBeforeInitializaiton会在Bean初始化前调用。PostProcessAfterInitializaiton则在Bean初始化之后调用。
  2. 发布事件:Spring可能会在启动过程中发布一些事件,比如容器启动事件。
  3. 完成启动:当所有Bean初始化完毕、依赖注入完成、AOP配置生效等都准备就绪时,Spring容器启动完成。

8. Spring的单例Bean是否有并发安全问题

存在并发安全问题,因为Spring容器默认将Bean作为单例管理,因此同一个Bean的实例会在整个应用程序中被多个线程共享。在多线程环境中,如果Bean中包含全局可变状态(如实例变量或者非线程安全资源),则可能会引发线程安全问题。

解决方案

  • 避免在单例Bean中使用可变状态:确保单例Bean是无状态的或仅使用线程安全的数据结构。
  • 使用@Scope(“prototype”) :对于有状态的Bean,Spring提供了原型作用域,每次请求都会创建一个新的Bean实例,从而避免共享同一个实例带来的并发问题。
  • 加锁:如果需要在单例Bean中管理共享资源,可以通过synchronize关键字或者其他线程同步机制来确保线程安全。
  • 使用ThreadLocal保存变量。

9. Spring和Spring MVC的关系

Spring是基础,Spring MVC构建于Spring核心之上,利用其提供的容器管理、依赖注入、AOP等功能来实现Web层的处理。

Spring MVC的核心功能

  1. 基于MVC模式的Web开发
  • Model:负责封装数据,可以是POJO、DTO或者其他形式对象
  • View:负责展示数据
  • Controller:负责用户请求处理。

请求处理流程
Spring MVC通过前端控制器拦截所有的请求,并将请求分发给合适的控制器进行处理。

  1. DispatcherServlet拦截请求
  2. HandlerMapping根据请求URL查找对应的控制器
  3. Controller处理业务逻辑,并返回数据
  4. ViewResolver 决定渲染哪个视图模板
  5. 将响应返回给客户端。

10. Spring MVC中的拦截器是什么?如何定义一个拦截器

拦截器用于在请求处理流程的不同阶段拦截HTTP请求和响应,并对其进行预处理或者后处理,拦截器可以用于实现注入权限验证、日志记录、性能监控等功能,而无需将这些逻辑直接耦合在控制器代码中。

定义一个拦截器的步骤
实现HandlerInterceptor接口:自定义的拦截器需要实现HandlerInterceptor接口,并且重写其三个核心方法

  • preHandle(): 请求到达控制器之前的预处理
  • postHandle():控制器执行之后但视图渲染之前的后处理
  • afterCompletion(): 整个请求结束之后的回调。

11. Spring MVC如何处理异常

可以利用全局和局部的异常处理机制,用于捕获应用程序中的异常并返回适当的响应。

Spring MVC中处理异常的核心方式

  1. 局部异常处理:@ExceptionHandler注解,用于局部的异常处理,通常定义在控制器类中。他可以捕获特定的异常,并返回自定义的错误信息或者视图。
  2. 全局异常处理:@ControllerAdvice,应用于所有的控制器,通过这个注解,可以定义全局的异常处理逻辑,避免在每个控制器中重复编写相同的异常处理代码。

使用@ExceptionHandler处理局部异常

@Controller
public class UserController {

    @GetMapping("/user/{id}")
    public String getUser(@PathVariable Long id) {
        if (id == null) {
            throw new IllegalArgumentException("ID cannot be null");
        }
        // 业务逻辑
        return "userProfile";
    }

    // 局部异常处理方法
    @ExceptionHandler(IllegalArgumentException.class)
    public ResponseEntity<String> handleIllegalArgumentException(IllegalArgumentException ex) {
        return new ResponseEntity<>(ex.getMessage(), HttpStatus.BAD_REQUEST);
    }
}

使用@ControllerAdvice处理全局异常

@ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(Exception.class)
    public ResponseEntity<String> handleAllExceptions(Exception ex) {
        return new ResponseEntity<>("An error occurred: " + ex.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
    }
}
显示全文