这里写目录标题
Spring的理解
谈谈你对spring的理解
设计目标
J2EE作为一种企业级应用开发平台,
构成
如果画一棵树:
- 核心:ioc容器
- AOP:spring AOP, AspectJ
- ORM框架:集成Mybatis,JPA等
- DAO,事务管理等
- J2EE的服务集成:JMX,JMS, EJB,JDBC,javaMail
- Web:spring MVC
- AOP:spring AOP, AspectJ
DI
依赖注入的几种方式
首先明白注解和xml只不过是两种外在的形式,注解能做的xml都有
- 手动注入指的是必须制定要注入的bean
- 自动装配指的是可以根据类型等(byTpye(根据类型),byName(根据名称)、constructor(根据构造函数)。)进行装配,所以具有自动扫描包的作用
注入的两种方式:
- 构造器
- setter
三级缓存
其实在Spring中,有两种循环依赖的场景…
第一种:构造器的循环依赖
第二种:setter的依赖注入
第一种是没有办法解决的,只能抛出BeanCurrentlyInCreationException
,而第二种可以使用提前暴露对象的方式进行解决。
根本原因:Spring解决循环依赖依靠的是Bean的“中间态”这个概念,而这个中间态指的是已经实例化,但还没初始化的状态。而构造器是完成实例化的东东,所以构造器的循环依赖无法解决~~~
-
三级缓存中存放什么样的对象
-
创建的流程
最后的最后,由于我太暖心了_,再来个纯文字版的总结。
依旧以上面A、B类使用属性field注入循环依赖的例子为例,对整个流程做文字步骤总结如下:
- 使用context.getBean(A.class),旨在获取容器内的单例A(若A不存在,就会走A这个Bean的创建流程),显然初次获取A是不存在的,因此走A的创建之路~
- 实例化A(注意此处仅仅是实例化),并将它放进缓存(此时A已经实例化完成,已经可以被引用了)
- 初始化A:@Autowired依赖注入B(此时需要去容器内获取B)
- 为了完成依赖注入B,会通过getBean(B)去容器内找B。但此时B在容器内不存在,就走向B的创建之路~
- 实例化B,并将其放入缓存。(此时B也能够被引用了)
- 初始化B,@Autowired依赖注入A(此时需要去容器内获取A)
- 此处重要:初始化B时会调用getBean(A)去容器内找到A,上面我们已经说过了此时候因为A已经实例化完成了并且放进了缓存里,所以这个时候去看缓存里是已经存在A的引用了的,所以getBean(A)能够正常返回
- B初始化成功(此时已经注入A成功了,已成功持有A的引用了),return(注意此处return相当于是返回最上面的getBean(B)这句代码,回到了初始化A的流程中~)。
- 因为B实例已经成功返回了,因此最终A也初始化成功
- 到此,B持有的已经是初始化完成的A,A持有的也是初始化完成的B,完美~
Spring IOC容器
几个重要的组件
-
BeanFactory 最基本的bean工厂,具有getBean,contains等方法
-
BeanDefinition:每一个受管的对象,在容器中都会有一个BeanDefinition的实例(instance)与之相对应,该BeanDefinition的实例负责保存对象的所有必要信息,包括其对应的对象的class类型、是否是抽象类、构造方法参数以及其他属性等。当客户端向BeanFactory请求相应对象的时候,BeanFactory会通过这些信息为客户端返回一个完备可用的对象实例。RootBeanDefinition和ChildBeanDefinition是BeanDefinition的两个主要实现类。
-
BeanDefinitionRegistry:
-
factoryBean:FactoryBean只定义了三个方法:
Object getObject() throws Exception;
Class getObjectType();
boolean isSingleton();
是bean。beanfactory是工厂在FactoryBean中,也就是说FactoryBean类型的bean定义,通过正常的id引用,容器返回的是FactoryBean所“生产”的对象类型,而非FactoryBean实现本身。
如果一定要取得FactoryBean本身的话,可以通过在bean定义的id之前加前缀&来达到目的。
Object factoryBean = container.getBean("&nextDayDate");
-
BeanFactoryPostProcessor
Spring提供了一种叫做BeanFactoryPostProcessor的容器扩展机制。该机制允许我们在容器实例化相应对象之前,对注册到容器的BeanDefinition所保存的信息做相应的修改。这就相当于在容器实现的第一阶段最后加入一道工序,让我们对最终的BeanDefinition做一些额外的操作,比如修改其中bean定义的某些属性,为bean定义增加其他信息等。可以手动实现
依赖注入是在哪里进行的
当bean信息被解析为beanDefination且注册到register中的时候,会对beanDefination中注入别的beanDefination的信息,这一切可以在DefaultListableBeanFactory中进行。因为它实现了BeanDefinitionRegistry和beanFactory接口。
IOC容器的原理
注意
-
ApplicationContext在容器启动的时候,就会马上对所有的“singleton的bean定义”进行实例化操作。懒加载的不会去加载
-
另外,既然这里提到abstract,对它就多说几句。容器在初始化对象实例的时候,不会关注将abstract属性声明为true的bean定义。如果你不想容器在初始化的时候实例化某些对象,那么可以将其abstract属性赋值true,以避免容器将其实例化。
-
容器启动之后,并不会马上就实例化相应的bean定义。我们知道,容器现在仅仅拥有所有对象的
BeanDefinition来保存实例化阶段将要用的必要信息。只有当请求方通过BeanFactory的getBean()方法来请求某个对象实例的时候,才有可能触发Bean实例化阶段的活动。BeanFactory的getBean方法可以被客户端对象显式调用,也可以在容器内部隐式地被调用。第二次被调用则会直接返回容器缓存的第一次实例化完的对象实例(prototype类型bean除外)。当getBean()方法内部发现该bean定义之前还没有被实例化之后,会通过createBean()方法来进行具体
的对象实例化:
实例化过程: -
加载和依赖注入bean是两码事,下面先介绍加载bean。加载需要三个过程:1.通过配置文件的文件路径或者类路径定位资源并解析读入资源,主要。2.
Bean的加载流程
最简单地分为两个阶段:
1.容器启动
2.bean实例化(调用getBean,从bean定义具体的bean,包含依赖注入,回调,初始化等步骤)
也可以拆成这几段
1.解析
2.加载
3.创建
4.初始化
5.增强
面试问到bean加载流程是可以从从容器刷新开始的(容器就是context,即内容更丰富的factory)这篇文章讲的很好
https://segmentfault.com/a/1190000012887776
和https://www.jianshu.com/p/5fd1922ccab1
https://www.linuxidc.com/Linux/2019-10/160914.htm
加载bean前先
整个bean加载的过程步骤相对繁琐,主要步骤有以下几点:
转换beanName
要知道平时开发中传入的参数name可能只是别名,也可能是FactoryBean,所以需要进行解析转换,一般会进行以下解析:
(1)消除修饰符,比如name="&test",会去除&使name=“test”;
(2)取alias表示的最后的beanName,比如别名test01指向名称为test02的bean则返回test02。从缓存中加载实例
实例在Spring的同一个容器中只会被创建一次,后面再想获取该bean时,就会尝试从缓存中获取;如果获取不到的话再从singletonFactories中加载。实例化bean
缓存中记录的bean一般只是最原始的bean状态,这时就需要对bean进行实例化。如果得到的是bean的原始状态,但又要对bean进行处理,这时真正需要的是工厂bean中定义的factory-method方法中返回的bean,上面源码中的getObjectForBeanInstance就是来完成这个工作的。检测parentBeanFacotory
从源码可以看出如果缓存中没有数据会转到父类工厂去加载,源码中的!containsBeanDefinition(beanName)就是检测如果当前加载的xml配置文件中不包含beanName所对应的配置,就只能到parentBeanFacotory去尝试加载bean。
(以上可以看做是在容器中的操作,没有涉及创建一个bean的过程)存储XML配置文件的GernericBeanDefinition转换成RootBeanDefinition. 之前的文章介绍过XML配置文件中读取到的bean信息是存储在GernericBeanDefinition中的,但Bean的后续处理是针对于RootBeanDefinition的,所以需要转换后才能进行后续操作。
初始化依赖的bean
这里应该比较好理解,就是bean中可能依赖了其他bean属性,在初始化bean之前会先初始化这个bean所依赖的bean属性。创建bean Spring容器根据不同scope创建bean实例。 整个流程就是如此,下面会讲解一些重要步骤的源码。
作者:weknow 链接:https://www.jianshu.com/p/5fd1922ccab1 来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
比如如何处理循环依赖?使用怎样的缓存策略
单例在Spring中的同一容器中只会被创建一次,后面再获取bean的话会直接从缓存中获取,这里是尝试加载,先从缓存中加载,再次就是从singletonFactories中加载;因为在bean中可能会在依赖注入,要避免循环依赖,Spring创建bean时会不等bean创建完成就会将创建该bean的ObjectFactory提前曝光加入到缓存中,但下一个bean创建时要依赖上个bean的话,就直接使用ObjectFacotry。
作者:weknow 链接:https://www.jianshu.com/p/5fd1922ccab1 来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
BeanPostProcessor是什么?
spring的另一个强大之处就是允许开发者自定义扩展bean的初始化过程,最主要的实现思路就是通过BeanPostProcessor来实现的,spring有各种前置和后置处理器,这些处理器渗透在bean创建的前前后后,穿插在spring生命周期的各个阶段,每一步都会影响着spring的bean加载过程。接下来我们就来分析具体的过程:
ApplicationContext
与beanFactory相比多了
1.统一资源加载策略
2.国际化信息支持
3.容器内部事件的发布
publishEvent的原理
AOP
在jdk的动态代理中,可以实现方法的增强,但是还有不足,比如:
- 需要一个个地将增强的方法与真实对象结合,且采用硬编码的方式
如果直接在spring中使用动态代理,可以定义代理的bean,然后注入被代理类,但是似乎也没有能解决问题。
3.
如果在代理类中定义一个被代理类的对象的集合,然后在扫描代理类中打上的注特殊的解,似乎
术语
为了更好的理解AOP,我们有必要先了解AOP的相关术语。
-
切面(Aspect)
横切关注点的模块化(跨越应用程序多个模块的功能,比如 日志功能),这个关注点实现可能另外横切多个对象。
切面指的就是包含增强方法的类 -
连接点(Join point)
连接点是在应用执行过程中能够插入切面的一个点。这个点可以是类的某个方法调用前、调用后、方法抛出异常后等。切面代码可以利用这些点插入到应用的正常流程之中,并添加行为。 -
通知(Advice)(在切面类里面写,通知方法上的注解通常是切点的名字)
在特定的连接点,AOP框架执行的动作。
Spring AOP 提供了5种类型的通知:- 前置通知(Before):在目标方法被调用之前调用通知功能。
- 后置通知(After):在目标方法完成之后调用通知,无论该方法是否发生异常。
- 后置返回通知(After-returning):在目标方法成功执行之后调用通知。
- 后置异常通知(After-throwing):在目标方法抛出异常后调用通知。
- 环绕通知(Around):通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为。
-
切点(Pointcut)
指定一个通知将被引发的一系列连接点的集合。AOP 通过切点定位到特定的连接点。切点和连接点不是一对一的关系,一个切点匹配多个连接点,切点通过 org.springframework.aop.Pointcut 接口进行描述,它使用类和方法作为连接点的查询条件。每个类都拥有多个连接点,例如 ArithmethicCalculator类的所有方法实际上都是连接点。
切点使用切点表达式来指定哪些方法是切点
类似
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern)throws-pattern?)
然后一般会为这些切点起个名字
@Pointcut("execution(* com.bytebeats.mq.MqProducer.*(..))")
private void mqSender(){
}
@Pointcut("execution(* com.bytebeats.mq.MqConsumer.*(..))")
private void mqReceiver(){
}
@Pointcut("mqSender() || mqReceiver()")
private void mqTrace(){
}
- 引入(Introduction) 添加方法或字段到被通知的类。 Spring允许引入新的接口到任何被通知的对象。例如,你可以使用一个引入使任何对象实现
IsModified接口,来简化缓存。Spring中要使用Introduction,
可有通过DelegatingIntroductionInterceptor来实现通知,通过DefaultIntroductionAdvisor来配置Advice和代理类要实现的接口
-
目标对象(Target Object)
包含连接点的对象。也被称作被通知或被代理对象。 -
AOP代理(AOP Proxy)
AOP框架创建的对象,包含通知。 在Spring中,AOP代理可以是JDK动态代理或者CGLIB代理。 -
织入(Weaving)
**织入描述的是把切面应用到目标对象来创建新的代理对象的过程。 Spring AOP 的切面是在运行时被织入,原理是使用了动态代理技术。**Spring支持两种方式生成代理对象:JDK动态代理和CGLib,默认的策略是如果目标类是接口,则使用JDK动态代理技术,否则使用Cglib来生成代理。
总结:AOP包括:定义增强方法(advice),与要增强的方法进行匹配(pointcut),增强方法的织入。织入使用的是动态代理的方法,织入的时机是在postPocessor
手写AOP
https://www.cnblogs.com/leeSmall/p/10050916.html