- 1. Spring是什么?
- 2. AOP是什么?
- 3. IOC是什么?
- 4. spring循环依赖问题解决原理:
- 5. BeanFactory和ApplicationContext有什么区别?
- 6. Spring Bean的生命周期
- 7. Spring支持的几种Bean的作用域
- 8. Spring框架中的单例Bean是线程安全的吗?
- 9. Spring框架用到了哪些设计模式(考察源码阅读->用在哪个地方)
- 10. Spring事务的实现方式和原理以及隔离级别
- 11. Spring的事务传播机制
- 12. Spring事务什么时候会失效?
- 13. 什么是bean的自动装配,有哪些方式
- 14. Spring Boot、Spring MVC、Spring有什么区别?
- 15. 静态代理和动态代理
- 16. Spring的两种动态代理:JDK和CGLIB的区别与实现
1. Spring是什么?
Spring 框架指的都是 Spring Framework,它是很多模块的集合:核⼼容器、数据访问/集成,、Web、AOP(⾯向切⾯ 编程)、⼯具、消息和测试模块。
理解简记
1.轻量级开源框架
2.IOC -> 松耦合
3.AOP -> 分离业务逻辑和系统服务 如打印日志
4.容器 -> 管理Bean对象(配置和生命周期)
5.框架 -> 组合组件成应用
2. AOP是什么?
https://blog.csdn.net/mu_wind/article/details/102758005
OOP面向对象 AOP面向切面
AOP(Aspect-Oriented Programming:⾯向切⾯编程)能够将那些与业务⽆关,却为业务模块所共同调⽤的逻辑或责任(例如事务处理、⽇志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。
理解简记
1.将共用部分封装成切面
2.注入目标对象(具体业务)
3.实现部分功能增强
优点
1.代码复用
2.降低耦合
3. IOC是什么?
Spring IOC的初始化过程
理解简记
1.容器概念(存放bean)——
map,存放对象(xml里配的bean、@repository、@service、@controller、@component)。
项目启动后,容器
读取配置中的bean节点,根据全类名反射动态地创建对象
扫描注解,反射创建对象
2.控制反转(控制和使用的主动权)——
原先:A依赖B,则A在初始化或运行到某一点时,就必须先去主动创建B,或使用B
变为:引入IOC容器,A和B失去直接联系,A运行到需要B时,由IOC容器主动创建一个B对象,注入到A需要的位置
即:全部对象的控制权给了IOC容器,由IOC粘合所有对象发挥作用
3.依赖注入(IOC的实现方式,获得依赖对象的过程反转)——
控制反转后,在运行期间,获得依赖对象的过程从自身管理变成IOC容器主动注入
深入理解
源码阅读:https://www.cnblogs.com/ITtangtang/p/3978349.html
IOC的概念
注入方式
解决问题:解耦合。在面向对象时,越来越多的对象,它们之间的耦合、依赖关系越来越复杂,甚至经常出现对象之间多重依赖关系,那如果在编写代码时,每个对象都要自己去主动创建的话,需要耗费大量时间去理清大量的依赖关系,这对整个系统的分析和业务逻辑的实现增大了难度。因此,引入IOC容器,可以借助这个容器,使对象A和B失去直接联系,将控制权交给容器,由它创建对象和注入,发挥起粘合所有对象的功能。这样,我们可以专注于业务逻辑的编写,而不用去理清复杂的依赖关系了。
IOC实现原理
beanfactory机制
spring的bean是否线程安全 7回答了
循环依赖问题解决原理
4. spring循环依赖问题解决原理:
循环依赖:即循环引用,两个或以上的bean互相持有对方,形成闭环
https://blog.csdn.net/a745233700/article/details/110914620
视频P231
4.1 循环依赖问题在Spring中的三种情况
1.通过构造方法进行依赖注入时产生的循环依赖问题
2.通过setter方法进行依赖注入且是在多例(原型)模式下产生的循环依赖问题
3.通过setter方法进行依赖注入且是在单例模式下产生的循环依赖问题 -> Spring解决了
在Spring中,只有第3种方式的循环依赖问题被解决了,其他两种方式在遇到循环依赖问题时都会产生异常。
第1种构造方法注入的情况下,在new对象的时候就会堵塞住了,其实也就是”先有鸡还是先有蛋“的历史难题。
第2种setter方法(多例)的情况下,每一次getBean()时,都会产生一个新的Bean,如此反复下去就会有无穷无尽的Bean产生了,最终就会导致OOM(Out Of Memory,内存溢出)问题的出现。
// Spring解决的setter方法进行依赖注入的单例模式下的循环依赖
// A和B中各自都以对方为自己的全局属性
@Component
public class A {
private B b;
public void setB(B b) {
this.b = b;
}
}
@Component
public class B {
private A a;
public void setA(A a) {
this.a = a;
}
}
4.2 IOC流程简单解决循环依赖问题的简单示意图
public void test() throws Exception {
// 创建IoC容器,并进行初始化
String resource = "spring/spring-ioc-circular-dependency.xml";
ApplicationContext context = new ClassPathXmlApplicationContext(resource);
// 获取ClassA的实例(此时会发生循环依赖)
ClassA classA = (ClassA) context.getBean(ClassA.class);
}
// getBean()是核心方法
4.3 Spring(两个缓存)如何解决单例下setter方法依赖注入引起的循环依赖问题
通过两个缓存(二级缓存、三级缓存)来解决
Spring中有三个缓存,用于存储单例的Bean实例,这三个缓存是彼此互斥的,不会针对同一个Bean的实例同时存储。如果调用getBean,则需要从三个缓存中依次获取指定的Bean实例。 读取顺序依次是一级缓存 ==> 二级缓存 ==> 三级缓存。
注意理解:Spring bean的创建,其本质上还是一个对象的创建,一个完整的对象包含两部分:当前对象实例化和对象属性的实例化。在Spring中,对象的实例化是通过反射实现的(上图中第2步),而对象的属性则是在对象实例化之后通过setter+@Autowired依赖注入的(上图中第3步)。
一级缓存:Map
作用:用于存储单例模式下创建的Bean实例(已经创建完毕)。
该缓存是对外使用的,指的就是使用Spring框架的程序员。
存储数据:K:bean的名称,V:bean的实例对象(有代理对象则指的是代理对象,已经创建完毕)。
二级缓存:Map
作用:存储单例模式下创建的Bean实例(该Bean被提前暴露的引用,该Bean还在创建中)。
该缓存是对内使用的,指的就是Spring框架内部逻辑使用该缓存。
已经解决循环依赖问题
三级缓存:Map
作用:存储动态代理的配置信息(通过配置信息可以拿到动态代理对象),发现有循环依赖,提前进行这个对象的AOP动态代理,生成代理对象B,存在。
该缓存是对内使用的,指的就是Spring框架内部逻辑使用该缓存。
针对引用对象配置AOP的情况,则一级缓存中应该要注入代理对象才对,而不是原对象。
存储数据:K:bean的名称,V:ObjectFactory,该对象持有提前暴露的bean的引用。
仅解决循环依赖,二级缓存即可。但是对象如果要实现AOP,注入到其他bean中的还是原始的bean,不是最终(实现了AOP)的代理对象,所以要通过三级缓存的ObjectFactory,提前产生最终需要代理的对象。
构造函数相互注入造成的循环依赖可以使用@Lazy注解解决,实现延迟加载。@Lazy实现原理是,当实例化对象时,如果发现参数或者属性有@Lazy注解修饰,那么就不直接创建所依赖的对象了,而是使用动态代理创建一个代理类。(注意,创建代理对象时需要一个空参构造器)
比如,类A的创建:A a=new A(B),需要依赖对象B,发现构造函数的形参上有@Lazy注解,那么就不直接创建B了,而是使用动态代理创建了一个代理类B1,此时A跟B就不是相互依赖了,变成了A依赖一个代理类B1,B依赖A。但因为在注入依赖时,类B并没有完全的初始化完,实际上注入的是一个代理对象,只有当他首次被使用的时候才会被完全的初始化。
单例池中放了一个B的代理对象
理解简记
Spring bean的创建包含两部分:当前对象实例化和对象属性的实例化
一级缓存:保存创建完毕的bean实例,直接返回(单例池)
二级缓存:保存对象创建中提前暴露引用的Bean实例(尚未设置属性的) -> 已经解决循环依赖问题
三级缓存:如果引用对象配置了AOP,则最终单例池中应该注入动态代理对象,而不是原对象。而生成动态代理是要在4对象初始化完成之后才开始的。因此在2实例化A之后,3依赖注入之前,通过三级缓存保存动态代理的配置信息,提前产生最终(实现了AOP)的代理对象,存在三级缓存中,ObjectFactory。
通俗解释
A的创建:
单例池中没有A
new A 放于二级缓存
准备依赖注入A的成员变量B,发现单例池中没有B
准备创建B
new B 放于二级缓存
B对其成员变量A进行依赖注入,这时候A已经在二级缓存了,顺利注入
初始化B
B创建完成,放入单例池
A顺利注入B
初始化A
A创建完成,放入单例池
为什么要三级缓存?——为了对A进行AOP
当对A进行AOP后,我们希望最终放入单例池的不是原始A,而是A的代理对象
不存在循环依赖问题时,AOP发生于初始化完成后
为了使B注入到的是A的代理对象,我们应该提前AOP
设计三级缓存,在new A 后,将A动态代理的配置信息保存,根据配置信息提前生成实现了AOP的代理对象——ObjectFactory (同时remove二级缓存中A的引用)
这样B在依赖注入时,获取到的就是A的代理对象了
5. BeanFactory和ApplicationContext有什么区别?
理解简记
ApplicationContext是BeanFactory的子接口
1.扩展功能
1.支持国际化
2.资源访问:统一的资源文件访问方式,扩展了资源加载器(ResourceLoader)接口,可加载多个Resource
3.事件传播:通过ApplicationEvent和ApplicationListener这两个接口来提供,当ApplicationContext中发布一个事件的时,所有扩展了ApplicationListener的Bean都将会接受到这个事件,并进行相应的处理
4.同时加载多个配置
5.载入多个上下文,专注于特定层
2.启动时一次性创建
——优点:易于发现配置错误 缺点:占用内存空间,启动慢(但运行快)
BeanFactory:延迟加载——要使用时调用getBean()才实例化
ApplicationContext:启动时一次性创建
3.创建方式
BeanFactory:编程的方式创建
ApplicationContext:还可以声明的方式,如使用ContextLoader
4.后置处理器的注册方式(BeanPostProcessor、BeanFactoryPostProcessor)
BeanFactory:手动注册(写代码)
ApplicationContext:自动注册
6. Spring Bean的生命周期
理解简记
1.实例化对象
1.@ComponentScan扫描路径,找到类,创建Bean的定义
2.多个构造方法要推断用哪个
3.确定构造方法后,实例化一个对象
2.设置对象属性(IOC依赖注入)
1.set()方法注入,在bean配置文件中手动设置properties——麻烦
2.@Autowired注解的属性进行填充,完成依赖注入——其实通过了属性名、类型等6种方式,使用Seter()方法自动注入
3.回调Aware方法,检查Aware相关接口并设置相关依赖
Aware感知,让bean感知到自身相关属性。 BeanNameAware就是获取自身在Spring容器中的id属性
4.前置处理,BeanPostProcessor中的前置处理方法
5.调用初始化方法
1.检查Spring中InitializingBean,决定是否调用afterPropertiesSet方法
2.检查是否配置了自定义初始化方法init-method(如果Bean在Spring配置文件中配置了init-method属性会自动调用其配置的初始化方法)
6.后置处理,BeanPostProcessor中的后置处理方法,在这步进行AOP(看那些类要动态代理,完成创建)
7.创建的Bean是单例,则放入单例池——Spring中默认Bean是单例
前面准备阶段,接下来程序运行阶段
8.使用bean
9.Bean的销毁
1.Spring容器调用DisposableBean中的destory()方法
2.如果配置了自定义的destory()方法,则调用
7. Spring支持的几种Bean的作用域
singleton(单例) : 每个容器(Spring中可能会有多个父子容器)只有 一个 bean 实例,Spring 中的 bean 默认都是单例的,生命周期和IOC容器一致
prototype(原型) : 每次请求都会创建⼀个新的 bean 实例
request : 每⼀次HTTP请求都会产⽣⼀个新的单例 bean,该bean仅在当前HTTP request内有效
session : 每⼀次HTTP请求都会产⽣⼀个新的单例 bean,该bean仅在当前 HTTP session 内有效
global-session:全局session作⽤域,仅仅在基于portlet的web应⽤中才有意义,Spring5已经没有了。Portlet是能够⽣成语义代码(例如:HTML)⽚段的⼩型Java Web插件。它们基于 portlet容器,可以像servlet⼀样处理HTTP请求。但是,与 servlet 不同,每个 portlet 都有不同的会话
application:bean被定义在ServletContext容器(同一个Web应用里就一个,可以跨容器(Spring中的父子容器共享))的生命周期中的复用单例对象
websocket:bean被定义在websocket(一个协议,HTTP只能客户端向服务端发送请求,而它可以双向平等对话:服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息)的生命周期中复用的单例对象
8. Spring框架中的单例Bean是线程安全的吗?
分两个问题:
1.Spring创建单例的方式是线程安全的吗?
2.Spring使用创建好的单例对象是线程安全的吗?
1.答:spring的框架里,对象是交给spring容器创建的,spring的创建单例的方式既不是懒汉式也不是饿汉式,是单例注册表模式实现单例模式,这种创建单例模式的方式是线程安全的。
怎么判断使用已经创建好的单例对象是否线程安全:
1.看这个单例里有没有全局变量(全局变量就是成员变量,成员变量又分实例变量和静态变量)
2.如果有全局变量,看它是不是只可以读取而不能写入(有没有发布set方法)
如果满足上面两个条件,那么这个单例就是不安全的。
2.答:有可能出现线程不安全的情况。解决:要么把bean作用域从单例变成原型,要么使用ThreadLocal。
理解简记
1.不是线程安全的——如6所言,Spring的Bean默认是单例,即全局共享,且框架没对Bean进行多线程的封装处理
2.安全的核心问题:Bean是否有状态
1.有状态——具有数据存储功能,即定义了一些可变的非静态属性,在多线程操作同一对象,写操作时会存在线程安全问题
2.无状态——不保存数据,control、servcie、dao层本身并不线程安全,主要作用是调用其方法,而多线程在调用一个实例方法时会在自己线程的内存中复制变量,安全
3.解决办法
1.避免定义可变属性——少
2.如果非要定义可变属性,且只想在自己线程里使用,则在类中定义ThreadLocal成员变量,将需要的可变成员变量保存在ThreadLocal中,保证了线程间不互相影响——推荐
ThreadLocal了解一下,项目中用到了
3.如果Bean的实例变量或类变量要在多线程共享,只能使用synchronized、lock、CAS等方法实现线程同步
9. Spring框架用到了哪些设计模式(考察源码阅读->用在哪个地方)
![image.png](https://cdn.nlark.com/yuque/0/2021/png/22162061/1628168991942-e885de02-c5fa-46cf-8e83-15cb92406ddd.png#height=94&id=ZQRkq&margin=%5Bobject%20Object%5D&name=image.png&originHeight=125&originWidth=941&originalType=binary&ratio=1&size=106814&status=done&style=none&width=706) <br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/22162061/1628169018077-74681621-4689-4243-9b2a-f1f36009ad54.png#height=110&id=dJFN2&margin=%5Bobject%20Object%5D&name=image.png&originHeight=147&originWidth=934&originalType=binary&ratio=1&size=106264&status=done&style=none&width=701)<br />工厂方法:特点即,Bean对象实例获取时调用getBean()获得bean后,调用其方法getObject()获取其返回值<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/22162061/1628169330157-a647c9da-511b-499f-80fd-4187374c1e72.png#height=84&id=DpM1K&margin=%5Bobject%20Object%5D&name=image.png&originHeight=112&originWidth=929&originalType=binary&ratio=1&size=105626&status=done&style=none&width=697)<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/22162061/1628169354911-218071e3-f61d-46ba-9d92-b5b29e883bfa.png#height=85&id=nUIkC&margin=%5Bobject%20Object%5D&name=image.png&originHeight=113&originWidth=933&originalType=binary&ratio=1&size=88682&status=done&style=none&width=700)<br />适配器模式主要用在SpringMVC中的HandlerAdapter。<br />因为外部的Controller中的调用方法不同,有的是要去实现接口中的方法,有的是注解方式,要根据HandleMapping。<br />每种Controller都有相应的Adapter,实际执行时是使用HandlerAdapter去触发Controller中的执行方法。<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/22162061/1628169852923-48c5d31a-53d8-4fbc-ba81-b9789e4c2cbc.png#height=67&id=HMdL7&margin=%5Bobject%20Object%5D&name=image.png&originHeight=89&originWidth=928&originalType=binary&ratio=1&size=96405&status=done&style=none&width=696)<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/22162061/1628169883009-3a592a2a-2041-47a5-8015-bdb3b40b55c5.png#height=122&id=U09Dc&margin=%5Bobject%20Object%5D&name=image.png&originHeight=162&originWidth=880&originalType=binary&ratio=1&size=96843&status=done&style=none&width=660)<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/22162061/1628169894484-8b475851-3468-42f8-a6a2-91884230d027.png#height=62&id=rrLvX&margin=%5Bobject%20Object%5D&name=image.png&originHeight=83&originWidth=837&originalType=binary&ratio=1&size=50996&status=done&style=none&width=628)<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/22162061/1628169910263-16d5c08e-f526-48e4-928a-dd6d6814f541.png#height=88&id=QCVXb&margin=%5Bobject%20Object%5D&name=image.png&originHeight=117&originWidth=917&originalType=binary&ratio=1&size=65615&status=done&style=none&width=688)<br />策略模式,针对不同类型的文件,采用不同的Resouce接口去实现。<br />**设计模式本身的概念**<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/22162061/1628495402050-56d8e96f-c60e-4156-a99a-32b73de6633c.png#height=315&id=ZUYhS&margin=%5Bobject%20Object%5D&name=image.png&originHeight=419&originWidth=778&originalType=binary&ratio=1&size=78122&status=done&style=none&width=584)
10. Spring事务的实现方式和原理以及隔离级别
理解简记
编程式:手动写代码控制事务,比如begin一个事物,然后调一个rollback或commit去提交一个事物。实际底层还是调的底层数据库的对事物的支持
事务是数据库层面的
声明式:方法上加@Transactional注解
1.Spring扫描到后,基于这个类生成代理对象,作为bean放到IOC容器中(不是原生对象)
2.调用注解的方法时,代理逻辑先把事务自动提交设为false,执行业务逻辑方法,没异常就提交,异常则事务回滚
另:针对哪些异常回滚事务,可通过rollbackFor属性配置(注意异常要抛出,不能catch),属性默认是对RuntimeException和Error回滚
Spring默认隔离级别即数据库的隔离级别
Spring和数据库的隔离级别设置不同时:
1.以Spring为准
2.但若Spring设置的隔离级别数据库不支持,效果取决于数据库
11. Spring的事务传播机制
12. Spring事务什么时候会失效?
1的解决方法,不要用this调用方法,使用@Autowired注入的UserService对象去调用方法,AOP就不会失效了
3比如MySQL现在用innodb是支持事务的,以前的myisam就是不支持事务的
13. 什么是bean的自动装配,有哪些方式
14. Spring Boot、Spring MVC、Spring有什么区别?
15. 静态代理和动态代理
15.1 代理模式
为其他对象提供一个代理以控制对某个对象的访问。代理类主要负责为委托类(真实对象)预处理消息、过滤消息、传递消息给委托类,代理类不现实具体服务,而是利用委托类来完成服务,并将执行结果封装处理。
其实就是代理类为被代理类预处理消息、过滤消息并在此之后将消息转发给被代理类,之后还能进行消息的后置处理。代理类和被代理类通常会存在关联关系(即上面提到的持有的被带离对象的引用),代理类本身不实现服务,而是通过调用被代理类中的方法来提供服务。
代理对象 = 增强代码 + 目标对象(原对象)
理解简记
一个对象(客户端)不能或者不想直接引用另一个对象(目标对象),这时可以应用代理模式在这两者之间构建一个桥梁—代理对象。按照代理对象的创建时期不同,可以分为两种:静态代理:程序员事先写好代理对象类,在程序发布前就已经存在了;动态代理:应用程序发布后,通过动态创建代理对象。
15.2 静态代理
创建一个接口,然后创建被代理的类实现该接口并且实现该接口中的抽象方法。之后再创建一个代理类,同时使其也实现这个接口。在代理类中持有一个被代理对象的引用,而后在代理类方法中调用该对象的方法。
使用静态代理很容易就完成了对一个类的代理操作。但是静态代理的缺点也暴露了出来:由于代理只能为一个类服务,如果需要代理的类很多,那么就需要编写大量的代理类,比较繁琐。
15.3 动态代理
能否不写代理类,而直接得到代理Class对象,然后根据它创建代理实例(反射)?
动态代理:利用反射机制在运行时创建代理类。
Class对象包含了一个类的所有信息,比如构造器、方法、字段等。如果我们不写代理类,这些信息从哪获取呢?——代理类和目标类理应实现同一组接口。之所以实现相同接口,是为了尽可能保证代理对象的内部结构和目标对象一致,这样我们对代理对象的操作最终都可以转移到目标对象身上,代理对象只需专注于增强代码的编写。
动态代理具体步骤
1.通过实现 InvocationHandler 接口创建自己的调用处理器;
2.通过为 Proxy 类指定 ClassLoader 对象和一组 interface 来创建动态代理类;
3.通过反射机制获得动态代理类的构造函数,其唯一参数类型是调用处理器接口类型;
4.通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数被传入。
https://www.zhihu.com/question/20794107
16. Spring的两种动态代理:JDK和CGLIB的区别与实现
16.1 实现原理及特点
JDK动态代理:基于反射,生成实现代理对象接口的匿名类,通过生成代理实例时传递的InvocationHandler处理程序实现方法增强。
CGLIB动态代理:基于操作字节码,通过加载代理对象的类字节码,为代理对象创建一个子类,并在子类中拦截父类方法并织入方法增强逻辑。底层是依靠ASM(开源的java字节码编辑类库)操作字节码实现的。
JDK动态代理特点:
代理对象必须实现一个或多个接口
以接口形式接收代理实例,而不是代理类
CGLIB动态代理特点:
代理对象不能被final修饰
以类或接口形式接收代理实例
JDK与CGLIB动态代理的性能比较:
生成代理实例性能:JDK > CGLIB
代理实例运行性能:JDK > CGLIB
Spring默认的动态代理方式是JDK
16.2 Spring中动态代理选择逻辑
![image.png](https://cdn.nlark.com/yuque/0/2021/png/22162061/1629361567018-973652d8-f214-4f9f-951e-5d75e39b9cc2.png#height=420&id=AWIEG&margin=%5Bobject%20Object%5D&name=image.png&originHeight=560&originWidth=831&originalType=binary&ratio=1&size=106750&status=done&style=none&width=623)<br />1、如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP <br />2、如果目标对象**实现了接口**,可以强制使用CGLIB实现AOP <br />3、如果目标对象没有实现接口,必须采用CGLIB库,Spring会自动在JDK动态代理和CGLIB之间转换
如何强制使用CGLIB实现AOP?
(1)添加CGLIB库,SPRING_HOME/cglib/.jar
(2)在Spring*配置文件中加入