Spring面试题集

1. Spring的两大特性:控制反转和依赖注入请解释一下?

控制反转,它是一种思想,不是具体的技术,它说的是将对象的控制权交给程序来决定。

依赖注入:对象和对象之间的依赖关系通过注解的方式实现。如Spring中常用的注解,@Autowired。对象y

2. 解释一下Spring的Aop特性?

切面技术,用于解决项目中散乱而又冗余的代码,可以使用在日志打印,权限校验,流量监控等。

参考博文:Spring AOP示例与实现原理总结——传统spring aop、基于切面注入、基于@Aspect注解的实现 - 浪荡绅士 - 博客园

1. 使用Aspect切面包实现

2.使用spring的aop框架实现,创建一个类继承methodInterceptor实现invoke方法。创建ProxyFactoryBean注入目标对象和拦截器。

实现原理:

Cglib动态代理:被代理的类可实现也可不实现接口。实现方式:Enhance(需要设置目标类和切面环绕逻辑)+环绕逻辑在MethodInterceptor接口中的interceptor中实现

java动态代理:被代理的类一定要实现一个接口,使用过java.lang.Proxy类实现,调用newProxyInstance方法,传入类加载器,接口类,实现调用方法invoke。

3. BeanFactory 和 ApplicationContext 有什么区别?

都是Bean对象获取可以使用的方式,注解的底层也是使用AppliactionContext接口的实现类来生成或获取bean对象的。@Compoent底层就是使用Application的实现类

AnnotationConfigApplicationContext来完成注入容器的过程的。

BeanFactory,底层接口,提供实例化对象和取对象的功能。

ApplicationContext, 更高级的容器,提供更多的功能。如访问文件资源,aop,国际化,消息发送和响应机制。

3. 请解释 Spring Bean 的生命周期?

实例化:调用构造方法的过程

属性赋值:调用setter方法的过程。bean依赖注入的方法,setter方法上需要加@autowired注解。

初始化:扩展的方法,InitializingBean接口实现

销毁:扩展方法,继承dispoalBean接口实现

备注:

Bean的注入通常使用@Autowired注解,该注解用于bean的field、setter方法以及构造方法上,显式地声明依赖。

4. Spring 框架中都用到了哪些设计模式?

参考博文:Spring中涉及的设计模式总结_iCoding91的博客-CSDN博客_spring中的设计模式

1. 抽象工厂:例如beanFactory和它的各种实现类。beanFactory只是定义了一些取对象的接口,具体对象如何生成如何获取由它的实现类取实现。其实factoryBean采用的方案也是抽象工厂模式。

2. 单例模式:spring中的bean的生成方式默认是单例的。当然也可以通过设置属性,改为多例。

3.适配器模式:衔接两个不兼容的接口,外界调用的方式不变,添加了适配类的处理逻辑。

4. 代理模式:Aop的底层使用动态代理的方式来实现。

5. 观察者模式:

5. 过滤器的实现方案和原理?

6. 请详细描述springmvc处理请求全流程?为什么SpringMVC需要使用适配器模式?

了解springMVC的请求全流程需要了解这几个类?

1. dispatchServlet,核心组件,请求的入口,负责协调HandleMapping,AdaptHandler, veiwResolver 等组件的工作。

2. HandlerMapping,为请求找到合适的适配器。

3. AdapterHandler,为请求找到支持的处理器,

4. Handler,这个处理器是对应controller方法的代理类,负责处理具体的业务逻辑。

5. ModelAndView, 处理器返回的处理结果

6. ViewResolver, 视图解析器,作用解析ModelAndView,得到View对象

7. View,渲染视图

步骤:

1. 请求通过网络到达应用端

2. 应用端将请求传给DispatchServlet处理,它通过HandlerMappinng找到合适的AdapterHandler

3. AdapterHandler,它帮助找到对应的Handler, handler的作用是代理对应的controller,执行业务逻辑

4. handler处理完之后,会得到一个ModelAndView对象,这个对将被返给dispatchServlet

5. dispatcherServlet然后会将ModelAndView, 交给ViewResolver进行处理

6. ViewResolver处理完之后会将得到的View对象,View对象是用来渲染视图的,最后会将渲染结果返给前端。

7. spring 一个bean装配的过程?


1. 调用构造方法实例化

2. 调用set方法设置对象属性值

3. 执行初始化的过程

4. 使用IOC容器中的对象

5. 对象销毁(应用程序关闭的时候,会执行)

初始化的方法流程:

1. 如果类执行了下面的Aware接口,就会调用对应的set方法。 如果实现了BeanNameAware接口,调用setBeanName设置Bean的ID或者Name;
如果实现BeanFactoryAware接口,调用setBeanFactory 设置BeanFactory;如果实现ApplicationContextAware,调用setApplicationContext设置ApplicationContext;
2. 调用BeanPostProcessor的postProcessBeforeInitialization方法;提供的功能和切面类似,他可以去统一的对一定范围的bean对象,做一些初始化的处理。

3. 执行初始化方法: @Compoent 修饰的类可以实现InitialingBean接口,重写afterPropertiesSet方法;@Bean注解的方式可以使用定义initMethod属性,指定bean的初始方法
4. 调用BeanPostProcessor的postProcessAfterInitialization

5. 调用BeanPostProcessor的后初始化方法;

8. 介绍下Spring IOC容器的初始化过程?

a . 资源定位,spring需要知道要加载的BeanDefinition有哪些,这些资源可以理解为定义了bean的xml文件资源,或者是打上@Component,@Configure,@Bean标签的类。其实在这个过程就会调用createBeabFactory方法来创建容器。

b. 资源加载,将a中定位到的资源,加载成ioc容器中的内部结构,这些内部结构其实就是用BeanDefinition对象来表示

c. 资源注册,向IOC容器容器中注册这些BeanDefinition的过程,调用BeanDefinitionRegistry接口完成,这些BeanDifinition对象会通过HashMap对象来完成

上面的过程是IOC容器的初始化过程,这个过程只是容器的初始化和资源的准备过程。依赖注入在这个阶段是没有发生的。

9. bean依赖注入的过程?

a. 用户调用getBean的时候或者容器完成bean的实例化的时候,会根据beanDefinition分析bean的依赖注入链

b. 如果实例化的对象有依赖注入的对象,那必须的完成依赖对象的实例化,才能实例化本对象。对象实例化的过程也伴随着bean初始化的过程。

10. SpringBoot中常见注解的含义?

(1) @EnableAutoConfiguration 激活SpringBoot内建的和自定义组件的自动装配特性。

(2)

11. SpringBoot中需要研究的特性?

reactive模式,使用webFlux框架,这个异常无阻塞框架可以解决高并发场景的什么问题。

11. SpringBoot的启动流程介绍?

SpringBoot的启动流程,非常适合和Spring的IOC容器和依赖注入的过程一起来说。

SpringBoot启动过程需要依赖的3大注解:

@EnableAutoConfiguration最为重要, 通过@Import注解,将bean资源注册到IOC容器中。其中bean资源注册的过程是由@AutoConfigurationPackage完成,autoConfigrationPackage又是通过@import(AutoConfigurationPackage.Registrar.class)实现。@Import(AutoConfigurationSelectorImportSelector.class)的类中有一个selectImports方法, 可以读取到spring.factories中的自动化配置的类信息。

@SpringBootConfiguration, 标记注解类,定位需要加载的资源

@ComponentScan,配置扫描路径,完成bean资源的加载过程

上面的过程和Spring容器初始化的过程是相符的,

那,ioc容器建立了之后,后面的流程就是bean初始化和依赖注入的过程。

SpringBoot启动流程及其原理 - rhyme - 博客园

12. spring中的bean是如何保证单例的?

spring中bean的获取是从ioc容器中获取的。

由AbstractBeanFactory可以,getBean方法源码,spring是从3级缓存中去获取bean对象的,这3级缓存分别是singletonObjects(concurrentHashMap结构),earlysingletonObjects, singletonFactory。

如果缓存中获取不到,并且bean又是单例模式(bean默认都是单例的)。

单例bean的创建过程,通过反射的方式创建bean对象,然后再完成bean初始化的过程。最后会将初始化完成的bean放在一级缓存singletonObjects。

怎么保证线程安全呢?

这段代码极其隐晦的藏在了getSingleton里面如下。其采用的就是synchronized + double check的方式。

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
        Object singletonObject = this.singletonObjects.get(beanName);
        if (singletonObject == null && this.isSingletonCurrentlyInCreation(beanName)) {
            singletonObject = this.earlySingletonObjects.get(beanName);
            if (singletonObject == null && allowEarlyReference) {
                synchronized(this.singletonObjects) {
                    singletonObject = this.singletonObjects.get(beanName);
                    if (singletonObject == null) {
                        singletonObject = this.earlySingletonObjects.get(beanName);
                        if (singletonObject == null) {
                            ObjectFactory<?> singletonFactory = (ObjectFactory)this.singletonFactories.get(beanName);
                            if (singletonFactory != null) {
                                singletonObject = singletonFactory.getObject();
                                this.earlySingletonObjects.put(beanName, singletonObject);
                                this.singletonFactories.remove(beanName);
                            }
                        }
                    }
                }
            }
        }

        return singletonObject;
    }

总结:bean的创建很像懒汉式的创建模式,也是bean使用的时候再去创建。这样的话,就需要保证并发情况下,依然是单例的。spring使用的是double check+ synchronized的方式,即保证并起下的单例,有不影响bean获取的效率。

13. Spring是如何防止循环引用的?

会使用的DefaultSingletonBeanRegistry中的三级缓存:singletonObjects, earlySingletonObjects, singletonFactory

举个例子:A依赖B, B又依赖A。假设A的bean先创建,实例化后调用set方法设置属性时发现,有个属性值是B的bean。其实在这个时候,A的bean已经创建好了,只是没有完成全部的初始化过程,这个半成品可以先暂时放在singletonFactory中。然后进行B的实例化,调用set方法时发现,它依赖A。这时,它可以从singletonFactory中取A的半成品进行依赖,然后完成后续的初始化动作。

参考文章:

spring为什么使用三级缓存而不是两级? - 知乎

一文告诉你Spring是如何利用"三级缓存"巧妙解决Bean的循环依赖问题的【享学Spring】-腾讯云开发者社区-腾讯云

核心流程图:

实现的关键:

1. 2,3级缓存用来存放bean对象的半成品。

2. 如果缓存中存在bean实例,就不会继续创建新的实例。

代理类提前暴露:

	protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
		Object exposedObject = bean;
		if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
			for (BeanPostProcessor bp : getBeanPostProcessors()) {
				if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
					SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
					exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
				}
			}
		}
		return exposedObject;
	}
populateBean 属性赋值
initializeBean bean初始化过程

14. 什么时候bean会被移动到一级缓存中?

在bean完成初始化后,会被移动到一级缓存中,并删除2,3级缓存中的数据。

org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String, org.springframework.beans.factory.ObjectFactory<?>)

面试必杀技,讲一讲Spring中的循环依赖-阿里云开发者社区

15. spring的bean的1,2,3级缓存存放的是什么,以及它的作用是什么?

第一级,singleObject,它存放的是完成了初始化过程的bean对象。

第二级缓存earlySingleObject和第三级缓存singleFactory是解决循环依赖的关键。singleFactory遵循的单例工厂,正常情况下只会被调一次。因为在调用singleFactory.getObject后,会生成earlySingleObject(这里如果没有aop时,生成的是原始对象,如果有aop生成的就是代理对象)放到二级缓存,出现循环应用时,提前依赖的也是这个对象。并且会将3级缓存中的工厂类删除。

存放的是ObjectFactory,这里是解决循环依赖的关键。AB循环依赖时,B通过A的singleObject对象获取到A的实例,从而建立依赖关系。

参考:Spring循环依赖-earlySingletonObjects的作用 - 知乎

16. 三级缓存为什么要使用工厂而不是直接使用引用?换而言之,为什么需要这个三级缓存,直接通过二级缓存暴露一个引用不行吗?

这个工厂的目的在于延迟对实例化阶段生成的对象的代理,只有真正发生循环依赖的时候,才去提前生成代理对象,否则只会创建一个工厂并将其放入到三级缓存中,但是不会去通过这个工厂去真正创建对象

我们思考一种简单的情况,就以单独创建A为例,假设AB之间现在没有依赖关系,但是A被代理了,这个时候当A完成实例化后还是会进入下面这段代码:

// A是单例的,mbd.isSingleton()条件满足
// allowCircularReferences:这个变量代表是否允许循环依赖,默认是开启的,条件也满足
// isSingletonCurrentlyInCreation:正在在创建A,也满足
// 所以earlySingletonExposure=true
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
                                  isSingletonCurrentlyInCreation(beanName));
// 还是会进入到这段代码中
if (earlySingletonExposure) {
    // 还是会通过三级缓存提前暴露一个工厂对象
    addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}

看到了吧,即使没有循环依赖,也会将其添加到三级缓存中,而且是不得不添加到三级缓存中,因为到目前为止Spring也不能确定这个Bean有没有跟别的Bean出现循环依赖。

假设我们在这里直接使用二级缓存的话,那么意味着所有的Bean在这一步都要完成AOP代理。这样做有必要吗?

不仅没有必要,而且违背了Spring在结合AOP跟Bean的生命周期的设计!Spring结合AOP跟Bean的生命周期本身就是通过AnnotationAwareAspectJAutoProxyCreator这个后置处理器来完成的,在这个后置处理的postProcessAfterInitialization方法中对初始化后的Bean完成AOP代理。如果出现了循环依赖,那没有办法,只有给Bean先创建代理,但是没有出现循环依赖的情况下,设计之初就是让Bean在生命周期的最后一步完成代理而不是在实例化后就立马完成代理。

17. 如果是使用构造器注入可以解决循环依赖问题吗?

不行。因为在构造器是在实例话的时候执行的,这个时候还没生成3级缓存。

整个bean创建的过程,可以看看下面这个方法。其实就是使用构造器去实例化对象。

这里就是会实例话出这个bean的工厂对象,是一个singleFactoy对象。

org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值