
01 Spring框架 笔记
SSM 全家桶
Spring 概述
什么是Spring框架?
答:
- Spring 是一个轻量级、非入侵式的控制反转(
IoC)和 面向切面(AOP)的框架 - Spring 是很多模块的集合,使用这些模块可以很方便地协助我们进行开发。这些模块是 核心容器、数据访问集成、Web、AOP(面向切面编程)、工具、消息和测试模块。比如:Core Container 中的 Core 组件是 Spring 所有组件的核心,Beans 组件和 Context 组件是实现 IOC 和 DI 的基础,AOP 组件 用来实现面向切面编程
- Spring 官网列出的 Spring 的 6 个特征:
- 核心技术:依赖注入(DI),AOP,事件(Events),资源,i18n,验证,数据绑定,类型转换,SpEL
- 测试:模拟对象,TestContext 框架,Spring MVC 测试,WebTestClient
- 数据访问:事务,DAO支持,JDBC,ORM,编组XML
- Web支持:Spring MVC 和 Spring WebFlux Web 框架
- 集成:远程处理,JMS,JCA,JMX,电子邮件,任务,调度,缓存
- 语言:Kotlin,Groovy,动态语言
使用Spring框架的好处是什么?
答:
- 轻量:Spring 是轻量的,基本的版本大约 2MB
- 控制反转:Spring通过控制反转实现了松散耦合,对象们给出它们的依赖,而不是创建或查找依赖的对象们。
- 面向切面的编程(AOP):Spring 支持面向切面的编程,并且把应用业务逻辑和系统服务分开
- 容器:Spring 包含并管理应用中对象的生命周期和配置
- MVC 框架:Spring 的 WEB 框架是个精心设计的框架,是 Web 框架的一个很好的替代品
- 事务管理:Spring 提供一个持续的事务管理接口,可以扩展到上至本地事务下至全局事务(JTA)
- 异常处理:Spring 提供方便的 API 把具体技术相关的异常(比如由JDBC,Hibernate or JDO抛出的)转化为一致的 unchecked 异常
Spring框架都有哪些模块?
答:

- 最主要的七大模块:
- Spring Core:提供了框架的基本组成部分,包括控制反转(Inversion of Control,
IOC)和依赖注入(Dependency Injection,DI)功能 - Spring Beans:提供了
BeanFactory,是工厂模式的一个经典实现,Spring 将管理对象称为 Bean - Spring Context:构建于 core 封装包基础上的 context 封装包,提供了一种框架式的对象访问方法
- Spring JDBC:提供了一个
JDBC的抽象层,消除了烦琐的JDBC编码和数据库厂商特有的错误代码解析用于简化JDBC - Spring AOP:提供了面向切面的编程实现,让你可以自定义拦截器、切点等
- Spring Web:提供了针对 Web 开发的集成特性,例如:文件上传,利用
servlet listeners进行IoC容器初始化和针对 Web 的ApplicationContext - Spring Test:主要为测试提供支持的,支持使用
JUnit或TestNG对Spring组件进行单元测试和集成测试
- Spring Core:提供了框架的基本组成部分,包括控制反转(Inversion of Control,
Spring MVC 和 Struts 2 的区别?
答:
- Spring MVC 特性如下:
- 具备
IOC/DI、AOP等通用能力,提高研发效率 - 除了支持 Web 层建设以外,还提供了 J2EE 整体服务
- 方便与其他不同技术结合使用,如:
Hibernate、MyBatis等 - Spring 拦截机制是方法级别
- 具备
- Struts 特性如下:
- 是一个基于 MVC 模式的一个 Web 层的处理
- 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 的实现方式有两种:
- 依赖注入(Dependency Injection,简称 DI):不通过
new()的方式在 类内部 创建依赖类对象,而是将依赖的类对象在外部创建好之后,通过构造器、函数参数等方式传递(或注入)给类使用 - 依赖查找(Dependency Lookup):容器中的受控对象通过容器的 API 来查找自己所依赖的资源和协作对象
- 依赖注入(Dependency Injection,简称 DI):不通过
- 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 的实现原理可以分为两个步骤:
- 扫描和解析 配置文件 或 注解信息,将其转换为 内部的对象定义 和 依赖关系
- 根据对象定义和依赖关系,使用 反射机制 动态创建和初始化对象,并将对象注入到需要使用它们的地方
- 具体来说,Spring IoC 的实现过程如下:
- 读取配置文件或解析注解信息,将其转换为内部的对象定义和依赖关系:在 Spring 中,可以使用 XML 文件或注解来配置对象和依赖关系。Spring 通过解析配置文件或注解信息,将其转换为内部的对象定义和依赖关系(
BeanDefinition)放到容器(BeanFactory)中。对象定义包括对象的类型、属性、构造函数等信息,依赖关系包括对象之间的依赖关系、依赖注入方式等信息 - 实例化 bean 对象:Spring 会根据对象定义的类型和构造函数信息,使用 反射机制 来创建对象
- 设置属性:实例化后的仍然是一个原生的状态,并没有进行依赖注入。这一步 Spring 根据 依赖关系 中的信息进行属性填充,依赖注入
- 调用 Aware 接口:Spring 会检测该对象是否实现了
xxxAware接口,如果有会在这里执行完成。Aware 主要是能获取到 Spring容器 中的一些资源,然后可以供后续步骤,例如:初始化阶段使用 - BeanPostProcessor 前置处理:
postProcessBeforeInitialzation方法。上述几个步骤后,bean对象已经被正确构造,但如果想要对象被初始化前再进行一些自定义的处理,就可以通过BeanPostProcessor 接口的该方法来实现 - 初始化阶段:该阶段 Spring 首先会看是否是实现了
InitializingBean接口的afterPropertiesset方法以及是否有自定义的init-method等,如果有会进行调用执行 - BeanPostProcessor 后置处理:
postProcessAfterInitialzation方法。当前正在初始化的 bean 对象会被传递进来,我们就可以对这个 bean 作任何处理,与前面前置处理相对的,这个函数会在 InitialzationBean 完成后执行,因此称为后置处理 - bean 初始化完成可以被使用了
- 读取配置文件或解析注解信息,将其转换为内部的对象定义和依赖关系:在 Spring 中,可以使用 XML 文件或注解来配置对象和依赖关系。Spring 通过解析配置文件或注解信息,将其转换为内部的对象定义和依赖关系(
- 总的来说,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)
- 总结一下:
- Bean 是对象,一个或者多个不限定
- Bean 托管在 Spring 中一个叫 IoC 的容器中
- 我们的程序是由一个个 Bean 构成的
Bean 生命周期是什么?
答:
- 单例对象
singleton总结:单例对象的生命周期和容器相同
- 多例对象:
prototype- 出生:使用对象时 Spring 框架为我们创建
- 活着:对象只要是在使用过程中就一直活着
- 死亡:当对象长时间不用且没有其它对象引用时,由 java 的垃圾回收机制回收

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

- 实例化一个 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()方法,BeanPostProcessor经常被用作是 Bean 内容的更改,由于这个是在 Bean 初始化结束时调用那个的方法,也可以被应用于内存或缓存技术 - 如果 Bean 在 Spring配置文件 中配置了
init-method属性会自动调用其配置的初始化方法 - 如果这个 Bean 关联了
BeanPostProcessor接口,将会调用postProcessAfterInitialization(),打印日志或者三级缓存技术里面的 bean 升级 - 以上工作完成以后就可以应用这个 Bean 了,那这个 Bean 是一个 Singleton 的,所以一般情况下我们调用同一个 id 的 Bean 会是在内容地址相同的实例,当然在 Spring 配置文件中也可以配置非 Singleton,这里我们不做赘述
- 当 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 之间的一些关键区别:
- 功能:Beanfactory 是一个容器,负责管理和创建 Bean 实例,处理依赖关系和属性注入等操作。FactoryBean 是一个接口,定义了创建 Bean 的规范和逻辑,它负责创建其他 Bean 实例
- 使用方式:Beanfactory 使用配置文件或注解来定义 Bean 和它们之间的关系,它使用延迟初始化策略,即:只有在需要时才创建 Bean 实例。FactoryBean 通常在 Spring 配置文件中配置,并由 Beanfactory 负责实例化和管理
- 创建的对象:BeanFactory 创建和管理普通的 Bean 实例,而 FactoryBean 创建其他 Bean 实例
- 灵活性:FactoryBean 具有更高的灵活性,因为它允许自定义的逻辑来创建和配置 Bean 实例。FactoryBean 的实现类可以根据特定的条件选择性地创建不同的 Bean 实例,或者在创建 Bean 之前进行一些初始化操作。这使得 FactoryBean 在某些情况下比 BeanFactory 更加强大和可扩展
- 返回类型: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 的支持,它的主要功能是用于创建、管理和查找 Bean 对象。BeanFactory 只是个接口,并不是 IoC 容器的具体实现,它为其他具体的 IoC 容器提供了最基本的规范,例如:
- 总的来说:Beanfactory 是 Spring 框架中最基本的容器,提供最基础的 IoC 和 DI 的支持;而Applicationcontext 是在 Beanfactory 的基础上扩展而来的,提供了更多的功能和特性。Applicationcontext 是 Spring 框架中使用较为广泛的容器
Bean的作用域有哪些?
答:

- singleton:IoC 容器中只有唯一的 bean 实例。
Spring中的bean默认都是单例的,是对单例设计模式的应用 - prototype:每次获取都会创建一个新的 bean 实例。也就是说,连续
getBean()两次,得到的是不同的Bean实例 - request(仅 Web 应用可用):每一次
HTTP请求都会产生一个新的bean(请求bean),该 bean 仅在当前 HTTP request 内有效 - session(仅 Web 应用可用):每一次来自新
session的HTTP请求都会产生一个新的bean(会话bean),该 bean 仅在当前 HTTP session 内有效 - application / global-session(仅 Web 应用可用):每个
Web应用在启动时创建一个Bean(应用Bean),该 bean 仅在当前应用启动时间内有效 - websocket(仅 Web 应用可用):每一次
WebSocket会话产生一个新的bean
默认作用域是 singleton,多个线程访问同一个 bean 时会存在线程不安全问题
保障线程安全方法:
- 在
Bean对象中尽量避免定义可变的成员变量(不太现实) - 在类中定义一个
ThreadLocal成员变量,将需要的可变成员变量保存在ThreadLocal中
ThreadLocal:
- 每个线程中都有一个自己的
ThreadLocalMap类对象,可以将线程自己的对象保持到其中,各管各的,线程可以正确的访问到自己的对象 - 将一个共用的
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的线程安全问题,常见的有两种解决办法:- 在
Bean中尽量避免定义可变的成员变量 - 在类中定义一个
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通过三级缓存解决循环依赖问题的步骤如下:Spring在创建Bean对象时,首先从一级缓存(singletonObjects)中查找是否存在已经创建完成的Bean对象,若存在则直接返回该Bean对象- 若一级缓存中不存在该
Bean对象,则从二级缓存(earlySingletonObjects)中查找是否存在该Bean对象的早期对象(还没进行属性填充和初始化的半成品对象),若存在则返回早期对象 - 若二级缓存中也不存在该
Bean对象的早期对象,则将正在创建的Bean对象放入三级缓存(singletonFactories)中,并在创建过程中进行依赖注入,即:为该Bean对象注入依赖的其他Bean对象。此时,如果其他Bean对象中依赖了正在创建的Bean对象,Spring将直接从三级缓存中获取正在创建的Bean对象,而不是重新创建一个新的Bean对象 - 当
Bean对象创建完成后,Spring将其从三级缓存中移除,并将其加入一级缓存中,以便下次获取该Bean对象时直接从一级缓存中获取
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来实现@Bean注解扮演与<bean/>元素相同的角色@Configuration类允许通过简单地调用同一个类中的其他@Bean方法来定义bean间依赖关系
1
2
3
4
5
6
7
public class StudentConfig {
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
10public 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
10class 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生成一个被代理对象的子类来作为代理,如下图所示:
-
当然你也可以使用
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主要由以下几个概念组成:- 切面(
Aspect):切面是一个类,它包含了一组横切关注点和相应的逻辑。一个切面通常会跨越多个对象,因此它不仅定义了横切关注点,还定义了横切关注点与业务逻辑的关系。 - 连接点(
Join Point):连接点是在程序执行期间可以插入切面的点。例如:方法调用、异常抛出等 - 切入点(
Pointcut):切入点是一组连接点的集合,它定义了在哪些连接点上应用切面。例如:所有的方法调用所有的异常抛出等 - 通知(
Advice):通知是切面在特定连接点执行的代码。Spring AOP提供了五种类型的通知:前置通知(Before)、后置通知(After)、返回通知(After-returning)、异常通知(After-throwing)和 环绕通知(Around) - 切面织入(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动态代理)- 基于接口的动态代理(
JDK动态代理)- 基于接口的动态代理是
Java官方提供的一种动态代理实现方式。在这种实现方式中,代理类必须实现一个或多个接口,然后在运行时动态创建代理对象。JDK中提供了一个Proxy类和一个InvocationHandler接口来实现基于接口的动态代理 - 首先,需要定义一个实现
InvocationHandler接口的代理类,该类实现了代理类的逻辑。这个类中有一个invoke方法,这个方法在代理类的方法被调用时被执行。在运行时通过Proxy类的静态方法newProxyInstance生成代理类对象。这个方法需要三个参数:ClassLoader、代理类需要实现的接口数组 和InvocationHandler实现类的实例。当通过代理类对象调用方法时,这个方法首先被转发到InvocationHandler的invoke方法中。在invoke方法中,可以根据代理类方法的不同来执行不同的逻辑,包括调用被代理对象的方法和执行其他的逻辑。最终,代理类的方法被执行完毕,返回结果
- 基于接口的动态代理是
- 基于类的动态代理(
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
6public class WireThirdLibclass {
public ThirdLibclass getThirdLibclass() {
return new ThirdLibclass();
}
}
@Required 注解有什么用?
答:
@Required 应用于 bean 属性 setter 方法。此注解仅指示必须在配置时使用 bean 定义中的显式属性值或使用自动装配填充受影响的 bean 属性。如果尚未填充受影响的 bean 属性,则容器将抛出 BeanInitializationException
1 | public class Employee{ |
@Autowired 注解有什么用?
答:
@Autowired 可以更准确地控制应该在何处以及如何进行自动装配。此注解用于在 setter 方法,构造器,具有任意名称或多个参数的属性或方法上自动装配 bean。默认情况下,它是类型驱动的注入
1 | public class Employee { |
@Qualifier 注解有什么用?
答:
-
当创建多个相同类型的
bean并希望仅使用属性装配其中一个bean时,可以使用@Qualifier注解和@Autowired通过指定应该装配哪个确切的bean来消除歧义 -
例如:这里我们分别有两个类,
Employee和EmpAccount。在EmpAccount中,使用@Qualifier指定了必须装配id为emp1的bean1
2
3
4
5
6
7
8
9
10
11
12
13
14// Employee.java
public class Employee {
private string name;
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 {
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控制器ControllerSpring 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_NEWPROPAGATION_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应用分成逻辑清晰的几部分,简化开发,减少出错,方便组内开发人员之间的配合 -
流程步骤:

- 用户通过
View页面向服务端提出请求,可以是表单请求、超链接请求、AJAX请求等 - 服务端
Controller控制器接收到请求后对请求进行解析,找到相应的Model,对用户请求进行处理Model处理 - 将处理结果再交给
Controller(控制器其实只是起到了承上启下的作用) - 根据处理结果找到要作为向客户端发回的响应
View页面,页面经渲染后发送给客户端
- 用户通过
-
MVC设计模式的好处:- 分层设计,实现了业务系统各个组件之间的解耦,有利于业务系统的可扩展性,可维护性
- 有利于系统的并行开发,提升开发效率
Spring MVC 常用的注解有哪些?
答:
@RequestMapping:用于处理请求url映射的注解,可用于类或方法上。用于类上,则表示类中的所有响应请求的方法都是以该地址作为父路径@RequestBody:注解实现接收HTTP请求的json数据,将json转换为java对象@ResponseBody:注解实现将Controller方法返回对象转化为json对象响应给客户@Controller:控制器的注解,表示是表现层,不能用用别的注解代替
Spring MVC 有哪些核心组件?
答:
DispatcherServlet:前置控制器,是整个流程控制的核心,控制其他组件的执行,进行统一调度,降低组件之间的耦合性,相当于总指挥Handler:处理器,完成具体的业务逻辑,相当于Servlet或ActionHandlerMapping:DispatcherServlet接收到请求之后,通过HandlerMapping将不同的请求映射到不同的HandlerHandlerInterceptor:处理器拦截器,是一个接口,如果需要完成一些拦截处理,可以实现该接口HandlerExecutionChain:处理器执行链,包括两部分内容:Handler和HandlerInterceptor(系统会有一个默认的HandlerInterceptor,如果需要额外设置拦截,可以添加拦截器)HandlerAdapter:处理器适配器,Handler执行业务方法之前,需要进行一系列的操作,包括表单数据的验证、数据类型的转换、将表单数据封装到JavaBean等,这些操作都是由HandlerAdapter来完成,开发者只需将注意力集中业务逻辑的处理上,DispatcherServlet通过HandlerAdapter执行不同的HandlerModelAndView:装载了模型数据和视图信息,作为Handler的处理结果,返回给DispatcherServletViewResolver:视图解析器,DispatcheServlet通过它将逻辑视图解析为物理视图,最终将渲染结果响应给客户端
Spring MVC 的执行流程了解吗?
答:

Spring MVC是基于MVC设计模式实现的Web框架,其工作流程如下:- 客户端发送
HTTP请求至前端控制器DispatcherServlet DispatcherServlet根据请求信息调用HandlerMapping,解析请求对应的Handler即处理器(Controller)HandlerMapping根据请求URL查找对应的Controller,同时生成用于执行该请求的HandlerExecutionChain对象(包含Interceptor链)DispatcherServlet调用HandlerAdapter执行Handler。在执行过程中,HandlerAdapter将把ModelAndView对象返回给DispatcherServletHandler执行完成后,返回一个ModelAndView对象给HandlerAdapterHandlerAdapter将ModelAndView对象传递给DispatcherServletDispatcherServlet调用ViewResolver解析视图(View)ViewResolver解析出View对象后,将其返回给DispatcherServletDispatcherServlet调用View对象的render()方法进行视图渲染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 | import org.springframework.stereotype.Component; |
其中:
void init(filterConfig filterConfig):容器启动(初始化Filter)时会被调用,整个程序运行期只会被调用一次。用于实现Filter对象的初始化void doFilter(ServletRequest request, ServletResponse response, FilterChain chain):具体的过滤功能实现代码,通过此方法对请求进行过滤处理,其中Filterchain参数是用来调用下一个过滤器或执行下一个流程void destroy():用于Filter销毁前完成相关资源的回收工作
拦截器和过滤器区别是什么?
答:
拦截器 和 过滤器 的区别主要体现在以下 5 点:
- 出身不同:过滤器来自于
Servlet,而拦截器来自于Spring框架 - 触发时机不同:请求的执行顺序是:请求进入容器 > 进入过滤器 > 进入
Servlet> 进入拦截器 > 执行控制器(Controller),所以过滤器和拦截器的执行时机,是过滤器会先执行,然后才会执行拦截器,最后才会进入真正的要调用的方法 - 底层实现不同:过滤器是基于 方法回调 实现的,拦截器是基于 动态代理(底层是反射)实现的
- 支持的项目类型不同:过滤器是
Servlet规范中定义的,所以过滤器要依赖Servlet容器,它只能用在Web项目中;而拦截器是Spring中的一个组件,因此拦截器既可以用在Web项目中,同时还可以用在Application或Swing程序中 - 使用的场景不同:因为拦截器更接近业务系统,所以拦截器主要用来实现项目中的业务判断的,比如:登录判断、权限判断、日志记录 等业务;而过滤器通常是用来实现通用功能过滤的,比如:敏感词过滤、字符集编码设置、响应数据压缩 等功能
Spring MVC异常处理是什么?
答:
-
异常处理是
Spring MVC中处理控制器方法抛出的异常的过程。可以使用@ExceptionHandler注解、HandlerExceptionResolver接囗或@ControllerAdvice注解来进行异常处理 -
例如,在控制器中使用
@ExceptionHandler注解进行异常处理的示例代码如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class MyController {
public string handleException(Exception ex) {
// 处理异常并返回错误视图
return "error";
}
public string handleRequest() throws Exception {
throw new Exception("something went wrong");
}
}- 在上面的代码中,
@ExceptionHandler注解处理控制器方法抛出的Exception异常,并返回一个名为 “error” 的错误视图
- 在上面的代码中,
MyBatis
什么是 MyBatis?
答:
MyBatis是一个ORM(对象关系映射)框架,它内部封装了JDBC,开发时只需要关注SQL语句本身,不需要花费精力去处理加载驱动,创建连接,创建statement等复杂的过程。开发人员不需要编写原生态SQL,可以严格控制SQL执行性能,灵活度高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 编程步骤是什么样的?
答:
- 创建
SqlSessionFactory - 通过
SqlSessionFactory创建SqlSession - 通过
sqlSession执行数据库操作 - 调用
session.commit()提交事务 - 调用
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、BatchExecutorSimpleExecutor:每执行一次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配置文件:mybatis-config.xml为MyBatis的全局配置文件,配置了MyBatis的运行环境等信息,例如:数据库连接信息 - 加载映射文件:映射文件即
SQL映射文件,该文件中配置了操作数据库的SQL语句,需要在MyBatis配置文件mybatis-config.xml中加载。mybatis-config.xml文件可以加载多个映射文件,每个文件对应数据库中的一张表 - 构造会话工厂:通过
MyBatis的环境等配置信息构建会话工厂SqlSessionFactory - 创建会话对象:由会话工厂创建
SqlSession对象,该对象中包含了执行SQL语句的所有方法 Executor执行器:MyBatis底层定义了一个Executor接口来操作数据库,它将根据SqlSession传递的参数动态地生成需要执行的SQL语句,同时负责查询缓存的维护MappedStatement对象:在Executor接口的执行方法中有一个MappedStatement类型的参数,该参数是对映射信息的封装,用于存储要映射的SQL语句的id、参数等信息- 输入参数映射:输入参数类型可以是
Map、List等集合类型,也可以是 基本数据类型 和POJO类型。输入参数映射过程类似于JDBC对preparedStatement对象设置参数的过程 - 输出结果映射:输出结果类型可以是
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、closeitem:表示集合中每一个元素进行迭代时的别名,随便起的变量名index:指定一个名字,用于表示在迭代过程中,每次迭代到的位置,不常用open:表示该语句以什么开始,常用"("separator:表示在每次进行迭代之间以什么符号作为分隔符,常用 “,”close:表示以什么结束,常用 “)”
-
在使用
foreach的时候最关键的也是最容易出错的就是collection属性,该属性是必须指定的,但是在不同情况下,该属性的值是不一样的,主要有以下 3 种情况:- 如果传入的是单参数且参数类型是一个
List的时候,collection属性值为list - 如果传入的是单参数且参数类型是一个
array数组的时候,collection的属性值为array - 如果传入的参数是多个的时候,就需要把它们封装成一个
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( String name, int 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应用的搭建以及开发过程- 具体体现在以下几点:
- 自动配置:根据应用程序的依赖,自动进行一些常用功能的配置,比如:数据库连接、日志、
Web服务器等 - 内嵌服务器:内嵌了多种常用的
Web容器,如:Tomcat、Jetty等,可以直接打包运行,不用额外的部署步骤 - 自动化依赖:提供了一系列的 “
starter” 依赖,简化了依赖管理的工作 - 监控与管理:提供了一些监控和管理功能,比如:健康检查、性能指标收集、日志记录等
- 自动配置:根据应用程序的依赖,自动进行一些常用功能的配置,比如:数据库连接、日志、
- Spring Boot 以 约定大于配置 核心思想开展工作,相比
Spring具有如下优势Spring Boot可以快速创建独立的Spring应用程序Spring Boot内嵌了如Tomcat、Jetty和Undertow这样的容器,也就是说可以直接跑起来,用不着再做部署工作了Spring Boot无需再像Spring一样使用一堆繁琐的xml文件配置Spring Boot可以自动配置(核心)Spring。SpringBoot将原有的XML配置改为Java配置,将bean注入改为使用注解注入的方式(@Autowire),并将多个xml、properties配置浓缩在一个appliaction.yml配置文件中Spring Boot提供了一些现有的功能,如:量度工具,表单数据验证以及一些外部配置这样的一些第三方功能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的?
答:
- 用
maven方式构建spring boot项目(jdkmaven配置) - 在
pom.xml中添加spring-boot-starter-web依赖 - 编写启动类,在
main包中填写业务 - 编写
web访问的controller(路由类) - 通过
java -jar xxx启动程序
运行 Spring Boot 有哪几种方式?
答:
- 打包用命令或者放到容器中运行
- 用
Maven / Gradle插件运行 - 直接执行
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 这个类主要做了以下四件事情:
- 推断应用的类型是普通的项目还是
Web项目 - 查找并加载所有可用初始化器,设置到
initializers属性中 - 找出所有的应用程序监听器,设置到
listeners属性中 - 推断并设置
main方法的定义类,找到运行的主类

Spring Boot 有几种方式读取配置文件?
答:
Spring Boot 中读取配置文件有以下 5 种方法:
- 使用
@Value读取配置文件 - 使用
@ConfigurationProperties读取配置文件 - 使用
@PropertySource读取配置文件 - 使用
Environment读取配置文件 - 使用原生方式读取配置文件
其中最常用的是前 3 种,如果读取某一个配置项可使用 @Value,如果读取一组配置项可使用@ConfigurationProperties,如果要指定读取某一个具体的配置文件可使用 @PropertySource 来指定
Spring Boot 配置文件的加载顺序?
答:
- 先会加载项目中的默认配置文件,通常位于
src/main/resources目录下的application.properties或application.yml - 加载特定命名的配置文件,如:
application-{profile}.properties或application-{profile}.yml - 如果存在
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 | <dependency> |
热部署主要用于开发环境,不建议在生产环境中使用
Spring Boot 中的 starter 是什么 ?
答:
starter就是一个组件或框架的依赖,通过引入某个组件的starter,可以简化开发人员的工作量- 以
mybatis-spring和mybatis-spring-boot-starter为例: - 在
Spring项目中使用mybatis大概有以下几个步骤:- 引入
spring、mybatis、jdbc等相关依赖 - 创建
mybatis-config.xml配置文件- 声明数据源
DataSource - 声明
SqlSessionFactoryBean - 声明
MapperScannerConfigurer - 声明等等配置
- 声明数据源
- 编写
xxxMapper.xml及xxMapper.java文件 - 业务编码调用
- 引入
- 在
SpringBoot项目中使用Mybatis大概有以下几个步骤:- 引入
mybatis-spring-boot-starter依赖 application.properties文件中添加相关配置- 编写
xxxMapper.xml及xxMapper.java文件 - 业务编码调用
- 引入
可以明显的感觉到使用 Starter 后,不用管理相关依赖,不用编写繁琐的配置文件。这就是 Starter
Spring Boot 有哪些 starter?
答:
spring-boot-starter-web:用于构建Web应用程序的Starter,包括Spring MVC和Tomcat服务器spring-boot-starter-data-jpa:用于与关系型数据库进行交互的Starter,用于简化数据库访问层的开发spring-boot-starter-data-redis:用于与Redis数据库进行交互的Starterspring-boot-starter-actuator:用于监控和管理Spring Boot应用的Startermybatis-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,一个可执行,一个可引用






