一、ioc容器比较:beanFactory,applicationContext
1、BeanFactory:基础类型的Ioc容器,采用懒加载(lazy-load),对象只有在用的时候才初始化和依赖注入。所以启动容器比较快。
BeanFactory在初始化容器时,并未实例化Bean,直到第一次访问某个Bean 时才实例目标Bean;
2、ApplicationContext:较高级类型的IOC容器,基于BeanFactory,在启动容器的时候就会初始化并注入依赖。并且还提供其他高级特性: 比如事件发布、国际化信息支持等。
ApplicationContext 则在初始化应用上下文时就实例化所有单实例的Bean
二、ioc的原理
Spring IoC的实现过程主要分为两部分,IoC容器初始化和依赖注入。
Spring IoC 的实现原理,就是工厂模式加反射机制。
1、概括的描述一下 Spring 背后的操作,Spring bean实例化的过程
加载 applicationgContext.xml,将 xml 中定义的 bean 解析成 Spring 内部的 BeanDefinition,
并以 beanName为 key,BeanDefinition为 value 存储到 DefaultListableBeanFactory 中的 beanDefinitionMap (其实就是一个 ConcurrentHashMap) 中,
同时将 beanName 存入 beanDefinitionNames(List 类型) 中,然后遍历 beanDefinitionNames 中的 beanName,进行 bean 的实例化并填充属性,在实例化的过程中,如果有依赖没有被实例化将先实例化其依赖,然后实例化本身,实例化完成后将实例存入单例 bean 的缓存中,
当调用 getBean 方法时,到单例 bean 的缓存中查找,如果找到并经过转换后返回这个实例,之后就可以直接使用了。
补充:
beanDefinitionMap 遍历,执行BeanFactoryPostProfessor这个bean工厂后置处理器逻辑(平时定义的占位符信息,也可以自定义后置工厂来对我们定义好的bean元数据进行获取或者修改),后置处理器执行完得到实例化对象
Spring一般情况下会通过反射选择合适的构造器来把对象实例化
spring 使用BeanDefinition来装载我们给bean定义的元数据
2、那bean怎样实例化呢?
当然是利用JDK的反射机制啦,由于前面的BeanDefinition对象保存了每个bean的详细信息。通过反射很容易实例化出一个对象。
3、由于spring默认都是创建单例对象,
所以实例化一个对象后不会马上返回给你,而是放到一个Map里面给程序重复使用。如果创建的对象不是单例,则不会放到Map里面保存,而是直接返回给你。
过程图:juejin.im/post/1
4、总结什么是反射
参考,
-
原理:java的反射机制
-
方法反射示例:深入分析Java方法反射的实现原理
-
反射的关系结构:Java Reflection
-
反射步骤:JAVA反射机制作用是什么
要想解剖一个类,必须先要获取到该类的字节码文件对象。而解剖使用的就是Class类中的方法.所以先要获取到每一个字节码文件对应的Class类型的对象.
Class对象的由来是将class文件读入内存,并为之创建一个Class对象。
反射的原理在于class对象。
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有信息;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
Java 反射的原理基于 JVM 在加载类时会为每个类创建一个唯一的 Class 对象,该对象包含了类的所有信息。通过 Class 对象以及相关的反射类(如 Constructor、Field、Method),可以在运行时动态地获取类的信息、创建对象、调用方法和访问字段。
反射的原理可以概括为:******.class** 文件是根源,**Class** 对象是入口,JVM 是后台管理者。具体可以描述为:****Java 代码 (**.java**) 经过编译后,会生成字节码文件 (**.class**),jVM 在加载类时会将class文件读入内存,并为之创建一个Class对象。该对象包含了类的所有信息。通过 Class 对象以及相关的反射类(如 Constructor、Field、Method),可以在运行时动态地获取类的信息、创建对象、调用方法和访问字段。
使用反射机制的步骤:
-
导入java.lang.relfect 包
-
遵循三个步骤
-
第一步是获得你想操作的类的 java.lang.Class 对象
-
第二步是调用诸如 getDeclaredMethods 的方法
-
第三步使用 反射API 来操作这些信息
5、注解的原理
参考:Java及spring 注解(反射原理) , Java注解基本原理
spring 的注解很强大,其应用机制主要就是反射原理 通过获取对象做判断逻辑;
使用注解最主要的部分在于对注解的处理,那么就会涉及到注解处理器。
从原理上讲,注解处理器就是通过反射机制获取被检查方法上的注解信息,然后根据注解元素的值进行特定的处理。
6、总结注解原理
1、什么是注解
注解的本质就是一个继承 Annotation 接口的接口
@Target(ElementType.METHOD)@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
其本质是:
public interface Override extends Annotation{}
查看 Annotation 类的定义:
public interface Annotation {
Class<? extends Annotation> annotationType();
boolean equals(Object obj);
int hashCode();
String toString();
}
所有注解都隐式实现这个接口:
// @Override 实际等效于
public interface Override extends Annotation {}
一般常用的注解可以分为三类:
-
一类是Java自带的标准注解,包括@Override(标明重写某个方法)、@Deprecated(标明某个类或方法过时)和@SuppressWarnings(标明要忽略的警告),使用这些注解后编译器就会进行检查。
-
一类为元注解,元注解是用于定义注解的注解,包括@Retention(标明注解被保留的阶段)、@Target(标明注解使用的范围)、@Inherited(标明注解可继承)、@Documented(标明是否生成javadoc文档)
-
一类为自定义注解,可以根据自己的需求定义注解
2、注解处理器
这个是注解使用的核心了
从getAnnotation进去可以看到java.lang.class实现了AnnotatedElement方法
MyAnTargetType t = AnnotationTest.class.getAnnotation(MyAnTargetType.class);
public final class Class<T> implements java.io.Serializable, GenericDeclaration, Type, AnnotatedElement
java.lang.reflect.AnnotatedElement 接口是所有程序元素(Class、Method和Constructor)的父接口,所以程序通过反射获取了某个类的AnnotatedElement对象之后,程序就可以调用该对象的如下四个个方法来访问Annotation信息:
- 方法1: T getAnnotation(Class annotationClass): 返回该程序元素上存在的、指定类型的注解,如果该类型注解不存在,则返回null。
- 方法2:Annotation[] getAnnotations():返回该程序元素上存在的所有注解。
- 方法3:boolean is AnnotationPresent(Class<?extends Annotation> annotationClass):判断该程序元素上是否包含指定类型的注解,存在则返回true,否则返回false.
- 方法4:Annotation[] getDeclaredAnnotations():返回直接存在于此元素上的所有注释。与此接口中的其他方法不同,该方法将忽略继承的注释。(如果没有注释直接存在于此元素上,则返回长度为零的一个数组。)该方法的调用者可以随意修改返回的数组;这不会对其他调用者返回的数组产生任何影响
3、注解的原理
注解本质是一个继承了Annotation 的特殊接口,其具体实现类是Java 运行时生成的动态代理类。而我们通过反射获取注解时,返回的是Java 运行时生成的动态代理对象$Proxy1。通过代理对象调用自定义注解(接口)的方法,会最终调用AnnotationInvocationHandler 的invoke 方法。该方法会从memberValues 这个Map 中索引出对应的值。而memberValues 的来源是Java 常量池。
----------------
自己总结:通过 getAnnotation 方法可获取一个注解类实例,其是通过动态代理生成的代理类
注解本质上是继承了 Annotation 接口的接口,而当你通过反射,也就是我们这里的 getAnnotation 方法去获取一个注解类实例的时候,其实 JDK 是通过动态代理机制生成一个实现我们注解(接口)的代理类。那么这样,一个注解的实例就创建出来了,它本质上就是一个代理类,你应当去理解好 AnnotationInvocationHandler 中 invoke 方法的实现逻辑,这是核心。一句话概括就是,通过方法名返回注解属性值。
三、aop原理就是动态代理
动态代理的原理,这里再简单的总结下:首先通过newProxyInstance方法获取代理类实例,而后我们便可以通过这个代理类实例调用代理类的方法,对代理类的方法的调用实际上都会调用中介类(调用处理器)的invoke方法,在invoke方法中我们调用委托类的相应方法,并且可以添加自己的处理逻辑。
Spring AOP 原理 : juejin.im/post/684490…
静态代理:使用AOP框架提供的命令进行编译,从而在编译阶段就可以生成AOP代理类,因此也称为编译时增强。Aspectj为代表
动态代理 - 在运行时在内存中“临时”生成 AOP 动态代理类,因此也被称为运行时增强。目前 Spring 中使用了两种动态代理库:
- JDK 动态代理
- CGLIB
JDK动态代理通过反射来接收被代理的类,并且要求被代理的类必须实现一个接口。JDK动态代理的核心是
InvocationHandler接口和Proxy类。
如果目标类没有实现接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。 CGLIB,是一个代码生成的类库,可以在运行时动态的生成某个类(目标类)的子类,(利用ASM开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。基于ASM的字节码框架,它允许我们在运行时对字节码进行修改和动态生成). 注意,CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。
- 两种代理的比较:
jdk:动态代理必须实现InvocationHandler接口,通过反射代理方法,比较消耗系统性能, 但可以减少代理类的数量,使用更灵活。
cglib:代理无需实现接口,通过生成类字节码实现代理,比反射稍快,不存在性能问题, 但cglib会继承目标对象,需要重写方法,所以目标对象不能为final类。
使用CGLib实现动态代理,CGLib底层采用ASM字节码生成框架,使用字节码技术生成代理类, 在jdk6之前比使用Java反射效率要高。唯一需要注意的是,CGLib不能对声明为final的方法进行代理, 因为CGLib原理是动态生成被代理类的子类。 2、在jdk6、jdk7、jdk8逐步对JDK动态代理优化之后,在调用次数较少的情况下,JDK代理效率高于CGLIB代理效率,只有当进行大量调用的时候,jdk6和jdk7比CGLIB代理效率低一点,但是到jdk8的时候,jdk代理效率高于CGLIB代理,总之,每一次jdk版本升级,jdk代理效率都得到提升,而CGLIB代理消息确有点跟不上步伐。
1、Spring 采用jdk动态代理模式来实现Aop机制。
Spring AOP采用动态代理过程:
-
1.将切面使用动态代理的方式动态织入到目标对象,形成一个代理对象。
-
2.目标对象如果没有实现代理接口,那么spring会采用CGLib来生成代理对象,该代理对象是目标对象的子类。
-
3.目标对象如果是final类,也没有实现接口,就不能运用AOP
2、简单说说 AOP 的设计:
- 每个 Bean 都会被 JDK 或者 Cglib 代理。取决于是否有接口。
- 每个 Bean 会有多个“方法拦截器”。注意:拦截器分为两层,外层由 Spring 内核控制流程,内层拦截器是用户设置,也就是 AOP。
- 当代理方法被调用时,先经过外层拦截器,外层拦截器根据方法的各种信息判断该方法应该执行哪些“内层拦截器”。内层拦截器的设计就是职责连的设计。
四、bean的初始化或者生命周期
-
1.Spring对Bean进行实例化,默认bean是单例。
-
2.Spring 将值和Bean的引用注入到Bean对应的属性中。将对象的属性注入
-
3.如果Bean实现了BeanNameAware接口,Spring将Bean的ID传递给setBeanName()接口方法。
-
4.如果Bean实现了BeanFactoryAware接口,Spring将调用setBeanFactory()接口方法,将BeanFactory容器实例传入。
-
5.如果Bean实现了ApplicationContextAware接口,Spring将调用setApplicationContext()接口方法,将上下文的引用传入。
-
6.如果Bean实现了BeanPostProcessor接口,Spring将调用它们的postProcessBeforeInitialization()接口方法。
-
7.如果Bean实现了InitializingBean接口,Spring将调用他们的afterPropertiesSet()接口方法,类似地,如果Bean使用init-method声明了初始化方法,该方法也会被调用。
-
8.如果Bean实现了BeanPostProcessor接口,Spring将调用他们的postPoressAfterInitialization()方法。
-
9.此时此刻,Bean准备就绪,可以被应用程序使用了,它们将一直驻留在应用上下文中,直到该应用上下文被销毁。
-
10.如果Bean实现了DisposableBean接口,Spring将调用他的destory()接口方法,同样,如果Bean使用destory-method声明了销毁方法,该方法也会被调用。
1、Spring如何解决循环依赖?
是在Spring的生命周期里面,如上:对象属性的注入是在实例化之后
过程:首先A对象实例化,然后对属性进行注入,发现依赖B对象;B对象还没创建出来,所以转头去实例化B对象,B对象实例化之后发现需要依赖A对象,那么A对象已经实例化了,所以b对象最终能完成创建。b对象返回到a对象的属性注入的方法上,a对象最终完成创建。
原理:其实用到了三级缓存,其实就是三个map
名称 描述
- singletonObjects 一级缓存,存放完整的 Bean。日常实际获取bean的地方。
- earlySingletonObjects 二级缓存,已经实例化的Bean但还没进行属性注入由三级缓存放进来,Bean 是不完整的,未完成属性注入和执行 init 方法。
- singletonFactories 三级缓存,value是 Bean 工厂,主要是生产 Bean,存放到二级缓存中。
返回到上述过程中,a对象实例化之后属性注入之前,其实会把a对象放到三级缓存中,key是beanname,value是objectFactory;等a属性注入时发现b,又实例化b时,b属性注入需要获取a对象,这里就是从三级缓存中拿出objectfactory,从objectFactory得到对应的bean(就是对象a),然后把三级缓存的a记录干掉,放到二级缓存中,显然二级缓存存储的key是beanName,value就是bean(这里的bean还没做完属性注入的工作);等完全初始化之后,就会把二级缓存给remove掉,塞到一级缓存中。我们。getBean的时候,实际上拿到的是一级缓存。
重点看的部分:
答:Spring通过三级缓存解决了循环依赖,其中一级缓存为单例池(`singletonObjects`),
二级缓存为早期曝光对象`earlySingletonObjects`,
三级缓存为早期曝光对象工厂(`singletonFactories`)。
当A、B两个类发生循环引用时,
在A完成实例化后生成原始对象,就使用实例化后的对象去创建一个对象工厂(将原始对象包装成ObjectFactory),
并添加到三级缓存中,
如果A被AOP代理,那么通过这个工厂获取到的就是A的代理对象,
如果A没有被AOP代理,那么这个工厂获取到的就是A实例化的对象。
当A进行属性注入时,会去创建B,同时B又依赖了A,所以创建B的同时又会去调用getBean(a)来获取需要的依赖,
此时的getBean(a)会从缓存中获取:第一步,先获取到三级缓存中的工厂;
第二步,调用对象工工厂的getObject方法来获取到对应的对象并将a代理放到二级缓存中,得到这个对象后将其注入到B中。
紧接着B会走完它的生命周期流程,包括初始化、后置处理器等。
当B创建完后,会将B再注入到A中,此时A再完成它的整个生命周期。
至此,循环依赖结束!
面试官:”为什么要使用三级缓存呢?二级缓存能解决循环依赖吗?“
答:如果要使用二级缓存解决循环依赖,意味着所有Bean在实例化后就要完成AOP代理,
这样违背了Spring设计的原则,
Spring在设计之初就是(初始化之后后置处理器完成前)通过后置处理器来在Bean生命周期的最后一步来完成AOP代理,
而不是在实例化后就立马进行AOP代理。
提前生成代理很可能引发一些问题:代理一致性被破环,提前创建的代理可能缺少必要的依赖
2、那为什么是三级缓存?
三级缓存中,key是beanname,value是objectFactory;对象上单例的,可能a对象依赖的b对象是有aop的(b对象需要代理)。假如没有三级缓存只有二级缓存(value存对象,而不是工厂对象)那如果有aop的情况下,岂不是在存入二级缓存之前都需要先去做aop代理,这不合适。
这里需要考虑代理的情况,比如a对象时一个被aop增量的对象,b依赖a时得到的a肯定是代理对象,所以三级缓存的value是objectFactory,可以从里面拿到代理对象;而二级缓存存在的必要就是为了性能,从三级缓存的工厂里创建出对象,再扔到二级缓存(这样就不用每次都要从工厂里拿)
在 Spring 容器初始化过程中,即使 没有 AOP 代理,实例对象仍然会 先经过三级缓存,但最终在 解决循环依赖时会被提升到二级缓存。具体流程如下:
1. 无 AOP 时的缓存流转流程 假设 没有代理(如没有 @Transactional、@Async 等 AOP 增强),且存在循环依赖(如 A → B → A):
(1) 实例化 A
步骤 1:通过构造函数创建 A 的原始对象(此时 A 还未完成属性注入和初始化)。
步骤 2:将 A 的 ObjectFactory 存入三级缓存(singletonFactories):这里的 ObjectFactory 直接返回原始对象(因为没有代理)
(2) 填充 A 的属性时发现依赖 B
步骤 3:开始创建 B,流程与 A 类似: 实例化 B 的原始对象 → 将 B 的 ObjectFactory 存入三级缓存。
(3) 填充 B 的属性时发现依赖 A
步骤 4:从三级缓存中获取 A 的 ObjectFactory,调用 getObject(): 无代理时:直接返回 A 的原始对象。
此时 A 的原始对象会被提升到二级缓存(earlySingletonObjects):
B 成功注入 A 的原始对象(来自二级缓存)
(4) 完成 A 的初始化
B 初始化完成后,A 继续完成属性赋值和初始化。
最终 A 的完整对象被放入一级缓存(singletonObjects),并清除二级缓存中的临时对象。
为什么Spring坚持使用三级缓存(即使有代理)
1. 代理对象生成时机问题
- 需要延迟决策:在Bean完全初始化前,无法确定最终是否需要代理(依赖注入可能影响代理逻辑)
- 三级缓存通过ObjectFactory实现了延迟生成:只有真正发生循环依赖时才决定生成原始对象还是代理对象
2. 去掉三级缓存的实际影响
代理一致性被破坏:
- 可能造成部分依赖注入的是原始对象,部分注入的是代理对象
- 特别是对于@Transactional等场景会导致事务失效
初始化顺序问题:
- 代理可能依赖于尚未初始化的Bean状态
- 提前创建的代理可能缺少必要的依赖
设计原则违背:
-
违反了"单一职责原则"(缓存机制应该与代理生成解耦)
-
增加了各组件间的耦合度
五、@scope:调整作用域
prototype:多实例的:ioc容器启动并不会去调用方法创建对象放在容器中。每次获取的时候才会调用方法创建对象;
singleton:单实例的(默认值):ioc容器启动会调用方法创建对象放到ioc容器中。以后每次获取就是直接从容器(map.get( ))中拿。
六、BeanFactory与FactoryBean的区别
摘自:Sping框架:BeanFactory与FactoryBean的区别
功能区别
- BeanFactory是Spring简单工厂模式的接口类,Spring IOC特性核心类,提供从工厂类中获取bean的各种方法,是所有bean的容器;
- FactoryBean仍然是一个bean,但不同于普通bean,它的实现类最终也需要注册到BeanFactory中。它也是一种简单工厂模式的接口类,但是生产的是单一类型的对象,与BeanFactory生产多种类型对象不同;
- FactoryBean是一个接口,实现了这个接口的类,在注册到Spring BeanFactory后,并不像其它类注册后暴露的是自己,它暴露的是FactoryBean中getObject方法的返回值;
- FactoryBean 通常是用来创建比较复杂的bean,一般的bean 直接用xml配置即可,但如果一个bean的创建过程中涉及到很多其他的bean 和复杂的逻辑,用xml配置比较困难,这时可以考虑用FactoryBean;