spring

Spring的理解

谈谈你对spring的理解

设计目标
J2EE作为一种企业级应用开发平台,
构成
如果画一棵树:

  • 核心:ioc容器
    • AOP:spring AOP, AspectJ
      • ORM框架:集成Mybatis,JPA等
      • DAO,事务管理等
    • J2EE的服务集成:JMX,JMS, EJB,JDBC,javaMail
    • Web:spring MVC

DI

依赖注入的几种方式

首先明白注解和xml只不过是两种外在的形式,注解能做的xml都有

  • 手动注入指的是必须制定要注入的bean
  • 自动装配指的是可以根据类型等(byTpye(根据类型),byName(根据名称)、constructor(根据构造函数)。)进行装配,所以具有自动扫描包的作用

注入的两种方式:

  • 构造器
  • setter

三级缓存

其实在Spring中,有两种循环依赖的场景…
第一种:构造器的循环依赖
第二种:setter的依赖注入
第一种是没有办法解决的,只能抛出BeanCurrentlyInCreationException,而第二种可以使用提前暴露对象的方式进行解决。

根本原因:Spring解决循环依赖依靠的是Bean的“中间态”这个概念,而这个中间态指的是已经实例化,但还没初始化的状态。而构造器是完成实例化的东东,所以构造器的循环依赖无法解决~~~

  • 三级缓存中存放什么样的对象
    在这里插入图片描述

  • 创建的流程
    最后的最后,由于我太暖心了_,再来个纯文字版的总结。
    依旧以上面A、B类使用属性field注入循环依赖的例子为例,对整个流程做文字步骤总结如下:

  1. 使用context.getBean(A.class),旨在获取容器内的单例A(若A不存在,就会走A这个Bean的创建流程),显然初次获取A是不存在的,因此走A的创建之路~
  2. 实例化A(注意此处仅仅是实例化),并将它放进缓存(此时A已经实例化完成,已经可以被引用了)
  3. 初始化A:@Autowired依赖注入B(此时需要去容器内获取B)
  4. 为了完成依赖注入B,会通过getBean(B)去容器内找B。但此时B在容器内不存在,就走向B的创建之路~
  5. 实例化B,并将其放入缓存。(此时B也能够被引用了)
  6. 初始化B,@Autowired依赖注入A(此时需要去容器内获取A)
  7. 此处重要:初始化B时会调用getBean(A)去容器内找到A,上面我们已经说过了此时候因为A已经实例化完成了并且放进了缓存里,所以这个时候去看缓存里是已经存在A的引用了的,所以getBean(A)能够正常返回
  8. B初始化成功(此时已经注入A成功了,已成功持有A的引用了),return(注意此处return相当于是返回最上面的getBean(B)这句代码,回到了初始化A的流程中~)。
  9. 因为B实例已经成功返回了,因此最终A也初始化成功
  10. 到此,B持有的已经是初始化完成的A,A持有的也是初始化完成的B,完美~

Spring IOC容器

几个重要的组件

  1. BeanFactory 最基本的bean工厂,具有getBean,contains等方法

  2. BeanDefinition:每一个受管的对象,在容器中都会有一个BeanDefinition的实例(instance)与之相对应,该BeanDefinition的实例负责保存对象的所有必要信息,包括其对应的对象的class类型、是否是抽象类、构造方法参数以及其他属性等。当客户端向BeanFactory请求相应对象的时候,BeanFactory会通过这些信息为客户端返回一个完备可用的对象实例。RootBeanDefinition和ChildBeanDefinition是BeanDefinition的两个主要实现类。

  3. BeanDefinitionRegistry:
    在这里插入图片描述

  4. 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");

  5. 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加载的过程步骤相对繁琐,主要步骤有以下几点:

  1. 转换beanName
    要知道平时开发中传入的参数name可能只是别名,也可能是FactoryBean,所以需要进行解析转换,一般会进行以下解析:
    (1)消除修饰符,比如name="&test",会去除&使name=“test”;
    (2)取alias表示的最后的beanName,比如别名test01指向名称为test02的bean则返回test02。

  2. 从缓存中加载实例
    实例在Spring的同一个容器中只会被创建一次,后面再想获取该bean时,就会尝试从缓存中获取;如果获取不到的话再从singletonFactories中加载。

  3. 实例化bean
    缓存中记录的bean一般只是最原始的bean状态,这时就需要对bean进行实例化。如果得到的是bean的原始状态,但又要对bean进行处理,这时真正需要的是工厂bean中定义的factory-method方法中返回的bean,上面源码中的getObjectForBeanInstance就是来完成这个工作的。

  4. 检测parentBeanFacotory
    从源码可以看出如果缓存中没有数据会转到父类工厂去加载,源码中的!containsBeanDefinition(beanName)就是检测如果当前加载的xml配置文件中不包含beanName所对应的配置,就只能到parentBeanFacotory去尝试加载bean。
    (以上可以看做是在容器中的操作,没有涉及创建一个bean的过程)

  5. 存储XML配置文件的GernericBeanDefinition转换成RootBeanDefinition. 之前的文章介绍过XML配置文件中读取到的bean信息是存储在GernericBeanDefinition中的,但Bean的后续处理是针对于RootBeanDefinition的,所以需要转换后才能进行后续操作。

  6. 初始化依赖的bean
    这里应该比较好理解,就是bean中可能依赖了其他bean属性,在初始化bean之前会先初始化这个bean所依赖的bean属性。

  7. 创建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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值