今天你准备了吗——Spring面试题

166 阅读14分钟

Spring面试题

1.Spring中的Bean的加载过程?

Bean的加载过程分为容器启动阶段Bean的创建阶段

容器启动阶段:

BeanDefinitionReader去读取并解析xml配置文件的元数据信息,并将这些元数据信息加载解析成BeanDefinition,然后将其注册到BeanDefinationRegistry中,将bean信息按照Map<name,BeanDefination>形式存储在beanFactory中的beanDefinitionMap属性中,它也是一种键值对的形式,通过特定的Bean定义的id,映射到相应的BeanDefination。

最后我们想要修改BeanFactory中的属性,BeanFactoryPostProcessor是容器启动阶段Spring提供的一个扩展点,主要负责对注册到BeanDefinationRegistry中的一个个的BeanDefination进行一定程度上的修改与替换。

Bean的创建阶段:

在容器启动阶段,已经完成了bean的注册。

如果该对象是配置成懒加载的方式,Spring只有在我们第一次需要获取依赖对象的时候才开启相应对象的实例化阶段。

而如果我们不是选择懒加载的方式,容器启动阶段完成之后,其中有一个步骤finishBeanFactoryInitialization(),在这一步将立即启动Bean实例化阶段。

2.Spring中的Bean的生命周期?

Spring容器帮助我们去管理对象,从对象的产生到销毁环节都由容器来控制,其中主要包含实例化和初始化两个关键环节,在整个过程中会有一些扩展点的存在,介绍一下各个环节和步骤:

1.实例化Bean,通过反射的方式进行生成,在源码中有一个createBeanInstance的方法时专门来生成对象的;

2.当bean的对象创建完成后,对象的属性都是默认值,所以要开始给bean填充属性,通过populateBean的方法来完成对象的属性填充,在这之间也可能会涉及到循环依赖的问题;

3.向bean对象设置容器的属性,会调用invokeAwareMethods方法来将容器对象设置到具体的bean对象中,例如:applicationContext、beanFactory属性。

4.调用BeanPostProcessor中的前置处理方法postProcessBeforeInitialization()来进行bean对象的扩展工作。

5.调用invokeInitMethods方法来完成初始化方法的调用,在此方法处理过程中,需要判断当前的bean对象是否实现了InitializingBean接口,如果实现了调用afterPropertiesSet方法来最后设置bean的对象

6.调用BeanPostProcessor的后置处理方法postProcessAfterInitialization,完成对bean对象的后置处理工作。我们所说的aop就是在此处实现的,实现的接口名称叫做AbstractAutoProxyCreator。

7.获取完整的对象,通过getBean的方式去进行对象的获取和使用;

8.当对象使用完成后,容器在关闭的时候,会销毁对象,首先判断是否实现了DispoableBean接口,然后去调用destoryMethod方法。

3.说一下Spring中的refresh的方法实现?

1.prepareRefresh()准备工作,设置启动时间、设置监听器的初始值,获取Environment等信息,加载当前系统的属性值到Environment对象中并验证;

2.obtainFreshBeanFactory():

初始化容器对象beanFactory,加载xml配置文件到当前工厂中,

xml中配置的对象的一些属性信息我们称之为元信息,然后通过BeanDefinitionReader下边的实现子类XmlBeanDefinationReader进行读取并解析xml配置文件中的元信息,将元信息转换为beanDefinition加载到内存中.

然后将其注册到BeanDefinationRegistry中,将bean信息按照Map<name,BeanDefination>形式存储在beanFactory中的beanDefinitionMap属性中,它也是一种键值对的形式,通过特定的Bean定义的id,映射到相应的BeanDefination.

3.prepareBeanFactory():准备上下文使用的beanFactory,例如设置EL表达式解析器、添加后置处理器等;

4.postProcessBeanFactory():空实现,我们对beanFactory进行后置处理;

5.invokeBeanFactoryPostProcessors():调用各种beanFactory处理器进行处理,

我们修改BeanFactory中的属性,实现BeanFactoryPostProcessors接口:用户手动加入的beanFactoryPostProcessor——》实现BeanDefinitioinRegistryPostProcessor接口的类——》实现BeanFactoryPostProcessor接口的类

BeanFactoryPostProcessor主要负责对注册到BeanDefinationRegistry中的一个个的BeanDefination进行一定程度上的修改与替换,例如xml文件中配置数据地址、密码等使用,所以我们通过实现BeanFactoryPostProcessor的自定义类就会对注册到BeanDefinationRegistry中的BeanDefination做最后的修改,替换,所以我们通过实现BeanFactoryPostProcessor的自定义类就会对注册到BeanDefinationRegistry中的BeanDefination做最后的修改,替换占位符为配置文件中的真实的数据。

BeanFactoryPostProcessor执行流程:外部BeanDefinitionRegistryPostProcessor-->实现了PriorityOrdered的BeanDefinitionRegistryPostProcessor-->实现了Ordered的BeanDefinitionRegistryPostProcessor-->无排序的BeanDefinitionRegistryPostProcessor-->调用BeanDefinitionRegistryPostProcessor#postProcessBeanFactory-->外部BeanFactoryPostProcessor-->实现了PriorityOrdered的BeanFactoryPostProcessor-->实现了Ordered的BeanFactoryPostProcessor-->无排序的BeanFactoryPostProcessor。

6.registerBeanPostProcessors(): 注册BeanPostProcessor

7.initMessageSource():初始化message资源

8.initApplicationEventMulticaster():初始化多路监听事件

9.registerListeners():注册监听器

10.finishBeanFactoryInitialization(beanFactory):初始化剩下的单实例(非懒加载的):该方法会实例化所有剩余的非懒加载单例 bean。除了一些内部的 bean、实现了 BeanFactoryPostProcessor 接口的 bean、实现了 BeanPostProcessor 接口的 bean, 其他的非懒加载单例 bean 都会在这个方法中被实例化,并且 BeanPostProcessor 的触发也是在这个方法中。

4.谈谈你对IOC的理解?

IOC:控制反转,原来我们使用对象的时候是由控制者创建的,有了Spring之后,是将对象交给容器来帮我们进行管理。

DI:依赖注入,将我们对应的属性注入到具体的对象中,一般我们使用的是注解方式 @Autwired@Resource以及populateBean方式完成属性注入。

容器:用来储存对象,使用map结构进行存储对象,在Spring存储对象的时候一般有三级缓存,SingletonObjects存放完整对象earlySingletonObjects存放半成品对象,singletonFactory用来存储lambde表达式和对象名称的映射,整个bean的生命周期,从创建到使用再到销毁都是通过容器来帮我们来完成的。

bean的生命周期:

1.实例化Bean,通过反射的方式进行生成,在源码中有一个createBeanInstance的方法时专门来生成对象的;

2.当bean的对象创建完成后,对象的属性都是默认值,所以要开始给bean填充属性,通过populateBean的方法来完成对象的属性填充,在这之间也可能会涉及到循环依赖的问题;

3.向bean对象设置容器的属性,会调用invokeAwareMethods方法来将容器对象设置到具体的bean对象中,例如:applicationContext、beanFactory属性。

4.调用BeanPostProcessor中的前置处理方法postProcessBeforeInitialization()来进行bean对象的扩展工作。

5.调用invokeInitMethods方法来完成初始化方法的调用,在此方法处理过程中,需要判断当前的bean对象是否实现了InitializingBean接口,如果实现了调用afterPropertiesSet方法来最后设置bean的对象。

6.调用BeanPostPriocessor的后置处理方法postProcessAfterInitialization,完成对bean对象的后置处理工作。我们所说的aop就是在此处实现的,实现的接口名称叫做AbstractAutoProxyCreator。

7.获取完整的对象,通过getBean的方式去进行对象的获取和使用;

8.当对象使用完成后,容器在关闭的时候,会销毁对象,首先判断是否实现了DispoableBean接口,然后去调用destoryMethod方法。

5.简述BeanFactory和FactoryBean的区别?

BeanFactory和Factory都可以创建对象只不过创建的流程和方式不同

当使用BeanFactory的时候,必须要求严格的遵守bean的生命周期,经过一系列繁杂的步骤之后才可以创建出单例对象,是流水线似的创建过程;

而FactoryBean是用户可以自定义bean对象的创建流程,不需要按照bean的生命周期来创建, 在此接口中包含三个方法:

isSingleton:判断是否是单例对象;

getObjectType:获取对象的类型;

getObject:在此方法中可以自己创建对象,使用new的方式或者使用代理的方式都可以,用户可以按照自己的需要随意的创建对象。例如Feign就是实现FactoryBean接口来实现的。

6.Spring中设计到哪些设计模式?

单例模式:spring中的bean都是单例;

工厂模式:BeanFactory;

模板模式:postProcessorBeanFactory,onFresh

装饰者模式:BeanWrapper

责任链模式:使用aop的时候会有一个责任链模式;

代理模式:aop动态代理

7.ApplicationContext和BeanFactory区别?

BeanFacotry是访问Spring容器的根接口,里面只是提供某些基本方法的约束和规范,为了满足更多的需求,ApplicationContext实现了此接口,并且在此接口的基础上做了某些扩展功能,提供了更加丰富的api调用。

8.谈谈你对循环依赖的理解?

循环依赖:类与类之间的依赖关系形成了闭环,就会导致循环依赖问题的产生。例如A类依赖了B类,B类依赖了C类,而最后C类又依赖了A类,这样就形成了循环依赖问题。

为什么会产生循环依赖:

Spring中对象的创建都需要经过实例化和初始化。现在A对象中包含B对象,B中包含A。在创建A对象过程中,需要实例化A,此时A对象中B默认为null,然后给A对象的B属性赋值,此时需要到容器中查找B对象,如果找到了直接给A中的B属性赋值,如果不到就会去创建B对象,创建B对象的过程跟A创建的对象的过程是一样的,最后去容器中查找A属性,找不到的话会去创建A对象吗,这样就形成了一个循环。

如何解决循环依赖?

解决办法就是提前暴露创建中的Bean,将这些bean存放在二级缓存中。

Spring中有三个缓存,用于存储单例的Bean实例,这三个缓存是彼此互斥的,不会针对同一个Bean的实例同时存储。如果调用getBean,则需要从三个缓存中依次获取指定的Bean实例。 读取顺序依次是一级缓存 ==> 二级缓存 ==> 三级缓存。

一级缓存:Map<String, Object> singletonObjects 用于存储单例模式下创建的Bean实例(已经创建完毕)

二级缓存:Map<String, Object> earlySingletonObjects 用于存储单例模式下创建的Bean实例(该Bean被提前暴露的引用,该Bean还在创建中

三级缓存:Map<String, ObjectFactory<?>> singletonFactories: key为bean的名称,value为ObjectFactory,是一个函数式接口,不是直接调用,只是在调用getObject方法的时候才会去调用里面存储的lambda表达式,存在的意义是保证整个容器运行过程中同名的beanName只有一个。

8.1只有一级缓存行不行?

不行,会把成品状态和半成品状态的Bean对象放到一起,而半成品对象是无法暴露给外部使用的,所以要把成品和半成品对象分别放到一二级缓存中。

8.2只有二级缓存行不行?

在整个应用中不涉及aop的存在,那么二级缓存足可以解决循环依赖的问题,如果aop中存在循环依赖,必须使用三级缓存。

在创建代理对象之前需要创建原始对象,那我们在调用的时候该用哪一个?

在实际的调用过程中,当某个对象调用的过程中,优先判断对象是否需要进行代理。当获取对象之后根据传入的lambda表达式来确定返回的是哪个对象,如果条件符合,返回代理对象

8.3 如何解决构造方法的循环依赖?

前面说Spring可以自动解决单例模式下通过setter()方法进行依赖注入产生的循环依赖问题。而对于通过构造方法进行依赖注入时产生的循环依赖问题没办法自动解决,那针对这种情况,我们可以使用@Lazy注解来解决。

也就是说,对于类A和类B都是通过构造器注入的情况,可以在A或者B的构造函数的形参上加个@Lazy注解实现延迟加载。@Lazy实现原理是,当实例化对象时,如果发现参数或者属性有@Lazy注解修饰,那么就不直接创建所依赖的对象了,而是使用动态代理创建一个代理类。

比如,类A的创建:A a=new A(B),需要依赖对象B,发现构造函数的形参上有@Lazy注解,那么就不直接创建B了,而是使用动态代理创建了一个代理类B1,此时A跟B就不是相互依赖了,变成了A依赖一个代理类B1,B依赖A。但因为在注入依赖时,类A并没有完全的初始化完,实际上注入的是一个代理对象,只有当他首次被使用的时候才会被完全的初始化。

9.谈谈你对aop的理解?

aop:面向切面编程

aop是ioc的一个扩展功能,现有ioc,再有aop,只是在ioc的整个流程中新增的一个扩展点而已:在实现BeanPostProcessor接口进行扩展。

Aop的实现大致分为三大步:基于javaConfig

当@EnableAspectJAutoProxy会通过@Import(AspectJAutoProxyRegistrar.Class)注册一个BeanPostProcessor处理AOP;

1.解析切面 :在创建Bean时调用BeanPostProcessor解析切面 @Aspect,将切面中所有通知解析为advisor(该对象包括通知、切点、通知方法)排好序放入List并缓存;

2.在Bean的初始化后调用BeanPostProcessor拿到之前缓存的advisor判断当前Bean是否被切点命中,如果匹配为Bean创建动态代理。

3.调用:通过之前创建的动态代理调用方法执行增强,通过调用链设计模式依次调用通知方法

10.@Resource和@Autowired的区别?

这两个注解都是作用在Spring框架里面去实现Bean的依赖注入。

首先@Autowired是Spring中的提供的一个注解,默认是按照类型来实现Bean的注入,里面有一个required属性值默认为true,表示强制要求bean的实例注入,在启动的时候,如果IOC容器里面不存在对应类型的bean,就会报错。当然如果不希望自动注入,可以把这个属性设置为false;

如果在Spring ioc容器中存在多个相同类型的Bean实例,由于@Autowired注解是根据类型来注入Bean实例的,所以在Spring启动的时候,会报错。可以使用 @Primary和@Qualifier注解来解决这个问题。

其次@Resource是JDK提供的注解,只是Spring在实现上提供了这个注解的功能支持。可以支持类型和名称来实现依赖注入。

如果使用name,Spring就会根据bean的名字进行依赖注入;如果使用type,Spring就会根据类型实现依赖注入。如果两个属性都没有配置,就会根据bean名字去匹配,如果没匹配成功在根据类型进行匹配。两个都没有匹配到就会报错。

11.有两个相同id的bean会报错吗?如果报错在哪个阶段?

在同一个XML配置文件里,不能存在相同的id的两个Bean,否则Spring容器启动的时候会报错。因为id属性是表示一个bean唯一标识符,所以在容器启动的时候会验证id的唯一性,一旦重复就会报错。这个错误发生在XML文件转化为BeanDenifition的阶段。

在Spring3.x版本里面提供了@Configuration注解去声明一个配置类,然后使用@Bean的注解实现Bean的声明,这种注解方式完全替代了XML。如果我们在同一个配置类中声明多个相同名字的Bean,在Spring IOC容器中指挥注册第一个声明的Bean的实例,后续重复名字的Bean就不会被注册。

12.mybatis的#{}和${}区别是?

这两个符号就是都是实现动态sql的一种方式,通过这两种方式把参数传递到XML,mybatis会对这两种占位符进行动态解析。

#:等同于jdbc里面的?占位符,相当于向PreparedStatement中的预处理语句中设置参数 ,而PreparedStatement中的sql语句是预编译的,sql语句使用了占位符,规定了sql语句的结构,并且在设置参数的时候,如果由特殊字符,会进行自动转义。可以防止sql注入的问题

$:字符串的拼接,相当于直接把参数拼接到sql里面。用于动态传递表名/动态设置排序等。

13.BeanPostProcessor的使用场景?

在bean实例化之后、初始化前后被执行,允许我们对bean实例进行自定义的修改。