[!note] 核心特性
- IoC容器:Spring通过控制反转实现了对象的创建和对象间的依赖关系管理。开发者只需要定义好Bean及其依赖关系,Spring容器负责创建和组装这些对象。
- AOP:面向切面编程,允许开发者定义横切关注点,例如事务管理、安全控制等,独立于业务逻辑的代码。通过AOP,可以将这些关注点模块化,提高代码的可维护性和可重用性。
- 事务管理:Spring提供了一致的事务管理接口,支持声明式和编程式事务。开发者可以轻松地进行事务管理,而无需关心具体的事务API。
- MVC框架:Spring MVC是一个基于Servlet API构建的Web框架,采用了模型-视图-控制器(MVC)架构。它支持灵活的URL到页面控制器的映射,以及多种视图技术。
1 Spring、Spring MVC、Spring Boot
Spring 包含了多个功能模块,其中最重要的是 Spring-Core(主要提供 IoC 依赖注入功能的支持) 模块, Spring 中的其他模块(比如 Spring MVC)的功能实现基本都需要依赖于该模块。
Spring MVC 是 Spring 中的一个很重要的模块,主要赋予 Spring 快速构建 MVC 架构的 Web 程序的能力。MVC 是模型(Model)、视图(View)、控制器(Controller)的简写,其核心思想是通过将业务逻辑、数据、显示分离来组织代码。Spring MVC 下我们一般把后端项目分为 Service 层(处理业务)、Dao 层(数据库操作)、Entity 层(实体类)、Controller 层(控制层,返回数据给前台页面)。
使用 Spring 进行开发各种配置过于麻烦比如开启某些 Spring 特性时,需要用 XML 或 Java 进行显式配置。于是,Spring Boot 诞生了!
Spring 旨在简化 J2EE 企业应用程序开发。Spring Boot 旨在简化 Spring 开发(减少配置文件,开箱即用!)。
1.1 Spring MVC工作原理
-
DispatcherServlet
:核心的中央处理器,负责接收请求、分发,并给予客户端响应。 -
HandlerMapping
:处理器映射器,根据 URL 去匹配查找能处理的Handler
,并会将请求涉及到的拦截器和Handler
一起封装。 -
HandlerAdapter
:处理器适配器,根据HandlerMapping
找到的Handler
,适配执行对应的Handler
; -
Handler
:请求处理器,处理实际请求的处理器。 -
ViewResolver
:视图解析器,根据Handler
返回的逻辑视图 / 视图,解析并渲染真正的视图,并传递给DispatcherServlet
响应客户端
流程说明(重要):
-
客户端(浏览器)发送请求,
DispatcherServlet
拦截请求。 -
DispatcherServlet
根据请求信息调用HandlerMapping
。HandlerMapping
根据 URL 去匹配查找能处理的Handler
(也就是我们平常说的Controller
控制器) ,并会将请求涉及到的拦截器和Handler
一起封装。 -
DispatcherServlet
调用HandlerAdapter
适配器执行Handler
。 -
Handler
完成对用户请求的处理后,会返回一个ModelAndView
对象给DispatcherServlet
,ModelAndView
顾名思义,包含了数据模型以及相应的视图的信息。Model
是返回的数据对象,View
是个逻辑上的View
。 -
ViewResolver
会根据逻辑View
查找实际的View
。 -
DispaterServlet
把返回的Model
传给View
(视图渲染)。 -
把
View
返回给请求者(浏览器)
2 Spring Bean
Bean 代指的就是那些被 IoC 容器所管理的对象。
2.1 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。
public Person personPrototype() { return new Person(); } |
2.2 Bean是否线程安全
prototype 作用域下,每次获取都会创建一个新的 bean 实例,不存在资源竞争问题,所以不存在线程安全问题。singleton 作用域下,IoC 容器中只有唯一的 bean 实例,可能会存在资源竞争问题(取决于 Bean 是否有状态)。如果这个 bean 是有状态的话,那就存在线程安全问题(有状态 Bean 是指包含可变的成员变量的对象)。
大部分 Bean 实际都是无状态(没有定义可变的成员变量)的(比如 Dao、Service),这种情况下, Bean 是线程安全的。
对于有状态单例 Bean 的线程安全问题,常见的有两种解决办法:
-
在 Bean 中尽量避免定义可变的成员变量。
-
在类中定义一个
ThreadLocal
成员变量,将需要的可变成员变量保存在ThreadLocal
中(推荐的一种方式)。
2.3 Bean生命周期
-
创建 Bean 的实例:Bean 容器首先会找到配置文件中的 Bean 定义,然后使用 Java 反射 API 来创建 Bean 的实例。
-
Bean 属性赋值/填充:为 Bean 设置相关属性和依赖,例如
@Autowired
等注解注入的对象、@Value
注入的值、setter
方法或构造函数注入依赖和值、@Resource
注入的各种资源。 -
Bean 初始化:
- 如果 Bean 实现了
BeanNameAware
接口,调用setBeanName()
方法,传入 Bean 的名字。 - 如果 Bean 实现了
BeanClassLoaderAware
接口,调用setBeanClassLoader()
方法,传入ClassLoader
对象的实例。 - 如果 Bean 实现了
BeanFactoryAware
接口,调用setBeanFactory()
方法,传入BeanFactory
对象的实例。 - 与上面的类似,如果实现了其他
*.Aware
接口,就调用相应的方法。 - 如果有和加载这个 Bean 的 Spring 容器相关的
BeanPostProcessor
对象,执行postProcessBeforeInitialization()
方法 - 如果 Bean 实现了
InitializingBean
接口,执行afterPropertiesSet()
方法。 - 如果 Bean 在配置文件中的定义包含
init-method
属性,执行指定的方法。 - 如果有和加载这个 Bean 的 Spring 容器相关的
BeanPostProcessor
对象,执行postProcessAfterInitialization()
方法。
- 如果 Bean 实现了
-
销毁 Bean:销毁并不是说要立马把 Bean 给销毁掉,而是把 Bean 的销毁方法先记录下来,将来需要销毁 Bean 或者销毁容器的时候,就调用这些方法去释放 Bean 所持有的资源
- 如果 Bean 实现了
DisposableBean
接口,执行destroy()
方法。 - 如果 Bean 在配置文件中的定义包含
destroy-method
属性,执行指定的 Bean 销毁方法。或者,也可以直接通过@PreDestroy
注解标记 Bean 销毁之前执行的方法。
- 如果 Bean 实现了
[!Note]
BeanNameAware
:注入当前 bean 对应 beanName;BeanClassLoaderAware
:注入加载当前 bean 的 ClassLoader;BeanFactoryAware
:注入当前BeanFactory
容器的引用。BeanPostProcessor
接口是 Spring 为修改 Bean 提供的强大扩展点
postProcessBeforeInitialization
:Bean 实例化、属性注入完成后,InitializingBean#afterPropertiesSet
方法以及自定义的init-method
方法之前执行;postProcessAfterInitialization
:类似于上面,不过是在InitializingBean#afterPropertiesSet
方法以及自定义的init-method
方法之后执行。
-
整体上可以简单分为四步:实例化 —> 属性赋值 —> 初始化 —> 销毁。
-
初始化这一步涉及到的步骤比较多,包含
Aware
接口的依赖注入、BeanPostProcessor
在初始化前后的处理以及InitializingBean
和init-method
的初始化操作。 -
销毁这一步会注册相关销毁回调接口,最后通过
DisposableBean
和destory-method
进行销毁。
3 注解
3.1 将一个类声明为Bean的注解
import org.springframework.stereotype.Component; |
-
@Component
:标注一个类为Spring容器的Bean,把普通pojo实例化到spring容器中,相当于配置文件中的<bean id=“” class=“”/> -
@Repository
:对应持久层即Dao层,主要用于数据库相关操作 -
@Service
:对应服务层,主要涉及一些复杂的逻辑,需要用到 Dao 层。 -
@Controller
:对应 Spring MVC 控制层,主要用于接受用户请求并调用 Service 层返回数据给前端页面。表示Web层实现。
3.1.1 @Component 和 @Bean 的区别是什么?
-
@Component
注解作用于类,而@Bean
注解作用于方法。 -
@Component
通常是通过类路径扫描来自动侦测以及自动装配到 Spring 容器中。@Bean
注解通常是我们在标有该注解的方法中定义产生这个 bean,@Bean
告诉了 Spring 这是某个类的实例,当我需要用它的时候还给我。
3.2 注入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 |
3.2.1 @Autowired 和 @Resource 的区别是什么?
Autowired
属于 Spring 内置的注解,默认的注入方式为byType
(根据类型进行匹配),也就是说会优先根据接口类型去匹配并注入 Bean (接口的实现类)。
[!note]
这会有什么问题呢? 当一个接口存在多个实现类的话,byType
这种方式就无法正确注入对象了,因为这个时候 Spring 会同时找到多个满足条件的选择,默认情况下它自己不知道选择哪一个。这种情况下,注入方式会变为
byName
(根据名称进行匹配),这个名称通常就是类名(首字母小写)。
//`SmsService` 接口有两个实现类: `SmsServiceImpl1`和 `SmsServiceImpl2`,且它们都已经被 Spring 容器所管理。 |
@Resource
属于 JDK 提供的注解,默认注入方式为 byName
。如果无法通过名称匹配到对应的 Bean 的话,注入方式会变为byType
。
如果仅指定 name
属性则注入方式为byName
,如果仅指定type
属性则注入方式为byType
,如果同时指定name
和type
属性(不建议这么做)则注入方式为byType
+byName
。
// 报错,byName 和 byType 都无法匹配到 bean |
[!tip]
@Autowired
是 Spring 提供的注解,@Resource
是 JDK 提供的注解。Autowired
默认的注入方式为byType
(根据类型进行匹配),@Resource
默认注入方式为byName
(根据名称进行匹配)。- 当一个接口存在多个实现类的情况下,
@Autowired
和@Resource
都需要通过名称才能正确匹配到对应的 Bean。Autowired
可以通过@Qualifier
注解来显式指定名称,@Resource
可以通过name
属性来显式指定名称。@Autowired
支持在构造函数、方法、字段和参数上使用。@Resource
主要用于字段和方法上的注入,不支持在构造函数或参数上使用。
4 事务
在 Spring 框架中, @Transactional注解是用来开启事务的,但它的工作原理是通过代理对象来实现的。 当你在一个 public方法上加上 @Transactional注解时,Spring 会生成一个代理对象,该代理对象负责管理事务。 但是,对于 private方法,由于其访问权限的限制,Spring 无法生成代理对象,因此事务也无法正常生效。
4.1 事务隔离级别
-
TransactionDefinition.ISOLATION_DEFAULT
:使用后端数据库默认的隔离级别 -
TransactionDefinition.ISOLATION_READ_UNCOMMITTED
:最低的隔离级别,使用这个隔离级别很少,因为它允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读 -
TransactionDefinition.ISOLATION_READ_COMMITTED
: 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生 -
TransactionDefinition.ISOLATION_REPEATABLE_READ
: 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。 -
TransactionDefinition.ISOLATION_SERIALIZABLE
: 最高的隔离级别,完全服从 ACID 的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。
5 BeanFactory
生产 bean 的工厂,它负责生产和管理各个 bean 实例。
-
ListableBeanFactory:通过这个接口,我们可以获取多个 Bean
-
HierarchicalBeanFactory:在应用中起多个 BeanFactory,然后可以将各个 BeanFactory 设置为父子关系。
-
AutowireCapableBeanFactory:自动装配Bean
-
ConfigurableListableBeanFactory