Interview
Spring

Spring IoC

IoC(Inversion of Control:控制反转) 是一种设计原则,将在程序中手动创建对象的控制权交给 Spring 框架来管理。

为什么叫控制反转?

  • 控制:创建对象的权利
  • 反转:将控制权交给外部环境(Spring IoC 容器)

依赖注入(DI)

依赖注入是 IoC 的一种实现方式,Spring 支持以下类型的依赖注入:

  • 构造器注入:通过类的构造器来注入依赖的组件。
  • 设值注入(Setter 注入):通过类的 setter 方法来注入依赖。
  • 字段注入:直接在字段上注入依赖,通常通过 @Autowired 注解实现。

优势和应用

  • 降低耦合度:组件间的依赖关系不再硬编码在组件内部,而是可以通过配置进行灵活管理。
  • 增强模块的可测试性:依赖可以在测试时被替换为 mock 对象。
  • 提高代码的可管理性和可扩展性:依赖关系的管理由 Spring 容器统一处理,易于更新和管理。

什么是 Spring Bean?

在 Spring 框架中,Spring Bean 是一个由 Spring IoC 容器管理的对象。这些对象的创建、管理和配置都是由 Spring 的 IoC 容器负责。

将一个类声明为 Bean 的注解有哪些?

  • Component
  • Repository
  • Service
  • Controller

@Autowired 和 @Resource 的区别是什么?

  • @Autowired 是 Spring 自带的注解

    基于类型(type-based)进行自动依赖注入。但如果有多个相同类型的 bean,则必须通过其他方式(如 @Qualifier 注解)来指定注入哪个 bean。

    可用于构造器、字段、setter 方法和普通方法。

  • @Resource 是 JDK 提供的注解

    基于名称(name-based)或类型(type-based)进行依赖注入。默认通过 bean 的名称进行匹配;如果没有指定名称或者指定的名称没有找到则根据类型进行匹配。

    通常用于字段或 setter 方法。

Bean 的作用域

  • Singleton(单例):Spring IoC 容器为每个 Spring 应用创建一个 bean 实例;Spring 中的 bean 默认都是单例的。
  • Prototype(原型):每次获取都会创建一个新的 bean 实例。
  • Request(请求):每个 HTTP 请求都会创建一个新的 bean 实例,且该实例仅在当前 HTTP 请求内有效。
  • Session(会话):每个 HTTP 会话会创建一个新的 bean 实例。该实例对于同一个会话中的所有请求是共享的,但不同会话之间互不影响。
  • Application(应用):每个 Web 应用在启动时创建一个 Bean(应用 Bean),该 bean 仅在当前应用启动时间内有效。
  • WebSocket(WebSocket):bean 的生命周期与 WebSocket 会话相同。

Bean 的生命周期

单例对象:singleton,单例对象的生命周期和容器相同

多例对象:prototype

  • 出生:使用对象时,Spring 框架为我们创建
  • 活着:对象只要是在使用过程中就一直活着
  • 死亡:当对象长时间不用且没有其他对象引用时,由 java 的垃圾回收机制回收

IoC 容器初始化加载 Bean 流程:

@Override
public void refresh() throws BeansException, IllegalStateException { 
    synchronized  (this.startupShutdown- Monitor) {
        // 第一步:刷新前的预处理
        prepareRefresh();
        // 第二步:获取BeanFactory并注册到 BeanDefitionRegistry 
        ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
        // 第三步:加载BeanFactory的预准备工作(BeanFactory进行一些设置,比如context的类加载器等)     
        prepareBeanFactory(beanFactory);
        try {
        // 第四步:完成BeanFactory准备工作后的前置处理工作 
            postProcessBeanFactory(beanFactory);
        // 第五步:实例化BeanFactoryPostProcessor接口的Bean
            invokeBeanFactoryPostProcessors(beanFactory);
        // 第六步:注册BeanPostProcessor后置处理器,在创建bean的后执行 
            registerBeanPostProcessors(beanFactory);
        // 第七步:初始化MessageSource组件(做国际化功能;消息绑定,消息解析); 
            initMessageSource();
        // 第八步:注册初始化事件派发器
            initApplicationEventMulticaster();
        // 第九步:子类重写这个方法,在容器刷新的时候可以自定义逻辑
            onRefresh();
        // 第十步:注册应用的监听器。就是注册实现了ApplicationListener接口的监听器
            registerListeners();
        // 第十一步:初始化所有剩下的非懒加载的单例bean 初始化创建非懒加载方式的单例 Bean 实例(未设置属性)
            finishBeanFactoryInitialization(beanFactory);
        // 第十二步:完成context的刷新。主要是调用 LifecycleProcessor的onRefresh() 方法,完成创建
            finishRefresh();
        } catch (Exception e) {
            // ......
        }
    }
    // ...... 
}

四个阶段

  • 实例化 Instantiation
  • 属性赋值 Populate
  • 初始化 Initialization
  • 销毁 Destruction

多个扩展点

  • 影响多个 Bean

    • BeanPostProcessor
    • InstantiationAwareBeanPostProcessor
  • 影响单个 Bean

    • Aware

完整流程

  • 实例化一个 Bean,也就是我们常说的 new;
  • 按照 Spring 上下文对实例化的Bean进行配置,也就是IOC注入;
  • 如果这个 Bean 已经实现了 BeanNameAware 接口,会调用它实现的 setBeanName(String) 方法,也就是根据就是 Spring 配置文件中 Bean 的 id 和 name 进行传递
  • 如果这个 Bean 已经实现了 BeanFactoryAware 接口,会调用它实现 setBeanFactory(BeanFactory) 也就是Spring 配置文件配置的 Spring 工厂自身进行传递;
  • 如果这个 Bean 已经实现了 ApplicationContextAware 接口,会调用 setApplicationContext(ApplicationContext) 方法,和4传递的信息一样但是因为 ApplicationContext 是 BeanFactory的子接口,所以更加灵活
  • 如果这个Bean关联了BeanPostProcessor接口,将会调用postProcessBeforeInitialization()方法,Bean PostProcessor经常被用作是Bean内容的更改,由于这个是在Bean初始化结束时调用那个的方法,也可以被应用于内存或缓存技术
  • 如果Bean在Spring配置文件中配置了init-method属性会自动调用其配置的初始化方法。
  • 如果这个Bean关联了BeanPostProcessor接口,将会调用postProcessAfterInitialization(),打印日志或者三级缓存技术里面的bean升级
  • 以上工作完成以后就可以应用这个Bean了,那这个Bean是一个Singleton的,所以一般情况下我们调用同一个id的Bean会是在内容地址相同的实例,当然在Spring配置文件中也可以配置非Singleton,这里我们不做赘述。
  • 当Bean不再需要时,会经过清理阶段,如果Bean实现了DisposableBean这个接口,或者根据spring配置的destroy-method属性,调用实现的destroy()方法

循环依赖

循环依赖其实就是循环引用,也就是两个或者两个以上的 Bean 互相持有对方,最终形成闭环。

Spring 中循环依赖场景有

  • prototype 原型 bean 循环依赖
  • 构造器的循环依赖(构造器注入)
  • Field 属性的循环依赖(set 注入)

其中,构造器的循环依赖问题无法解决,在解决属性循环依赖时,可以使用懒加载,Spring 采用的是提前暴露对象的方法。

懒加载 @Lazy 解决循环依赖问题

Spring 启动的时候会把所有 Bean 信息(包括 XML 和注解)解析转化成 Spring 能够识别的 BeanDefinition 并存到 HashMap 里面供下面的初始化时用,然后对每个 BeanDefinition 进行处理。

普通 Bean 的初始化是在容器启动初始化阶段执行的,而被 lazy-initialization=true 修饰的 Bean 则是在容器里第一次进行 context.getBean() 时进行触发。

Spring Boot 2.2 新增了全局懒加载属性,开启后全局 bean 被设置为懒加载,需要时再去创建。

#默认false
spring.main.lazy-initialization=true

三级缓存解决循环依赖问题

三级缓存解决循环依赖问题(图片来自于网络)

  • Spring 容器初始化 ClassA 通过构造器初始化对象后提前暴露到 Spring 容器中的 singletonFactorys(三级缓存);

  • ClassA 调用 setClassB 方法,Spring 首先尝试从容器中获取 ClassB,此时 ClassB 不存在 Spring 容器中;

  • Spring 容器初始化 ClassB,ClassB 首先将自己暴露在三级缓存中,然后从 Spring 容器一级、二级、三级缓存中依次获取 ClassA;

  • 获取到 ClassA 后将自己实例化放入单例池中,实例 ClassA 通过 Spring 容器获取到 ClassB,完成了自己对象初始化操作;

  • 这样 ClassA 和 ClassB 都完成了对象初始化操作,从而解决了循环依赖问题。

AOP 动态代理

AOP(Aspect-Oriented Programming:面向切面编程)能够将那些与业务无关,却为业务模块所共同调用的逻辑 或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。

Spring AOP 就是基于动态代理的

  • 如果要代理的对象,实现了某个接口,那么 Spring AOP 会使用 JDKProxy,去创建代理对象
  • 对于没有实现接口的对象,就无法使用 JDK Proxy 去进行代理了,这时候 Spring AOP 会使用基于 asm 框架字节流的 Cglib 动态代理 ,这时候 Spring AOP 会使用 Cglib 生成一个被代理对象的子类来作为代理。

Spring AOP 和 AspectJ AOP 有什么区别?

Spring AOP 属于运行时增强,而 AspectJ 是编译时增强。 Spring AOP 基于代理(Proxying),而 AspectJ 基于字节码操作(Bytecode Manipulation)。

Spring AOP 已经集成了 AspectJ ,AspectJ 应该算的上是 Java 生态系统中最完整的 AOP 框架了。AspectJ 相比于 Spring AOP 功能更加强大,但是 Spring AOP 相对来说更简单,

如果我们的切面比较少,那么两者性能差异不大。但是,当切面太多的话,最好选择 AspectJ ,它比 Spring AOP 快很多。

AspectJ 定义的通知类型有哪些?

  • Before(前置通知):目标对象的方法调用之前触发
  • After (后置通知):目标对象的方法调用之后触发
  • AfterReturning(返回通知):目标对象的方法调用完成,在返回结果值之后触发
  • AfterThrowing(异常通知):目标对象的方法运行中抛出 / 触发异常后触发。AfterReturning 和 AfterThrowing 两者互斥。如果方法调用成功无异常,则会有返回值;如果方法抛出了异常,则不会有返回值。
  • Around (环绕通知):编程式控制目标对象的方法调用。环绕通知是所有通知类型中可操作范围最大的一种,因为它可以直接拿到目标对象,以及要执行的方法,所以环绕通知可以任意的在目标对象的方法调用前后搞事,甚至不调用目标对象的方法

多个切面的执行顺序如何控制?

  • 通常使用 @Order 注解直接定义切面顺序,值越小优先级越高

    @Order(3)
    @Component
    @Aspect
    public class LoggingAspect implements Ordered {}
  • 实现 Ordered 接口重新 getOrder() 方法,返回值越小优先级越高

    @Component
    @Aspect
    public class LoggingAspect implements Ordered {
        // ....
        @Override
        public int getOrder() {
            return 1;
        }
    }

SpringMVC 工作原理

  • 客户端(浏览器)发送请求,直接请求到 DispatcherServlet 。
  • DispatcherServlet 根据请求信息调用 HandlerMapping ,解析请求对应的 Handler 。
  • 解析到对应的 Handler(也就是 Controller 控制器)后,开始由 HandlerAdapter 适配器处理。
  • HandlerAdapter 会根据 Handler 来调用真正的处理器开处理请求,并处理相应的业务逻辑。
  • 处理器处理完业务后,会返回一个 ModelAndView 对象, Model 是返回的数据对象
  • ViewResolver 会根据逻辑 View 查找实际的 View 。
  • DispaterServlet 把返回的 Model 传给 View (视图渲染)。
  • 把 View 返回给请求者(浏览器)

Spring 管理事务的方式有几种?

  • 编程式事务:在代码中硬编码(在分布式系统中推荐使用) : 通过 TransactionTemplate 或者 TransactionManager 手动管理事务,事务范围过大会出现事务未提交导致超时,因此事务要比锁的粒度更小。
  • 声明式事务:在 XML 配置文件中配置或者直接基于注解(单体应用或者简单业务系统推荐使用):实际是通过 AOP 实现(基于@Transactional 的全注解方式使用最多)

常用注解

Spring 注解

声明 Bean 的注解

  • @Component 通用的注解,可标注任意类为 Spring 组件
  • @Service 在业务逻辑层使用
  • @Repository 在数据访问层使用
  • @Controller 在展现层使用,控制器的声明

注入 Bean 的注解

  • @Autowired 默认按照类型来装配注入,@Qualifier 可以改成名称
  • @Resource 默认按照名称来装配注入,JDK的注解,新版本已经弃用

SpringMVC 注解

  • @RestController
  • @RequestMapping
  • @PathVariable
  • @ResponseBody

Spring Boot 注解

@SpringBootApplication

等同于下面三个注解:

  • @SpringBootConfiguration 底层是 @Configuration 注解,就是支持 JavaConfig 的方式来进行配置
  • @EnableAutoConfiguration 开启自动配置功能
  • @ComponentScan 扫描注解,默认是扫码当前类下的 package

其中 @EnableAutoConfiguration 是关键(启用自动配置),内部实际上就去加载 META-INF/spring.factories 文件的信息,然后筛选出以 EnableAutoConfiguration 为 Key 的数据,加载到 IOC 容器中,实现自动配置功能!

全局异常处理注解

  • @ControllerAdvice 统一处理异常
  • @ExceptionHandler(Exception.class) 用在方法上面表示遇到这个异常就执行以下方法

事务

@Transactional

注意事项

  • 事务函数中不要处理耗时任务,会导致长期占有数据库连接。
  • 事务函数中不要处理无关业务,防止产生异常导致事务回滚。

事务传播属性

  1. REQUIRED(默认属性) 如果存在一个事务,则支持当前事务。如果没有事务则开启一个新的事务。
  2. MANDATORY 支持当前事务,如果当前没有事务,就抛出异常。
  3. NEVER 以非事务方式执行,如果当前存在事务,则抛出异常。
  4. NOT_SUPPORTED 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
  5. REQUIRES_NEW 新建事务,如果当前存在事务,把当前事务挂起。
  6. SUPPORTS 支持当前事务,如果当前没有事务,就以非事务方式执行。
  7. NESTED (局部回滚) 支持当前事务,新增 Savepoint 点,与当前事务同步提交或回滚。 嵌套事务一个非常 重要的概念就是内层事务依赖于外层事务。外层事务失败时,会回滚内层事务所做的动作。而内层事务操作失败并 不会引起外层事务的回滚。

MyBatis

MyBatis 如何防止 SQL 注入?

简单的说就是 #{} 是经过预编译的,是安全的;${} 是未经过预编译的,仅仅是取变量的值,是非安全的,存在 SQL 注入。在编写 MyBatis 的映射语句时,尽量采用 #{} 这样的格式。如果需要实现动态传入表名、列名,还需要做如下修改:添加属性 statementType="STATEMENT",同时 SQL 里的属有变量取值都改成 ${}

MyBatis 和 Hibernate 的区别

Hibernate 框架:是一个开源的对象关系映射框架,它对 JDBC 进行了非常轻量级的对象封装,建立对象与数据库表的映射。是一个全自动、完全面向对象的持久层框架。

MyBatis 框架:是一个开源对象关系映射框架,是一个半自动化的持久层框架。

区别

  • 开发方面

    • Hibernate 开发中,SQL 语句已经被封装,直接可以使用,加快系统开发;
    • MyBatis 数据半自动化,SQL 需要手工完成,稍微繁琐。

    但凡事都不是绝对的,如果对于庞大复杂的系统项目来说,复杂语句较多,Hibernate 就不是好方案。

  • SQL 优化方面

    • Hibernate 自动生成 SQL,有些语句较为繁琐,会多消耗一些性能;
    • MyBatis 手动编写 SQL,可以避免不需要的查询,提高系统性能。
  • 对象管理对比

    • Hibernate 是完整的对象-关系映射的框架,开发工程中,无需过多关注底层实现,只要去管理对象即可;
    • Mybatis 需要自行管理映射关系。

Spring Boot

介绍一下 Spring Boot,有哪些优点?

  • 简化配置:通过提供一系列的起步依赖,Spring Boot 使得项目配置更快捷简单。它自动配置Spring和第三方库,所以你几乎不需要进行配置。
  • 独立运行:Spring Boot应用包含内嵌的Web服务器(如Tomcat, Jetty或Undertow),使得你可以以java -jar的方式运行你的应用,无需部署到外部服务器。
  • 提供监控:内置的健康检查和外部化配置支持(如Spring Actuator)帮助监控和管理应用。
  • 易于理解:通过提供操作指南和大量的文档,Spring Boot让新手和经验丰富的开发者都能快速上手。
  • 社区和插件支持:强大的社区支持和丰富的插件生态系统使得Spring Boot非常适合快速开发企业级应用。
  • 微服务友好:Spring Boot天然支持微服务架构的构建,与Spring Cloud等工具无缝集成,支持服务的注册与发现、配置管理等微服务必需功能。

Spring Boot 和 Spring MVC 的区别

Spring Boot和Spring MVC分别针对不同的开发需求:

  • 目标和功能:如前所述,Spring MVC主要是一个Web框架,用于构建Web应用程序,处理HTTP请求和响应。Spring Boot是一个开发平台,旨在简化任何Spring应用的开发和部署过程,包括Web应用。
  • 配置:Spring MVC需要手动配置Web组件和依赖,包括视图解析器、消息转换器等。Spring Boot提供自动配置,尤其是在整合Spring MVC时,它自动配置DispatcherServlet等核心组件。
  • 独立性:Spring MVC应用通常需要部署到一个Web服务器或应用服务器上。Spring Boot应用是自包含的,包括一个嵌入式Web服务器,可以独立运行。
  • 使用案例:如果你正在构建一个传统的Spring Web应用并且需要完全控制其配置,Spring MVC是一个不错的选择。如果你需要快速开发、易于部署和良好的默认配置支持,Spring Boot是更合适的选择。

Spring Boot 自动配置原理了解吗?

Spring Boot 的自动配置是其核心功能之一,极大地简化了Spring应用的配置过程。这一功能允许开发者以极少的配置开始一个项目,同时还能保持运行时的灵活性和配置的可定制性。这一功能主要通过以下几个机制实现:

  • 条件注解(Conditional Annotations)

    Spring Boot 自动配置的核心是一系列的条件注解,它们控制着配置类的加载。这些注解包括:

    • @ConditionalOnClass:只有当指定的类在类路径上存在时,相关的配置才会生效。

    • @ConditionalOnMissingClass:只有当指定的类在类路径上不存在时,相关的配置才会生效。

    • @ConditionalOnBean:只有当Spring上下文中存在指定的Bean时,相关的配置才会生效。

    • @ConditionalOnMissingBean:只有当Spring上下文中不存在指定的Bean时,相关的配置才会生效。

    • @ConditionalOnProperty:只有当指定的配置属性有一个明确的值时,相关的配置才会生效。

    • @ConditionalOnResource:只有当指定的资源存在时,相关的配置才会生效。

​ 这些注解允许Spring Boot在运行时动态地决定哪些配置是活跃的,哪些不是。

  • 自动配置类(Auto-configuration Classes)

    Spring Boot使用一个名为@EnableAutoConfiguration的注解,它本质上告诉Spring Boot开始扫描自动配置类。这些自动配置类通常是通过上述条件注解来有条件地配置Spring容器的。例如,如果你的应用中添加了Spring Web的起步依赖,Spring Boot的自动配置机制会自动配置DispatcherServlet、WebMvcConfigurer等必需的Bean。

  • spring.factories 文件

    所有的自动配置类都列在META-INF/spring.factories文件中,这个文件位于各个自动配置模块的资源目录下。当@EnableAutoConfiguration被声明时,Spring Boot会加载这个文件,并根据文件中指定的配置类来应用自动配置。

    这个文件允许开发者以非侵入的方式扩展Spring Boot的自动配置功能,因为它们可以简单地通过添加自己的spring.factories文件来提供额外的配置。

  • 外部化配置

    Spring Boot允许开发者通过应用属性文件(如application.propertiesapplication.yml)外部化配置。这使得开发者可以在不改动代码的情况下覆盖自动配置的默认属性值。这种设计支持了Spring Boot的“约定优于配置”的理念,同时也提供了必要时进行细粒度配置的能力。

  • 总结

    通过这些机制,Spring Boot实现了一个强大而灵活的自动配置系统,它不仅减少了项目的配置复杂性,而且提供了良好的默认行为,同时保留了高度的可配置性和扩展性。这些特性使得Spring Boot非常适合快速开发和部署各种大小和复杂度的Spring应用。

如何自定义一个 Spring Boot Srarter?

Spring Boot 启动原理?

Spring Boot 的启动过程包含了多个关键步骤,这些步骤共同工作以确保应用能够快速、有效地启动并运行。理解这个启动过程有助于更好地利用Spring Boot的功能,优化应用的配置和行为。以下是Spring Boot启动过程的主要步骤:

  • 启动入口

    Spring Boot 应用通常从一个带有 public static void main(String[] args) 方法的主类开始,该方法调用 SpringApplication.run()。这是启动Spring Boot应用的标准方式。

    @SpringBootApplication
    public class Application {
        public static void main(String[] args) {
            SpringApplication.run(Application.class, args);
        }
    }

    @SpringBootApplication 注解是一个方便的注解,它包括了 @Configuration@EnableAutoConfiguration@ComponentScan 三个注解。

  • SpringApplication 实例创建

    当调用 SpringApplication.run() 方法时,首先会创建 SpringApplication 的一个实例。这个实例负责管理Spring应用的启动和初始化。

  • 推断应用的类型

    Spring Boot 会根据类路径中的类(例如是否存在 SpringMvc)来推断应用的类型,例如是否是Web应用。

  • 加载应用上下文

    接下来,根据推断出的应用类型加载合适的 ApplicationContext 类型。对于Web应用,通常是 AnnotationConfigServletWebServerApplicationContext;对于非Web应用,通常是 AnnotationConfigApplicationContext

  • 自动配置

    通过 @EnableAutoConfiguration 注解,Spring Boot 自动配置的魔法开始发挥作用。这个注解告诉Spring Boot基于类路径中的内容、其他Bean的定义以及各种属性设置尝试去猜测和配置Bean。

  • 配置类的读取

    Spring Boot 会读取配置类(带有 @Configuration 注解的类),这些类中可能定义了额外的Bean或者导入了其他配置类。

  • 启动内嵌的Web服务器

  • 如果应用是一个Web应用,Spring Boot 将配置并启动内嵌的Web服务器(默认是Tomcat,但也可以配置为Jetty或Undertow)。

  • spring.factories

    所有通过 spring.factories 文件注册的自动配置类都会被处理。这些自动配置类可以提供额外的Bean定义、条件配置等。

  • 应用上下文刷新

    完成所有配置和Bean的注册后,应用上下文被刷新。这意味着所有的Bean定义都将被加载成Bean实例,并且如果这些Bean实现了 ApplicationContextAwareInitializingBean 接口,相应的方法也会被调用。

  • 运行器(Runners)

    如果应用中定义了 CommandLineRunnerApplicationRunner,这些运行器将在此时执行。这些接口允许在Spring Boot应用完全启动后执行一些代码。

  • 应用准备就绪

    此时,应用已完全启动并准备好接收请求或者执行用户定义的任务。

这个启动过程说明了Spring Boot如何通过一系列优化的步骤来简化Spring应用的配置和启动。通过自动配置和各种智能默认设置,Spring Boot让开发者可以更专注于业务逻辑的实现,而不是花费时间在配置和管理应用启动的复杂性上。

参考