目录
什么是Spring?
Spring的优缺点?
优点:
缺点:
Spring IOC的理解
Spring AOP的理解
事务的边界为什么放在service层?
Spring Bean的生命周期
什么是单例池?作用是什么?
单例Bean的优势
BeanFactory的作用
BeanDefinition的作用
BeanFactory和ApplicationContext有什么区别?
配置Bean有哪几种方式?
Spring实例化Bean方式的几种方式
什么是Bean的自动装配?
自动装配需要注意
Spring自动装配Bean有哪些方式
通过XML文件
通过注解
Spring如何解决循环依赖问题?
什么是循环依赖
Spring是如何解决循环依赖的
是否只要是构造方法注入的Bean所产生的的循环依赖都是无法解决的?
Spring Bean是不是线程安全的?
如何解决单例bean线程安全问题?
Spring中的事务
事务的四大特征(ACID)
Spring支持的事务管理类型
实现声明式事务的两种方式
Spring事务的传播行为
什么是Spring?
Spring是轻量级的开源的框架,它是一个容器框架,用来封装javaBean对象,并维护对象之间的关系,是一个整合其他框架的框架,可以让我们更加快速,更加简洁的开发。
Spring的核心是IOC(控制反转)和AOP(面向切面)。
IOC是指将对象的创建权交给Spring容器。在Spring之前对象的创建都是需要程序员自己通过new关键字创建的,而使用Spring之后,对象的创建交由Spring创建。对象之间的依赖关系也是交由Spring处理,降低耦合度。
AOP面向切面编程,可以在不修改原有代码的基础上添加非业务功能,将主业务功能与非业务功能分离,可以让编程人员全身心投入到主业务代码中。
Spring的优缺点?
优点:
-
通过Spring提供的IOC容器,我们可以将对象之间的依赖关系交由Spring进行控制,降低耦合。
-
通过AOP功能,方便进行面向切面编程,在不修改代码的情况下可以对业务代码进行增强,减少代码,提高开发效率维护方便。
-
对主流框架提供了很好地支持,例如Mybatis等。
-
低入侵式设计。
-
提供了事务管理,只需要一个注解@Transaction。
-
方便程序的测试,spring实现测试,使我们结合Junit非常方便测试。
缺点:
使用大量的反射机制,占用内存,影响效率。
Spring IOC的理解
三方面:IOC是什么?(容器)、控制反转的理解、依赖注入
IOC容器:实际上就是一个Map,里面存的是各种对象(在xml里配置的bean节点,@Repository、@Service、@Controller、@Component等),在项目启动的时候会读取配置文件里面的bean节点,根据全限定类名使用反射创建对象放到map里面,扫描注解的类还是用反射创建对象并放到map中。
这个时候map里就有各种对象了,接下来我们在代码里需要用到里面的对象时,在通过DI注入(@Autowired、@Resource等注解,xml中bean节点里的ref属性,项目启动的时候会读取xml节点ref属性根据id注入)
控制反转:在没有引入控制反转之前,对象A依赖于对象B,对象A在初始化或者运行到某个点的时候就需要主动的创建对象B或者是使用已经创建的对象B,无论是已经创建的还是主动创建的对象B,控制权都在程序员自己手中(也就是说B对象什么时候创建,都有A对象决定)。
引入IOC之后,A对象和B对象都依赖于IOC容器,而A和B对象之间没有联系,当对象A运行到需要对象B的时候,IOC容器会主动创建(或者对象时单例的话就直接调用)对象B注入到对象A需要的地方。
通过前后的对比,不难看出来:对象A获得依赖对象B的过程,由主动变为了被动行为,控制权颠倒过来了,这就是“控制反转”这个名词的由来。
依赖注入:控制被反转之后,获得依赖对象的过程由自身管理变成了由IOC容器主动注入。依赖注入是实现IOC的方法,就是由IOC容器在运行期间,动态的将某种依赖关系注入到对象之中。
控制反转的作用主要是降低代码之间的耦合度,Spring IOC除了减低代码的耦合度外,还会管理对象的创建和依赖关系的维护。
Spring AOP的理解
AOP,面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。
能够将那些与业务代码无关,却又被业务模块所使用的功能(例如日志管理,事务管理等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的扩展和维护。
SpringAOP是基于动态代理的,如果要代理的对象实现了某个接口,就会使用jdk动态代理去创建代理对象;如果没有实现接口的对象,使用CGLIB动态代理生成一个被代理对象的子类作为代理。
事务的边界为什么放在service层?
事务通常可能会涉及到多张表的操作,也就意味着,在service层涉及到多个dao的操作,假设一个dao调用成功并且已经提交事务了,而另一个dao执行失败了,而我们的事务的边界在service层,在同一个地方里可以进行回滚;但是如果我们把事务放在各个dao层里面的话,第一个成功了,第二个即使失败了也无法控制第一个dao的回滚,所以我们的事务的边界要放在service层。
Spring Bean的生命周期
-
解析类得到BeanDefinition
-
如果有多个构造方法,则要推断构造方法
-
确定好构造方法之后,进行实例化得到对象
-
对对象中添加了@Autowired的属性进行属性填充
-
回调Aware方法,比如BeanNameAware,BeanFactoryAware
-
调用BeanPostProcessor的初始化前的方法
-
调用初始化方法
-
调用BeanPostProcessor的初始化后的方法,在这里会进行AOP
-
如果当前创建的Bean是单例的则会放入到单例池中
-
使用Bean
-
Spring容器关闭时调用DisposableBean中destory()方法
什么是单例池?作用是什么?
如果Bean对象是singleton单例Bean时,在Spring在创建Bean的时候会有将Bean对象存入一个Map集合中,在集合中以Bean的名称为key,在后续程序中有使用到该bean的时候直接从集合中获取,以达到单例bean的效果。
单例Bean的优势
-
减少了新生成实例的消耗新生成实例消耗包括两个方面:①Spring通过反射来创建Bean对象;②对象内存分配。
-
减少jvm垃圾回收。由于不会每次都重写创建Bean对象,自然就会减少垃圾回收。
-
可以快速获取到bean。
BeanFactory的作用
BeanFactory是Spring中非常核心的一个顶层接口;
是Bean的“工厂”、它的主要职责是生产Bean;
它实现了简单工厂的设计模式,通过调用getBean传入标识生产一个Bean;
它有非常多的实现类,每个工厂都有不同的职责功能,最强大的工厂是:DefaultListableBeanFactory ,Spring底层就是用该实现工厂生产Bean的
BeanDefinition的作用
它主要存储Bean的定义信息:决定Bean的生产方式。
<bean class="com.exp.User" id="user" scope="singleton" lazy="false" ></bean>
BeanFactory会根据Bean的这些信息生产Bean:比如实例化,可以通过class进行反射进而得到实例对象;比如lazy ,则不会在IOC加载是创建Bean
BeanFactory和ApplicationContext有什么区别?
BeanFactory和ApplicationContext是Spring的两大核心接口,都可以当做Spring的容器。它们之间的关系,ApplicationContext是BeanFactory的子接口。
二者区别:
作用
BeanFactory接口作用:是Spring里面最底层的接口,包含了各种Bean的定义,读取Bean配置文件,管理BeanDefinition加载,实例化,控制Bean的生命周期,维护Bean之间的依赖关系。BeanFactory简单粗暴,可以理解为就是一个HashMap,Key是BeanName,Value是Bean实例。通常只提供put和get功能,可以称之为“低级容器”。
配置Bean有哪几种方式?
-
xml:<bean class="com.exp.User" id="user">
-
注解@Component等,前提是需要配置扫描包<component-scan>,反射调用构造方法
-
javaConfig:@Bean 可以自己控制实例化过程
-
import
Spring实例化Bean方式的几种方式
-
构造方法(反射)
-
静态工厂方式;factory-method
-
实例工厂方式(@Bean);factory-bean+factory-method
-
FactoryBean方式
什么是Bean的自动装配?
Bean的配置是将Bean组装在一起,前提是需要知道Bean的依赖关系。
在Spring框架中,在配置文件中设定Bean的依赖关系是一个很好地机制,Spring容器能够自动配置相互合作的Bean,这意味着容器不需要手动配置,能通过Bean工厂处理Bean之间的协作。
自动装配需要注意
-
一定要声明set方法
-
仍然可以使用<constructor-arg>和<property>配置来定义依赖,这些配置将始终覆盖自动注入
-
不能自动装配简单数据类型的属性
Spring自动装配Bean有哪些方式
Spring中实现自动装配Bean主要有两种方式:通过xml文件、通过注解。
通过XML文件
xml文件中bean标签有一个指定自动装配类型的属性Autowired,Autowired有4中装配类型。
-
byName,根据名称自动装配。
-
byType,根据类型自动装配。
-
constructor,Spring容器会尝试找到那些类型与构造函数相匹配的Bean。
<bean id="student5" class="com.www.spring.modle.Student" autowire="byName"><property name="name" value="张其"/><property name="id" value="7"/><property name="num" value="1007"/><!--在这里可以不用写<property name="admin" ref="admin"/> 因为autowire="byName"就表示通过实体类中set方法的名称在容器中找到相同id值的bean然后赋值--></bean>
通过注解
主要是通过@Autowired等注解完成,前面提到了将类声明为bean的几个注解,通过@Component将类声明为Bean,通过@Value将某个常量值注入到变量中,通过@Autowired将Bean装配到属性中。
Spring如何解决循环依赖问题?
什么是循环依赖
循环依赖的定义其实就是,A依赖与B,B也依赖于A,或者是A自身依赖。
Spring是如何解决循环依赖的
Spring是通过三级缓存来解决循环依赖的。
在DefaultSingletonBeanRegistry类中提供了三个缓存,其实就是三个map集合。
一级缓存singletonObjects,用于保存实例化、注入、初始化完成的Bean对象;
二级缓存earlySingletonObject,用户保存实例化完成的Bean实例,提前暴露对象。
三级缓存singletonFactories,用于保存创建bean的工厂。
spring在创建对象A时先将A对象的工厂对象放入到三级缓存中去,然后实例化A,到属性注入时需要B对象,此时容器中没有B对象,又转而创建B对象,同样将B对象的工厂对象存入三级缓存中,实例化B对象,属性注入时需要A对象,从三级缓存中获取到A对象的工厂对象,对象B属性注入完成之后将其放入到二级缓存中,同时从三级缓存中删除B工厂,接下来初始化B对象,初始化完成后将B对象放入到一级缓存中,并且从二级缓存中删除B,B对象创建结束;返回创建A,完成属性b的注入,后续步骤与B对象属性注入完成后的步骤相同。
创建对象和属性注入时都会执行getBean()方法,来获取对象,在获取对象时最重要的是执行getSingleton方法:
由源码可知,在该方法中,先从一级缓存中获取对象,如果没有就去二级缓存中找,二级没有就去三级中找,三级中如果找到,就将该对象放入到二级缓存中,并且删除三级缓存,如果三级还没有就表明该对象还没有被创建过。
是否只要是构造方法注入的Bean所产生的的循环依赖都是无法解决的?
不是的,两个Bean都是通过构造方法注入的是无法解决的,但是一个是属性注入一个是构造注入时就可能会解决,如果先创建的Bean是构造方法注入,那么开始就无法提供半成品Bean,这时是无法解决的;但是如果先创建的是通过属性注入方式的Bean,那么是可以解决的。
依赖注入方式 | 是否可以解决 |
---|---|
两个都是构造方法注入 | 否 |
两个都是属性注入 | 是 |
A中的注入方式是构造方法注入,B中注入方式是属性注入 | 否 |
A中的注入方式是属性注入,B中注入方式是构造方法注入 | 是 |
Spring Bean是不是线程安全的?
这个要看Bean的作用域scope,如果是原型Bean的话,每次多会创建一个新的对象,线程之间不会存在共享,自然不存在线程安全问题;如果是单例Bean,全局只有一个对象,存在资源竞争。
Bean又分为:①有状态Bean(有数据存储功能,包含成员变量),会对这个Bean的成员变量进行写操作,线程不安全;②无状态Bean(不会保存数据,例如controller,service等)线程安全的。
如何解决单例bean线程安全问题?
-
Bean找那个尽量不定义成员变量
-
将Bean定义为多例Bean
Spring中的事务
事务的四大特征(ACID)
-
原子性:原子性是指事务包含的所有操作要么全部执行,要么全部不执行。
-
一致性:是指事务必须使得数据库从一个一致性状态转换到另一个一致性状态,也就是说一个事务执行之前和之后都必须处于一致性状态。例如转账:AB两个用户一共有10000元,无论两个人如何转账,始终都是10000元不变
-
隔离性:是指当多个用户并发访问数据库时,比如操作同一张表,数据库为每个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。
-
持久性:是指事务一旦被提交,数据将永久性保存。
Spring支持的事务管理类型
Spring支持两种事务类型:
编程式事务管理:这意味着将通过编程的方式管理事务,会带来极大的灵活,但是难维护。
声明式事务管理:意味着你可以将业务代码和事务管理分离,只需要通过注解和XML配置来管理事务。
实现声明式事务的两种方式
-
基于<tx>和<aop>命名空间的声明式事务管理:其最大特点是与Spring结合紧密,可以充分利用切点表达式的强大支持,使得事务管理更加灵活。
-
基于@Transactional的全注解方式:将声明式事务管理简化到了极致。开发人员只需要在配置文件中加入一行启用相关后处理Bean的配置,然后在需要实施事务管理的方法或者类上使用@Transaction指定事务规则可实现事务管理,而且功能也不比其他方式逊色。
Spring事务的传播行为
事务的传播特性指的是当一个事务方法被另一个事务方法调用时,这个方法应该如何进行。
PROPAGATION_REQUIRED:如果当前没有事务
事务传播行为类型 | 外部不存在事务 | 外部存在事务 | 使用方式 |
---|---|---|---|
REQUIED | 开启新的事务 | 融合到外部事务中 | @Transaction(propagation=Propagation.REQUIRED) 适合增删改查 |
SUPPORTS | 不开启新的事务 | 融合到外部事务中 | @Transaction(propagation=Propagation.SUPPORTS) 适合查询 |
REQUIEDS_NEW | 开启新的事务 | 不用外部事物,创建新的事务 | @Transaction(propagation=Propagation.REQUIEDS_NEW) 适合内部事务和外部事务不存在业务关系情况,如日志 |
NOT_SUPPORTED | 不开启新的事务 | 不用外部事物 | @Transaction(propagation=Propagation.NOT_SUPPORTED) 不常用 |
NEVER | 不开启新的事务 | 抛异常 | @Transaction(propagation=Propagation.NEVER) 不常用 |
MANDATORY | 抛出异常 | 融合到外部事务中 | @Transaction(propagation=Propagation.MANDATORY) 不常用 |
NESTED | 开启新的事务 | 融合到外部事务中,SavePoint机制,外层印象内层,内层不会影响外层 | @Transaction(propagation=Propagation.NESTED) 不常用 |