一、版本特性
1.1 Spring 4
- 泛型限定式依赖注入
- 核心容器的改进
- web开发增强
- 集成Bean Validation 1.1(JSR-349)到SpringMVC
- Groovy Bean定义DSL
- 更好的Java泛型操作API
- JSR310日期API的支持
- 注解、脚本、任务、MVC等其他特性改进
1.2 Spring 5
- JDK8的增强
- 核心容器的改进
- 新的SpringWebFlux模块
- 测试方面的改进
二、Spring 包含的模块
Spring的包含如下几个核心子模块,这些模块构成了Spring应用的基石,基于此,还可以有更多的延伸以及根据需要添加的依赖
2.1 spring-core
提供了框架的基本组成部分,包括控制翻转(Inversion of Control, IOC)
和依赖注入(Dependency Injection, DI)
功能。他的主要类和作用如下。
2.2.1、基础设施工具类(spring框架基础部分的核心工具类)
ClassUtils
:Class的类加载 、查找类全部信息、类信息的处理等ObjectUtilsTest
:基本类型的比较或数组比较,如果认为两个数组是相等的PatternMatchUtils
:模式匹配 xx 模式匹配 、xx 、xx 、xx 、xx*yPropertyPlaceholderHelper
:解析文本中占位符并替换成属性值ReflectionUtils
:非常好用的反射类,得到字段、方法及过滤方法和方法回调ResourceUtils
:这个解析定位资源非常好用 比如 classpath–URL–>FileSerializationUtils
:序列化工具SystemPropertyUtils
:使用系统环境变量值替换占位符来生成字符串AnnotationUtils
:注解工具类 想要解析注解请找它,绝对给力。BridgeMethodResolver
:判断是否是桥梁方法 把桥梁方法转化为正常方法CollectionFactory
:使用Map.class
、List.class
等常用集合类型生产对象Map List等集合对象Constants
:加载类中的public static final常量进行分析其信息 查找 转换等Conventions
:根据方法返回值 、 字段、返回相应的字符串类型名称,可以用以自动化给其起别名ControlFlowFactory
:栈轨迹信息查询 判断 类 方法 及输入值是否在栈的轨迹中MethodIntrospector
:为类中相关方法注入数据或过滤、把类中的相应的代理方法转化为接口方法ResolvableType
:统一所有类型为ResolvableType
对象来操作类型,很赞,使用很方便
2.2.2、解析类元数据
类中的元数据包含了类元数据、方法元数据及注解元数据;spring使用了两种方式来注入元数据获得类、注解、方法元数据信息:
1)使用ASM方式,通过加载class资源读取到相应的访问器里,来填充相应的元数据,比反射速度快很多
2)使用标准的反射,从Class对象获取类、方法、字段、注解等信息注入到元数据中
2.2.3.底层Resource资源描述及基本资源加载器实现
2.2.4.Property管理
2.2.5.类型转换服务
2.2.6.spring环境搭建
2.2 spring-beans
包含访问配置文件、创建和管理 bean 以及进行 Inversion of Control / Dependency Injection(IOC/DI) 操作相关的所有类。
BeanFactory
:用于管理Bean的一个工厂类,获取spring bean容器的根接口FactoryBean
:它是实现了FactoryBean<T>
接口的Bean,根据该Bean的Id从BeanFactory
中获取的实际上是FactoryBean
的getObject()返回的对象,而不是FactoryBean本身, 如果要获取FactoryBean对象,可以在id前面加一个&符号来获取。
2.3 spring-context
spring-context
模块构架与spring-core
和spring-beans
模块之上,提供了一个框架式的对象访问方式,是访问定义和配置的任意对象的媒介。它扩展了BeanFactory
,为其增加了Bean
生命周期控制、框架事件体系以及资源加载透明化等功能。
ApplicationContext
是该模块的核心接口,它是BeanFactory
的子类,与BeanFactory
不同的是ApplicationContext
容器实例化后会自动对所有的单实例Bean
进行实例化与依赖关系的装配,使之处于待用状态。
2.4 spring-aop
由spring-aop
、spring-aspects
和 spring-instrument
3个模块组成。
2.4.1 spring-aop
spring-aop
是spring的另一个核心模块,提供了一个符合AOP要求的面相切面的编程实现。作为继OOP
之后,对程序员影响最大的编程思想之一,AOP
极大的开拓了人们对于编程的思路。
在spring
中,以JDK动态代理
的技术为基础,设计出了一系列的AOP
横切实现,比如:前置通知
、返回通知
和异常通知
等。同时使用 Pointcut
接口匹配切入点,可以使用现有的切入点设计横切面;也可以扩展相关方法根据需求进行切入,将代码按照功能进行分离,以便干净的解耦。
2.4.2 spring-aspects
提供了与AspectJ
的集成功能,主要是为AOP
提供了多种实现方法。
2.4.3 spring-instrument
该模块是spring-aop
的一个支援模块,提供了类植入(Instrumentation)支持和类加载器的实现。主要作用于JVM启动时,生成一个代理类,程序员通过代理类在运行时修改类的字节,从而改变一个类的功能,实现AOP
的功能。
2.5 spring-jcl
2.6 数据访问与集成(Data Access/Integration)
由spring-jdbc
、spring-orm
、spring-oxm
、spring-jms
和spring-tx
组成。
2.6.1 spring-jdbc
spring-jdbc
模块是spring
提供的JDBC
抽象层,消除了繁琐的编码以及数据库厂商特有的错误代码解析。用于简化JDBC
,主要提供JDBC
的模板方法、关系数据库对象化方式、事务管理来简化JDBC
编程,主要实现类有JdbcTemplate
、SimpleJdbcTemplate
以及NamedParameterJdbcTemplate
。
2.6.2 spring-orm
spring-orm
模块是ORM
的支持模块,主要集成Hibernate
、Java Persistence API(JPA)
和Java Data Object(JDO)
用于资源管理、数据访问对象(DAO)的实现和事务策略。
2.6.3 spring-oxm
spring-oxm
模块主要提供一个抽象层支撑OXM(Object-to-XML-Mapping),例如:JAXB
、Castor
、XMLBeans
、JiBX
和XStream
等。
2.6.4 spring-jms
spring-jms
模块(Java Message Service)为Java消息传递服务,能够发送和接收信息,自Spring Framework 4.1 以后,它还提供了对spring-messaging
模块的继承。
2.6.5 spring-tx
spring-tx
模块是spring-jdbc
事务控制实现模块,支持用于实现所有接口和所有POJO
类的编程和声明式事务的管理
2.7 Web
由spring-websocket
、spring-webmvc
、spring-web
和spring-webflux
组成
2.7.1 spring-web
spring-web
模块为spring
提供了最基础的web
支持,主要建立在核心容器之上,通过Servlet
或者Listeners
来初始化IOC容器以及Web应用上下文,自动装载WebApplicationContext
,也包含一些与web
相关的支持,如:Struts
集成类、文件上传支持的类、FIlter
类和大量辅助工具类。
2.7.2 spring-webmvc
也称web-servlet
模块,包含用于Web
应用程序的Spring MVC
和REST Web Service
实现。Spring MVC框架提供了领域模型代码和Web
表单之间的清晰分离,并与Spring Framework
的所有其他功能集成。
2.7.3 spring-websocket
Spring4.0
以后新增的模块,实现双工异步通讯协议,实现了WebSocket
和SocketJS
,提供Socket
通信和Web
端的推送功能。
2.7.3 spring-webflux
是一个新的非堵塞函数式Reactive Web
框架,可以用来建立异步的,非阻塞,事件驱动的服务,并且扩展性非常好。
三、Ioc
3.1 核心概念
Spring 通过一个配置文件描述 Bean 及 Bean 之间的依赖关系,利用 Java 语言的反射功能实例化 Bean并建立 Bean 之间的依赖关系。 Spring 的 IoC 容器在完成这些底层工作的基础上,还提供了 Bean 实例缓存、生命周期管理、 Bean 实例代理、事件发布、资源装载等高级服务
3.2 核心容器类
BeanFactory 是 Spring 框架的基础设施,面向 Spring 本身;ApplicationContext 面向使用 Spring 框架的开发者,几乎所有的应用场合我们都直接使用 ApplicationContext 而非底层的 BeanFactory。
3.2.1 BeanFactory
- BeanDefinitionRegistry注册表 :Spring 配置文件中每一个节点元素在 Spring 容器里都通过一个 BeanDefinition 对象表示,它描述了 Bean 的配置信息。而 BeanDefinitionRegistry 接口提供了向容器手工注册 BeanDefinition 对象的方法
- BeanFactory 顶层接口 位于类结构树的顶端 ,它最主要的方法就是 getBean(String beanName),该方法从容器中返回特定名称的 Bean,BeanFactory 的功能通过其他的接口得到不断扩展
- ListableBeanFactory 该接口定义了访问容器中 Bean 基本信息的若干方法,如查看 Bean 的个数、获取某一类型 Bean的配置名、查看容器中是否包括某一 Bean 等方法;
- HierarchicalBeanFactory父子级联 :父子级联 IoC 容器的接口,子容器可以通过接口方法访问父容器; 通过HierarchicalBeanFactory 接口, Spring 的 IoC 容器可以建立父子层级关联的容器体系,子容器可以访问父容器中的 Bean,但父容器不能访问子容器的 Bean。Spring 使用父子容器实现了很多功能,比如在Spring MVC 中,展现层 Bean 位于一个子容器中,而业务层和持久层的 Bean 位于父容器中。这样,展现层 Bean 就可以引用业务层和持久层的 Bean,而业务层和持久层的 Bean 则看不到展现层的 Bean。
- ConfigurableBeanFactory 增强了 IoC 容器的可定制性,它定义了设置类装载器、属性编辑器、容器初始化后置处理器等方法;
- AutowireCapableBeanFactory 定义了将容器中的 Bean 按某种规则(如按名字匹配、按类型匹配等)进行自动装配的方法;
- SingletonBeanRegistry 定义了允许在运行期间向容器注册单实例 Bean 的方法;对于单实例( singleton)的 Bean 来说,BeanFactory 会缓存 Bean 实例,所以第二次使用 getBean() 获取 Bean 时将直接从 IoC 容器的缓存中获取 Bean 实例。Spring 在 DefaultSingletonBeanRegistry 类中提供了一个用于缓存单实例 Bean 的缓存器,它是一个用 HashMap 实现的缓存器,单实例的 Bean 以 beanName 为键保存在这个 HashMap 中。
3.2.2 ApplicationContext
ApplicationContext 由 BeanFactory 派 生 而 来 , 提 供 了 更 多 面 向 实 际 应 用 的 功 能 。ApplicationContext 继承了 HierarchicalBeanFactory 和 ListableBeanFactory 接口,在此基础上,还通过多个其他的接口扩展了 BeanFactory 的功能:
- ClassPathXmlApplicationContext:默认从类路径加载配置文件
- FileSystemXmlApplicationContext:默认从文件系统中装载配置文件
- ApplicationEventPublisher:让容器拥有发布应用上下文事件的功能,包括容器启动事件、关闭事件等。
- MessageSource:为应用提供 i18n 国际化消息访问的功能;
- ResourcePatternResolver : 所 有 ApplicationContext 实现类都实现了类似于PathMatchingResourcePatternResolver 的功能,可以通过带前缀的 Ant 风格的资源文件路径装载 Spring 的配置文件。
- LifeCycle:该接口是 Spring 2.0 加入的,该接口提供了 start()和 stop()两个方法,主要用于控制异步处理过程。在具体使用时,该接口同时被 ApplicationContext 实现及具体 Bean 实现,ApplicationContext 会将 start/stop 的信息传递给容器中所有实现了该接口的 Bean,以达到管理
和控制 JMX、任务调度等目的。 - ConfigurableApplicationContext 扩展于 ApplicationContext,它新增加了两个主要的方法:
refresh()和 close(),让 ApplicationContext 具有启动、刷新和关闭应用上下文的能力。在应用上下文关闭的情况下调用 refresh()即可启动应用上下文,在已经启动的状态下,调用 refresh()则清除缓存并重新装载配置信息,而调用 close()则可关闭应用上下文。
3.2.3 WebApplicationContext
WebApplicationContext 是专门为 Web 应用准备的,它允许从相对于 Web 根目录的路径中装载配置文件完成初始化工作。从 WebApplicationContext 中可以获得 ServletContext 的引用,整个 Web 应用上下文对象将作为属性放置到 ServletContext 中,以便 Web 应用环境可以访问 Spring 应用上下文 。
3.2.4 BeanFactory和FactoryBean的区别
- 「BeanFactory 是 IOC 容器」,是用来承载对象的
- 「FactoryBean 是一个接口」,为 Bean 提供了更加灵活的方式,通过代理一个Bean对象,对方法前后做一些操作。
3.3 Spring Bean 作用域
3.3.1 Singleton
单例模式,Spring IoC 容器中只会存在一个共享的 Bean 实例,无论有多少个 Bean 引用它,始终指向同一对象。该模式在多线程下是不安全的。Singleton 作用域是 Spring 中的缺省作用域 ,也是最常见的作用域。
3.3.2 Prototype
原型模式,每次通过 Spring 容器获取 prototype 定义的 bean 时,容器都将创建一个新的 Bean 实例,每个 Bean 实例都有自己的属性和状态,而 singleton 全局只有一个对象。根据经验,对有状态的bean使用prototype作用域,而对无状态的bean使用singleton 作用域。
3.3.3 Request
在一次 Http 请求中,容器会返回该 Bean 的同一实例。而对不同的 Http 请求则会产生新的 Bean,而且该 bean 仅在当前 Http Request 内有效,当前 Http 请求结束,该 bean 实例也将会被销毁。
3.3.4 Session
在一次 Http Session 中,容器会返回该 Bean 的同一实例。而对不同的 Session 请求则会创建新的实例,该 bean 实例仅在当前 Session 内有效。同 Http 请求相同,每一次 session 请求创建新的实例,而不同的实例之间不共享属性,且实例仅在自己的 session 请求内有效,请求结束,则实例将被销毁。
3.3.5 Global Session
在一个全局的 Http Session 中,容器会返回该 Bean 的同一个实例,仅在使用portlet context 时有效。
3.4 Spring Bean 生命周期
该流程建议反复查看[时序图](https://www.processon.com/view/605833d9e0b34d780ef6440a)来进行理解,这段我每次理解起来感觉都很吃力,中间还穿插了Spring 的循环依赖问题。总的来说可以分为四个阶段来分析,实例化和初始化阶段还分别配有相对应的钩子接口进行一些增强处理。
3.4.1 实例化阶段
InstantiationAwareBeanPostProcessor接口的postProcessBeforeInstantiation和postProcessAfterInstantiation方法,分别作为实例化中间的前置和后置方法,他是Spring提供的钩子,可以供Bean在实例化前后使用。
3.4.2 属性赋值
调用了PopulateBean方法,里面会根据Autowire的方式ByType还是ByName来决定填充Bean的方式来进行属性填充
3.4.3 初始化
- Aware接口的依赖注入
- BeanPostProcessor 在初始化前后的处理 以及InitializingBean(会根据判断当前bean有无继成关系来判断调用反射的哪个具体方法来进行bean的实例化)的初始化操作
3.4.4 销毁 Destruction
bean 标签有两个重要的属性(init-method 和 destroy-method)。用它们你可以自己定制初始化和注销方法。它们也有相应的注解(@PostConstruct 和@PreDestroy)
3.5 Spring 依赖注入的方式
3.5.1 构造器注入
Spring容器先创建单例A,A依赖B,然后将A放在“当前创建Bean池”中,此时创建B,B依赖C ,然后将B放在“当前创建Bean池”中,此时创建C,C又依赖A, 但是,此时A已经在池中,所以会报错,,因为在池中的Bean都是未初始化完的,所以会依赖错误 , Spring容器会将每一个正在创建的Bean 标识符放在一个“当前创建Bean池”中,Bean标识符在创建过程中将一直保持在这个池中,因此如果在创建Bean过程中发现自己已经在“当前创建Bean池”里时将抛出BeanCurrentlyInCreationException异常表示循环依赖;而对于创建完毕的Bean将从“当前创建Bean池”中清除掉。
3.5.2 Setter方式
3.5.2.1 单例(Singleton)
可以解决循环依赖
Spring容器会将每一个正在创建的Bean 标识符放在一个“当前创建Bean池”中,Bean标识符在创建过程中将一直保持在这个池中,因此如果在创建Bean过程中发现自己已经在“当前创建Bean池”里时将抛出BeanCurrentlyInCreationException异常表示循环依赖;而对于创建完毕的Bean将从“当前创建Bean池”中清除掉。
3.5.2.2 原型(Prototype)
无法解决循环依赖
3.5.3 Field注入(基于注解的方式进行依赖注入)
通过Autowired来进行属性注入
3.5.3.1 单例(Singleton)
3.5.3.2 原型(Prototype)
3.5.4 构造器注入和Setter方法注入的区别
两种依赖方式都可以使用,构造器注入和Setter方法注入。最好的解决方案是用构造器参数实现强制依赖,setter方法实现可选依赖。
构造函数注入 | setter注 入 |
---|---|
没有部分注入 | 有部分注入 |
不会覆盖 setter 属 性 | 会覆盖 setter 属 性 |
任意修改都会创建一个新实例 | 任意修改不会创建一个新实例 |
适用于设置很多属性 | 适用于设置少量属性 |
3.6 Spring是如何解决循环依赖的
3.6.1 解决循环依赖的方式
循环依赖就是说两个对象相互依赖,形成了一个环形的调用链路
spring 使用三级缓存去解决循环依赖的,其「核心逻辑就是把实例化和初始化的步骤分开,然后放入缓存中」,供另一个对象调用
- 「第一级缓存」:用来保存实例化、初始化都完成的对象
- 「第二级缓存」:用来保存实例化完成,但是未初始化完成的对象
- 「第三级缓存」:用来保存一个对象工厂,提供一个匿名内部类,用于创建二级缓存中的对象
当 A、B 两个类发生循环引用时 大致流程
1.A 完成实例化后,去「创建一个对象工厂,并放入三级缓存」当中
- 如果 A 被 AOP 代理,那么通过这个工厂获取到的就是 A 代理后的对象
- 如果 A 没有被 AOP 代理,那么这个工厂获取到的就是 A 实例化的对象
2.A 进行属性注入时,去「创建 B」
3.B 进行属性注入,需要 A ,则「从三级缓存中去取 A 工厂代理对象」并注入,然后删除三级缓存中的 A 工厂,将 A 对象放入二级缓存
4.B 完成后续属性注入,直到初始化结束,将 B 放入一级缓存
5.「A 从一级缓存中取到 B 并且注入 B」, 直到完成后续操作,将 A 从二级缓存删除并且放入一级缓存,循环依赖结束
spring 解决循环依赖有两个前提条件:
- 1.「不全是构造器方式」的循环依赖(否则无法分离初始化和实例化的操作)
- 2.「必须是单例」(否则无法保证是同一对象)
3.6.2 为什么使用三级缓存,而不是二级缓存
三级缓存的功能是只有真正发生循环依赖的时候,才去提前生成代理对象,否则只会「创建一个工厂并将其放入到三级缓存」中,但是不会去通过这个工厂去真正创建对象。
如果使用二级缓存解决循环依赖,意味着所有 Bean 在实例化后就要完成 AOP 代理,这样「违背了 Spring 设计的原则」,Spring 在设计之初就是在 Bean 生命周期的最后一步来完成 AOP 代理,而不是在实例化后就立马进行 AOP 代理。
3.7 Autowired和Resource注解有什么区别
- 「@Resource 是 Java 自己的注解」,@Resource 有两个属性是比较重要的,分是 name 和 type;Spring 将 @Resource 注解的 name 属性解析为 bean 的名字,而 type 属性则解析为 bean 的类型。所以如果使用 name 属性,则使用 byName 的自动注入策略,而使用 type 属性时则使用 byType 自动注入策略。如果既不指定 name 也不指定 type 属性,这时将通过反射机制使用 byName 自动注入策略。
- 「@Autowired 是spring 的注解」,是 spring2.5 版本引入的,Autowired 只根据 type 进行注入,「不会去匹配 name」。如果涉及到 type 无法辨别注入对象时,那需要依赖 @Qualifier 或 @Primary 注解一起来修饰。
四、Aop
4.1 应用场景
- 事务管理
- 安全检查
- 权限控制
- 日志
- 错误处理
4.2 常用术语
具体可以参看Spring的官方文档有关这方面的介绍,我觉得官方的概念有点晦涩难懂,尝试着翻译成人话来理解一下其中的一些核心概念,供日常使用。
首先,我搜索到了一个比较容易理解的概念图进行说明
4.2.1 连接点(Joinpoint)
连接点,这是一个可以用来进行增强的方法点,说白了就是对具体哪个方法进行增强。
4.2.2 切入点(Pointcut)
切入点是告诉我们在方法的什么位置进行增强,比如在方法执行之前增强,还是执行之后增强,或者两者都有。
4.2.3 Advice
Advice表示需要增强的功能
4.2.3.1 前置通知 (Before Advice)
在某连接点之前执行的通知,但这个通知不能阻止连接点之前的执行流程(除非抛出了一个异常)
4.2.3.2 后置通知 (After Returning Advice)
在某连接点正常完成后执行的Advice。
4.2.3.3 异常通知 (After throwing advice)
在方法抛出异常退出时执行的Adivce
4.2.3.4 最终通知 (After (finally) advice)
当某连接点退出的时候执行的Advice,不论是正常结束流程还是异常退出
4.2.3.5 环绕通知(Around advice)
包围一个连接点的Advice,这是最强大的一种通知类型。环绕通知可以在方法调用前后完成自定义的行为。它也会选择是否继续执行连接点或者直接返回返回值或者抛出异常来结束执行。
4.3 Spring Aop 和 AspectJ
Spring 依然采用运行时生成动态代理的方式来增强目标对象,所以它不需要增加额外的编译,也不需要 AspectJ 的织入器支持;而 AspectJ 在采用编译时增强,所以 AspectJ 需要使用自己的编译器来编译 Java 文件,还需要织入器。
4.3 实现方式
4.3.1 JDK动态代理
使用的是Jdk原生的动态代理技术,只能代理接口,而不能代理实际使用的类。Jdk实现的动态代理,具体实现是基于Reflect包中的Proxy和InvocationHandler两个接口去实现的。
4.3.2 CGLib动态代理
4.3.2.1 使用示例
被代理类:
1 | /** |
实际代理类:
1 | import org.springframework.cglib.proxy.Enhancer; |
4.3.2.2 原理说明
CGLIB是通过字节码增强处理框架 ASM,来生成字节码并装载到JVM。和JDK代理基于接口实现方式不同的是,CGLIB没有局限于接口,采用的是生成子类的方式。这个子类本质上就是一个Class 对象,换句话说原来是执行原有的Class,CGLIB会通过字节码增强的方式,在字节码的层面生成一个子类去集成需要增强的类,在子类中加入需要增强的方法,让这个子类代替原有的类,完成增强的操作。
CGLIB生成Class对象分为三个步骤:
- 生成指定类的Class对象字节数组。
- 将Class对象字节数组转换为Class对象。
- 通过 Class.forName 方法将Class对象装载到JVM。
4.3.3 区别
JDK 动态代理时业务类「必须要实现某个接口」,它是「基于反射的机制实现的」,生成一个实现同样接口的一个代理类,然后通过重写方法的方式,实现对代码的增强。
CGLIB 动态代理是使用字节码处理框架 ASM,其原理是通过字节码技术为一个类「创建子类,然后重写父类的方法」,实现对代码的增强。
五、常见问题
5.1 循环依赖问题
5.2 Bean线程安全问题
5.3 BeanFactory和FactoryBean的区别
六、Spring事务
6.1 数据库事务的隔离级别
隔离级别 | 效果 |
---|---|
读未提交 | 会产生数据脏读 |
读已提交 | 能解决数据脏读,没有提交的事物数据不会读取到,但在同一个事物中,可能会产生多次读取的数据不一致的问题,又称为不可重复读的问题 |
可重复读 | 能解决脏读、不可重复读的问题。但是对于幻读问题,对于Mysql而言,Mysql是通过了引入了Next-lock算法来解决的该问题,说人话就是基于行级锁和间隙锁的组合来解决这个问题的 |
串行化 | 虽说能解决各种读的问题,但是串行化后只能同时使用一个线程,排队场景对于实际生产使用来说,毫无意义 |
6.2 Spring 对事务的支持
6.2.1 Spring事务分类
- 编程式事务
- 声明式事务(通过Transaction注解来使用,目前使用较多)
6.2.2 Spring事务的传播机制
事务传播特性就是,事务中嵌套其他的事务,事务与被嵌套事务之间如何相互影响,如何执行的,这就是事务传播性。
1 | import org.springframework.beans.factory.annotation.Autowired; |
存在上述嵌套事务的情况那么如何描述两个方法的事务行为的互相作用和互相影响呢,这里将事务传播行为分为了7种,主要集中在被嵌套的方法methodB这端的定义,也就是@Transcational(propagation = PROPAGATION_REQUIRES_NEW) ,这句定义中propagation的赋值:
- PROPAGATION_REQUIRED:这个是最常见的,就是说,如果ServiceA.methodA调用了ServiceB.methodB,如果ServiceA.methodA开启了事务,然后ServiceB.methodB也声明了事务,那么ServiceB.methodB不会开启独立事务,而是将自己的操作放在ServiceA.methodA的事务中来执行,ServiceA.methodA和ServiceB.methodB任何一个报错都会导致整个事务回滚。
- PROPAGATION_SUPPORTS:如果ServiceA.methodA开了事务,那么ServiceB.methodB就将自己加入ServiceA.methodA中来运行,如果ServiceA.methodA没有开事务,那么ServiceB.methodB自己也不开事务
- PROPAGATION_MANDATORY:必须被一个开启了事务的方法来调用自己,否则报错。
- PROPAGATION_REQUIRES_NEW:ServiceB.method强制性自己开启一个新的事务,然后ServiceA.methodA的事务会等待ServiceB.methodB事务完了再继续执行。这就是影响的回滚了,如果ServiceA.methodA报错了,ServiceB.methodB是不会受到影响的,ServiceB.methodB报错了,ServiceA.methodA也可以选择性的回滚或者是提交。
- PROPAGATION_NOT_SUPPORTED:就是ServiceB.methodB不支持事务,ServiceA.methodA的事务执行到ServiceB.methodB那儿,就挂起来了,ServiceB.methodB用非事务方式运行结束,ServiceA.methodA事务再继续运行。这个好处就是ServiceB.methodB代码报错不会让ServiceA.methodA回滚。
- PROPAGATION_NEVER:不能被一个事务来调用,ServiceA.methodA开事务了,但是调用了ServiceB.methodB会报错。
- PROPAGATION_NESTED:开启嵌套事务,ServiceB.methodB开启一个子事务,如果回滚的话,那么ServiceB.methodB就回滚到开启子事务的这个save point。
6.2.3 Spring事务失效的场景和原理
6.2.3.1 失效场景
- 没有被Spring管理的方法:没有被Spring管理的Bean,如果其中出现了方法需要进行事务处理的情况,此时的事务不会执行
- 方法不是public:@Transactional 只能用于 public 的方法上,否则事务不会失效,如果要用在非 public 方法上
- 方法被final修饰
- 自身调用问题:当一个Service中存在多个方法,都标注了事务@Transactional,当其中一个方法调用另外一个方法的时候,被调用方法中的事务是不会执行的。
- 数据源没有配置事务管理器:Datasource没有配置事务,当然不会生效
- 事务传播级别选择:选择了错误的传播级别,会导致事物无法生效
- 事务中出现异常:如果在执行的方法中执行的事务,由于异常退出的情况,那么这个事务是无法完成的。(异常没有抛出的情况下)
- 错误定义异常类型:在事务中出现异常的时候,需要对异常进行定义才能正确执行事务。如果出现错误的定义,当出现事务中的异常时,还可以继续执行事务。
6.2.3.2 原理
为什么失效,这些场景都和Aop的原理有着很大的关系,因为Spring的事务使用了Aop机制,而无论是使用Cglib还是Jdk动态代理,都有一个特点,接口和子类都是无法实现private或者final方法的,所以如果出现这些场景,会导致Spring 的事务无法生效。
6.2.4 Spring事务原理
基于Aop机制去实现的,
七、设计模式
7.1 工厂模式
通过BeanFactory和ApplicationContext来生产Bean对象
7.2 单例模式
Spring中的Bean默认都是单例
7.3 模版模式
父类定义了骨架(调用哪些方法及顺序),某些特定方法由子类实现。
最大的好处:代码复用,减少重复代码。除了子类要实现的特定方法,其他方法及方法调用顺序都在父类中预先写好了。
所以父类模板方法中有两类方法:
共同的方法:所有子类都会用到的代码
不同的方法:子类要覆盖的方法,分为两种:
- 抽象方法:父类中的是抽象方法,子类必须覆盖
- 钩子方法:父类中是一个空方法,子类继承了默认也是空的
注:为什么叫钩子,子类可以通过这个钩子(方法),控制父类,因为这个钩子实际是父类的方法(空方法)!
7.4 责任链模式
7.5 观察者模式
7.6 代理模式
7.7 装饰器模式
7.8 适配器模式
7.9 策略模式
举例:Spring框架的资源访问Resource接口。该接口提供了更强的资源访问能力,Spring 框架本身大量使用了 Resource 接口来访问底层资源。
Resource 接口介绍
Rsource 接口是具体资源访问策略的抽象,也是所有资源访问类所实现的接口。
Resource 接口主要提供了如下几个方法:
- getInputStream():定位并打开资源,返回资源对应的输入流。每次调用都返回新的输入流。调用者必须负责关闭输入流。
- exists():返回 Resource 所指向的资源是否存在。
- isOpen():返回资源文件是否打开,如果资源文件不能多次读取,每次读取结束应该显式关闭,以防止资源泄漏。
- getDescription():返回资源的描述信息,通常用于资源处理出错时输出该信息,通常是全限定文件名或实际 URL。
- getFile:返回资源对应的 File 对象。
- getURL:返回资源对应的 URL 对象。
最后两个方法通常无须使用,仅在通过简单方式访问无法实现时,Resource 提供传统的资源访问的功能。
Resource 接口本身没有提供访问任何底层资源的实现逻辑,针对不同的底层资源,Spring 将会提供不同的 Resource 实现类,不同的实现类负责不同的资源访问逻辑。Spring 为 Resource 接口提供了如下实现类:
- UrlResource:访问网络资源的实现类。
- ClassPathResource:访问类加载路径里资源的实现类。
- FileSystemResource:访问文件系统里资源的实现类。
- ServletContextResource:访问相对于 ServletContext 路径里的资源的实现类.
- InputStreamResource:访问输入流资源的实现类。
- ByteArrayResource:访问字节数组资源的实现类。
这些 Resource 实现类,针对不同的的底层资源,提供了相应的资源访问逻辑,并提供便捷的包装,以利于客户端程序的资源访问。
八、Spring MVC
8.1 Servlet 生命周期
- 实例化
- 初始化
- 执行处理
- 销毁
8.2 Filter和Interceptor有什么区别,使用场景有什么不同
九、Spring Boot
Spring Boot 有好几个功能和特性,但是最主要,也最常见(最常问)的是自动配置的特性。这里可以主要分析一下该功能点的实现原理,以及如何实现一个自己的 spring boot starter功能。这块详细可以参照 官方的reference,我觉得写的非常好。
9.1 什么是 Spring Boot 的自动装配
9.1.1 Spi机制
SPI是Service Provider Interface的缩写,这种技术用人话来理解,就是官方定义一套规范,由各个厂商、个人根据官方定义的Service接口,自行实现具体的技术细节。而Spi技术,则通过该机制为某个接口寻找相对应的服务。在Java的世界中,最常见的使用该技术的案例有:
- Jdbc连接
- Dubbo
- Spring Boot 的自动依赖
9.1.2 Spring Boot 的自动装配
Spring Boot定义了一套接口规范,根据规定,我们在META-INF下面写的spring.factories文件会被SpringBoot扫描,并将该文件中的内容进行加载到Spring到容器中去。对于外包的Jar包,我们可以按照SpringBoot规定的标准,将自己的bean 进行自动装配,共SpringBoot调用。
9.2 SpringBoot 实现自动装配的原理
9.2.1 SpringBoot的核心注解
如果去看官网,会发现官方的Reference里面会强调一个核心注解SpringBootApplication
,这个注解其实是一个组合注解,当你使用这个注解的时候,标明了他同时集成了如下三个注解:
SpringBootConfiguration
:这是Configuration类的子集,标明这是一个config类,可以被注入进去EnableAutoConfiguration
:开启自动注入功能,该注解如果加上了,表明了项目是打开了自动注入的功能等ComponentScan
:组件扫描功能,该注解加上,表明会将所有的configuration类扫描并装配成Spring的bean
对于自动装配功能来说,他就是EnableAutoConfiguration
注解起的作用。下面详细说一下这个注解所起到的功能。
9.2.2 EnableAutoConfiguration 注解的作用
这个注解其实也没有特别的黑魔法,主要还是一层层的嵌套调用的。如果说核心代码和思路的话,我觉得可以重点可以看这一段
1 | protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) { |
9.2.3 Conditional注解的使用
官方Reference还是强推,写的也很详细,conditional注解还细分如下几种:
- Class Conditions
- Bean Conditions
- Property Conditions
- Resource Conditions
- WebApplication Conditions
- SpEl Conditions Spring spel表达式类condition
具体使用的时候,经常可以看见如下图这样的autoconfiguration,我随机打开了RedisAutoConfiguration来进行解释。
在这个图片里面,由于我没有引入spring-boot-data-redis的相关依赖,导致相关类是红色的。而conditionalOnClass类的作用是什么呢?他表明只有类路径下有相关类导入时,才会加载相关类。而conditionalOnSingleCandidate表明只有一个唯一存在的单例类时,该bean才会被装配。
更多的condition的用法可以翻阅官方Api,其实还是蛮好理解的。还有一些诸如存在相关配置文件才会加载类的行为。
9.3 Spring Boot 其他特性
其实Spring Boot还有不少其他特性,诸如Spring Boot actuator(用于监控Spring boot应用),Spring Cli(命令行程序,实际使用比较少)。