Spring分为3层,最底层是核心层,包括IOC、AOP等核心模块,中间层是封装的JavaEE服务、作为中间的驱动组件,最上层是各个应用。
Spring框架中用到的设计模式
- 代理模式:AOP
- 单例模式:在spring配置文件中配置的bean默认是单例模式
- 模板方法:用来解决重复代码的问题,eg:RestTtemplate JdbcTemplate RedisTemplate
- 依赖注入:BeanFactory、AppliCationContext
- 工厂模式:BeanFactory用来创建对象
1. SpringIOC
1. IOC(控制反转)
控制反转,通过容器创建Bean对象来管理整个生命周期
IOC—Inversion of Control,即“控制反转”,不是什么技术,而是一种设计思想。在Java开发中,Ioc意味着将你设计好的对象交给容器控制,而不是传统的在你的对象内部直接控制
2. DI(依赖注入)
在依赖注入中,不需要手动的创建对象,但是必须描述对象是如何创建的,通常是在配置文件中声明所需要的服务,在由IOC容器进行装配
DI—Dependency Injection,即“依赖注入”:是组件之间依赖关系由容器在运行期决定,形象的说,即由容器动态的将某个依赖关系注入到组件之中。依赖注入的目的并非为软件系统带来更多功能,而是为了提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台。通过依赖注入机制,我们只需要通过简单的配置,而无需任何代码就可指定目标需要的资源,完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁实现。
总结:
控制反转IOC(Inversion of Control)是说创建对象的控制权进行转移,以前创建对象的主动权和创建时机是由自己把控的,而现在这种权力转移到第三方,比如转移交给了IOC容器,它就是一个专门用来创建对象的工厂,你要什么对象,它就给你什么对象,有了 IOC容器,依赖关系就变了,原先的依赖关系就没了,它们都依赖IOC容器了,通过IOC容器来建立它们之间的关系。
DI(依赖注入)其实就是IOC的另外一种说法,DI是由Martin Fowler 在2004年初的一篇论文中首次提出的。他总结道:控制的什么被反转了?就是获得依赖对象的方式反转了。
3. 依赖注入的方式
- 接口注入
- 构造函数注入
- setter注入
4. spring ioc 容器
spring提供了两种容器BeanFactory和ApplicationContext
BeanFactory: 是一个包含Bean集合的工厂类,它会在容器中按照客户端要求时进行实例化Bean对象
ApplicationContext: 扩展了BeanFactory接口,在此基础上提供了额外的功能
MessageSource:管理message,实现国际化等功能。
ApplicationEventPublisher:事件发布。
ResourcePatternResolver:多资源加载。
EnvironmentCapable:系统Environment(profile+Properties)相关。
Lifecycle:管理生命周期。
Closable:关闭,释放资源
InitializingBean:自定义初始化。
BeanNameAware:设置beanName的Aware接口。
BeanFactory也被称为低级容器,而ApplicationContext被称为高级容器。
常用的BeanFactory容器: XmlBeanFactory 可以根据xml文件中定义的内容,创建对应的Bean
常用的ApplicationContext容器:
- ClassPathXmlApplicationContext:从ClassPath的XML配置文件中读取上下文,并生成上下文定义。应用程序上下文从程序环境变量中取得。示例代码如下:
ApplicationContextcontext=newClassPathXmlApplicationContext(“bean.xml”);
- FileSystemXmlApplicationContext:由文件系统中的XML配置文件读取上下文。示例代码如下:
ApplicationContextcontext=newFileSystemXmlApplicationContext(“bean.xml”);
-
XmlWebApplicationContext:由Web应用的XML文件读取上下文。例如我们在SpringMVC使用的情况。当然,目前我们更多的是使用SpringBoot为主,所以使用的是第四种ApplicationContext容
器,ConfigServletWebServerApplicationContext。 -
IOC容器的优点
- 将最小化应用程序中的代码量。
- 以最小的影响和最少的侵入机制促进松耦合。
- 支持即时的实例化和延迟加载Bean对象。
- 将使您的应用程序易于测试,因为它不需要单元测试用例中的任何单例或JNDI查找机制。
- IOC的实现原理
interface Fruit{
public abstract void eat();
}
class Apple implements Fruit{
public void eat(){
System.out.println("Apple");
}
}
class Orange implements Fruit{
public void eat(){
System.out.println("Orange");
}
}
class Factory{
public static Fruit getInstance(Class clazz){
Fruitf=null;
try{
f=(Fruit)Class.forName(clazz.getName()).newInstance();
}catch(Exception e){
e.printStackTrace();}
return f;
}
}
classClient{
public static void main(String[] args){
Fruitf=Factory.getInstance(Apple.class);
if(f!=null){
f.eat();
}
}
}
- Fruit接口,有Apple和Orange两个实现类。
- Factory工厂,通过反射机制,创建className对应的Fruit对象。
- Client通过Factory工厂,获得对应的Fruit对象。
- 实际情况下,SpringIoC比这个复杂很多很多,例如单例Bean对象,Bean的属性注入,相互依赖的Bean的处理,以及等等。
2. Spring Bean
Bean 是由 springIOC 容器实例化、配置、装配和管理
1. spring配置的方式
- xml配置方式
Bean 所需的依赖项和服务在 XML
格式的配置文件中指定。这些配置文件通常包含许多 bean 定义和特定于应用程序的配置选项,使用标签的方式来定义
<bean id="studentBean" class="org.edureka.firstSpring.StudentBean">
<property name="name" value="Edureka"></property>
</bean>
- 注解配置
您可以通过在相关的类,方法或字段声明上使用注解,将 Bean 配置为组件类本身,而不是使用 XML 来描述 Bean 装配。默认情况下,Spring 容器中未打开注解装配。因此,您需要在使用它之前在 Spring 配置文件中启用它。
<beans>
<context:annotation-config/>
<!-- bean definitions go here -->
</beans>
- spring config 配置
Spring 的 Java 配置是通过使用 @Bean 和 @Configuration 来实现
@Bean 注解扮演与 元素相同的角色。
@Configuration 类允许通过简单地调用同一个类中的其他 @Bean 方法来定义 Bean 间依赖关系。
@Configuration
public class StudentConfig {
@Bean
public StudentBean myStudent() {
return new StudentBean();
}
}
目前采用spring config配置比较多,尤其是在使用springboot项目中
1. spring作用域(bean scope)
spring中有5种作用域
- Singleton: 每个 Spring IoC 容器仅有一个单 Bean 实例
- Prototype(默认): 每次请求都会产生一个新的实例。
- Request: 每一次 HTTP 请求都会产生一个新的 Bean 实例,并且该 Bean 仅在当前 HTTP 请求内有效。
- Session: 每一个的 Session 都会产生一个新的 Bean 实例,同时该 Bean 仅在当前 HTTP Session 内有效。
- Application: 每一个 Web Application 都会产生一个新的 Bean ,同时该 Bean 仅在当前 Web Application 内有效。
2. spring 容器的生命周期
- spring bean初始化流程
- spring bean销毁流程
3. spring装配
当 Bean 在 Spring 容器中组合在一起时,它被称为装配或 Bean 装配。
Spring 容器需要知道需要什么 Bean 以及容器应该如何使用依赖注入来将 Bean 绑定在一起,同时装配 Bean 。
装配,和上文提到的 DI 依赖注入,实际是一个东西。
自动装配的方式
- no: 这是默认设置,表示没有自动装配。应使用显式 Bean 引用进行装配。 - - byName: 它根据 Bean 的名称注入对象依赖项。它匹配并装配其属性与 XML 文件中由相同名称定义的 Bean 。
- byType(最常用): 它根据类型注入对象依赖项。如果属性的类型与 XML 文件中的一个 Bean 类型匹配,则匹配并装配属性。
- 构造函数: 它通过调用类的构造函数来注入依赖项。它有大量的参数。
- autodetect: 首先容器尝试通过构造函数使用 autowire 装配,如果不能,则尝试通过 byType 自动装配。
4. 延迟加载
默认情况下,容器启动之后会将所有作用域为单例的 Bean 都创建好,但是有的业务场景我们并不需要它提前都创建好。此时,我们可以在 Bean 中设置 lzay-init = “true” 。 这样,当容器启动之后,作用域为单例的 Bean ,就不在创建。 而是在获得该 Bean 时,才真正在创建加载。
5. spring bean 解决循环依赖的问题
TODO
参考如下链接
3. Spring 注解
基于注解的容器配置:不使用 XML 来描述 Bean 装配,开发人员通过在相关的类,方法或字段声明上使用注解将配置移动到组件类本身。它可以作为 XML 设置的替代方案。
Spring 的 Java 配置是通过使用 @Bean 和 @Configuration 来实现。
参考以下文章
4. Spring AOP
AOP(Aspect Orient Programming),一般称为面向切面编程,作为面向对象的一种补充,用于处理系统中分布于各个模块的横切关注点,比如事务管理,日志,缓存等等.AOP 实现的关键在于AOP框架自动创建的AOP代理,AOP代理主要分为静态代理和动态代理,静态代理的代表为AspectJ;而动态代理则以Spring AOP为代表。静态代理是编译期实现,动态代理是运行期实现,可想而知前者拥有更好的性能。
1. Aspect
Aspect 由 PointCut 和 Advice 组成。
- 它既包含了横切逻辑的定义,也包括了连接点的定义。
- Spring AOP 就是负责实施切面的框架,它将切面所定义的横切逻辑编织到切面所指定的连接点中。
- AOP 的工作重心在于如何将增强编织目标对象的连接点上, 这里包含两个工作:
- 如何通过 PointCut 和 Advice 定位到特定的 JoinPoint 上。
- 如何在 Advice 中编写切面代码。 可以简单地认为, 使用 @Aspect 注解的类就是切面
2. JoinPoint
JoinPoint ,切点,程序运行中的一些时间点, 例如: 一个方法的执行。 或者是一个异常的处理。 在 Spring AOP 中,JoinPoint 总是方法的执行点。
3. PointCut
PointCut ,匹配 JoinPoint 的谓词(a predicate that matches join points)。 简单来说,PointCut 是匹配 JoinPoint 的条件。 Advice 是和特定的 PointCut 关联的,并且在 PointCut 相匹配的 JoinPoint 中执行。即 Advice => PointCut => JoinPoint 。 在 Spring 中, 所有的方法都可以认为是 JoinPoint ,但是我们并不希望在所有的方法上都添加 Advice 。而 PointCut 的作用,就是提供一组 规则(使用 AspectJ PointCut expression language 来描述) 来匹配 JoinPoint ,给满足规则的 JoinPoint 添加 Advice 。
4. PointCut和JoinPoint区别
- 首先,Advice 通过 PointCut 查询需要被织入的 JoinPoint 。
- 然后,Advice 在查询到 JoinPoint 上执行逻辑。
5. Advice
Advice:通知
特定 JoinPoint 处的 Aspect 所采取的动作称为 Advice 。
Spring AOP 使用一个 Advice 作为拦截器,在 JoinPoint “周围”维护一系列的拦截器。
advice通知类型
- Before: Advice 在 JoinPoint 方法之前执行,并使用 @Before 注解标记进行配置。
- After Returning: Advice 在连接点方法正常执行后执行,并使用 @AfterReturning 注解标记进行配置。
- After Throwing: Advice 仅在 JoinPoint 方法通过抛出异常退出并使用 @AfterThrowing 注解标记配置时执行。
- After Finally: Advice 在连接点方法之后执行,无论方法退出是正常还是异常返回,并使用 @After 注解标记进行配置。
- Around:Advice 在连接点之前和之后执行,并使用 @Around 注解标记进行配置。
6. AOP的实现方式
-
静态代理
指使用 AOP 框架提供的命令进行编译,从而在编译阶段就可生成 AOP 代理类,因此也称为编译时增强。 -
动态代理
在运行时在内存中“临时”生成 AOP 动态代理类,因此也被称为运行时增强。
目前 Spring 中使用了两种动态代理库:
- JDK 动态代理
- CGLIB
原理区别:
java动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。
而cglib动态代理是利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。
- 如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP
- 如果目标对象实现了接口,可以强制使用CGLIB实现AOP
- 如果目标对象没有实现了接口,必须采用CGLIB库,spring会自动在JDK动态代理和CGLIB之间转换
JDK动态代理和CGLIB字节码生成的区别?
(1)JDK动态代理只能对实现了接口的类生成代理,而不能针对类
(2)CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法
TODO 此处有代码实现
5. Spring Transaction
1. 事务
事务就是对一系列的数据库操作(比如插入多条数据)进行统一的提交或回滚操作,如果插入成功,那么一起成功,如果中间有一条出现异常,那么回滚之前的所有操作。
2. 事务的ACID
-
原子性( Atomicity ) :一个事务(transaction)中的所有操作,或者全部完成,或者全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被恢复(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。即,事务不可分割、不可约简。
-
一致性 ( Consistency ) :在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设约束、触发器、级联回滚等。
-
隔离性 ( Isolation ) :数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括读未提交(Read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(Serializable)。
-
持久性 ( Durability ) :事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。
3. Spring 事务管理
- 编程式事务:编程式事务指的是通过编码方式实现事务
- 声明式事务:基于 AOP,将具体业务逻辑与事务处理解耦。声明式事务管理使业务代码逻辑不受污染, 因此在实际使用中声明式事务用的比较多
声明式事务有两种方式
1. 是在配置文件(xml)中做相关的事务规则声明
2. 是基于 @Transactional 注解的方式
- 默认配置下 Spring 只会回滚运行时、未检查异常(继承自 RuntimeException 的异常)或者 Error。
- @Transactional 注解只能应用到 public 方法才有效。
4. @Transactional 注解属性
属性 | 类型 | 说明 |
---|---|---|
value | String | 当在配置文件中有多个 TransactionManager , 可以用该属性指定选择哪个事务管理器。 |
propagation | enum: Propagation | 事务的传播行为,默认值为 REQUIRED。 |
isolation | enum: Isolation | 事务的隔离度,默认值采用 DEFAULT。 |
timeout | int (in seconds granularity) | 事务的超时时间,默认值为-1。如果超过该时间限制但事务还没有完成,则自动回滚事务。 |
read-only | boolean | 指定事务是否为只读事务,默认值为 false;为了忽略那些不需要事务的方法,比如读取数据,可以设置 read-only 为 true。 |
rollback-for | Class对象数组,必须继承自Throwable | 用于指定能够触发事务回滚的异常类型,如果有多个异常类型需要指定,各类型之间可以通过逗号分隔。 |
no-rollback- for | Class对象数组,必须继承自Throwable | 抛出 no-rollback-for 指定的异常类型,不回滚事务。 |
@Transactional 注解也可以添加到类级别上。当把@Transactional 注解放在类级别时,表示所有该类的公共方法都配置相同的事务属性信息。见清单 2,EmployeeService 的所有方法都支持事务并且是只读。当类级别配置了@Transactional,方法级别也配置了@Transactional,应用程序会以方法级别的事务属性信息来管理事务,换言之,方法级别的事务属性信息会覆盖类级别的相关配置信息。|
5. 事务隔离级别(5种)
- TransactionDefinition.ISOLATION_DEFAULT:这是默认值,表示使用底层数据库的默认隔离级别。对大部分数据库而言,通常这值就是-TransactionDefinition.ISOLATION_READ_COMMITTED。
- TransactionDefinition.ISOLATION_READ_UNCOMMITTED(读未提交):该隔离级别表示一个事务可以读取另一个事务修改但还没有提交的数据。该级别不能防止脏读,不可重复读和幻读,因此很少使用该隔离级别。比如PostgreSQL实际上并没有此级别。
- TransactionDefinition.ISOLATION_READ_COMMITTED(读已提交):该隔离级别表示一个事务只能读取另一个事务已经提交的数据。该级别可以防止脏读,这也是大多数情况下的推荐值。
- TransactionDefinition.ISOLATION_REPEATABLE_READ(可重复读):该隔离级别表示一个事务在整个过程中可以多次重复执行某个查询,并且每次返回的记录都相同。该级别可以防止脏读和不可重复读。
- TransactionDefinition.ISOLATION_SERIALIZABLE(可串行化):所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。
- mysql默认的事务处理级别是’REPEATABLE-READ’,也就是可重复读
- oracle数据库支持READ COMMITTED 和 SERIALIZABLE这两种事务隔离级别。默认系统事务隔离级别是READ COMMITTED,也就是读已提交
- SQL Server 默认系统事务隔离级别是read committed,也就是读已提交
6. 事务的传播行为(7种)
事务传播行为(propagation behavior)指的就是当一个事务方法被另一个事务方法调用时,这个事务方法应该如何进行
eg:
A事务方法调用B事务方法时,B是继续在调用者A的事务中运行呢,还是为自己开启一个新事务运行,这就是由B的事务传播行为决定
- 事务的七种传播行为
传播行为 | 含义 |
---|---|
PROPAGATION_REQUIRED | 表示当前放必须运行在事务中。如果存在一个事务,则支持当前事务;如果没有事务则开启一个新的事务 |
PROPAGATION_SUPPORTS | 表示当前事务不需要事务上下文。如果存在一个事务,支持当前事务;如果没有事务,则非事务的执行。 |
PROPAGATION_MANDATORY | 表示该事务必须在事务中运行。如果已经存在一个事务,支持当前事务,如果没有一个活动的事务,则抛出异常。 |
PROPAGATION_REQUIRES_NEW | 表示当前方法必须运行在自己的事务中。总是开启一个新的事务。如果一个事务已经存在,则将这个存在的事务挂起。 |
PROPAGATION_NOT_SUPPORTED | 表示该方法不应该运行在事务中。总是非事务地执行,并挂起任何存在的事务。 |
PROPAGATION_NEVER | 表示该方法不应该运行在事务中。总是非事务地执行,如果存在一个活动事务,则抛出异常 |
PROPAGATION_NESTED | 表示如果当前存在一个事务,那么该方法会在嵌套事务中运行。如果一个活动的事务存在,则运行在一个嵌套的事务中. 如果没有活动事务, 则按PROPAGATION_REQUIRED 属性执行 |
代码示例:
@Transactional(propagation = Propagation.REQUIRED)
methodA(){
doSomeThingA();
methodB();
doSomeThingB();
}
@Transactional(propagation = Propagation.NEWSTED)
methodB(){
……
}