SSM 全家桶


Spring 概述


什么是Spring框架?

答:

  • Spring 是一个轻量级、非入侵式的控制反转(IoC)和 面向切面(AOP)的框架
  • Spring 是很多模块的集合,使用这些模块可以很方便地协助我们进行开发。这些模块是 核心容器、数据访问集成、Web、AOP(面向切面编程)、工具、消息和测试模块。比如:Core Container 中的 Core 组件是 Spring 所有组件的核心,Beans 组件和 Context 组件是实现 IOC 和 DI 的基础,AOP 组件 用来实现面向切面编程
  • Spring 官网列出的 Spring 的 6 个特征:
    1. 核心技术:依赖注入(DI),AOP,事件(Events),资源,i18n,验证,数据绑定,类型转换,SpEL
    2. 测试:模拟对象,TestContext 框架,Spring MVC 测试,WebTestClient
    3. 数据访问:事务,DAO支持,JDBC,ORM,编组XML
    4. Web支持:Spring MVC 和 Spring WebFlux Web 框架
    5. 集成:远程处理,JMS,JCA,JMX,电子邮件,任务,调度,缓存
    6. 语言:Kotlin,Groovy,动态语言

使用Spring框架的好处是什么?

答:

  1. 轻量:Spring 是轻量的,基本的版本大约 2MB
  2. 控制反转:Spring通过控制反转实现了松散耦合,对象们给出它们的依赖,而不是创建或查找依赖的对象们。
  3. 面向切面的编程(AOP):Spring 支持面向切面的编程,并且把应用业务逻辑和系统服务分开
  4. 容器:Spring 包含并管理应用中对象的生命周期和配置
  5. MVC 框架:Spring 的 WEB 框架是个精心设计的框架,是 Web 框架的一个很好的替代品
  6. 事务管理:Spring 提供一个持续的事务管理接口,可以扩展到上至本地事务下至全局事务(JTA)
  7. 异常处理:Spring 提供方便的 API 把具体技术相关的异常(比如由JDBC,Hibernate or JDO抛出的)转化为一致的 unchecked 异常

Spring框架都有哪些模块?

答:

Spring框架

  • 最主要的七大模块:
    1. Spring Core:提供了框架的基本组成部分,包括控制反转(Inversion of Control,IOC)和依赖注入(Dependency Injection,DI)功能
    2. Spring Beans:提供了 BeanFactory,是工厂模式的一个经典实现,Spring 将管理对象称为 Bean
    3. Spring Context:构建于 core 封装包基础上的 context 封装包,提供了一种框架式的对象访问方法
    4. Spring JDBC:提供了一个 JDBC 的抽象层,消除了烦琐的 JDBC 编码和数据库厂商特有的错误代码解析用于简化 JDBC
    5. Spring AOP:提供了面向切面的编程实现,让你可以自定义拦截器、切点等
    6. Spring Web:提供了针对 Web 开发的集成特性,例如:文件上传,利用 servlet listeners 进行 IoC 容器初始化和针对 Web 的 ApplicationContext
    7. Spring Test:主要为测试提供支持的,支持使用 JUnit 或 TestNG 对 Spring 组件进行单元测试和集成测试

Spring MVC 和 Struts 2 的区别?

答:

  • Spring MVC 特性如下:
    1. 具备 IOC/DI、AOP 等通用能力,提高研发效率
    2. 除了支持 Web 层建设以外,还提供了 J2EE 整体服务
    3. 方便与其他不同技术结合使用,如:Hibernate、MyBatis 等
    4. Spring 拦截机制是方法级别
  • Struts 特性如下:
    1. 是一个基于 MVC 模式的一个 Web 层的处理
    2. Struts 拦截机制是类级别

Spring 中的设计模式有哪些?

答:

  • 工厂模式:Spring 容器本质是一个大工厂,使用工厂模式通过 Beanfactory、ApplicationContext 创建bean 对象
  • 代理模式:Spring AOP 功能功能就是通过代理模式来实现的,分为:动态代理 和 静态代理
  • 单例模式:Spring 中的 Bean 默认都是 单例 的,这样有利于容器对 Bean 的管理
  • 模板模式:Spring 中 JdbcTemplate、RestTemplate 等以 Template 结尾的对数据库、网络等等进行操作的模板类,就使用到了模板模式
  • 观察者模式:Spring 事件驱动模型就是观察者模式很经典的一个应用
  • 适配器模式:Spring AOP 的增强或通知(Advice)使用到了适配器模式、Spring MVC 中也是用到了适配器模式适配 Controller
  • 策略模式:Spring 中有一个 Resource 接口,它的不同实现类,会根据不同的策略去访问资源

Spring IoC


什么是 IoC?

答:

  • IoC 即 控制反转(Inversion of Control,缩写为 IoC)。IoC 又称为 依赖倒置原则(设计模式六大原则之一),它的要点在于:程序要依赖于抽象接口,不要依赖于具体实现。它的作用就是 用于降低代码间的耦合度
  • IoC 的实现方式有两种:
    1. 依赖注入(Dependency Injection,简称 DI):不通过 new() 的方式在 类内部 创建依赖类对象,而是将依赖的类对象在外部创建好之后,通过构造器、函数参数等方式传递(或注入)给类使用
    2. 依赖查找(Dependency Lookup):容器中的受控对象通过容器的 API 来查找自己所依赖的资源和协作对象
  • Spring loC 是 IoC 的一种实现。DI 是 Spring IoC 的主要实现原则

谈谈你对 IOC 的理解?

答:

  • Spring IoC(Inversion of Control,控制反转)是 Spring 框架的核心,它实现了一种基于 容器 的对象管理机制。在 Spring IoC 中,控制权由应用程序代码转移到了 Spring 框架中,Spring 框架负责创建对象、管理对象之间的依赖关系、调用对象的方法等操作,应用程序只需要声明需要使用的对象和依赖关系,无需自己负责对象的创建和管理,从而实现了控制反转
  • 在 Spring IoC 中,容器负责创建和管理对象,容器根据配置文件或者注解中的信息,自动创建和管理对象之间的依赖关系,然后将这些对象注入到应用程序中。应用程序只需要声明需要使用的对象和依赖关系,通过 注入 的方式获取这些对象,从而避免了硬编码和耦合性的问题
  • Spring IoC 的主要实现方式是通过依赖注入(Dependency Injection,DI)来实现的。依赖注入是指在对象创建的过程中,自动注入该对象所依赖的其他对象,从而构建对象之间的依赖关系。Spring IoC 支持多种依赖注入的方式,如:构造函数注入、Setter 方法注入、字段注入等

什么是依赖注入?依赖注入有哪些实现方式?

答:

  • 所谓依赖注入,是指:程序运行过程中,如果需要调用另一个对象协助时,无须在代码中创建被调用者,而是依赖于外部的注入
  • Spring 的依赖注入对调用者和被调用者几乎没有任何要求,完全支持对 POJO 之间依赖关系的管理
依赖注入方式 配置元数据举例
Setter 方法注入 <property name="user" ref="userBean" />
构造器注入 <constructor-arg name="user" ref="userBean" />
字段注入 @Autowired User user;
方法注入 @Autowired public void user(User user) { ... }
接口回调注入 class MyBean implements BeanFactoryAware { ... }
  • Setter 注入:可选的注入方式,好处是在有变更的情况下,可以重新注入
  • 构造器注入:Spring 倡导构造函数注入,因为构造器注入返回给客户端使用的时候一定是完整的
  • Autowired 注入:就是平日我们用 @Autowired 标记字段
  • 接口回调注入:就是实现 Spring 定义的一些内建接口,例如:BeanfactoryAware,会进行 BeanFactory 的注入,不提倡

IoC 的实现原理是什么?

  • Spring IoC 的实现原理可以分为两个步骤:
    1. 扫描和解析 配置文件 或 注解信息,将其转换为 内部的对象定义 和 依赖关系
    2. 根据对象定义和依赖关系,使用 反射机制 动态创建和初始化对象,并将对象注入到需要使用它们的地方
  • 具体来说,Spring IoC 的实现过程如下:
    1. 读取配置文件或解析注解信息,将其转换为内部的对象定义和依赖关系:在 Spring 中,可以使用 XML 文件或注解来配置对象和依赖关系。Spring 通过解析配置文件或注解信息,将其转换为内部的对象定义和依赖关系(BeanDefinition)放到容器(BeanFactory)中。对象定义包括对象的类型、属性、构造函数等信息,依赖关系包括对象之间的依赖关系、依赖注入方式等信息
    2. 实例化 bean 对象:Spring 会根据对象定义的类型和构造函数信息,使用 反射机制 来创建对象
    3. 设置属性:实例化后的仍然是一个原生的状态,并没有进行依赖注入。这一步 Spring 根据 依赖关系 中的信息进行属性填充,依赖注入
    4. 调用 Aware 接口:Spring 会检测该对象是否实现了 xxxAware 接口,如果有会在这里执行完成。Aware 主要是能获取到 Spring容器 中的一些资源,然后可以供后续步骤,例如:初始化阶段使用
    5. BeanPostProcessor 前置处理:postProcessBeforeInitialzation 方法。上述几个步骤后,bean对象已经被正确构造,但如果想要对象被初始化前再进行一些自定义的处理,就可以通过BeanPostProcessor 接口的该方法来实现
    6. 初始化阶段:该阶段 Spring 首先会看是否是实现了 InitializingBean 接口的 afterPropertiesset 方法以及是否有自定义的 init-method 等,如果有会进行调用执行
    7. BeanPostProcessor 后置处理:postProcessAfterInitialzation 方法。当前正在初始化的 bean 对象会被传递进来,我们就可以对这个 bean 作任何处理,与前面前置处理相对的,这个函数会在 InitialzationBean 完成后执行,因此称为后置处理
    8. bean 初始化完成可以被使用了
  • 总的来说,Spring IoC 的实现原理是通过反射机制动态创建对象,依赖注入,对象初始化。通过解耦对象之间的依赖关系,使得应用程序更加灵活、可维护、可扩展

怎么理解 Spring 中的 IOC 容器?

答:

  • Spring IoC 就是把创建对象的权利交给框架去控制,而不需要人为的去创建,这样就实现了可插拔式的接口编程,有效地降低代码的耦合度,降低了扩展和维护的成本
  • 比如:去某地旅游不再用自己亲自为订购 A 酒店还是 B 酒店而发愁了,只需要把住店的需求告诉给某个托管平台,这个托管平台就会帮你订购一个既便宜又舒适的酒店,而这个帮你订购酒店的行为就可以称之为控制反转

怎么理解 Spring 中的依赖注入?

答:

  • 依赖注入是指 组件之间的依赖关系由容器在 运行期 决定,即:由容器动态的将某个依赖关系注入到组件之中
  • 依赖注入的目的并非为软件系统带来更多功能,而是为了提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台
  • 通过依赖注入机制,我们只需要通过简单的配置,而无需任何代码就可指定目标需要的资源,完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁实现

IoC 和 DI 有什么关系?

答:

  • IoC 和 DI 都是 Spring 框架中的核心概念,它们的区别在于:
    • IoC(Inverse of Control,控制反转):它是一种思想,主要解决程序设计中的对象依赖关系管理问题。在 IoC 思想中,对象的创建权反转给第三方容器,由容器进行对象的创建及依赖关系的管理
    • DI(Dependency Injection,依赖注入):它是 IoC 思想的具体实现方式之一,用于实现 IoC。在Spring 中,依赖注入是指:在对象创建时,由容器自动将依赖对象注入到需要依赖的对象中
  • 简单来说,它们的关系是:
    • IoC 是一种思想、理念,定义了对象创建和依赖关系处理的方式
    • DI 是 IoC 思想的具体实现方式之一,实际提供对象依赖关系的注入功能
  • 所以 IoC 是更基础和广义的概念,DI 可以说是 IoC 的一种实现手段。大多数情况下,我们提到 IoC 的时候,其实意味着 DI,因为 DI 已经是 IoC 最常见和广泛使用的实现方式了
  • 例如:在 Spring 框架中:
    • IoC 体现为 Spring 容器承担了对象创建及依赖关系管理的控制权
    • DI 体现为 Spring 容器通过 构造方法注入、Setter 方法注入 等方式,将依赖对象注入到需要依赖的对象中
  • 所以综上,IoC 和 DI 之间的关系可以这样理解:
    • IoC 是理论,DI 是实践
    • IoC 是思想,DI 是手段
    • IoC 是整体,DI 是部分

Spring Bean


什么是 Bean?

答:

  • 在 Spring 中,构成应用程序主体 由 Spring IoC 容器管理的对象称为 Bean。Bean 是由 Spring loC 容器 实例化、装配 和 管理 的对象。 Bean 以及它们之间的依赖关系反映在 容器使用的配置元数据 中
  • Spring loC 容器本身,并不能识别配置的元数据。为此,要将这些配置信息转为 Spring 能识别的格式BeanDefinition (依赖关系)对象
  • BeanDefinition (依赖关系)是 Spring 中定义 Bean 的配置元信息接口,它包含:
    • Bean 类名
    • Bean 行为配置元素,如:作用域、自动绑定的模式、生命周期回调 等
    • 其他 Bean 引用,也可称为 合作者(Collaborators)或 依赖(Dependencies)
    • 配置设置,如:Bean 属性(Properties)
  • 总结一下:
    1. Bean 是对象,一个或者多个不限定
    2. Bean 托管在 Spring 中一个叫 IoC 的容器中
    3. 我们的程序是由一个个 Bean 构成的

Bean 生命周期是什么?

答:

  • 单例对象
    • singleton 总结:单例对象的生命周期和容器相同
  • 多例对象:prototype
    • 出生:使用对象时 Spring 框架为我们创建
    • 活着:对象只要是在使用过程中就一直活着
    • 死亡:当对象长时间不用且没有其它对象引用时,由 java 的垃圾回收机制回收

Bean的流程

  • 总结:
    • 四个阶段:
      • 实例化 Instantiation
      • 属性赋值 Populate
      • 初始化 Initialization
      • 销毁 Destruction
    • 多个扩展点
      • 影响多个 Bean
        • BeanPostProcessor
        • InstantiationAwareBeanPostProcessor
      • 影响单个Bean
        • Aware

Bean的完整流程

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

什么是FactoryBean?

答:

  • FactoryBean 是 Spring 所提供的一种较灵活的创建 Bean 的方式,可以通过实现 FactoryBean 接口中的getObject() 方法来返回一个对象,这个对象就是最终的 Bean 对象
  • 如果一个对象实现了这接口,那它就成为一种特殊的 Bean,注册到 IOC 容器之后,如果调用 getBean 获取,得到的其实是 FactoryBean.getObject() 方法返回的结果

BeanFactory 和 FactoryBean区别是什么?

答:

  • BeanFactory 和 FactoryBean 是 Spring 框架中的两个关键概念,用于创建和管理 Bean 实例
  • Beanfactory 是 Spring 的基本容器,负责创建和管理 Bean 实例的,而 FactoryBean 是一个特殊的 Bean(实现了 FactoryBean 接口)负责创建其他 Bean 实例,并提供一些初始化 Bean 的设置
  • 下面是 BeanFactory 和 FactoryBean 之间的一些关键区别:
    1. 功能:Beanfactory 是一个容器,负责管理和创建 Bean 实例,处理依赖关系和属性注入等操作。FactoryBean 是一个接口,定义了创建 Bean 的规范和逻辑,它负责创建其他 Bean 实例
    2. 使用方式:Beanfactory 使用配置文件或注解来定义 Bean 和它们之间的关系,它使用延迟初始化策略,即:只有在需要时才创建 Bean 实例。FactoryBean 通常在 Spring 配置文件中配置,并由 Beanfactory 负责实例化和管理
    3. 创建的对象:BeanFactory 创建和管理普通的 Bean 实例,而 FactoryBean 创建其他 Bean 实例
    4. 灵活性:FactoryBean 具有更高的灵活性,因为它允许自定义的逻辑来创建和配置 Bean 实例。FactoryBean 的实现类可以根据特定的条件选择性地创建不同的 Bean 实例,或者在创建 Bean 之前进行一些初始化操作。这使得 FactoryBean 在某些情况下比 BeanFactory 更加强大和可扩展
    5. 返回类型:Beanfactory 返回的是 Bean 实例本身,而 FactoryBean 返回的是由 FactoryBean 创建的Bean 实例。因此,当使用 FactoryBean 时,需要通过调用 getObject() 方法来获取创建的 Bean 实例

BeanFactory 与 ApplicationContext 的关系是什么?

答:

BeanFactory ApplicationContext
它使用懒加载 它使用即时加载
它使用语法显式提供资源对象 它自己创建和管理资源对象
不支持国际化 支持国际化
不支持基于依赖的注解 支持基于依赖的注解
  • 相同点:BeanFactory 和 ApplicationContext 是Spring 框架中的两个重要的接口。它们都用于管理 Spring Bean 对象,但是它们在功能上有一些不同点
    • Beanfactory 是 Spring 框架中最基本的容器,它提供了最基础的 IoC 和 DI 的支持,它的主要功能是用于创建、管理和查找 Bean 对象。BeanFactory 只是个接口,并不是 IoC 容器的具体实现,它为其他具体的 IoC 容器提供了最基本的规范,例如:DefaultListableBeanFactory、ApplicationContext 等容器实现或容器接口都是基于 BeanFactory,再在其基础之上附加了其他的功能,原始的 BeanFactory 无法支持 Spring 的许多插件,如:AOP功能、Web应用 等。原始 BeanFactory 是延时加载,也就是说在容器启动时不会注入 bean,而是在需要使用 bean 的时候,才会对该 bean 进行加载实例化
    • **ApplicationContext 接口是基于 Beanfactory 扩展而来,也是一个容器接口,具有 Beanfactory 所有的功能,同时继承了 MessageSource,所以提供了更完整的框架功能,支持国际化、资源文件访问、载入多个上下文配置文件,使得每一个上下文都专注于一个特定层次,提供在监听器中注册bean事件,**ApplicationContext 是预加载,在容器启动的时候一次性加载所有的 bean,所以运行的时候速度相对BeanFactory 比较快,缺点就是耗内存
  • 总的来说:Beanfactory 是 Spring 框架中最基本的容器,提供最基础的 IoC 和 DI 的支持;而Applicationcontext 是在 Beanfactory 的基础上扩展而来的,提供了更多的功能和特性。Applicationcontext 是 Spring 框架中使用较为广泛的容器

Bean的作用域有哪些?

答:

官网说明

  1. singleton:IoC 容器中只有唯一的 bean 实例。Spring 中的 bean 默认都是单例的,是对单例设计模式的应用
  2. prototype:每次获取都会创建一个新的 bean 实例。也就是说,连续 getBean() 两次,得到的是不同的 Bean 实例
  3. request(仅 Web 应用可用):每一次 HTTP 请求都会产生一个新的 bean(请求 bean),该 bean 仅在当前 HTTP request 内有效
  4. session(仅 Web 应用可用):每一次来自新 session 的 HTTP 请求都会产生一个新的 bean(会话bean),该 bean 仅在当前 HTTP session 内有效
  5. application / global-session(仅 Web 应用可用):每个 Web 应用在启动时创建一个 Bean(应用Bean),该 bean 仅在当前应用启动时间内有效
  6. websocket(仅 Web 应用可用):每一次 WebSocket 会话产生一个新的 bean

默认作用域是 singleton,多个线程访问同一个 bean 时会存在线程不安全问题

保障线程安全方法:

  1. 在 Bean 对象中尽量避免定义可变的成员变量(不太现实)
  2. 在类中定义一个 ThreadLocal 成员变量,将需要的可变成员变量保存在 ThreadLocal 中

ThreadLocal:

  1. 每个线程中都有一个自己的 ThreadLocalMap类对象,可以将线程自己的对象保持到其中,各管各的,线程可以正确的访问到自己的对象
  2. 将一个共用的 ThreadLocal 静态实例作为 key,将不同对象的引用保存到不同线程的 ThreadLocalMap 中,然后在线程执行的各处通过这个静态 ThreadLocal 实例的 get()方法取得自己线程保存的那个对象,避免了将这个对象作为参数传递的麻烦

Bean 是线程安全的吗?

答:

  • Spring 框架中的 Bean 是否线程安全,取决于其作用域和状态
  • 我们这里以最常用的两种作用域 prototype 和 singleton 为例介绍。几乎所有场景的 Bean 作用域都是使用默认的 singleton ,重点关注 singleton 作用域即可
  • prototype 作用域下,每次获取都会创建一个新的 bean 实例,不存在资源竞争问题,所以不存在线程安全问题,singleton 作用域下,IoC 容器中只有唯一的 bean 实例,可能会存在资源竞争问题(取决于 Bean 是否有状态),如果这个 bean 是有状态的话,那就存在线程安全问题(有状态 Bean 是指包含可变的成员变量的对象)
  • 不过,大部分 Bean 实际都是无状态(没有定义可变的成员变量)的(比如: Dao、Service),这种情况下,Bean 是线程安全的
  • 对于有状态单例 Bean 的线程安全问题,常见的有两种解决办法:
    1. 在 Bean 中尽量避免定义可变的成员变量
    2. 在类中定义一个 ThreadLocal 成员变量,将需要的可变成员变量保存在 ThreadLocal 中(推荐的一种方式)

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

答:

我们一般使用 @Autowired 注解自动装配 bean,要想把类标识成可用于 @Autowired 注解自动装配的 bean 的类,采用以下注解可实现:

  • @Component:通用的注解,可标注任意类为 Spring 组件。如果一个 Bean 不知道属于哪个层, 可以使用 @Component 注解标注
  • @Repository:对应持久层即 Dao 层,主要用于数据库相关操作
  • @Service:对应服务层,主要涉及一些复杂的逻辑,需要用到 Dao 层
  • @Controller:对应 Spring MVC 控制层,主要用户接受用户请求并调用 Service 层返回数据给前端页面

注入 Bean 的注解有哪些?

答:

Spring 内置的 @Autowired 以及 JDK 内置的 @Resource 和 @Inject 都可以用于注入 Bean

Annotation Package Source
@Autowired org.springframework.bean.factory Spring 2.5+
@Resource javax.annotation Java JSR-250
@Inject javax.inject Java JSR-330
  • @Autowired 和 @Resource 使用的比较多一些

@Autowired 底层的实现原理是什么?

答:

  • Spring 中的 @Autowired 注解是通过依赖注入(DI)实现的,依赖注入是一种设计模式,它将对象的创建和依赖关系的管理从应用程序代码中分离出来,使得应用程序更加灵活和可维护
  • 具体来说,当 Spring 容器启动时,它会扫描应用程序中的所有 Bean,并将它们存储在一个 BeanFactory 中。当应用程序需要使用某个 Bean 时,Spring 容器会自动将该 Bean 注入到应用程序中
  • 但再往底层说,DI 是通过 Java 反射机制实现的。具体来说,当 Spring 容器需要注入某个 Bean 时,它会使用 Java 反射机制来查找符合条件的 Bean,并将其注入到应用程序中
  • 所以说,@Autowired 注解是通过 DI 的方式,底层通过 Java 的反射机制来实现的

说说 @Autowired 和 @Resource 注解的区别?

答:

  • @Autowired 是 Spring 提供的注解, @Resource 是 JDK 提供的注解
  • @Autowired 默认的注入方式为 byType(根据类型进行匹配),@Resource 默认注入方式为 byName (根据名称进行匹配)
  • 当一个接口存在多个实现类的情况下,@Autowired 和 @Resource 都需要通过名称才能正确匹配到对应的 Bean。 @Autowired 可以通过 @Qualifier 注解来显式指定名称,@Resource 可以通过 name 属性来显式指定名称
  • @Autowired 支持在构造函数、方法、字段和参数上使用。@Resource 主要用于字段和方法上的注入,不支持在构造函数或参数上使用

什么是三级缓存?

答:

  • Spring 的 三级缓存,一般指的是 Spring 单例注册表 DefaultSingletonBeanRegistry 中的三个本地缓存:
    • 一级缓存 singletonobjects:用于存放已经完成初始化的单例 Bean
    • 二级缓存 earlySingletonObjects:用于存放在某个 Bean 初始化过程中,由于循环依赖而在真正完成初始化之前就提前保留的早期 Bean
    • 三级缓存 singletonFactories:用于存放生成代理对象的临时代理工厂,当出现循环依赖的时候,如果有 Bean 需要生成代理,则会从此获取代理对象,并暴露到二级缓存中
  • 当创建 Bean 的时候,三级缓存分别在三个阶段被使用到:
    • 当创建 Bean 实例后,Spring 会通过 addSingletonFactory 方法向三级缓存添加一个 ObjectFactory,而当调用它的 getObject 方法时,最终将会调用 getEarlyBeanReference 方法创建一个代理对象
    • 当对 Bean 进行依赖注入和初始化时,如果存在循环依赖,那么这个阶段 Spring 会从三级缓存中获取ObjectFactory 并创建代理对象,并在此后将获得的代理对象添加到二级缓存,然后将 ObjectFactory 从三级缓存删除
    • 若 Bean 存在循环依赖,那么当 Bean 完成初始化后,将会主动调用一次 getSingleton 从二级缓存中获取代理对象,然后返回给调用方,此后该 Bean 将会被重新注册到一级缓存中,此时 Spring 会主动清除二级和三级缓存

为什么需要三级缓存?

答:

  • 由于 Spring 的代理发生在 Bean 初始化完成后,当存在循环依赖时,由于进行依赖注入的 Bean 尚未初始化,因此被进行依赖注入的 Bean 实际上获得的是尚未代理的原始 Bean,此时对其来说被依赖 Bean 的 AOP 实际上是失效的
  • 为了解决这个问题,Spring 需要让尚未初始化的 Bean 在进行依赖注入时也能够被代理,因此需要尽可能早的为其指定代理方法,不过由于并不是所有的 Bean 都需要进行代理,并且生成代理的过程本身也会触发相关 Bean 的加载(比如:Advice 与 Advisor 等相关组件),所以这个真正创建代理对象的时机又要尽可能的延迟
  • 出于这种 “提前占位,延迟代理” 的原则,Spring 最终选择通过 ObjectFactory 来实现创建代理这个操作的延迟执行,并额外增加了第三级缓存来保存 ObjectFactory,通过让第二级缓存只允许通过第三级缓存获取数据的方式,保证了在当通过依赖注入获取一个尚未完成初始化的 Bean 时,也能正确的获取到被代理的 Bean

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

答:

  • Spring 循环依赖问题指的是在 Spring 容器中出现相互依赖的情况,即:两个或多个 Bean 之间相互依赖,形成了一个循环依赖链。例如:Bean A 依赖 Bean B,Bean B 又依赖 Bean A,这就构成了一个循环依赖
  • Spring 是通过 三级缓存 解决循环依赖问题的,基本思路是:在 Bean 创建过程中,将正在创建的 Bean 对象放入一个专门用于缓存正在创建中的 Bean 对象的缓存池中,当后续创建其他 Bean 对象时,若需要依赖于该缓存池中正在创建的 Bean,则直接使用缓存池中的 Bean 对象,而不是重新创建一个新的 Bean 对象
  • 具体而言,Spring 通过三级缓存解决循环依赖问题的步骤如下:
    1. Spring 在创建 Bean 对象时,首先从一级缓存(singletonObjects)中查找是否存在已经创建完成的 Bean 对象,若存在则直接返回该 Bean 对象
    2. 若一级缓存中不存在该 Bean 对象,则从二级缓存(earlySingletonObjects)中查找是否存在该 Bean 对象的早期对象(还没进行属性填充和初始化的半成品对象),若存在则返回早期对象
    3. 若二级缓存中也不存在该 Bean 对象的早期对象,则将正在创建的 Bean 对象放入三级缓存(singletonFactories)中,并在创建过程中进行依赖注入,即:为该 Bean 对象注入依赖的其他 Bean 对象。此时,如果其他 Bean 对象中依赖了正在创建的 Bean 对象,Spring 将直接从三级缓存中获取正在创建的 Bean 对象,而不是重新创建一个新的 Bean 对象
    4. 当 Bean 对象创建完成后,Spring 将其从三级缓存中移除,并将其加入一级缓存中,以便下次获取该 Bean 对象时直接从一级缓存中获取

详细链接:Spring 是如何解决循环依赖的? - 知乎


Spring 中可以出现两个 ID 相同的 bean 吗,如果不行会在什么时候报错?

答:

  • 分情况
    • 同一个 spring 配置文件里不能存在 id 相同的 bean,会在解析 xml 文件转换为 BeanDefinition 阶段报错
    • 不同的 spring 配置文件里可以存在 id 相同的两个 bean,默认会把多个 id 相同的 bean 进行覆盖,
    • spring3.x 版本后使用 @Configuration 进行配置的时候,同一个配置类中使用 @Bean 声明多个相同名字的 bean 默认只会注册第一个,使用 @Autowired 可能会提示找不到未注册的类,使用 @Resource 注解会在 bean 初始化之后依赖注入的时候可能会提示类型不匹配错误

详细链接:【Java面试】Spring 中,有两个 id 相同的 bean,会报错吗,如果会报错,在哪个阶段报错 - 知乎


Spring 提供了哪些配置方式?

答:

将 Spring 配置到应用开发中有以下三种方式:

  • 基于 XML 的配置
  • 基于 注解 的配置
  • 基于 Java 的配置

补充:

  • 基于 xml 配置

    • bean 所需的依赖项和服务在 XML 格式的配置文件中指定。这些配置文件通常包含许多 bean 定义和特定于应用程序的配置选项。它们通常以 bean 标签开头。例如:

      1
      2
      3
      <bean id= "studentbean" class= "org.edureka.firstSpring.StudentBean">
      <property name= "name" value="Edureka"></property>
      </bean>
  • 基于 注解 配置

    • 可以通过在相关的类,方法或字段声明上使用注解,将 bean 配置为组件类本身,而不是使用 XML 来描述 bean 装配。默认情况下,Spring 容器中未打开注解装配。因此,需要在使用它之前在 Spring 配置文件中启用它。例如:

      1
      2
      3
      4
      <beans>
      <context:annotation-config/>
      <!--bean definitions go here-->
      </beans>
  • 基于 Java API 配置

    • Spring 的 Java 配置是通过使用 @Bean 和 @Configuration 来实现

      1. @Bean 注解扮演与 <bean/> 元素相同的角色
      2. @Configuration 类允许通过简单地调用同一个类中的其他 @Bean 方法来定义 bean 间依赖关系
      1
      2
      3
      4
      5
      6
      7
      @Configuration
      public class StudentConfig {
      @Bean
      public StudentBean mystudent() {
      return new studentBean();
      }
      }

什么是 Spring 的内部 bean?

答:

  • 只有将 bean 用作另一个 bean 的属性时,才能将 bean 声明为内部 bean。为了定义 bean,Spring 的基于 XML 的配置元数据在 <property> 或 <constructor-arg> 中提供了 <bean> 元素的使用。内部 bean 总是匿名的,它们总是作为原型

  • 例如,假设我们有一个 Student 类,其中引用了 Person 类。这里我们将只创建一个 Person 类实例并在 Student 中使用它

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public class student {
    private Person person;
    // Setters and Getters
    }

    public class Person {
    private string name;
    private string address;
    //setters and Getters
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    <bean id= "studentBean" class= "com.edureka.student">
    <property name="person">
    <!--This is inner bean -->
    <bean class= "com.edureka.Person">
    <property name="name" value="Scott"></property>
    <property name="address" value="Bangalore"></property>
    </bean>
    </property>
    </bean>

Spring AOP


什么是AOP?

答:

  • AOP(Aspect-Oriented Programming),即:面向切面编程,它与 OOP(Object-Oriented Programming 面向对象编程)相辅相成,提供了与 OOP 不同的抽象软件结构的视角

  • 在 OOP 中, 我们以类(class)作为我们的基本单元,而 AOP 中的基本单元是 Aspect(切面)

  • 具体是含义可以理解为:通过代理的方式,在调用想要的对象方法时候,进行拦截处理,执行切入的逻辑,然后再调用真正的方法实现

  • 例如,你实现了一个 A 对象,里面有 addUser 方法,此时你需要记录该方法的调用次数。 那么你就可以搞个代理对象,这个代理对象也提供了 addUser 方法,最终你调用的是代理对象的 addUser ,在这个代理对象内部填充记录调用次数的逻辑,最终的效果就类似下面代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    class A代理{
    A a; // 被代理的 A
    void adduser(User user) {
    count(); // 计数
    a.addUser(user);
    }
    }

    最终使用的是:
    A代理.adduser(user);
  • 这就叫做 面向切面编程,当然具体的代理的代码不是像上面这样写死的,而是动态切入

  • 实现上代理大体上可以分为:动态代理 和 静态代理

    • 动态代理:即在运行时将切面的逻辑进去,按照上面的逻辑就是你实现 A 类,然后定义要代理的 切入点 和 切面 的实现,程序会自动在运行时生成类似上面的代理类
    • 静态代理:在编译时或者类加载时进行切面的织入,典型的 AspectJ 就是静态代理
  • Spring AOP 就是基于动态代理的,如果要代理的对象,实现了某个接口,那么 Spring AOP 会使用 JDK Proxy,去创建代理对象,而对于没有实现接口的对象,就无法使用 JDK Proxy 去进行代理了,这时候 Spring AOP 会使用 Cglib 生成一个被代理对象的子类来作为代理,如下图所示:

    Spring AOP Process

  • 当然你也可以使用 Aspect,Spring AOP 已经集成了 AspectJ,AspectJ 应该算的上是 Java 生态系统中最完整的 AOP 框架了

  • AOP 切面编程涉及的一些专业术语:

    术语 含义
    目标(Target) 被通知的对象
    代理(Proxy) 向目标对象应用通知之后创建的代理对象
    连接点(JoinPoint) 目标对象的所属类中,定义的所有方法均为连接点
    切入点(Pointcut) 被切面拦截/增强的连接点(切入点一定是连接点,连接点不一定是切入点)
    通知(Advice) 增强的逻辑/代码,即拦截到目标对象的连接点之后要做的事情
    切面(Aspect) 切入点(Pointcut) + 通知(Advice)
    Weaving(织入) 将通知应用到目标对象,进而生成代理对象的过程动作

谈谈你对AOP的理解?

答:

  • Spring AOP(面向切面编程)是 Spring 框架中的一个重要模块,用于解决系统中的横切关注点(cross-cuttingconcerns)问题。所谓横切关注点,指的是系统中分散在各个模块中、与主业务逻辑无关的代码,例如:日志记录。事务管理、权限控制等
  • Spring AOP 采用 代理模式 实现,它通过在运行期间动态代理目标对象,将横切关注点织入到系统中,从而实现了业务逻辑与横切关注点的分离。Spring AOP 主要由以下几个概念组成:
    1. 切面(Aspect):切面是一个类,它包含了一组横切关注点和相应的逻辑。一个切面通常会跨越多个对象,因此它不仅定义了横切关注点,还定义了横切关注点与业务逻辑的关系。
    2. 连接点(Join Point):连接点是在程序执行期间可以插入切面的点。例如:方法调用、异常抛出等
    3. 切入点(Pointcut):切入点是一组连接点的集合,它定义了在哪些连接点上应用切面。例如:所有的方法调用所有的异常抛出等
    4. 通知(Advice):通知是切面在特定连接点执行的代码。Spring AOP 提供了五种类型的通知:前置通知(Before)、后置通知(After)、返回通知(After-returning)、异常通知(After-throwing)和 环绕通知(Around)
    5. 切面织入(Weaving):切面织入是将切面应用到目标对象并创建代理对象的过程。
  • Spring AOP 通过配置文件或注解的方式来定义切面、连接点、切入点和通知等信息,并使用代理模式将切面织入到目标对象中。通过 AOP 技术,可以有效地解耦业务逻辑和横切关注点,提高了系统的可维护性和可扩展性

AOP 有哪些实现方式?

答:

  • AOP 有两种实现方式:静态代理 和 动态代理
    • 静态代理:代理类 在 编译阶段 生成,在编译阶段将通知织入 Java 字节码中,也称编译时增强。AspectJ 使用的是静态代理
      • 缺点:代理对象需要与目标对象实现一样的接口,并且实现接口的方法,会有冗余代码。同时,一旦接口增加方法,目标对象与代理对象都要维护
    • 动态代理:代理类 在 程序运行时 创建,AOP 框架不会去修改字节码,而是在内存中临时生成一个代理对象,在运行期间对业务方法进行增强,不会生成新类。动态代理主要有两种实现方式:
      • JDK 动态代理:JDK 动态代理要求被代理的类必须实现一个接口,它通过反射来接收被代理的类,并使用 InvocationHandler 接口和 Proxy 类实现代理
      • CGLIB 动态代理: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:这些类型的 Advice 在 joinpoint 方法之前执行,并使用 @Before 注解标记进行配置
  • After Returning:这些类型的 Advice 在连接点方法正常执行后执行,并使用 @AfterReturning 注解标记进行配置
  • After Throwing:这些类型的 Advice 仅在 joinpoint 方法通过抛出异常退出并使用 @AfterThrowing 注解标记配置时执行
  • After (finally):这些类型的 Advice 在连接点方法之后执行,无论方法退出是正常还是异常返回,并使用 @After 注解标记进行配置
  • Around:这些类型的 Advice 在连接点之前和之后执行,并使用 @Around 注解标记进行配置

动态代理了解吗?

答:

  • Java动态代理 是 Java 中一种重要的代理模式,它允许在运行时动态地生成代理类和对象,无需编写静态代理类
  • 在 Java 中,动态代理可以通过 Java 自带的两种方式实现:基于接口的动态代理(JDK动态代理)和基于类的动态代理(CGLIB 动态代理)
    1. 基于接口的动态代理(JDK动态代理)
      • 基于接口的动态代理是 Java 官方提供的一种动态代理实现方式。在这种实现方式中,代理类必须实现一个或多个接口,然后在运行时动态创建代理对象。JDK中提供了一个 Proxy 类和一个 InvocationHandler 接口来实现基于接口的动态代理
      • 首先,需要定义一个实现 InvocationHandler 接口的代理类,该类实现了代理类的逻辑。这个类中有一个 invoke 方法,这个方法在代理类的方法被调用时被执行。在运行时通过 Proxy 类的静态方法 newProxyInstance 生成代理类对象。这个方法需要三个参数:ClassLoader、代理类需要实现的接口数组 和 InvocationHandler 实现类的实例。当通过代理类对象调用方法时,这个方法首先被转发到 InvocationHandler 的 invoke 方法中。在 invoke 方法中,可以根据代理类方法的不同来执行不同的逻辑,包括调用被代理对象的方法和执行其他的逻辑。最终,代理类的方法被执行完毕,返回结果
    2. 基于类的动态代理(CGLIB 动态代理)
      • 基于类的动态代理是通过字节码生成技术实现的。在这种实现方式中,代理类不需要实现接口,而是通过继承一个已有的类来实现代理功能。在 Java 中,可以通过 CGLIB 库实现基于类的动态代理
      • CGLIB(Code Generation Library)是一个高性能的代码生成库,它可以在运行时动态生成字节码来实现类的增强功能。通过 CGLIB 库,可以直接在运行时创建目标对象的子类,从而实现基于类的动态代理
      • 基于类的动态代理相比于基于接口的动态代理,可以代理那些没有实现任何接口的类,更加灵活。但是它的实现原理比较复杂,需要在运行时动态生成字节码,会带来一定的性能开销

JDK 动态代理和 CGLIB 动态代理区别是什么?

答:

  • JDK 代理和 CGLIB 代理都是 Spring 默认支持的代理模式,它们的区别如下:
    • 代理对象:JDK 代理只支持面向接口代理,而 CGLIB 代理除了接口外,也可以面向普通的类进行代理
    • 实现原理:JDK 代理是生成接口的匿名实现类,而 CGLIB 则还可以生成目标类的子类
    • 拦截方法:JDK 代理只支持拦截接口中的公共抽象方法,而 CGLIB 支持拦截任何非私有的实例方法
    • 内部调用支持:JDK 代理不支持代理内部调用,而 CGLIB 支持,理由同上一点
  • 在默认情况下,Spring 会优先使用 JDK 代理,不过如目标类没有实现一个公共接口,那就会基于 CGLIB 进行代理。此外,还有一种特殊情况,那就是基于 @Configuration 的配置类,在 Full 模式下,总是固定使用 CGLIB 代理

Spring 注解


你用过哪些重要的 Spring 注解?

答:

  • @Controller:用于 Spring MVC 项目中的控制器类
  • @Service:用于服务类
  • @RequestMapping:用于在控制器处理程序方法中配置 URI 映射
  • @ResponseBody:用于发送 Object 作为响应,通常用于发送 XML 或 JSON 数据作为响应
  • @PathVariable:用于将动态值从 URI 映射到处理程序方法参数
  • @Autowired:用于在 Spring Bean 中自动装配依赖项
  • @Qualifier:使用 @Autowired 注解,以避免在存在多个 bean 类型实例时出现混淆
  • @Scope:用于配置 Spring Bean 的范围
  • @Configuration、@ComponentScan 和 @Bean:用于基于 Java 的配置
  • @Aspect、@Before、@After、@Around、@Pointcut:用于切面编程(AOP)

如何在 Spring 中启动注解装配?

答:

默认情况下,Spring 容器中未打开注解装配。因此,要使用基于注解装配,我们必须通过配置<context:annotation-config /> 元素在 Spring 配置文件中启用它


@Configuration 和 @Component 有什么区别?

答:

  • @configuration 是基于 @component 的组合注解,它们都能实现一些类似的功能:
    • 将被注解的类声明为 Spring 容器中的 Bean
    • 内部带有 @Bean 注解的工厂方法返回的对象,都会被视为 Spring 容器中的 Bean
  • 它们最主要的区别在于:
    • 基于 @configuration 的配置方式被称为 Full 模式(Full @Configuration mode)
    • 基于非 @configuration 的配置方式称为 Lite 模式("lite" @Bean mode)

@Component、@Controller、@Repository、@service 有何区别?

答:

  • @Component:这将 Java 类标记为 bean。它是任何 Spring 管理组件的通用构造型。Spring 的组件扫描机制现在可以将其拾取并将其拉入应用程序环境中
  • @Controller:这将一个类标记为 Spring Web MVC 控制器。标有它的 Bean 会自动导入到 IoC 容器中
  • @Service:此注解是组件注解的特化。它不会对 @Component 注解提供任何其他行为。可以在服务层类中使用 @Service 而不是 @Component,因为它以更好的方式指定了意图
  • @Repository:这个注解是具有类似用途和功能的 @Component 注解的特化。它为 DAO 提供了额外的好处,它将 DAO 导入 IoC 容器,并使未经检查的异常有资格转换为 Spring DataAccessException

@Component 和 @Bean 有什么区别?

答:

  • 它们的作用对象不同:@Component 作用于类,而 @Bean 注解作用于方法

  • @Component 通常是通过类路径扫描来自动侦测和装配对象到 Spring 容器中,比如:@ComponentScan 注解就是定义扫描路径中的类装配到 Spring 的 Bean 容器中

  • @Bean 注解是告诉 Spring 这是某个类的实例,当我需要用它的时把它给我,@Bean 注解比 @Component 注解自定义性更强,很多地方我们只能通过 @Bean 注解来注册 Bean,比如:当我们引用第三方库中的类需要装配到 Spring 容器时,则只能通过 @Bean 来实现,比如:以下示例,只能通过 @Bean 注解来实现:

    1
    2
    3
    4
    5
    6
    public class WireThirdLibclass {
    @Bean
    public ThirdLibclass getThirdLibclass() {
    return new ThirdLibclass();
    }
    }

@Required 注解有什么用?

答:

@Required 应用于 bean 属性 setter 方法。此注解仅指示必须在配置时使用 bean 定义中的显式属性值或使用自动装配填充受影响的 bean 属性。如果尚未填充受影响的 bean 属性,则容器将抛出 BeanInitializationException

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Employee{

private string name;

@Required
public void setName(string name) {
this.name=name;
}

public string getName() {
return name;
}
}

@Autowired 注解有什么用?

答:

@Autowired 可以更准确地控制应该在何处以及如何进行自动装配。此注解用于在 setter 方法,构造器,具有任意名称或多个参数的属性或方法上自动装配 bean。默认情况下,它是类型驱动的注入

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Employee {

private string name;

@Autowired
public void setName(string name) {
this.name=name;
}

public string getName() {
return name;
}
}

@Qualifier 注解有什么用?

答:

  • 当创建多个相同类型的 bean 并希望仅使用属性装配其中一个 bean 时,可以使用 @Qualifier 注解和@Autowired 通过指定应该装配哪个确切的 bean 来消除歧义

  • 例如:这里我们分别有两个类,Employee 和 EmpAccount。在 EmpAccount 中,使用 @Qualifier 指定了必须装配 id 为 emp1 的 bean

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // Employee.java

    public class Employee {
    private string name;

    @Autowired
    public void setName(string name) {
    this.name=name;
    }

    public string getName() {
    return name;
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    // EmpAccount.java

    public class EmpAccount {

    @Autowired
    @Qualifier(emp1)
    private Employee emp;

    public void showName() {
    System.out.println("Employee name :" + emp.getName);
    }
    }

@RequestMapping 注解有什么用?

答:

  • @RequestMapping 注解用于将特定 HTTP 请求方法映射到将处理相应请求的控制器中的特定类 / 方法。此注解可应用于两个级别:
    • 类级别:映射请求的 URL
    • 方法级别:映射 URL 以及 HTTP 请求方法

@RequestMapping 和 @GetMapping 注解的不同之处在哪里?

答:

  • @RequestMapping:可注解在类和方法上
  • @GetMapping:仅可注册在方法上
  • @RequestMapping:可进行 GET、POST、PUT、DELETE 等请求方法
  • @GetMapping:是 @RequestMapping 的 GET 请求方法的特例,目的是为了提高清晰度

@Controller 注解有什么用?

答:

  • @Controller 注解标记一个类为 Spring Web MVC 控制器 Controller
  • Spring MVC 会将扫描到 该注解的类,然后扫描这个类下面带有 @RequestMapping 注解的方法,根据注解信息,为这个方法生成一个对应的处理器对象

@RestController 和 @Controller 有什么区别?

答:

@Restcontroller 注解,在 @Controller 基础上,增加了 @ResponseBody 注解,更加适合目前前后端分离的架构下,提供 Restful API ,返回例如 JSON 数据格式。当然,返回什么样的数据格式,根据客户端的 ACCEPT 请求头来决定


@RequestParam 和 @PathVariable 两个注解的区别?

答:

两个注解都用于方法参数,获取参数值的方式不同,@RequestParam 注解的参数从请求携带的参数中获取,而@PathVariable 注解从请求的 URI 中获取


返回 JSON 格式使用什么注解?

答:

  • 可以使用 @ResponseBody 注解,或者使用包含 @ResponseBody 注解的 @RestController 注解
  • 当然,还是需要配合相应的支持 JSON 格式化的 HttpMessageConverter 实现类。例如:Spring MVC 默认使用 MappingJackson2HttpMessageConverter

Spring 事务


Spring 事务实现方式有哪些?

答:

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

事务的传播级别有哪些?

答:

  • 事务的传播机制定义了在方法被另一个事务方法调用时,这个方法的事务行为应该如何
  • Spring 提供了一系列事务传播行为,这些传播行为定义了事务的边界和事务上下文如何在方法调用链中传播
  • Spring 事务定义了 7 种传播机制:
    • PROPAGATION_REQUIRED:默认的 Spring 事物传播级别,若当前存在事务,则加入该事务,若不存在事务,则新建一个事务
    • PAOPAGATION_REQUIRE_NEW:若当前没有事务,则新建一个事务。若当前存在事务,则新建一个事务,新老事务相互独立。外部事务抛出异常回滚不会影响内部事务的正常提交
    • PROPAGATION_NESTED:如果当前存在事务,则嵌套在当前事务中执行。如果当前没有事务,则新建一个事务,类似于 REQUIRE_NEW
    • PROPAGATION_SUPPORTS:支持当前事务,若当前不存在事务,以非事务的方式执行
    • PROPAGATION_NOT_SUPPORTED:以非事务的方式执行,若当前存在事务,则把当前事务挂起
    • PROPAGATION_MANDATORY:强制事务执行,若当前不存在事务,则抛出异常
    • PROPAGATION_NEVER:以非事务的方式执行,如果当前存在事务,则抛出异常
  • 这些事务传播机制是使用 ThreadLocal 实现的,所以,如果调用的方法是在新线程中的,事务传播会失效
  • Spring 事务传播级别一般不需要定义,默认就是 PROPAGATION_REQUIRED,除非在嵌套事务的情况下需要重点了解

Spring 事务中的隔离级别有哪几种?

答:

  • 在 TransactionDefinition 接口中定义了五个表示隔离级别的常量:
    • ISOLATION_DEFAULT:使用后端数据库默认的隔离界别,MySQL 默认可重复读,Oracle 默认读已提交
    • ISOLATION_READ_UNCOMMITTED:读未提交,最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读
    • ISOLATION_READ_COMMITTED:读已提交,允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生
    • ISOLATION_REPEATABLE_READ:可重复读,对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生
    • ISOLATION_SERIALIZABLE:串行化,最高的隔离级别,完全服从 ACID 的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别

声明式事务实现原理了解吗?

答:

  • Spring 的声明式事务管理是通过 AOP(面向切面编程)和 代理机制 实现的

  • 第一步,在 Bean 初始化阶段创建代理对象:

    • Spring 容器在初始化单例 Bean 的时候,会遍历所有的 BeanPostProcessor 实现类,并执行其postProcessAfterInitialization 方法
    • 在执行 postProcessAfterInitialization 方法时会遍历容器中所有的切面,查找与当前 Bean 匹配的切面,这里会获取事务的属性切面,也就是 @Transactional 注解及其属性值
    • 然后根据得到的切面创建一个代理对象,默认使用 JDK 动态代理创建代理,如果目标类是接口,则使用 JDK 动态代理,否则使用 Cglib
  • 第二步,在执行目标方法时进行事务增强操作:

    • 当通过代理对象调用 Bean 方法的时候,会触发对应的 AOP 增强拦截器,声明式事务是一种环绕增强,对应接口为 MethodInterceptor,事务增强对该接口的实现为 TransactionInterceptor,类图如下:

      类图

    • 事务拦截器 TransactionInterceptor 在 invoke 方法中,通过调用父类 TransactionAspectsupport 的invokewithinTransaction 方法进行事务处理,包括开启事务、事务提交、异常回滚等


Spring 声明式事务无效可能的原因有哪些?

答:

  • 在开发过程中,可能会遇到使用 @Transactional 进行事务管理时出现失效的情况。这里讨论的是基于事务的默认传播行为是 REQUIRED
  • 常见失效场景:
    • 如果使用 MySQL 且引擎是 MYISAM,则事务会不起作用,原因是 MYISAM 不支持事务,改成 InnoDB 引擎则支持事务
    • 注解 @Trasactional 只能加在 public 修饰的方法上事务才起效。如果加在 protect、private 等非public 修饰的方法上,事务将失效
    • 如果在开启了事务的方法内,使用了 try-catch 语句块对异常进行了捕获,而没有将异常抛到外层,事务将不起效
    • 在不同类之间的方法调用中,如果 A 方法开启了事务,B 方法没有开启事务,B 方法调用了 A 方法
      • 如果 B 方法中发生异常,但不是调用的 A 方法产生的,则异常不会使 A 方法的事务回滚,此时事务无效
      • 如果 B 方法中发生异常,异常是调用的 A 方法产生的,则 A 方法的事务回滚,此时事务有效
      • 在 B 方法上加上注解 @Trasactional,这样 A 和 B 方法就在同一个事务里了,不管异常产生在哪里,事务都是有效的
      • 简单地说,不同类之间方法调用时,异常发生在无事务的方法中,但不是被调用的方法产生的,被调用的方法的事务无效。只有异常发生在开启事务的方法内,事务才有效
    • 在同一个类的方法之间调用中,如果 A 方法调用了 B 方法,不管 A 方法有没有开启事务,由于 Spring 的代理机制 B 方法的事务是无效的
    • 如果使用了 Spring + MVC,则 context:component-scan 重复扫描问题可能会引起事务失效
  • 原因分析:
    • 在应用系统调用声明 @Transactional 的目标方法时,Spring Framework 默认使用 AOP 代理,在代码运行时生成一个代理对象,再由这个代理对象来统一管理
    • Spring 事务是使用 AOP 环绕通知和异常通知,就是对方法进行拦截,在方法执行前开启事务,在捕获到异常时进行事务回滚,在方法执行完成后提交事务

protected 和 private 加事务会生效吗?

答:

  • 在 Spring 中,只有通过 Spring 容器的 AOP 代理调用的公开方法(public method)上的 @Transactional 注解才会生效
  • 如果在 protected、private 方法上使用 @Transactional,这些事务注解将不会生效。如果要用在非 public 方法上,可以开启 AspectJ 代理模式

加入事务和嵌套事务有什么区别?

答:

  • 在 Spring 事务管理中,加入事务(Propagatin.REQUIRED)和嵌套事务(Propagation.NESTED)是两种不同的事务传播行为
    • Propagation.REQUIRED:表示如果当前存在事务,则在当前事务中执行;如果当前没有事务,则创建一个新的事务并在其中执行。即:方法被调用时会尝试加入当前的事务,如果不存在事务,则创建一个新的事务。如果外部事务回滚,那么内部事务也会被回滚
    • Propagation.NESTED:表示如果当前存在事务,则在嵌套事务中执行;如果当前没有事务,则创建一个新的事务并在其中执行。嵌套事务是独立于外部事务的子事务,它具有自己的保存点,并且可以独立于外部事务进行回滚。如果嵌套事务发生异常并回滚,它将会回滚到自己的保存点,而不影响外部事务
  • 区别:
    • Propagation.REQUIRED 是默认的传播行为,方法调用将加入当前事务,或者创建一个新事务。
    • Propagation.NESTED 是嵌套的传播行为,方法调用将在独立的子事务中执行,具有自己的保存点,可以独立于外部事务进行回滚,而不影响外部事务
  • 如果希望内部方法能够独立于外部事务进行回滚,可以选择 Propagation.NESTED,如果希望内部方法与外部事务一同回滚或提交,可以选择 Propagation.REQUIRED

Spring MVC


MVC是什么?MVC设计模式的好处有哪些

答:

  • Spring MVC 是一个基于 Java 的实现了 MVC 设计模式的请求驱动类型的 轻量级Web框架,通过把 **模型(model)- 视图(view)- 控制器(controler)**分离,将 web 层进行职责解耦,把复杂的 web 应用分成逻辑清晰的几部分,简化开发,减少出错,方便组内开发人员之间的配合

  • 流程步骤:

    Spring MVC流程图

    1. 用户通过 View 页面向服务端提出请求,可以是表单请求、超链接请求、AJAX 请求等
    2. 服务端 Controller 控制器接收到请求后对请求进行解析,找到相应的 Model,对用户请求进行处理Model 处理
    3. 将处理结果再交给 Controller(控制器其实只是起到了承上启下的作用)
    4. 根据处理结果找到要作为向客户端发回的响应 View 页面,页面经渲染后发送给客户端
  • MVC 设计模式的好处:

    1. 分层设计,实现了业务系统各个组件之间的解耦,有利于业务系统的可扩展性,可维护性
    2. 有利于系统的并行开发,提升开发效率

Spring MVC 常用的注解有哪些?

答:

  • @RequestMapping:用于处理请求 url 映射的注解,可用于类或方法上。用于类上,则表示类中的所有响应请求的方法都是以该地址作为父路径
  • @RequestBody:注解实现接收 HTTP 请求的 json 数据,将 json 转换为 java 对象
  • @ResponseBody:注解实现将 Controller 方法返回对象转化为 json 对象响应给客户
  • @Controller:控制器的注解,表示是表现层,不能用用别的注解代替

Spring MVC 有哪些核心组件?

答:

  • DispatcherServlet:前置控制器,是整个流程控制的核心,控制其他组件的执行,进行统一调度,降低组件之间的耦合性,相当于总指挥
  • Handler:处理器,完成具体的业务逻辑,相当于 Servlet 或 Action
  • HandlerMapping:DispatcherServlet 接收到请求之后,通过 HandlerMapping 将不同的请求映射到不同的 Handler
  • HandlerInterceptor:处理器拦截器,是一个接口,如果需要完成一些拦截处理,可以实现该接口
  • HandlerExecutionChain:处理器执行链,包括两部分内容:Handler 和 HandlerInterceptor(系统会有一个默认的 HandlerInterceptor,如果需要额外设置拦截,可以添加拦截器)
  • HandlerAdapter:处理器适配器,Handler 执行业务方法之前,需要进行一系列的操作,包括表单数据的验证、数据类型的转换、将表单数据封装到 JavaBean 等,这些操作都是由 HandlerAdapter 来完成,开发者只需将注意力集中业务逻辑的处理上,DispatcherServlet 通过 HandlerAdapter 执行不同的 Handler
  • ModelAndView:装载了模型数据和视图信息,作为 Handler 的处理结果,返回给 DispatcherServlet
  • ViewResolver:视图解析器,DispatcheServlet 通过它将逻辑视图解析为物理视图,最终将渲染结果响应给客户端

Spring MVC 的执行流程了解吗?

答:

Spring MVC 的执行流程

  • Spring MVC 是基于 MVC 设计模式实现的 Web 框架,其工作流程如下:
    1. 客户端发送 HTTP 请求至前端控制器 DispatcherServlet
    2. DispatcherServlet 根据请求信息调用 HandlerMapping,解析请求对应的 Handler 即处理器(Controller)
    3. HandlerMapping 根据请求 URL 查找对应的 Controller,同时生成用于执行该请求的HandlerExecutionChain 对象(包含Interceptor链)
    4. DispatcherServlet 调用 HandlerAdapter 执行 Handler。在执行过程中,HandlerAdapter 将把ModelAndView 对象返回给 DispatcherServlet
    5. Handler 执行完成后,返回一个 ModelAndView 对象给 HandlerAdapter
    6. HandlerAdapter 将 ModelAndView 对象传递给 DispatcherServlet
    7. DispatcherServlet 调用 ViewResolver 解析视图(View)
    8. ViewResolver 解析出 View 对象后,将其返回给 DispatcherServlet
    9. DispatcherServlet 调用 View 对象的 render() 方法进行视图渲染
    10. DispatcherServlet 将渲染后的视图(生成好的 HTML 内容)返回给客户端
  • 在这个过程中,DispatcherServlet 是整个 Spring MVC 的核心,它负责协调各个组件的工作。HandlerMapping 负责将请求映射到对应的 Controller,而 HandlerAdapter 负责执行 Controller。ViewResolver 则根据逻辑视图名(如:JSP文件名)解析出 View 对象,最后由 View 渲染出实际的页面内容。通过这种分工协作的方式,Spring MVC可以实现灵活、高效、可扩展的 Web 应用程序开发

Spring MVC 拦截器是什么?

答:

  • Spring 的处理程序映射机制包括处理程序拦截器,当你希望将特定功能应用于某些请求时,例如:检查用户主题时,这些拦截器非常有用。
  • 拦截器必须实现 org.springframework.web.servlet 包的 HandlerInterceptor。此接口定义了三种方法:
    • preHandle:在执行实际处理程序之前调用
    • postHandle:在执行完实际程序之后调用
    • afterCompletion:在完成请求后调用

拦截器的使用场景有哪些?

答:

拦截器的典型使用场景如下:

  • 日志记录:可用于记录请求日志,便于信息监控和信息统计
  • 权限检查:可用于用户登录状态的检查
  • 统一安全处理:可用于统一的安全效验或参数的加密 / 解密等

Spring MVC 怎么配置拦截器?

答:

  • 有两种写法

  • 一种是实现 HandlerInterceptor 接口,另外一种是继承适配器类,接着在接口方法当中,实现处理逻辑,然后在 Spring MVC 的配置文件中配置拦截器即可:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <!-- 配置Spring MVC的拦截器 -->
    <mvc:interceptors>
    <!-- 配置一个拦截器的Bean就可以了 默认是对所有请求都拦截 -->
    <bean id="myInterceptor" class="com.zwp.action.MyHandlerInterceptor"></bean>
    <!-- 只针对部分请求拦截-->
    <mvc:interceptor>
    <mvc:mapping path="/modelMap.do"/>
    <bean class="com.zwp.action.MyHandlerInterceptorAdapter" />
    </mvc:interceptor>
    </mvc:interceptors>

如何实现过滤器?

答:

过滤器可以使用 Servlet 3.0 提供的 @WebFilter 注解,配置过滤的 URL 规则,然后再实现 Filter 接口,重写接口中的 doFilter 方法,具体实现代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import org.springframework.stereotype.Component;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;

@Component
@WebFilter(urlPatterns = "/*")
public class TestFilter implements Filter {

@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("过滤器:执行 init 方法。");
}

@Override
public void doFilter(ServletRequest servletRequest,
ServletResponse servletResponse,
FilterChain filterChain) throws IOException, ServletException {
System.out.println("过滤器:开始执行 doFilter 方法。");
// 请求放行
filterChain.doFilter(servletRequest, servletResponse);
System.out.println("过滤器:结束执行 doFilter 方法。");
}

@Override
public void destroy() {
System.out.println("过滤器:执行 destroy 方法。");
}
}

其中:

  • void init(filterConfig filterConfig):容器启动(初始化 Filter)时会被调用,整个程序运行期只会被调用一次。用于实现 Filter 对象的初始化
  • void doFilter(ServletRequest request, ServletResponse response, FilterChain chain):具体的过滤功能实现代码,通过此方法对请求进行过滤处理,其中 Filterchain 参数是用来调用下一个过滤器或执行下一个流程
  • void destroy():用于 Filter 销毁前完成相关资源的回收工作

拦截器和过滤器区别是什么?

答:

拦截器 和 过滤器 的区别主要体现在以下 5 点:

  1. 出身不同:过滤器来自于 Servlet,而拦截器来自于 Spring 框架
  2. 触发时机不同:请求的执行顺序是:请求进入容器 > 进入过滤器 > 进入 Servlet > 进入拦截器 > 执行控制器(Controller),所以过滤器和拦截器的执行时机,是过滤器会先执行,然后才会执行拦截器,最后才会进入真正的要调用的方法
  3. 底层实现不同:过滤器是基于 方法回调 实现的,拦截器是基于 动态代理(底层是反射)实现的
  4. 支持的项目类型不同:过滤器是 Servlet 规范中定义的,所以过滤器要依赖 Servlet 容器,它只能用在 Web 项目中;而拦截器是 Spring 中的一个组件,因此拦截器既可以用在 Web 项目中,同时还可以用在 Application 或 Swing 程序中
  5. 使用的场景不同:因为拦截器更接近业务系统,所以拦截器主要用来实现项目中的业务判断的,比如:登录判断、权限判断、日志记录 等业务;而过滤器通常是用来实现通用功能过滤的,比如:敏感词过滤、字符集编码设置、响应数据压缩 等功能

Spring MVC异常处理是什么?

答:

  • 异常处理是 Spring MVC 中处理控制器方法抛出的异常的过程。可以使用 @ExceptionHandler 注解、HandlerExceptionResolver 接囗或 @ControllerAdvice 注解来进行异常处理

  • 例如,在控制器中使用 @ExceptionHandler 注解进行异常处理的示例代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    @controller
    public class MyController {

    @ExceptionHandler(Exception.class)
    public string handleException(Exception ex) {
    // 处理异常并返回错误视图
    return "error";
    }

    @RequestMapping("/hello")
    public string handleRequest() throws Exception {
    throw new Exception("something went wrong");
    }
    }
    • 在上面的代码中,@ExceptionHandler 注解处理控制器方法抛出的 Exception 异常,并返回一个名为 “error” 的错误视图

MyBatis


什么是 MyBatis?

答:

  1. MyBatis 是一个 ORM(对象关系映射)框架,它内部封装了JDBC,开发时只需要关注 SQL 语句本身,不需要花费精力去处理加载驱动,创建连接,创建 statement 等复杂的过程。开发人员不需要编写原生态SQL,可以严格控制 SQL 执行性能,灵活度高
  2. MyBatis 可以使用 xml 或者注解来配置映射原生信息,将 POJO 映射成数据库中的记录,避免了几乎所有的 JDBC 代码和手动设置的参数以及获取结果集

MyBatis 优缺点是什么?

答:

  • Mybatis 的优点:
    • 基于 SQL 语句编程,相当灵活,不会对应用程序或者数据库的现有设计造成任何影响,SQL 写在 XML 里,解除 SQL 与程序代码的耦合,便于统一管理
    • 提供 XML 标签,支持编写动态 SQL 语句,并可重用
    • 与 JDBC 相比,减少了 50% 以上的代码量,消除了 JDBC 大量冗余的代码,不需要手动开关连接
    • 很好的与各种数据库兼容(因为 MyBatis 使用 JDBC 来连接数据库,所以只要 JDBC 支持的 数据库MyBatis 都支持)
    • 能够与 Spring 很好的集成
    • 提供映射标签,支持对象与数据库的 ORM 字段关系映射;提供对象关系映射标签,支持对象关系组件维护
  • Mybatis 的缺点:
    • SQL 语句依赖:MyBatis 需要手动编写 SQL 语句,这意味着开发人员需要具备一定的 SQL 知识。此外,如果数据库模式发生变化,需要手动修改 SQL 语句,这可能会导致一些问题
    • XML 配置文件冗长:MyBatis 的配置文件通常比较冗长,这可能会导致一些维护问题。此外,如果使用注解配置,代码可能会变得混乱
    • 缺乏自动化创建:相比于其他 ORM 框架,MyBatis 缺乏自动化。例如:它不支持自动创建表和字段
  • MyBatis 框架适用场合:
    • MyBatis 专注于 SQL 本身,是一个足够灵活的 DAO 层解决方案
    • 对性能的要求很高,或者需求变化较多的项目,如:互联网项目,MyBatis 将是不错的选择

Mybatis 和 Hibernate 的区别是什么?

答:

  • 介绍:
    • Hibernate 框架:Hibernate是一个开放源代码的对象关系映射框架,它对 JDBC 进行了非常轻量级的对象封装,建立对象与数据库表的映射。是一个全自动的、完全面向对象的持久层框架
    • Mybatis 框架:Mybatis 是一个开源对象关系映射框架,原名:ibatis,2010年由谷歌接管以后更名。是一个半自动化的持久层框架
  • 区别:
    • 开发方面:在项目开发过程当中,就速度而言:Hibernate 开发中,SQL 语句已经被封装,直接可以使用,加快系统开发;Mybatis 属于半自动化,SQL 需要手工完成,稍微繁琐;但是,凡事都不是绝对的,如果对于庞大复杂的系统项目来说,复杂语句较多,hibernate 就不是好方案
    • SQL优化方面:Hibernate 自动生成 SQL,有些语句较为繁琐,会多消耗一些性能;Mybatis 手动编写SQL,可以避免不需要的查询,提高系统性能
    • 对象管理比对:Hiberate 是完整的 对象-关系映射的框架,开发工程中,无需过多关注底层实现,只要去管理对象即可;Mybatis 需要自行管理映射关系
  • 适应场景:
    • Hibernate 是标准的 ORM 框架,SQL 编写量较少,但不够灵活,适合于需求相对稳定,中小型的软件项目,比如:办公自动化系统
    • MyBatis 是半 ORM 框架,需要编写较多 SQL,但是比较灵活,适合于需求变化频繁,快速迭代的项目,比如:电商网站

JDBC编程有哪些不足之处,MyBatis是如何解决这些问题的?

答:

  • 数据库链接创建、释放频繁造成系统资源浪费从而影响系统性能,如果使用数据库链接池可解决此问题
    • 解决:在 SqlMapConfig.xml 中配置数据链接池,使用连接池管理数据库链接
  • SQL 语句写在代码中造成代码不易维护,实际应用 sql 变化的可能较大,sql 变动需要改变 java代码
    • 解决:将 sql 语句配置在 XXXXmapper.xml 文件中与 java 代码分离
  • 向 sql 语句传参数麻烦,因为 sql 语句的 where 条件不一定,可能多也可能少,占位符需要和参数一一对应
    • 解决:Mybatis 自动将 java 对象映射至 sql 语句
  • 对结果集解析麻烦,sql 变化导致解析代码变化,且解析前需要遍历,如果能将数据库记录封装成 pojo 对象解析比较方便
    • 解决:Mybatis 自动将 sql 执行结果映射至 java 对象

MyBatis 编程步骤是什么样的?

答:

  1. 创建 SqlSessionFactory
  2. 通过 SqlSessionFactory 创建 SqlSession
  3. 通过 sqlSession 执行数据库操作
  4. 调用 session.commit() 提交事务
  5. 调用 session.close() 关闭会话

Xml 映射文件中,除了常见的 select | insert | update | delete 标签之外,还有哪些标签?

答:

还有很多其他的标签,<resultMap>、<parameterMap>、<sql>、<include>、<selectKey>,加上动态 sql 的 9 个标签,trim | where | set | foreach | if | choose | when | otherwise | bind 等,其中为 sql 片段标签,通过 <include> 标签引入 sql 片段,<selectKey> 为不支持自增的主键生成策略标签


Mybatis 如何防止 SQL 注入?

答:

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

#{} 和 ${} 的区别?

答:

  • #{} 是占位符,预编译处理;${} 是拼接符,字符串替换,没有预编译处理
  • Mybatis 在处理 #{} 时, #{} 传入参数是以 字符串 传入,会将 SQL 中的 #{} 替换为 ? 号,调用PreparedStatement 的 set 方法来赋值
  • #{} 可以有效的防止 SQL 注入,提高系统安全性;${} 不能防止 SQL 注入
  • #{} 的变量替换是在 DBMS 中;${} 的变量替换是在 DBMS 外

介绍 MyBatis 的一级缓存和二级缓存?

答:

  • 一级缓存:基于 PerpetualCache 的 HashMap 本地缓存,其存储作用域为 SqlSession,各个 SqlSession 之间的缓存相互隔离,当 Session flush 或 close 之后,该 SqlSession 中的所有 Cache 就将清空,MyBatis 默认打开一级缓存
  • 二级缓存与一级缓存其机制相同,默认也是采用 PerpetualCache,HashMap 存储,不同之处在于其存储作用域为 Mapper(Namespace),可以在多个 SqlSession 之间共享,并且可自定义存储源,如 Ehcache。默认不打开二级缓存,要开启二级缓存,使用二级缓存属性类需要实现 Serializable 序列化接口(可用来保存对象的状态),可在它的映射文件中配置
  • 当开启二级缓存后,数据的查询执行的流程就是:二级缓存 → 一级缓存 → 数据库
  • 缓存更新机制:当某一个作用域(一级缓存 Session / 二级缓存 Mapper)进行了 C/U/D 操作后,默认该作用域下所有 select 中的缓存将被 clear

MyBatis 有哪些设计模式?

答:

  • 工厂模式:工厂模式在 MyBatis 中的典型代表是 SqlSessionFactory。SqlSession 是 MyBatis 中的重要 Java 接口,可以通过该接口来执行 SQL 命令、获取映射器示例和管理事务,而 SqlSessionfactory 正是用来产生 SqlSession 对象的,所以它在 MyBatis 中是比较核心的接口之一
  • 建造者模式:建造者模式在 MyBatis 中的典型代表是 SqlSessionfactoryBuilder。普通的对象都是通过 new 关键字直接创建的,但是如果创建对象需要的构造参数很多,且不能保证每个参数都是正确的或者不能一次性得到构建所需的所有参数,那么就需要将构建逻辑从对象本身抽离出来,让对象只关注功能,把构建交给构建类,这样可以简化对象的构建,也可以达到分步构建对象的目的,而SqlSessionfactoryBuilder 的构建过程正是如此
  • 单例模式:单例模式在 MyBatis 中的典型代表是 ErrorContext。ErrorContext 是线程级别的的单例,每个线程中有一个此对象的单例,用于记录该线程的执行环境的错误信息
  • 适配器模式:适配器模式在 MyBatis 中的典型代表是 Log。MyBatis 中的日志模块适配了以下多种日志类型:SLF4J、Apache Commons Logging、Log4j 2、Log4j、JDK logging
  • 代理模式:代理模式在 MyBatis 中的典型代表是 MapperProxyFactory。MapperProxyfactory 的newInstance() 方法就是生成一个具体的代理来实现某个功能
  • 模板方法模式:模板方法在 MyBatis 中的典型代表是 BaseExecutor,在 MyBatis 中 BaseExecutor 实现了大部分 SQL 执行的逻辑
  • 装饰器模式:装饰器模式在 MyBatis 中的典型代表是 Cache。Cache 除了有数据存储和缓存的基本功能外(由 PerpetualCache 永久缓存实现),还有其他附加的 Cache 类,比如:先进先出的 FIFOCache、最近最少使用的LRUCache、防止多线程并发访问的 SynchronizedCache 等众多附加功能的缓存类

详细链接:第13讲:MyBatis 使用了哪些设计模式?在源码中是如何体现的? · Java 源码剖析 34 讲 · 看云


为什么说Mybatis是半自动 ORM 映射工具?它与全自动的区别在哪里?

答:

  • Hibernate 属于全自动 ORM 映射工具,使用 Hibernate 查询关联对象或者关联集合对象时,可以根据对象关系模型直接获取,所以它是全自动的
  • 而 Mybatis 在查询关联对象或关联集合对象时,需要手动编写 SQL 来完成,所以,称之为半自动 ORM 映射工具

Mybatis都有哪些Executor执行器?它们之间的区别是什么?

答:

  • Mybatis 有三种基本的 Executor 执行器,SimpleExecutor、ReuseExecutor、BatchExecutor
    • SimpleExecutor:每执行一次 update 或 select,就开启一个 Statement 对象,用完立刻关闭 Statement 对象
    • ReuseExecutor:执行 update 或 select,以 sql 作为 key 查找 Statement 对象,存在就使用,不存在就创建,用完后,不关闭 Statement 对象,而是放置于 Map<String,Statement> 内,供下一次使用。简言之,就是重复使用 Statement 对象
    • BatchExecutor:执行 update(没有 select,JDBC 批处理不支持 select),将所有 sql 都添加到批处理中(addBatch()),等待统一执行(executeBatch()),它缓存了多个 Statement 对象,每个 Statement 对象都是 addBatch() 完毕后,等待逐一执行 executeBatch() 批处理。与 JDBC 批处理相同
  • 作用范围:Executor 的这些特点,都严格限制在 SqlSession 生命周期范围内

Mybatis中如何指定使用哪一种Executor执行器?

答:

  • 在 Mybatis 配置文件中,可以指定默认的 ExecutorType 执行器类型,也可以手动给 DefaultSqlSessionFactory 的创建 SqlSession 的方法传递 ExecutorType 类型参数
  • 配置默认的执行器,SIMPLE 就是普通的执行器,REUSE 执行器会重用预处理语句,BATCH 执行器将重用语句并执行批量更新

Mybatis 的工作原理是什么?

答:

Mybatis 的工作原理

  1. 读取 MyBatis 配置文件:mybatis-config.xml 为 MyBatis 的全局配置文件,配置了 MyBatis 的运行环境等信息,例如:数据库连接信息
  2. 加载映射文件:映射文件即 SQL 映射文件,该文件中配置了操作数据库的 SQL 语句,需要在 MyBatis 配置文件 mybatis-config.xml 中加载。mybatis-config.xml 文件可以加载多个映射文件,每个文件对应数据库中的一张表
  3. 构造会话工厂:通过 MyBatis 的环境等配置信息构建会话工厂 SqlSessionFactory
  4. 创建会话对象:由会话工厂创建 SqlSession 对象,该对象中包含了执行 SQL 语句的所有方法
  5. Executor 执行器:MyBatis 底层定义了一个 Executor 接口来操作数据库,它将根据 SqlSession 传递的参数动态地生成需要执行的 SQL 语句,同时负责查询缓存的维护
  6. MappedStatement 对象:在 Executor 接口的执行方法中有一个 MappedStatement 类型的参数,该参数是对映射信息的封装,用于存储要映射的 SQL 语句的 id、参数等信息
  7. 输入参数映射:输入参数类型可以是 Map、List 等集合类型,也可以是 基本数据类型 和 POJO 类型。输入参数映射过程类似于 JDBC 对 preparedStatement 对象设置参数的过程
  8. 输出结果映射:输出结果类型可以是 Map、 List 等集合类型,也可以是基本数据类型和 POJO 类型。输出结果映射过程类似于 JDBC 对结果集的解析过程

Mybatis 动态 sql 是做什么的?都有哪些动态 sql?能简述一下动态 sql 的执行原理不?

答:

  • Mybatis 动态 sql 可以让我们在 xml 映射文件内,以标签的形式编写动态 sql,完成逻辑判断和动态拼接 sql 的功能
  • Mybatis 提供了 9 种动态 sql 标签: trim|where|set|foreach|if|choose when|otherwise|bind
  • 执行原理为:使用 OGNL 从 sql 参数对象中计算表达式的值,根据表达式的值动态拼接 sql,以此来完成动态 sql 的功能

Mybatis 能执行一对一、一对多的关联查询吗?

答:

  • 能,MyBatis 不仅可以执行一对一、一对多的关联查询,还可以执行多对一,多对多的关联查询
  • 多对一查询,其实就是一对一查询,只需要把 selectone() 修改为即可;多对多查询,其实 selectList() 就是一对多查询,只需要把 selectone() 修改为 selectList() 即可
  • 关联对象查询,有两种实现方式,一种是单独发送一个 sql 去查询关联对象,赋给主对象,然后返回主对象。另一种是使用嵌套查询,嵌套查询的含义为使用 join 查询,一部分列是 A 对象的属性值,另外一部分列是关联对象 B 的属性值,好处是只发一个 sql 查询,就可以把主对象和其关联对象查出来
  • 那么问题来了,join 查询出来 100 条记录,如何确定主对象是 5 个,而不是 100 个?
    • 其去重复的原理是<resultMap> 标签内的 <id> 子标签,指定了唯一确定一条记录的 id 列,MyBatis 根据 <id> 列值来完成 100 条记录的去重复功能,<id> 可以有多个,代表了联合主键的语意
  • 同样主对象的关联对象,也是根据这个原理去重复的,尽管一般情况下,只有主对象会有重复记录,关联对象一般不会重复
  • 举例: join 查询出来 6 条记录,一、二列是 Teacher 对象列,第三列为 Student 对象列,MyBatis 去重复处理后,结果为 1 个老师 6 个学生,而不是 6 个老师 6 个学生

Mybatis 是否支持延迟加载?原理?

答:

  • Mybatis 支持 association 关联对象和 collection 关联集合对象的延迟加载,association 指的就是一对一,collection 指的就是一对多查询。在 Mybatis 配置文件中,可以配置是否启用延迟加载:lazyLoadingEnabled=true / false
  • 它的原理是:使用 CGLIB 创建目标对象的代理对象,当调用目标方法时,进入拦截器方法,比如:调用a.getB().getName(),拦截器 invoke() 方法发现 a.getB() 是 null 值,那么就会单独发送事先保存好的查询关联 B 对象的 sql,把 B 查询上来,然后调用 a.setB(b),于是 a 的对象 b 属性就有值了,接着完成 a.getB().getName() 方法的调用。这就是延迟加载的基本原理
  • 当然了,不光是 Mybatis,几乎所有的包括 Hibernate,支持延迟加载的原理都是一样的

什么是MyBatis的接口绑定?有哪些实现方式?

答:

  • 接口绑定,就是在 MyBatis 中任意定义接口,然后把接口里面的方法和 SQL 语句绑定,我们直接调用接口方法就可以,这样比起原来了 SqlSession 提供的方法我们可以有更加灵活的选择和设置
  • 接口绑定有两种实现方式:
    • 通过注解绑定,就是在接口的方法上面加上 @Select、@Update 等注解,里面包含 Sql 语句来绑定
    • 通过 xml 里面写 SQL 来绑定, 在这种情况下,要指定 xml 映射文件里面的 namespace 必须为接口的全路径名。当 Sql 语句比较简单时候,用注解绑定,当 SQL 语句比较复杂时候,用 xml 绑定,一般用 xml 绑定的比较多

Mybatis是如何将sql执行结果封装为目标对象并返回的?都有哪些映射形式?

答:

  • 第一种是使用 <resultMap> 标签,逐一定义列名和对象属性名之间的映射关系
  • 第二种是使用 sql 列的别名功能,将列别名书写为对象属性名,比如:T_NAME_AS_NAME,对象属性名一般是 name,小写,但是列名不区分大小写,Mybatis 会忽略列名大小写,智能找到与之对应对象属性名,甚至可以写成 T_NAME_AS_NaMe ,Mybatis 一样可以正常工作
  • 有了列名与属性名的映射关系后,Mybatis 通过反射创建对象,同时使用反射给对象的属性逐一赋值并返回,那些找不到映射关系的属性,是无法完成赋值的

Mybatis的Xml映射文件中,不同的Xml映射文件,id是否可以重复?

答:

  • 不同的 Xml 映射文件:
    • 如果配置了 namespace,那么 id 可以重复
    • 如果没有配置 namespace,那么 id 不能重复
  • 原因就是 namespace+id 是作为 Map<String, MapperStatement> 的 key 使用的,如果没有 namespace,就剩下 id,那么,id 重复会导致数据互相覆盖。有了 namespace,自然 id 就可以重复,namespace 不同,namespace+id 自然也就不同

MyBatis 如何执行批量操作?

答:

MyBatis 批量操作有两种方式:

  • 第一种方法:使用 foreach 标签
  • 第二种方法:使用 ExecutorType.BATCH

补充:

  • foreach 的主要用在构建 in 条件中,它可以在 SQL 语句中进行迭代一个集合。foreach 标签的属性主要有 item、index、collection、 open、separator、 close

    • item:表示集合中每一个元素进行迭代时的别名,随便起的变量名
    • index:指定一个名字,用于表示在迭代过程中,每次迭代到的位置,不常用
    • open:表示该语句以什么开始,常用"("
    • separator:表示在每次进行迭代之间以什么符号作为分隔符,常用 “,”
    • close:表示以什么结束,常用 “)”
  • 在使用 foreach 的时候最关键的也是最容易出错的就是 collection 属性,该属性是必须指定的,但是在不同情况下,该属性的值是不一样的,主要有以下 3 种情况:

    1. 如果传入的是单参数且参数类型是一个 List 的时候,collection 属性值为 list
    2. 如果传入的是单参数且参数类型是一个 array 数组的时候,collection 的属性值为 array
    3. 如果传入的参数是多个的时候,就需要把它们封装成一个 Map 了,当然单参数也可以封装成 map,实际上如果你在传入参数的时候,在 MyBatis 里面也是会把它封装成一个 Map 的,map 的 key 就是参数名,所以这个时候 collection 属性值就是传入的 List 或 array 对象在自己封装的 map 里面的 key
    1
    2
    3
    4
    5
    6
    7
    8
    <!-- MySQL下批量保存,可以foreach遍历 mysql支持values(),(),()语法 -->//推荐使用1
    <insert id="addEmpsBatch">
    INSERT INTO emp(ename, gender, email, did)
    VALUES
    <foreach collection="emps" item="emp" separator=",">
    (#{emp.eName},#{emp.gender},#{emp.email},#{emp.dept.id})
    </foreach>
    </insert>

在 mapper 中如何传递多个参数?

答:

有四种方式:

  • 方法 1:顺序传参法
  • 方法 2:@Param 注解传参法
  • 方法 3:Map 传参法
  • 方法 4:Java Bean 传参法

补充:

  • 方法 1:顺序传参法

    1
    public User selectUser(string name, int deptId);,
    1
    2
    3
    4
    <select id= "selectUser" resultMap="UserResultMap">
    select * from user
    where user_name = #{0} and dept_id = #{1}
    </select>
    • \#{}:里面的数字代表传入参数的顺序
    • 这种方法不建议使用,sql 层表达不直观,且一旦顺序调整容易出错
  • 方法 2:@Param 注解传参法

    1
    public User selectuser(@Param("userName") String name, int @Param("deptId") deptId);
    1
    2
    3
    4
    <select id="selectUser" resultMap="UserResultMap">
    select *from user
    where user_name =#{userName} and dept_id = #{deptId}
    </select>
    • \#{} 里面的名称对应的是注解 @Param 括号里面修饰的名称
    • 这种方法在参数不多的情况还是比较直观的(推荐使用)
  • 方法 3:Map 传参法

    1
    public User selectUser(Map<string,object> params);
    1
    2
    3
    4
    <select id="selectUser" parameterType="java.util.Map" resultMap="UserResultMap">
    select * from user
    where user_name = #{userName} and dept_id = #{deptId}
    </select>
    • \#{} 里面的名称对应的是 Map 里面的 key 名称
    • 这种方法适合传递多个参数,且参数易变能灵活传递的情况
  • 方法 4:Java Bean 传参法

    1
    public User selectUser(User user),
    1
    2
    3
    4
    <select id="selectUser" parameterType="com.jourwon.pojo.User" resultMap="UserResultMap">
    select * from user
    where user_name = #{userName} and dept_id = #{deptId}
    </select>
    • \#{} 里面的名称对应的是 User 类里面的成员属性
    • 这种方法直观,需要建一个实体类,扩展不容易,需要加属性,但代码可读性强,业务逻辑处理方便,推荐使用(推荐使用)

MyBatis 是如何进行分页的?

答:

MyBatis 使用 RowBounds 对象进行分页,它是针对 ResultSet 结果集执行的内存分页,而非物理分页。可以在 sql 内直接书写带有物理分页的参数来完成物理分页功能,也可以使用分页插件来完成物理分页


分页插件的原理是什么?

答:

  • 分页插件的基本原理是使用 Mybatis 提供的插件接口,实现自定义插件,拦截 Executor 的 query 方法
  • 在执行查询的时候,拦截待执行的 sql,然后重写 sql,根据 dialect 方言,添加对应的物理分页语句和物理分页参数
  • 举例:select * from student;,拦截 sql 后重写为:select t.* from (select * from student) t limit 0,10;

简述Mybatis的插件运行原理,以及如何编写一个插件?

答:

  • Mybatis 仅可以编写针对 ParameterHandler、ResultSetHandler、StatementHandler、Executor 这4种接口的插件
  • Mybatis使用 JDK 的动态代理,为需要拦截的接口生成代理对象以实现接口方法拦截功能,每当执行这4种接口对象的方法时,就会进入拦截方法,具体就是 InvocationHandler 的 invoke() 方法,当然,只会拦截那些指定需要拦截的方法
  • 实现 Mybatis 的 Interceptor 接口并复写 intercept() 方法,然后在给插件编写注解,指定要拦截哪一个接口的哪些方法即可,记住,别忘了在配置文件中配置你编写的插件

SpringBoot


SpringBoot是什么?有哪些优点?

答:

  • Spring Boot 是一个基于 Spring 的全新框架,简化了 Spring 应用的搭建以及开发过程
  • 具体体现在以下几点:
    1. 自动配置:根据应用程序的依赖,自动进行一些常用功能的配置,比如:数据库连接、日志、Web 服务器等
    2. 内嵌服务器:内嵌了多种常用的 Web 容器,如:Tomcat、Jetty等,可以直接打包运行,不用额外的部署步骤
    3. 自动化依赖:提供了一系列的 “starter” 依赖,简化了依赖管理的工作
    4. 监控与管理:提供了一些监控和管理功能,比如:健康检查、性能指标收集、日志记录等
  • Spring Boot 以 约定大于配置 核心思想开展工作,相比 Spring 具有如下优势
    1. Spring Boot 可以快速创建独立的 Spring 应用程序
    2. Spring Boot 内嵌了如 Tomcat、Jetty 和 Undertow 这样的容器,也就是说可以直接跑起来,用不着再做部署工作了
    3. Spring Boot 无需再像 Spring 一样使用一堆繁琐的 xml 文件配置
    4. Spring Boot 可以自动配置(核心)Spring。SpringBoot 将原有的 XML 配置改为 Java 配置,将bean 注入改为使用注解注入的方式(@Autowire),并将多个 xml、properties 配置浓缩在一个appliaction.yml 配置文件中
    5. Spring Boot 提供了一些现有的功能,如:量度工具,表单数据验证以及一些外部配置这样的一些第三方功能
    6. Spring Boot 可以快速整合常用依赖(开发库,例如:spring-webmvc、jackson-json、validation-api 和 tomcat 等),提供的 POM 可以简化 Maven 的配置。当我们引入核心依赖时,SpringBoot 会自引入其他依赖

Spring与SpringBoot的区别?

答:

  • SpringBoot = Spring + Boot
  • 没有 SpringBoot 之前,写 Spring 程序,维护 bean,维护配置文件,维护依赖,开发企业应用会极其麻烦
  • SpringBoot 就是把 Spring 这些繁琐的东西,通过自动化配置、代码即配置、约定即配置等方式,封装成了一个脚手架,对开发者屏蔽了大量 Spring 的配置细节,并实现依赖的自动装配,让你可以快速开始一个应用,并且很好维护

Spring、Spring MVC 和 SpringBoot 有什么区别?

答:

  • Spring 最重要的特征是 依赖注入。核心是 IoC/DI 和 AOP 的内容,使用 IoC/DI 管理依赖可以开发松耦合应用,使用 AOP 可以轻松的去实现面向切面编程
  • Spring MVC 是基于 Spring 框架的一个模块,提供了一种 Model-View-Controller(模型-视图-控制器)的开发模式
  • Spring 和 Spring MVC 的问题在于需要配置大量的参数,SpringBoot 通过一个自动配置和启动的项来解决这个问题。Spring Boot 旨在简化 Spring 应用的配置和部署过程,提供了大量的自动配置选项,以及运行时环境的内嵌 Web 服务器,这样就可以更快速地开发一个 SpringMVC 的 Web 项目

项目怎么用SpringBoot的?

答:

  1. 用 maven 方式构建 spring boot 项目(jdk maven 配置)
  2. 在 pom.xml 中添加 spring-boot-starter-web 依赖
  3. 编写启动类,在 main 包中填写业务
  4. 编写 web 访问的 controller(路由类)
  5. 通过 java -jar xxx 启动程序

运行 Spring Boot 有哪几种方式?

答:

  1. 打包用命令或者放到容器中运行
  2. 用 Maven / Gradle 插件运行
  3. 直接执行 main 方法运行

Spring Boot 有哪些核心注解?

答:

Spring Boot 的核心注解主要包括以下几个:

  • @SpringBootApplication:标识一个类作为 Spring Boot 项目的启动类。实际上,这个注解是@SpringBootConfiguration、@EnableAutoConfiguration 和 @ComponentScan 这三个注解的组合
  • @SpringBootConfiguration:这个注解表示该类是一个 Spring Boot 配置类,用于定义 bean。实际上是一个特殊的 @Configuration注解,用于加载 Spring Boot 项目的配置
  • @EnableAutoConfiguration:这个注解用于启用 Spring Boot 的自动化配置功能。通过添加此注解,SpringBoot 可以自动引入相关的配置,减少开发人员的配置成本
  • @ComponentScan:这个注解用于定义 Spring 扫描包的路径,用于发现应用程序中的 bean、组件、配置类和服务等。可以自动扫描并注册包路径下的带有 @Component、@Service 等注解的类

Spring Boot 自动装配是什么?

答:

  • 自动装配可以根据项目中添加的依赖和其他因素,自动创建和配置 Spring 应用所需的 Bean。这意味着开发者不需要编写大量的 XML 配置或 Java 配置类来设置 Spring 容器
  • 例如:当你添加了 spring-boot-starter-web 依赖时,Spring Boot 会自动配置一个 Servlet 容器(如: Tomcat),并设置相关的 Servlet、Filter 和 Listener

SpringBoot 自动化配置原理是什么?

答:

  • 自动配置首先从注解说起。@SpringBootApplication 由三个注解组成 @ComponentScan、@EnableAutoConfiguration、@SpringBootConfiguration
  • 其实就是 @Configuration 注解其中 @EnableAutoConfiguration 通过 @Import 注解 将AutoConfigurationImportSelector.class 这个类引进来
    • 该类会去加载所有 jar 包的 META-INF 下面的 spring-factories 配置文件,这里其实用到了 spring 里面的 SPI 机制
    • 这个文件是个 key-value 的形式,key 是 EnableAutoConfiguration 的全路径名,value 是各个需要自动配置的类然后 SpringBoot 默认在这个配置文件中定义了大约 100 多个常用的配置类,然后再根据 Condition 按需加载我们需要的配置类,比如:在配置文件中增加了对应的配置,对应的配置类就会生效

SpringBoot 启动流程是什么?

答:

SpringApplication 这个类主要做了以下四件事情:

  1. 推断应用的类型是普通的项目还是 Web 项目
  2. 查找并加载所有可用初始化器,设置到 initializers 属性中
  3. 找出所有的应用程序监听器,设置到 listeners 属性中
  4. 推断并设置 main 方法的定义类,找到运行的主类

SpringBoot 启动流程


Spring Boot 有几种方式读取配置文件?

答:

Spring Boot 中读取配置文件有以下 5 种方法:

  • 使用 @Value 读取配置文件
  • 使用 @ConfigurationProperties 读取配置文件
  • 使用 @PropertySource 读取配置文件
  • 使用 Environment 读取配置文件
  • 使用原生方式读取配置文件

其中最常用的是前 3 种,如果读取某一个配置项可使用 @Value,如果读取一组配置项可使用@ConfigurationProperties,如果要指定读取某一个具体的配置文件可使用 @PropertySource 来指定


Spring Boot 配置文件的加载顺序?

答:

  1. 先会加载项目中的默认配置文件,通常位于 src/main/resources 目录下的 application.properties 或application.yml
  2. 加载特定命名的配置文件,如:application-{profile}.properties 或 application-{profile}.yml
  3. 如果存在 spring.config.location 命令行参数,Spring Boot 会按照指定的路径加载配置文件

加载过程中,如果同一个属性在多个配置文件中都有定义,那么后加载的配置文件中的值将覆盖先加载的配置文件中的值


bootstrap.properties 和 application.properties 有何区别?

答:

  • 加载顺序和优先级:
    • bootstrap.properties(或 bootstrap.yml)是 Spring Boot应用程序启动时最先加载的配置文件
    • application.properties(或 application.yml)是在 bootstrap.properties 之后加载
  • 配置范围:
    • bootstrap.properties 的作用范围是全局的,会被该项目下所有的Spring应用程序共享。
    • application.properties 主要关注应用程序本身的配置属性,比如:数据库连接、端口设置、日志级别、缓存配置等

Spring Boot Actuator是什么?

答:

  • Spring Boot Actuator 是 Spring Boot 提供的一个用于监控和管理应用程序的模块。包括:不限于健康检查、日志监控、指标收集、审计功能、报警功能等。开发人员可以通过这些监控及时发现问题并解决
  • 默认情况下,Spring Boot Actuator 已经集成在 Spring Boot 中,只需要在项目中添加依赖并配置一些参数即可使用

Spring Boot 项目如何热部署?

答:

热部署意味着能够在不重启应用程序服务器的情况下,重新加载代码的更改,可以大大提高开发效率,实现Spring Boot 项目的热部署只需要在 pom.xml 中添加以下依赖:

1
2
3
4
5
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>

热部署主要用于开发环境,不建议在生产环境中使用


Spring Boot 中的 starter 是什么 ?

答:

  • starter 就是一个组件或框架的依赖,通过引入某个组件的 starter,可以简化开发人员的工作量
  • 以 mybatis-spring 和 mybatis-spring-boot-starter 为例:
  • 在 Spring 项目中使用 mybatis 大概有以下几个步骤:
    1. 引入 spring、mybatis、jdbc 等相关依赖
    2. 创建 mybatis-config.xml 配置文件
      • 声明数据源 DataSource
      • 声明 SqlSessionFactoryBean
      • 声明 MapperScannerConfigurer
      • 声明等等配置
    3. 编写 xxxMapper.xml 及 xxMapper.java 文件
    4. 业务编码调用
  • 在 SpringBoot 项目中使用 Mybatis 大概有以下几个步骤:
    1. 引入 mybatis-spring-boot-starter 依赖
    2. application.properties 文件中添加相关配置
    3. 编写 xxxMapper.xml 及 xxMapper.java 文件
    4. 业务编码调用

可以明显的感觉到使用 Starter 后,不用管理相关依赖,不用编写繁琐的配置文件。这就是 Starter


Spring Boot 有哪些 starter?

答:

  1. spring-boot-starter-web:用于构建 Web 应用程序的 Starter,包括 Spring MVC 和 Tomcat 服务器
  2. spring-boot-starter-data-jpa:用于与关系型数据库进行交互的 Starter,用于简化数据库访问层的开发
  3. spring-boot-starter-data-redis:用于与 Redis 数据库进行交互的 Starter
  4. spring-boot-starter-actuator:用于监控和管理 Spring Boot 应用的 Starter
  5. mybatis-spring-boot-starter:用于在 Spring Boot 应用中集成 MyBatis

除了上述常用的 Starter,Spring Boot 还提供了许多其他的 Starter。比如:数据库连接池、安全、消息队列等


Spring Boot 打成的 jar 和普通的 jar 有什么区别?

答:

  • Spring Boot 项目最终打包成的 jar 是可执行 jar ,这种 jar 可以直接通过 java -jar xxx.jar 命令来运行,这种 jar 不可以作为普通的 jar 被其他项目依赖,即使依赖了也无法使用其中的类
  • Spring Boot 的 jar 无法被其他项目依赖,主要还是他和普通 jar 的结构不同
  • 普通的 jar 包,解压后直接就是包名,包里就是我们的代码,而 Spring Boot 打包成的可执行 jar 解压后,在 \BOOT-INF\classes 目录下才是我们的代码,因此无法被直接引用。如果非要引用,可以在 pom.xml 文件中增加配置,将 Spring Boot 项目打包成两个 jar ,一个可执行,一个可引用