Spring面试篇

Spring面试篇,第1张

Spring面试篇

1、谈谈对SpringIOC的理解、原理和底层实现

        1、理解

        IOC是一种编程思想,用来解耦提高代码的可用性。也是一种设计模式,我认为是抽象工厂模式的升级版。抽象工厂方法是从抽象工厂类获取同一接口的不同实现,虽然看似减少了耦合,但是耦合代码还是实际存在的。而ioc模式将耦合代码移出去,通过xml配置文件替代耦合的代码,在ioc容器启动的时候,ioc根据配置文件生成依赖对象并注入,所以在使用ioc模式,使抽象工厂内部耦合的代码耦合转到外部xml配置文件,从而实现真正的解耦。

        控制反转就是将对象创建的权力从程序的代码本身转移到外部xml文件。依赖注入是控制反转的实现方式。依赖注入就是将 有依赖关系的实例变量注入到容器中,容器根据依赖关系创建类。        

        2、原理

        A、容器有一个最上层的跟接口,叫做BeanFactory,它只是一个接口,没提供对应的子类实现,在实际调用中使用最普通的是DefaultListableBeanFactory。在创建容器时优先创建BeanFactory,然后向BeanFactory设置一些参数,例如BeanPostProcessor或awer接口的子类。

        B、接下来是解析配置文件在容器内生成BeanDefinition对象,之后就是调用执行BeanFactoryPostProcessor,对BeanFactory进行增强处理。例如调用PlaceHolderPostProcessor实现对BeanDefinition对象中占位符的替换。

        C、之后就是BeanFactory通过发射的方式将BeanDefinition对象实例成具体的对象。

        D、然后就是对象的初始化。首先是对象属性填充,然后就是执行aware接口的实现类,然后根据类是否进行了aop代理的前置增强、是否实现init(),是否实现后置增强,进行相应的处理,最后生成完整的对象。

        E、最后就是对象的获取使用和销毁处理。

        3、底层实现

        A、首先通过createBeanFactory方法创建一个Bean工厂,最常用的Bean工厂是DefaultListableBeanFactory

        B、开始循环创建对象,因为容器中Bean默认是单例的,所以优先通过GetBean和doGetBean查找,找不到的话,就通过createBean和doCreateBean方法,以反射的方式来创建Bean,一般情况下使用无参构造

        C、接下来就是对象属性的填充,一般使用populateBean方法来实现

        D、最后进行对象的初始化,使用initializingBean方法实现。

2、三级缓存解决循环依赖问题

        1、什么是循环依赖?

在Spring中,对象Bean默认是单例的,意味着整个容器中只有一个对象。循环依赖就是 类A中包含类B的实例,类B中包含类A的实例

        2、三级缓存存储什么?

        A、一级缓存:存储经过实例化和初始化的对象(成品对象)

        B、二级缓存:存储只经过实例化但还没初始化完成的对象(半成品对象)

        C、三级缓存:存储一个lambda表达式,通过调用

        lambda表示式.getEarlySingletonObjects()生成一个半成品对象

        即三级缓存实现半成品对象的提前暴露

        D、缓存的放置

                三级缓存:createBeanInstance之后,addSingletonFactory

                二级缓存:第一次从三级缓存确认对象是代理对象还是普通对象的时候,同时删除三级缓存, getSingletn

                一级缓存:生成完整对象之后放到一级缓存,删除二三级缓存:addSingleton

        E、源码

	
	private final Map singletonObjects = new ConcurrentHashMap<>(256);

	
	private final Map> singletonFactories = new HashMap<>(16);

	
	private final Map earlySingletonObjects = new ConcurrentHashMap<>


@FunctionalInterface
public interface ObjectFactory {

	
	T getObject() throws BeansException;

}

        3、循环依赖现象

        A、前置知识:Spring容器创建对象的步骤

        实例化 -> 初始化( 填充属性-> 类增强处理)->返回地址给引用对象

        即如果类进行aop动态代理,则根据原始类产生一个动态代理类

        B、图解循环依赖

        

                上图存在一个闭环,循环依赖产生。要解决循环依赖,就必须打破闭环。

         对象的创建中实例化和初始化必须是连续进行的,可以将实例化和初始化分开进行,即进行实例后将地址返回给引用对象,后序再进行初始化。大白话就是提前暴露bean。

        4、循环依赖示例代码

        

public class A{
	private  B b;

	public B getB() {
		return b;
	}

	public void setB(B b) {
		this.b = b;
	}
}
public  class B{
	private A a;

	public A getA() {
		return a;
	}

	public void setA(A a) {
		this.a = a;
	}
}

        5、解决过程

        A、创建对象a,然后填充属性b 通过getBean和doGetBean方法去容器内查找是否已存在该bean。先在一级缓存找,如果找不到,则判断容器内是否存在正在创建的对象,不存在,则将当前创建的对象存在三级缓存,即 singletonFactory.put(a,lambda).

        B、创建对象b,然后填充属性a,通过getBean和doGetBean方法,先在一级缓存查找,发现不存在,则判断是否存在正在创建的对象,因为之前创建a,所以此时存在正在创建的对象,所以在二级缓存查找,发现不存在,然后去三级缓存查找,发现存在。因为三级缓存是一个工厂还没创建对象,此时调用getEarlyBeanReference()创建一个半成品对象放在二级缓存,然后讲这个半成品对象赋值给对象b的属性a,对象b完成初始化,最后再对对象a进行初始化。

        6、为什么需要三级缓存?二级缓存不也能实现吗?

        A、使用一级缓存存在的问题?

        将经过实例化和初始化的对象和只经过实例化但还没初始化完成的对象存放在一级缓存,则用户获取对象时会获取到还没创建完成的对象,值为null。(用户获取对象是在一级缓存获取)

        B、为什么不用二级缓存?

        使用二级缓存,能解决普通Bean的循环依赖,但不能解决AOP代理类的循环依赖。

      说明:根据Bean生命周期中的初始化阶段,对象先填充属性,然后再进行关于AOP相关的处理。使用二级缓存,暴露的Bean a是一个原始的Bean,此时将a注入到对象b的属性之后,对象b创建成功。接下来要进行的是对象a的初始化,因为对象a有aop实现,所以需要根据原始类a生成一个动态代理类,此时存在注入到对象b的原始bean a和新生成代理类冲突。(即注入b的对象a和生成对象a不一致问题)

        C、三级缓存解决的问题

        三级缓存 存储的是一个lambda表达式,是一个工厂模式的实现,根据类的定义判断创建原始类还是代理类

        源码解析

        

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
    //默认最终公开的bean是原始bean,通过createBeanInstance创建处理的bean
   Object exposedObject = bean;
    //判断bean是否进行了aop代理的相关增强处理
   if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
      for (BeanPostProcessor bp : getBeanPostProcessors()) {
         if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
            SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
            //公开的bean是新生成的动态代理类
            exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
         }
      }
   }
   return exposedObject;
}

        

3、SpringAOP的理解和底层实现

        A、AOP的理解

             AOP就是面向切面编程,在程序运行期间动态的将某段代码切入到指定方法指定位置进行运行的 *** 作。常运用在性能监控、日志记录 、权限控制等。通过aop可以解决代码耦合,让职责更加单一。

        B、AOP术语

        连接点:定义哪些地方可以切入

        切入点:定义了通知被应用的位置

        通知:切入连接点的时机和切入连接点的内容

        目标对象:被通知的对象

        织入:将通知添加到目标类具体连接点的过程

        切面:连接点、切入点和通知所在的那个类。

        C、底层实现

       底层实现分为两部分,代理创建和代理调用

       首先,代理创建。 在Spring底层,IOC容器会根据类是否配置了代理模式,为类建立一个对应的代理工厂。创建代理工厂时,需要目标对象接口数组、目标对象、拦截器数组三个信息,还会在拦截器数组的尾部增加一个默认拦截器用于调用最终的调用目标方法。代理工厂创建完成后,调用getProxy方法 根据接口数量是否大于1,判断执行jdk动态代理或cglib动态代理来生成代理对象,同时还会创建一个外层拦截器,这个Spring通过外层拦截器来控制AOP的流程。

        最后,代理调用。对代理对象进行调用时,会触发外层拦截器。外层拦截器根据代理配置信息,创建内层拦截链。创建过程中,会根据表达式判断当前拦截是否匹配这个拦截器,当整个拦截器执行到最后,就会触发创建代理时那个尾部的拦截器,从而调用最终的方法返回,最后返回。

        D、拦截器数组具体存储顺序和 责任链模式实现解析

        拦截器数组存储的顺序 采用的是拓扑结构。

        

         责任链模式:通过一个拦截器数组存在通知,如何从上到下遍历通知,直至执行目标方法,然后回溯。

        

 

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/zaji/5611768.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-15
下一篇 2022-12-15

发表评论

登录后才能评论

评论列表(0条)

保存