1. 什么是控制反转IoC,什么是依赖注入,二者什么关系
什么是控制反转?控制反转:IoC(Inversion of Control)反转是什么呢? 反转的是两件事: 第一件事:我不在程序中采用硬编码的方式来new对象了。(new对象我不管了,new对象的权利交出去了。) 第二件事:我不在程序中采用硬编码的方式来维护对象的关系了。(对象之间关系的维护权,我也不管了,交出去了。)
控制反转:是一种编程思想。或者叫做一种新型的设计模式。由于出现的比较新,没有被纳入GoF23种设计模式范围内。
Spring框架
Spring框架实现了控制反转IoC这种思想Spring框架可以帮你new对象。Spring框架可以帮你维护对象和对象之间的关系。
Spring是一个实现了IoC思想的容器。
控制反转的实现方式有多种,其中比较重要的叫做:依赖注入(Dependency Injection,简称DI)。
控制反转是思想。依赖注入是这种思想的具体实现。
依赖注入DI,又包括常见的两种方式:第一种:set注入(执行set方法给属性赋值)第二种:构造方法注入(执行构造方法给属性赋值)
依赖注入 中 “依赖”是什么意思? “注入”是什么意思?依赖:A对象和B对象的关系。注入:是一种手段,通过这种手段,可以让A对象和B对象产生关系。依赖注入:对象A和对象B之间的关系,靠注入的手段来维护。而注入包括:set注入和构造注入。
2. 控制反转是解决什么问题的
使代码符合ocp原则和依赖倒置原则
控制反转是一种思想。
控制反转是为了降低程序耦合度,提高程序扩展力,达到OCP原则,达到DIP原则。
控制反转,反转的是什么?
将对象的创建权利交出去,交给第三方容器负责。
将对象和对象之间关系的维护权交出去,交给第三方容器负责。
控制反转这种思想如何实现呢?
DI(Dependency Injection):依赖注入
3. Spring是怎么实例化对象的
默认情况下Spring会通过反射机制,调用类的无参数构造方法来实例化对象。实现原理如下:Class clazz = Class.forName("com.powernode.spring6.bean.User");Object obj = clazz.newInstance();
4. 把创建好的对象存储到一个什么样的数据结构当中了呢?
Map<String,Object>
5. set注入原理
set注入的核心实现原理:通过反射机制调用set方法来给属性赋值,让两个对象之间产生关系。
6. Bean的实例化方式
第一种:通过构造方法实例化
第二种:通过简单工厂模式实例化
第三种:通过factory-bean实例化
第四种:通过FactoryBean接口实例化
具体看笔记
7. BeanFactory和FactoryBean的区别
BeanFactory
Spring IoC容器的顶级对象,BeanFactory被翻译为“Bean工厂”,在Spring的IoC容器中,“Bean工厂”负责创建Bean对象。
BeanFactory是工厂。
FactoryBean
FactoryBean:它是一个Bean,是一个能够辅助Spring实例化其它Bean对象的一个Bean。
在Spring中,Bean可以分为两类:
第一类:普通Bean
第二类:工厂Bean(记住:工厂Bean也是一种Bean,只不过这种Bean比较特殊,它可以辅助Spring实例化其它Bean对象。)
8. Bean的生命周期
* Bean的生命周期按照粗略的五步的话: * 第一步:实例化Bean(调用无参数构造方法。) * 第二步:给Bean属性赋值(调用set方法。) * 第三步:初始化Bean(会调用Bean的init方法。注意:这个init方法需要自己写,自己配。) * 第四步:使用Bean * 第五步:销毁Bean(会调用Bean的destroy方法。注意:这个destroy方法需要自己写,自己配。)
Bean生命周期七步:比五步添加的那两步在哪里?在初始化Bean的前和后。 第一步:实例化Bean 第二步:Bean属性赋值 第三步:执行“Bean后处理器”的before方法。 第四步:初始化Bean 第五步:执行“Bean后处理器”的after方法。 第六步:使用Bean 第七步:销毁Bean
<!--配置Bean后处理器。--> <!--注意:这个Bean后处理器将作用于整个配置文件中所有的bean。--> <bean class="com.powernode.spring6.bean.LogBeanPostProcessor"/>
一定要注意:在spring.xml文件中配置的Bean后处理器将作用于当前配置文件中所有的Bean。
Bean生命周期十步:比七步添加的那三步在哪里? 点位1:在“Bean后处理器”before方法之前 干了什么事儿? 检查Bean是否实现了Aware相关的接口,如果实现了接口则调用这些接口中的方法。BeanNameAware, BeanClassLoaderAware, BeanFactoryAware 然后调用这些方法的目的是为了给你传递一些数据,让你更加方便使用。 点位2:在“Bean后处理器”before方法之后 干了什么事儿? 检查Bean是否实现了InitializingBean接口,如果实现了,则调用接口中的方法。 点位3:使用Bean之后,或者说销毁Bean之前 干了什么事儿? 检查Bean是否实现了DisposableBean接口,如果实现了,则调用接口中的方法。 添加的这三个点位的特点:都是在检查你这个Bean是否实现了某些特定的接口,如果实现了这些接口,则Spring容器会调用这个接口中的方法。
9. Bean的循环依赖问题
Spring为什么可以解决set + singleton模式下循环依赖?
根本的原因在于:这种方式可以做到将“实例化Bean”和“给Bean属性赋值”这两个动作分开去完成。
实例化Bean的时候:调用无参数构造方法来完成。此时可以先不给属性赋值,可以提前将该Bean对象“曝光”给外界。
给Bean属性赋值的时候:调用setter方法来完成。
两个步骤是完全可以分离开去完成的,并且这两步不要求在同一个时间点上完成。
也就是说,Bean都是单例的,我们可以先把所有的单例Bean实例化出来,放到一个集合当中(我们可以称之为缓存),所有的单例Bean全部实例化完成之后,以后我们再慢慢的调用setter方法给属性赋值。这样就解决了循环依赖的问题。
详细可看之前的博客
10. 解决循环依赖的流程
-
实例化A对象,并创建
ObjectFactory
存入三级缓存。 -
A在初始化时需要B对象,开始B的创建逻辑。
-
B实例化完成,也创建
ObjectFactory
存入三级缓存。 -
B需要注入A,通过三级缓存获取
ObjectFactory
生成A对象,存入二级缓存。 -
B通过二级缓存获得A对象后,B创建成功,存入一级缓存。
-
A对象初始化时,由于B已创建完成,可以直接注入B,A创建成功存入一级缓存。
-
清除二级缓存中的临时对象A。
11. 构造方法出现了循环依赖怎么解决?
由于构造函数是bean生命周期中最先执行的,Spring框架无法解决构造方法的循环依赖问题。可以使用
@Lazy
懒加载注解,延迟bean的创建直到实际需要时。
12. 声明Bean的注解
负责声明Bean的注解,常见的包括四个:
-
@Component
-
@Controller 表示层
-
@Service 业务层
-
@Repository 持久层
13. 注入的注解
-
@Value
-
@Autowired
-
@Qualifier
-
@Resource
-
@Resource注解是JDK扩展包中的,也就是说属于JDK的一部分。所以该注解是标准注解,更加具有通用性。(JSR-250标准中制定的注解类型。JSR是Java规范提案。)
-
@Autowired注解是Spring框架自己的。
-
@Resource注解默认根据名称装配byName,未指定name时,使用属性名作为name。通过name找不到的话会自动启动通过类型byType装配。
-
@Autowired注解默认根据类型装配byType,如果想根据名称装配,需要配合@Qualifier注解一起用。
-
@Resource注解用在属性上、setter方法上。
-
@Autowired注解用在属性上、setter方法上、构造方法上、构造方法参数上。
14. AOP
AOP,即面向切面编程,在Spring中用于将那些与业务无关但对多个对象产生影响的公共行为和逻辑抽取出来,实现公共模块复用,降低耦合。常见的应用场景包括公共日志保存和事务处理。
Spring的AOP底层是基于动态代理技术来实现的,也就是说在程序运行的时候,会自动的基于动态代理技术为目标对象生成一个对应的代理对象。在代理对象当中就会对目标对象当中的原始方法进行功能的增强。
在我们的项目中我们自己写AOP的场景其实很少 , 但是我们使用的很多框架的功能底层都是AOP , 例如 : 权限认证、日志、事务处理等
接点描述的是位置
-
切点本质上是方法,真正织入切面的那个方法叫做切点
-
通知,又叫做增强,就是具体增强的代码
-
-
比如具体的事务代码,日志代码,安全代码,统计时长代码
-
通知描述的是代码
-
-
切面,切点+通知就是切面
-
织入:把通知应用到目标对象上的过程
-
代理对象:一个目标对象被织入通知后产生的新对象
-
目标对象:被织入通知的对象
15. 事务
-
事务的四个特性:
-
-
A 原子性:事务是最小的工作单元,不可再分。
-
C 一致性:事务要求要么同时成功,要么同时失败。事务前和事务后的总量不变。
-
I 隔离性:事务和事务之间因为有隔离性,才可以保证互不干扰。
-
D 持久性:持久性是事务结束的标志。
-
Spring中的事务是如何实现的?
Spring实现事务的本质是利用AOP完成的。它对方法前后进行拦截,在执行方法前开启事务,在执行完目标方法后根据执行情况提交或回滚事务。
16. 事务传播行为
什么是事务的传播行为?
在service类中有a()方法和b()方法,a()方法上有事务,b()方法上也有事务,当a()方法执行过程中调用了b()方法,事务是如何传递的?合并到一个事务里?还是开启一个新的事务?这就是事务传播行为。
一共有七种传播行为:
REQUIRED:支持当前事务,如果不存在就新建一个(默认)【没有就新建,有就加入】
SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行【有就加入,没有就不管了】
MANDATORY:必须运行在一个事务中,如果当前没有事务正在发生,将抛出一个异常【有就加入,没有就抛异常】
REQUIRES_NEW:开启一个新的事务,如果一个事务已经存在,则将这个存在的事务挂起【不管有没有,直接开启一个新事务,开启的新事务和之前的事务不存在嵌套关系,之前事务被挂起】
NOT_SUPPORTED:以非事务方式运行,如果有事务存在,挂起当前事务【不支持事务,存在就挂起】
NEVER:以非事务方式运行,如果有事务存在,抛出异常【不支持事务,存在就抛异常】
NESTED:如果当前正有一个事务在进行中,则该方法应当运行在一个嵌套式事务中。被嵌套的事务可以独立于外层事务进行提交或回滚。如果外层事务不存在,行为就像REQUIRED一样。【有事务的话,就在这个事务里再嵌套一个完全独立的事务,嵌套的事务可以独立的提交和回滚。没有事务就和REQUIRED一样.】
17. Spring容器中的Bean是线程安全的吗?
答:不是线程安全的;
Spring容器中的Bean默认是singleton单例的,所有线程都共享一个单例Bean,因此是存在资源竞争的;
如果Bean是有状态的,就需要开发人员自己来保证线程安全的保证,最简单的方法就是改变bean的作用域,把singleton改成prototype,这样每次请求bean对象就相当于是创建新的对象来保证线程的安全。有状态就是有数据存储功能
但在实际开发中,单例Bean一般都以无状态的方式来使用,即线程之间的操作不会对Bean的成员执行除查询以外的操作,所以这个单例Bean又可以说是线程安全的。比如:Controller、Service、Dao等这些Bean大多数是无状态的,我们不会对这些Bean中的属性进行修改操作,只需要关注方法本身即可;
在开发中不要再bean中声明任何有状态的实例变量或者类变量,如果必须有,可以使用ThreadLocal把变量变成线程私有。如果bean的实例变量或者类变量需要在多个线程之间共享,那么需要使用synchronized,lock等实现线程同步
18. 如何保证Bean的线程安全
1、把默认的singleton单例的Bean的改为prototype多例的Bean;
2、在Bean对象中避免定义可变的成员变量;
3、如果Bean对象中需要定义可变成员变量,将可变成员变量保存在ThreadLocal中;
19. 什么情况下会触发Spring事务回滚?
异常场景,Spring默认的事务回滚规则,默认是发生RuntimeException异常时触发事务回滚。如果想触发IOException异常事务回滚,需要指定回滚的规则;
@Transactional(rollbackFor = IOException.class)
20. 什么情况下Spring事务会失效呢?
1、同一个service中,方法a标注事务注解,则方法b没有标注事务注解;失效
2、同一个service中,没有标注事务注解的b方法调用标注了事务注解的a方法;失效
3、不同的Service中,没有标注事务注解的b方法调用标注了事务注解的a方法;生效
4、标注了事务注解的public方法、protected方法、默认无修饰方法、private方法,final方法,static方法;只有pubilc生效
5、多线程中的事务;失效,只会在本线程回滚
1、异常类型错误,默认RuntimeException
2、方法或类上没有标注@Transactional注解;
3、同一类中,方法内部自调用;
4、事务方法不是public的;
5、多线程调用;
6、异常被try ... catch
7、手动抛了别的异常
8、事务方法所在的Bean未被 Spring 容器管理
9、方法的事务传播类型不支持事务
10、表的数据库引擎不支持事务,比如MyISAM存储引擎不支持事务;