Spring
2020-09-04 10:21:14 0 举报
AI智能生成
Spring-知识体系梳理
作者其他创作
大纲/内容
组成
Core
AOP
ORM
DAO
Web
Spring EE
IOC
解决对象管理和对象依赖的问题
概念
IOC容器
可以理解为是一个「工厂」,我们把对象都交由这个「工厂」来管理,包括对象的创建和对象之间的依赖关系等等。等我们要用到对象的时候,就从这个「工厂」里边取出来
控制反转
本来是「由我们自己」new出来的对象,现在交给了IOC容器。把这个对象的「控制权」给「他方」了。「控制反转」更多的是一种思想或者说是设计模式,把原有由自己掌控的事交给「别人」来处理
正控:若要使用某个对象,需要自己去负责对象的创建
反控:若要使用某个对象,只需要从 Spring 容器中获取需要使用的对象,不关心对象的创建过程,也就是把创建对象的控制权反转给了Spring框架
依赖注入
指的是「控制反转」这个思想的实现方式:对象无需自行创建或管理它们的依赖关系,依赖关系将被「自动注入」到需要它们的对象当中去
一句话理解:本来我们的对象都是「由我们自己」new出来的,现在我们把这个对象的创建权限和对象之间的依赖关系交由「IOC容器」来管理,对象集中统一管理,便于修改和配置
优点
降低对象之间的耦合
我们不需要理解一个类的具体实现,只需要知道它有什么用就好了(直接向 IoC 容器拿)
实现
通过构造器或者属性(setting方法)的方式来注入对象的依赖
配置对象方式
注解
XML
JavaConfig
基于 Groovy DSL 配置
步骤
读取标注或者配置文件,看看JuiceMaker依赖的是哪个Source,拿到类名
使用反射的API,基于类名实例化对应的对象实例
将对象实例,通过构造函数或者 setter,传递给 JuiceMaker
主要基于工厂模式
BeanFactory
getBean
① 按照类型拿 bean:
bean = (Bean) factory.getBean(Bean.class);
要求在 Spring 中只配置了一个这种类型的实例,否则报错。(如果有多个那 Spring 就懵了,不知道该获取哪一个)
② 按照 bean 的名字拿 bean:
bean = (Bean) factory.getBean("beanName");
这种方法不太安全,IDE 不会检查其安全性(关联性)
③ 按照名字和类型拿 bean:(推荐)
bean = (Bean) factory.getBean("beanName", Bean.class);
isSingleton
判断是否单例,如果判断为真,其意思是该 Bean 在容器中是作为一个唯一单例存在的。而【isPrototype】则相反,如果判断为真,意思是当你从容器中获取 Bean,容器就为你生成一个新的实例
在默认情况下,【isSingleton】为 ture,而【isPrototype】为 false
ApplicationContext
Bean定义
Resource 定位
Spring IoC 容器先根据开发者的配置,进行资源的定位,在 Spring 的开发中,一般通过XML或者注解方式,定位的内容是由开发者提供的
BeanDefinition 的载入
这个时候只是将 Resource 定位到的信息,保存到 Bean 定义(BeanDefinition)中,此时并不会创建 Bean 的实例
BeanDefinition 的注册
这个过程就是将 BeanDefinition 的信息发布到 Spring IoC 容器中
AOP
解决非业务代码抽取的问题
Aspect Object Programming 「面向切面编程」
主要基于代理模式
静态代理
编译时增强,AOP 框架会在编译阶段生成 AOP 代理类,在程序运行前代理类的.class文件就已经存在了。常见的实现:JDK静态代理,AspectJ(Spring AOP 主要使用动态代理)
动态代理
运行时增强,它不修改代理类的字节码,而是在程序运行时,运用反射机制,在内存中临时为方法生成一个AOP对象,这个AOP对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法。常见的实现:JDK、CGLIB、Javassist(Hibernate中使用的动态代理)
JDK动态代理
代理对象是接口类型或是代理类,使用JDK代理
原理
基于反射,生成实现代理对象接口的匿名类,通过生成代理实例时传递的InvocationHandler处理程序实现方法增强
Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
ClassLoader loader: the class loader to define the proxy class,用于定义代理类的类加载器
Class<?>[] interfaces: the list of interfaces for the proxy class,代理类的接口列表
InvocationHandler h: to implement,由代理实例的调用处理程序实现的接口
特点
代理对象必须实现一个或多个接口
返回的代理实例是指定接口的代理类的实例,也就是必须以对象实现的接口接收实例,而不是代理类
CGLib动态代理
Code Generator Library
代理对象不是接口类型或不是代理类时,指定proxyTargetClass=true后,执行CGLIB代理
原理
基于操作字节码,通过加载代理对象的类字节码,为代理对象创建一个子类,并在子类中拦截父类方法并织入方法增强逻辑。底层是依靠ASM(开源的java字节码编辑类库)操作字节码实现的
org.springframework.cglib.proxy.Enhancer
特点
代理对象不能被final修饰,因为cglib代理的实现原理是操作字节码生成代理对象子类,而被final修饰的类不能被继承
因为是子类,所以不必像jdk代理一样必须以对象实现的接口接收实例,代理对象类同样可以接收代理实例
性能比较
JDK在创建代理对象时的性能要高于CGLib代理,而生成代理对象的运行性能却比CGLib的低
Spring默认的动态代理方式是JDK
常用
连接点(Join point)
能够被拦截的地方:Spring AOP是基于动态代理的,所以是方法拦截的。每个成员方法都可以称之为连接点
切点(Poincut)
具体定位的连接点:上面也说了,每个方法都可以称之为连接点,我们具体定位到某一个方法就成为切点
增强/通知(Advice)
表示添加到切点的一段逻辑代码,并定位连接点的方位信息
简单来说就定义了是干什么的,具体是在哪干
Spring AOP提供了5种Advice类型给我们:前置、后置、返回、异常、环绕给我们使用
织入(Weaving)
将增强/通知添加到目标类的具体连接点上的过程
引入/引介(Introduction)
引入/引介允许我们向现有的类添加新方法或属性。是一种特殊的增强
切面(Aspect)
切面由切点和增强/通知组成,它既包括了横切逻辑的定义、也包括了连接点的定义。
增强
前置增强(org.springframework.aop.BeforeAdvice):在目标方法执行之前进行增强;
后置增强(org.springframework.aop.AfterReturningAdvice):在目标方法执行之后进行增强;
环绕增强(org.aopalliance.intercept.MethodInterceptor):在目标方法执行前后都执行增强;
异常抛出增强(org.springframework.aop.ThrowsAdvice):在目标方法抛出异常后执行增强;
引介增强(org.springframework.aop.IntroductionInterceptor):为目标类添加新的方法和属性。
SpringBoot
概念
它使用 “习惯优于配置” (项目中存在大量的配置,此外还内置一个习惯性的配置,让你无须)的理念让你的项目快速运行起来。
它并不是什么新的框架,而是默认配置了很多框架的使用方式,就像 Maven 整合了所有的 jar 包一样,Spring Boot 整合了所有框架
优点
简单、快速、方便地搭建项目;对主流开发框架的无配置集成;极大提高了开发、部署效率
循环依赖
定义
循环依赖就是N个类中循环嵌套引用,如果在日常开发中我们用new 对象的方式发生这种循环依赖的话程序会在运行时一直循环调用,直至内存溢出报错
单例对象初始化
createBeanInstance:实例化,其实也就是调用对象的构造方法实例化对象
populateBean:填充属性,这一步主要是多bean的依赖属性进行填充
initializeBean:调用spring xml中的init 方法。
分类
构造器的循环依赖
这种依赖spring是处理不了的,直 接抛出BeanCurrentlylnCreationException异常
this .singletonsCurrentlylnCreation.add(beanName)将当前正要创建的bean 记录在缓存中
Spring 容器将每一个正在创建的bean 标识符放在一个“当前创建bean 池”中, bean 标识在创建过程中将一直保持在这个池中,因此如果在创建bean 过程中发现自己已经在“当前
创建bean 池” 里时,将抛出BeanCurrentlylnCreationException 异常表示循环依赖;而对于创建
完毕的bean 将从“ 当前创建bean 池”中清除掉
非单例循环依赖
无法处理
单例模式下的setter循环依赖
通过“三级缓存”处理循环依赖
三类缓存
singletonObjects:完成初始化的单例对象的cache(一级缓存)
earlySingletonObjects :完成实例化但是尚未初始化的,提前曝光的单例对象的Cache (二级缓存)
singletonFactories : 进入实例化阶段的单例对象工厂的cache (三级缓存)
解决循环依赖的过程
A 创建过程中需要 B,于是 A 将自己放到三级缓存里面 ,去实例化 B
B 实例化的时候发现需要 A,于是 B 先查一级缓存,没有,再查二级缓存,还是没有,再查三级缓存,找到了!
然后把三级缓存里面的这个 A 放到二级缓存里面,并删除三级缓存里面的 A
B 顺利初始化完毕,将自己放到一级缓存里面(此时B里面的A依然是创建中状态)
然后回来接着创建 A,此时 B 已经创建结束,直接从一级缓存里面拿到 B ,然后完成创建,并将自己放到一级缓存里面
如此一来便解决了循环依赖的问题
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return (singletonObject != NULL_OBJECT ? singletonObject : null);
}
父子容器
问题描述
如果使用传统的方式来开发Spring项目,要部署在Tomcat上面,一般会依赖Spring与Spring MVC,在Tomcat的web.xml中会配置一个加载service的配置文件,这个在Tomcat启动的时候会进行加载,会生成一个Spring的容器
默认情况下,Tomcat会在资源目录下加载配置servlet名称的另外一个xml配置文件,比如servlet名称为test,那么会加载test-servlet.xml配置文件,如果使用Spring MVC的话,会解析这个配置文件并且再生成一个Spring的容器,同时设置Tomcat启动时创建的那个容器为父容器
影响
一般在父容器中会加载service,dao之类的东西,不加载controller
在mvc容器中只加载controller
两个容器负责不同的bean,但是如果在父容器中配置了一些AOP想要处理controller的内容,因为容器的隔离,AOP就不会生效
解决
如果只需要处理service的bean,那么只在父容器的配置文件中操作
如果只处理controller的bean,那么在mvc的配置文件中修改
如果同时要处理service,还要处理controller,那么在两个配置文件中都进行修改
事务
定义
按照给定的事务规则来执行提交或者回滚操作
特性
原子性: 事务是最小的执行单位,不允许分割。事务的原子性确保动作要么全部完成,要么完全不起作用;
一致性: 执行事务前后,数据保持一致;
隔离性: 并发访问数据库时,一个用户的事物不被其他事物所干扰,各并发事务之间数据库是独立的;
持久性: 一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响。
并发事务会造成的问题
更新丢失(Lost Update)
事务A和事务B选择同一行,然后基于最初选定的值更新该行时,由于两个事务都不知道彼此的存在,就会发生丢失更新问题
脏读(Dirty Reads)
事务A读取了事务B更新的数据,然后B回滚操作,那么A读取到的数据是脏数据
不可重复读(Non-Repeatable Reads)
事务 A 多次读取同一数据,事务B在事务A多次读取的过程中,对数据作了更新并提交,导致事务A多次读取同一数据时,结果不一致
幻读(Phantom Reads)
幻读与不可重复读类似。它发生在一个事务A读取了几行数据,接着另一个并发事务B插入了一些数据时。在随后的查询中,事务A就会发现多了一些原本不存在的记录,就好像发生了幻觉一样,所以称为幻读
幻读和不可重复读的区别
不可重复读的重点是修改:在同一事务中,同样的条件,第一次读的数据和第二次读的数据不一样。(因为中间有其他事务提交了修改)
幻读的重点在于新增或者删除:在同一事务中,同样的条件,第一次和第二次读出来的记录数不一样(因为中间有其他事务提交了插入/删除)
@Transactional
name 当在配置文件中有多个 TransactionManager , 可以用该属性指定选择哪个事务管理器。
接口
PlatformTransactionManager: (平台)事务管理器
TransactionDefinition: 事务定义信息(事务隔离级别、传播行为、超时、只读、回滚规则)
TransactionStatus: 事务运行状态
propagation 事务的传播机制,默认值为 REQUIRED。
支持当前事务的情况
TransactionDefinition.PROPAGATION_REQUIRED: 如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务,Spring默认采用此种事务传播机制
TransactionDefinition.PROPAGATION_SUPPORTS: 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
TransactionDefinition.PROPAGATION_MANDATORY: 如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。(mandatory:强制性)
不支持当前事务的情况
TransactionDefinition.PROPAGATION_REQUIRES_NEW: 创建一个新的事务,如果当前存在事务,则把当前事务挂起。
TransactionDefinition.PROPAGATION_NOT_SUPPORTED: 以非事务方式运行,如果当前存在事务,则把当前事务挂起。
TransactionDefinition.PROPAGATION_NEVER: 以非事务方式运行,如果当前存在事务,则抛出异常。
其他情况
TransactionDefinition.PROPAGATION_NESTED: 如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED
isolation 事务的隔离度,默认值采用 DEFAULT。
定义了一个事务可能受其他并发事务影响的程度
分类
TransactionDefinition.ISOLATION_DEFAULT: 使用后端数据库默认的隔离级别,Mysql 默认采用的 REPEATABLE_READ隔离级别 Oracle 默认采用的 READ_COMMITTED隔离级别.
TransactionDefinition.ISOLATION_READ_UNCOMMITTED: 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读
TransactionDefinition.ISOLATION_READ_COMMITTED: 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生
TransactionDefinition.ISOLATION_REPEATABLE_READ: 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。
TransactionDefinition.ISOLATION_SERIALIZABLE: 最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。
timeout 事务的超时时间,默认值为-1。如果超过该时间限制但事务还没有完成,则自动回滚事务。
指一个事务所允许执行的最长时间,如果超过该时间限制但事务还没有完成,则自动回滚事务。在 TransactionDefinition 中以 int 的值来表示超时时间,其单位是秒
read-only 指定事务是否为只读事务,默认值为 false;为了忽略那些不需要事务的方法,比如读取数据,可以设置 read-only 为 true。
对事务性资源进行只读操作或者是读写操作。所谓事务性资源就是指那些被事务管理的资源,比如数据源、 JMS 资源,以及自定义的事务性资源等等。如果确定只对事务性资源进行只读操作,那么我们可以将事务标志为只读的,以提高事务处理的性能。在 TransactionDefinition 中以 boolean 类型来表示该事务是否只读
rollback-for 用于指定能够触发事务回滚的异常类型,如果有多个异常类型需要指定,各类型之间可以通过逗号分隔。
这些规则定义了哪些异常会导致事务回滚而哪些不会。默认情况下,事务只有遇到运行期异常时才会回滚,而在遇到检查型异常时不会回滚, 但是你可以声明事务在遇到特定的检查型异常时像遇到运行期异常那样回滚。同样,你还可以声明事务遇到特定的异常不回滚,即使这些异常是运行期异常
no-rollback- for 抛出 no-rollback-for 指定的异常类型,不回滚事务
事务失效
同类中非事务方法调事务方法
执行链:代理类noTransactionalSave()-->MethodInterceptor.intercept方法-->methodProxy.invoke方法--->目标类.noTransactionalSave()-->目标.save1()方法
save1()虽然有事务注解,但是因为其被非事务方法调用。在本次调用链中,没有走事务增强分支,所以导致了事务的失效
同类中非事务方法调用事务方法,因为执行链不会走事务增强,导致此次调用中事务方法的事务失效
解决办法:
1:出现此中业务场景时,可将非事务方法与事务方法抽象到不同的类中。事务方法所在类会创建事务代理,调用事务方法,走事务增强。
2:看MethodInterceptor.intercept()中开头有一个if(this.advised.exposeProxy)的判断,如果为真,就会把事务代理对象放到事务上下文中,我们可以在非事务方法中,使用AopContext.currentProxy()获取代理对象,强制save1方法走代理执行
同类中事务方法调用事务方法
同类中事务方法调用事务方法,虽然不会出现事务失效问题。但是却会出现事务传播失效问题
执行链:
代理方法.save1()--->MethodInterceptor.intercept方法--->methodProxy.invoke方法--->MethodInvocation.proceed执行增强器链-->Adivce.invoke增强方法--->目标方法.save1--->目标方法.save2
save1是事务方法,会走事务增强,但是save2的调用,是在save1方法增强调用链中,也就是说save2是依靠save1的事务。save2的事务配置,因为没有走Adivce(TransactionInterceptor).invoke增强方法,他的事务传播属性不会被解析
解决方法同上
调用同步事务方法
两个线程都阻塞到目标方法上,当第一个线程离开目标方法时,他的调用回到了TransactionInterceptor中,做提交事务动作。但是此时第二个线程进入目标方法去执行数据库查询操作,但第一个线程的提交事务动作可能为完成,此时数据库中并没有,所以第二个线程也会创建一个实例数据。这样就可能出现创建两条的情况
锁的范围没有包括到整个事务,把synchronized加到方法上出发点时好的。但是真实的调用确实从代理类开始的,生成的代理类中并没有加synchronized,结合事务的增强。导致了synchronized同步的失效
@Transcational注解和synchronized锁一起使用会导致同步失效。加锁的范围没有包括到整个事务
创造一个覆盖整个事务的调用。锁方法与事务方法分离到不同的类中,在锁方法中调用事务方法
Bean
生命周期
分支主题
Spring 只帮我们管理单例模式 Bean 的完整生命周期,对于 prototype 的 bean ,Spring 在创建好交给使用者之后则不会再管理后续的生命周期
详细步骤
1.首先容器启动后,会对scope为singleton且非懒加载的bean进行实例化,
2.按照Bean定义信息配置信息,注入所有的属性,
3.如果Bean实现了BeanNameAware接口,会回调该接口的setBeanName()方法,传入该Bean的id,此时该Bean就获得了自己在配置文件中的id,
4.如果Bean实现了BeanFactoryAware接口,会回调该接口的setBeanFactory()方法,传入该Bean的BeanFactory,这样该Bean就获得了自己所在的BeanFactory,
5.如果Bean实现了ApplicationContextAware接口,会回调该接口的setApplicationContext()方法,传入该Bean的ApplicationContext,这样该Bean就获得了自己所在的ApplicationContext,
6.如果有Bean实现了BeanPostProcessor接口,则会回调该接口的postProcessBeforeInitialzation()方法,
7.如果Bean实现了InitializingBean接口,则会回调该接口的afterPropertiesSet()方法,
8.如果Bean配置了init-method方法,则会执行init-method配置的方法,
9.如果有Bean实现了BeanPostProcessor接口,则会回调该接口的postProcessAfterInitialization()方法,
10.可以正式使用该Bean了,对于scope为singleton的Bean,Spring的ioc容器中会缓存一份该bean的实例,而对于scope为prototype的Bean,每次被调用都会new一个新的对象,其生命周期就交给调用方管理了,不再是Spring容器进行管理了
11.容器关闭后,如果Bean实现了DisposableBean接口,则会回调该接口的destroy()方法,
12.如果Bean配置了destroy-method方法,则会执行destroy-method配置的方法,至此,整个Bean的生命周期结束
作用域
singleton
当把一个Bean定义设置为singleton作用域是,Spring IoC容器中只会存在一个共享的Bean实例,并且所有对Bean的请求,只要id与该Bean定义相匹配,则只会返回该Bean的同一实例。singleton作用域是Spring中的缺省作用域
singleton只有一个实例,也即是单例模式
默认容器启动时,自动实例化所有singleton的bean并缓存在容器中
对bean提前的实例化操作,会及早发现一些潜在的配置的问题。
Bean以缓存的方式运行,当运行到需要使用该bean的时候,就不需要再去实例化了。加快了运行效率。
prototype
prototype作用域的Bean会导致在每次对该Bean请求(将其注入到另一个Bean中,或者以程序的方式调用容器的getBean()方法)时都会创建一个新的Bean实例。根据经验,对有状态的Bean应使用prototype作用域,而对无状态的Bean则应该使用singleton作用域。
对于具有prototype作用域的Bean,有一点很重要,即Spring不能对该Bean的整个生命周期负责。具有prototype作用域的Bean创建后交由调用者负责销毁对象回收资源。
prototype访问一次创建一个实例,相当于new
request
Request作用域针对的是每次的Http请求,Spring容器会根据相关的Bean的
定义来创建一个全新的Bean实例。而且该Bean只在当前request内是有效的。
session
对http session起作用,Spring容器会根据该Bean的定义来创建一个全新的Bean的实例。而且该Bean只在当前http session内是有效的。
global session
类似标准的http session作用域,不过仅仅在基于portlet的web应用当中才有意义。Portlet规范定义了全局的Session的概念。他被所有构成某个portlet外部应用中的各种不同的portlet所共享。在global session作用域中所定义的bean被限定于全局的portlet session的生命周期范围之内
设计模式
BeanFactory和ApplicationContext应用了工厂模式
Bean创建,提供了单例和原型等模式实现
AOP 领域则是使用了代理模式、装饰器模式、适配器模式等
各种事件监听器,是观察者模式的典型应用
类似 JdbcTemplate 等则是应用了模板模式
责任链模式,processor
0 条评论
下一页