Spring
- 目的:解决企业应用开发的复杂性
- 功能:使用基本的JavaBean代替EJB,并提供了更多的企业应用功能
- 范围:任何Java应用
所有Spring的这些特征使你能够编写更干净、更可管理、并且更易于测试的代码。它们也为Spring中的各种模块提供了基础支持。
文章目录
一、两大特性
AOP
AOP(Aspect-Oriented Programming,面向方面编程),是Spring提供的关键特性之一,是OOP编程的有效补充。
将原有自上而下与主业务流程不相关的模块抽离出来再横向的插入之前业务中,达到解耦的目的。
接下来介绍一下提到AOP就必须要了解的知识点:
切面:拦截器类,其中会定义切点以及通知
切点:具体拦截的某个业务点。
通知:切面当中的方法,声明通知方法在目标业务层的执行位置,通知类型如下:
前置通知:@Before 在目标业务方法执行之前执行
后置通知:@After 在目标业务方法执行之后执行
返回通知:@AfterReturning 在目标业务方法返回结果之后执行
异常通知:@AfterThrowing 在目标业务方法抛出异常之后
环绕通知:@Around 功能强大,可代替以上四种通知,还可以控制目标业务方法是否执行以及何时执行
原理:
源码中用到了两种动态代理来实现拦截切入功能:
jdk动态代理和cglib动态代理。两种方法同时存在,各有优劣。
jdk动态代理是由java内部的反射机制来实现的。必须借助接口才能产生代理对象。
cglib动态代理底层则是借助asm来实现的。不需要接口 。
- CGLib采用了非常底层的字节码技术,其原理是通过目标类的字节码为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。 使用字节码技术生成代理类 .
- 底层使用字节码处理框架ASM,来转换字节码并生成新的类。
IOC
概述
IOC是spring的两大核心概念之一,IOC给我们提供了一个IOCbean容器,这个容器会帮我们自动去创建对象,不需要我们手动创建,IOC容器有个非常强大的功能叫DI(Dependency Injection 依赖注入),我们可以通过写Java注解代码或者是XML配置方式,把我们想要注入对象所依赖的一些其他的bean,自动的注入进去,他是通过byName或byType类型的方式来帮助我们注入。正是因为有了依赖注入,使得IOC有这非常强大的好处,解耦。 同时整个bean的生命周期从创建到使用到销毁都是由他来管理。
流程 (IOC bean创建的过程)
(xml\注解)根据bean的定义信息------------------->由IOC容器创建需要的对象——————>由BeanFactory通过反射创建(所有容器都是BeanFactory的子类)
Bean的生命周期
主要分为五个阶段:
创建前准备、创建实例、依赖注入、容器缓存、销毁实例
第一个阶段:创建前准备 是bean在加载前要从上下文和一些配置注解中去解析和查找和Bean有关的拓展实现,比如像init-method,容器在初始话会调用的一个方法.
第二个阶段创建实例阶段,BeanNameAware 接口,Spring 传递 bean 的 ID 到 setBeanName 方法。主要是通过beanFectory 反射去创建Bean的实例对象,扫描和解析bean的属性
第三个阶段依赖注入,如果被实例化Bean存在依赖其他bean对象的一些情况,则需要对这些依赖的bean进行依赖注入,比如@Autowired等一配置信息,这个阶段会触发一些拓展的调用。比如常见的拓展类BeanFectoryPostProcessor用来实现Bean初始化前后的扩展回调,以及像BeanFectoryAware等等。
第四个阶段容器缓存阶段,容器缓存主要是把bean保存到容器以及spring的缓存中,这个阶段的Bean就可以被开发者去使用了,这个阶段的常见操作像init-method 这个属性配置方法,以及BeanPostProcessor后置处理方法也会在这个阶段触发
第五个阶段,销毁阶段,当Bean的应用上下文被销毁的时候bean也会被销毁,如果存在bean实现disablebean接口或者配置了distory-method方法会被在这个阶段被调用。
二、自动装配的原理
当我们使用 @EnableAutoConfiguration
注解激活自动装配时,实质对应着很多 XXXAutoConfiguration
类在执行装配工作,这些 XXXAutoConfiguration
类是在spring-boot-autoconfigure jar中的META-INF/spring.factories文件中配置好的, @EnableAutoConfiguration
通过SpringFactoriesLoader机制创建 XXXAutoConfiguration
这些bean。 XXXAutoConfiguration
的bean会依次执行并判断是否需要创建对应的bean注入到Spring容器中。
在每个 XXXAutoConfiguration
类中,都会利用多种类型的条件注解 @ConditionOnXXX
对当前的应用环境做判断,如应用程序是否为Web应用、classpath路径上是否包含对应的类、Spring容器中是否已经包含了对应类型的bean。如果判断条件都成立, XXXAutoConfiguration
就会认为需要向Spring容器中注入这个bean,否则就忽略。
三、spring 常用的注入方式
Spring通过DI(依赖注入)实现IOC(控制反转),常用的注入方式主要有三种:
- 构造方法注入
- setter注入
- 基于注解的注入
四、spring 自动装配 bean 的方式
Spring容器负责创建应用程序中的bean同时通过ID来协调这些对象之间的关系。作为开发人员,我们需要告诉Spring要创建哪些bean并且如何将其装配到一起。
spring中bean装配有两种方式:
- 隐式的bean发现机制和自动装配
- 在java代码或者XML中进行显示配置
五、Spring中使用的设计模式
1、单例模式
2、原型模式
spring中单例bean和原型bean两种方式的体现
3、简单工厂
4、工厂方法
由一个工厂类根据传入的参数,动态决定应该创建哪一个产品类。 spring容器中BeanFactory通过配置或注解对对bean进行注册实例化。
spring与mybatis的结合。 我们看上面该bean,因为实现了FactoryBean接口,所以返回的不是 SqlSessionFactoryBean 的实例,而是它的 SqlSessionFactoryBean.getObject() 的返回值。
5、代理模式
spring中AOP的底层就是代理模式中的动态代理实现的。
6、模板模式
Spring中jdbcTemplate、hibernateTemplate等以Template结尾的对数据库操作的类,就是用到了模板模式。
7、装饰器模式
sessionFactory根据客户的每次请求,将dataSource属性设置成不同的数据源,以到达切换数据源的目的。
Spring 中配置 DataSource 的时候,DataSource 可能是不同的数据库和数据源,项目需要连接多个数据库,这种模式让我们可以根据客户需求切换不同的数据源。
8、 观察者模式
ApplicationListener接口[事件监听器]
继承自jdk的EventListener,所有的监听器都要实现这个接口。 当事件触发时所有的监听器都会收到消息。
9、策略模式
Spring框架的资源访问Resource接口。该接口提供了更强的资源访问能力,Spring 框架本身大量使用了 Resource 接口来访问底层资源。
10、适配器模式
SpringAOP中有一个很重要的功能就是使用的 Advice(通知) 来增强被代理类的功能,Advice主要有MethodBeforeAdvice、AfterReturningAdvice、ThrowsAdvice这几种。 Spring需要将每个 Advice 都封装成对应的拦截器类型返回给容器,所以需要使用适配器模式对 Advice 进行转换。
六、Spring循环依赖问题
循环依赖:多个bean之间相互依赖,形成了一个闭环。
Spring为了解决单例的循环依赖问题,使用了三级缓存。
因为加入singletonFactories三级缓存的前提是执行了构造器
两种注入方式对循环依赖造]成的影响
官网:
对构造注入不友好,会抛出异常。 A实例化需要B ,B实例化又等着A实例化
用set注入是可以的
spring容器中只有单例bean会通过三级缓存提前暴露来解决循环依赖问题
主要通过DefaultSingletonBeanRegistry类三级缓存解决的
三个map
一级缓存(也叫单例池):存放已经经历了完整生命周期的Bean对象
二级缓存:存放属性还未填完的对象
三级缓存:存放可以生成Bean的工厂
流程:主要是四个方法
getSingleton\doCreatBean\populateBean\addSingleton
先从容器里面拿,没有在创建,然后赋属性值,然后添加到一级缓存
A\B两对象迁移过程
A实例化需要B,B还没有创建,A先把自己放到三级缓存里面,去实例化B
B实例化需要A,依次寻找发现A在三级缓存里,将A移到二级缓存并删除三级缓存的A
B顺利完成初始化,但是A仍然处于创建状态,接着回来创建A,此时B已经创建完成,直接从一级缓存里面拿,并将A自己放到一级缓存里
为什么一定要使用三级缓存(singletonFactories)?
解决代理对象(如aop)循环依赖的问题。
例: a依赖b,b依赖a,同时a,b都被aop增强。
首先明确aop的实现是通过 postBeanProcess后置处理器,在初始化之后做代理操作的。
为什么使用三级缓存原因:
如果有AOP,A依赖于B,B依赖于A,A实例化完成暴露出去,开始注入属性,发现引用B,B开始实例化,使用A暴露的对象,初始化完成后封装成代理对象,A再将代理后的B注入,再做代理,那么代理A中的B就是代理后的B,但是代理后的B中的A是没用代理的A。
七、spring 中的 bean
Spring容器中的Bean是否线程安全,容器本身并没有提供Bean的线程安全策略,因此可以说spring容器中的Bean本身不具备线程安全的特性,但是具体还是要结合具体scope的Bean去研究。
spring 中的 bean 默认是单例模式,spring 框架并没有对单例 bean 进行多线程的封装处理。 实际上大部分时候 spring bean 无状态的(比如 dao 类),所有某种程度上来说 bean 也是安全的,但如果 bean 有状态的话(比如 view model 对象),那就要开发者自己去保证线程安全了,最简单的就是改变 bean 的作用域,把“singleton”变更为“prototype”,这样请求 bean 相当于 new Bean()了,所以就可以保证线程安全了。
有状态就是有数据存储功能。
无状态就是不会保存数据
原型Bean
对于原型Bean,每次创建一个新对象,也就是线程之间并不存在Bean共享,自然是不会有线程安全的问题。
单例Bean
对于单例Bean,所有线程都共享一个单例实例Bean,因此是存在资源的竞争。
如果单例Bean,是一个无状态Bean,也就是线程中的操作不会对Bean的成员执行查询以外的操作,那么这个单例Bean是线程安全的。比如Spring mvc 的 Controller、Service、Dao等,这些Bean大多是无状态的,只关注于方法本身。
有状态对象(Stateful Bean) :就是有实例变量的对象,可以保存数据,是非线程安全的。每个用户有自己特有的一个实例,在用户的生存期内,bean保持了用户的信息,即“有状态”;一旦用户灭亡(调用结束或实例结束),bean的生命期也告结束。即每个用户最初都会得到一个初始的bean。
无状态对象(Stateless Bean):就是没有实例变量的对象,不能保存数据,是不变类,是线程安全的。bean一旦实例化就被加进会话池中,各个用户都可以共用。即使用户已经消亡,bean 的生命期也不一定结束,它可能依然存在于会话池中,供其他用户调用。由于没有特定的用户,那么也就不能保持某一用户的状态,所以叫无状态bean。但无状态会话bean 并非没有状态,如果它有自己的属性(变量),那么这些变量就会受到所有调用它的用户的影响,这是在实际应用中必须注意的。
对于有状态的bean,Spring官方提供的bean,一般提供了通过ThreadLocal去解决线程安全的方法,比如RequestContextHolder、TransactionSynchronizationManager、LocaleContextHolder等。
八、 spring bean 的作用域
当通过spring容器创建一个Bean实例时,不仅可以完成Bean实例的实例化,还可以为Bean指定特定的作用域。Spring支持如下5种作用域:
- singleton:单例模式,在整个Spring IoC容器中,使用singleton定义的Bean将只有一个实例
- prototype:原型模式,每次通过容器的getBean方法获取prototype定义的Bean时,都将产生一个新的Bean实例
- request:为每一个网络请求创建一个实例,在请求完成以后,bean会失效并被垃圾回收器回收。 只有在Web应用中使用Spring时,该作用域才有效
- session:与request范围类似,确保每个session中有一个bean的实例,在session过期后,bean会随之失效。 同样只有在Web应用中使用Spring时,该作用域才有效
- globalsession:每个全局的HTTP Session,使用session定义的Bean都将产生一个新实例。典型情况下,仅在使用portlet context的时候有效。同样只有在Web应用中使用Spring时,该作用域才有效。
九、spring 事务实现方式
spring支持编程式事务管理和声明式事务管理两种方式。
- 编程式事务管理对基于 POJO 的应用来说是唯一选择。我们需要在代码中调用beginTransaction()、commit()、rollback()等事务管理相关的方法,这就是编程式事务管理。
- 基于 TransactionProxyFactoryBean 的声明式事务管理
- 基于 @Transactional 的声明式事务管理
- 基于 Aspectj AOP 配置事务
总:
spring的声明式事务是由aop来实现的,首先要生成具体的代理对象,然后按照aop的整套流程来执行具体的操作逻辑,正常情况下要通过通知来完成核心功能,但是事务不是通过通知来实现的,而是通过一个TransactionInterceptor(Spring的事务拦截器)来实现的,然后调用invoke来实现具体的逻辑。
分:
1、先做准备工作,解析各个方法上事务相关的属性,根据具体的属性来判断是否开始新事务
2、当需要开启的时候,获取数据库连接,关闭自动提交功能,开起事务执行具体的sql逻辑操作
3、在操作过程中,如果执行失败了,那么会通过completeTransactionAfterThrowing看来完成事务的回滚操 作,回滚的具体逻辑是通过doRollBack方法来实现的,实现的时候也是要先获取连接对象,通过连接对象来回滚
4、如果执行过程中,没有任何意外情况的发生,那么通过commitTransactionAfterReturning来完成事务的 提交操作,提交的。具体逻辑是通过doCommit方法来实现的,实现的时候也是要获取连接,通过连接对象来提交。
5、当事务执行完毕之后需要清除相关的事务信息
十、spring 的事务隔离
spring 四种特性五种隔离级别七大传播行为
四种特性:ACID
⑴ 原子性(Atomicity)
原子性是指事务包含的所有操作要么全部成功,要么全部失败回滚,这和前面两篇博客介绍事务的功能是一样的概念,因此事务的操作如果成功就必须要完全应用到数据库,如果操作失败则不能对数据库有任何影响。
⑵ 一致性(Consistency)
一致性是指事务必须使数据库从一个一致性状态变换到另一个一致性状态,也就是说一个事务执行之前和执行之后都必须处于一致性状态。
拿转账来说,假设用户A和用户B两者的钱加起来一共是5000,那么不管A和B之间如何转账,转几次账,事务结束后两个用户的钱相加起来应该还得是5000,这就是事务的一致性。
⑶ 隔离性(Isolation)
隔离性是当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。
即要达到这么一种效果:对于任意两个并发的事务T1和T2,在事务T1看来,T2要么在T1开始之前就已经结束,要么在T1结束之后才开始,这样每个事务都感觉不到有其他事务在并发地执行。
关于事务的隔离性数据库提供了多种隔离级别,稍后会介绍到。
⑷ 持久性(Durability)
持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作。
五种隔离级别
隔离级别是指若干个并发的事务之间的隔离程度。TransactionDefinition 接口中定义了五个表示隔离级别的常量:
- TransactionDefinition.ISOLATION_DEFAULT:这是默认值,表示使用底层数据库的默认隔离级别。对大部分数据库而言。(默认=读已提交)
- TransactionDefinition.ISOLATION_READ_UNCOMMITTED:该隔离级别表示一个事务可以读取另一个事务修改但还没有提交的数据。该级别不能防止脏读,不可重复读和幻读,因此很少使用该隔离级别。比如PostgreSQL实际上并没有此级别。(读未提交)
- TransactionDefinition.ISOLATION_READ_COMMITTED:该隔离级别表示一个事务只能读取另一个事务已经提交的数据。该级别可以防止脏读,这也是大多数情况下的推荐值。(读已提交)
- TransactionDefinition.ISOLATION_REPEATABLE_READ:该隔离级别表示一个事务在整个过程中可以多次重复执行某个查询,并且每次返回的记录都相同。该级别可以防止脏读和不可重复读。(可重复读)
- TransactionDefinition.ISOLATION_SERIALIZABLE:所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。(序列化)
七种传播行为
REQUIRED、REQUIRES_NEW、SUPPORTS、NOT_SUPPORTED、MANDATORY、NEVER、NESTED
一、REQUIRED
表示当前方法必须在一个具有事务的上下文中运行,如有调用端有事务在进行,那么被调用端将在该事务中运行,否则的话重新开启一个事务。(如果被调用端发生异常,那么调用端和被调用端事务都将回滚)
方法A中调用方法B
若A有事务,B有事务:B加入到A事务
若A无事务,B有事务:B开启一个新的事务
二、REREQUIRES_NEW
暂停外层事务开启一个新的
A有事务,事务A暂停,新开启B事务
三、SUPPORTS
表示当前方法不必需要具有一个事务上下文,但是如果有一个事务的话,它也可以在这个事务中运行
A有事务,B是 SUPPORTS,B加入A
A无事务,都无事务
四、NOT_SUPPORTED
A有事务,事务A被暂停
A无事务,B无事务,无事务运行
五、MANDATORY(mandatory)
表示当前方法必须在一个事务中运行,如果没有事务,将抛出异常
A有事务,B(MANDATORY):B加入A
A无事务,B抛出异常
六、NEVER
A有事务,B抛出异常
七、NESTED
十一、spring和springboot 关系
Spring 是于 2003 年兴起的一个轻量级的 Java 开发框架,它是为了解决企业应用开发的复杂性而创建的。Spring 的核心是控制反转(IoC)和面向切面编程(AOP)。Spring 是可以在 Java SE/EE 中使用的轻量级开源框架。
SpringMVC是SpringMVC是基于Spring功能之上添加的Web框架,想用SpringMVC必须先依赖Spring。
Spring Boot基本上是Spring框架的扩展,它消除了设置Spring应用程序所需的XML配置,为更快,更高效的开发生态系统铺平了道路。