Java面试突击每日十题【Day01】——Spring上篇

马云曾说过离职的两个原因:1、要么钱少;2、要么心里委屈。总之一句话就是干的不爽,反正我现在心里很是不爽,就又开始了面试新征程,和上次不一样的是这次不裸辞了,实在是顶不住裸辞的压力了,在加上互联网裁员裁的人心惶惶,有工作就不错了,但是也不能委屈自己呀,工作实在干的不爽还是要换的,面试面试也好,在这公司一年了感觉没学到多少东西,再不走出舒适区真的要废了。而真正到面试时又开始了焦虑,解决焦虑的最好办法就是找到具体的原因,一说到具体我们其实是知道方法、路径的,就像面试题,记不了那么多一天巩固十道高频有代表性的,或者是容易忘记的,一个周下来就是几十道,贵在坚持。为了收到满意的offer,接下来将会每日打卡面试题,复盘面试回答不好的题,每日十题。

为了使记忆更深刻,可以先试着自己回答一遍,然后再去看总结的答案,这样也能反思自己答的不好的地方,或者想到更合理的答案。
Notes:答案在图片下面!!!
下面的题将会给一个评分(1-10),分值越高面试出现的次数越多越重要,8分及以上的务必记住!!!

  1. 谈谈你对Spring的理解,看过源码吗,哪个最熟悉?(9分)
  2. Spring bean的生命周期?(10分)
  3. Spring的启动流程(IOC的创建构建流程)?(10分)
  4. Spring是如何解决循环依赖问题的,能解决构造函数依赖吗,不能说出原因,缓存放置时间和删除时间?(9分)
  5. SpringBoot自动配置原理?(10分)
  6. BeanFactory和FactoryBean的区别?(8分)
  7. BeanFactory和ApplicationContext的区别?(7分)
  8. Spring Aop的底层实现,Aop有哪几种创建代理的方式及区别?(8分)
  9. Spring事务的实现原理,事务传播行为?(8分)
  10. Spring中的设计模式,你工作中用过什么设计模式?(8分)
    在这里插入图片描述

答案:
一、Spring是一个最基本的框架,Spring全家桶是依赖于Spring框架来构成的,在Spring中包括两个核心的关键点,第一个是IOC,控制反转表示Spring是最基本的一个容器,控制就是指对对象的创建、维护、销毁等生命周期的控制,这个过程一般由我们的程序主动控制,反转就是指对象的创建、维护、销毁等生命周期的控制改为spring的容器控制;第二个是AOP,它提供了面向切面编程的这种思路,动态的将某段代码插入到原来方法代码的某些位置中。可以帮助我们在日志事务等各个层面,进行相关的一些扩展工作,有助于减少系统的代码重复,降低耦合度。
这些是较浅层次的理解,其实Spring就是一个基本的框架,它提供了基本的容器功能,该容器是用来存放具体的Bean对象的,当有了这些Bean对象后,为方便我们对Bean对象的获取,需要先声明好配置文件或注解,告诉Spring容器,你需要什么样的对象。通过注解或配置文件定义好就开始解析了,通过IO流,Document文档去解析里边的标签,解析里边的属性值,解析后的文件我们称为BeanDefinition简称BD,根据BD中的描述信息来进行对象的实例化操作,通过反射的方式创建具体的对象,之后就可以通过Context.getBean()获取Bean对象。
Spirng作为一个容器,从创建到销毁,都不需要人为干预。
作为一个框架,最基本最主要的就是扩展性,在Spring中提供了一扩展点,让用户来随意进行扩展工作。在创建对象之前会有一个扩展点,BeanFactoryPostProcessor可以替换配置文件中的占位符,在BeanFactoryPostProcessor还包含了BeanDefiintionRegistryPostProcessor可以随意地创建BD对象,SpingBoot的自动装配原理:主要就是通过ConfigurationClassPostProcessor来实现的。
Spring扩展在设计的时候留出了很多口子,让用户能够进行扩展工作。
Spring中启动流程中postprocessBeanFactory和onRefresh方法就可以扩展实现
使用第一个方法的时候,其实很简单,方法的参数是BeanFactory,所以可以通过add,set,register的方式向容器中添加任何的对象。
第二个方法OnRefresh没有参数,可以任意定义核心的功能组件,当具体进行调用的时候,需要继承当前的父类,在子类方法中进行扩展实现,调用的时候会直接找到子类方法执行,像SpringBoot中的Tomcat就是通过createWebServer方法实现的。
在前期完成基本的扩展工作后,开始进行Bean对象的实例化操作,也就是整个生命周期。具体分为实例化和初始化,其中实例化是通过CreateBeanInstance来完成的,比如有new factorybean。

二、这个图没事多在脑子里过几遍吧,同时结合源码看看自己能填充进去点什么,回答的时候不要只说图中有的关键点,要学会扩展描述:
Spring Bean的生命周期
1. 首先在实例化的时候通过反射生成对象。
2. 然后通过PopulateBean方法填充Bean的属性。(在此过程中可以引出循环依赖的问题,反客为主,引导面试官。)
3. 调用Aware相关接口的方法,invokeAwareMethod(完成BeanName、BeanFactory、BeanClassLoader对象的属性设置)
4. 调用BeanPostProcessor中的前置处理方法:使用比较多的有(ApplicationContextPostProcessor,设置ApplicationContext、Environment、ResourceLoader、EmbeddedValueResolver等对象)
5. 调用initMethod方法:invokeInitMethod判断是否实现了initializingBean接口,如果有调用afterPropertiesSet方法,没有就不调用。
6. 调用BeanPostProcessor的后置处理方法:Spring的aop会在此处实现(AbstractAutoProxyCreator实现类,前置方法也会实现aop)
7. 注册Destruction相关的回调接口:钩子函数
8. 这时候就可以通过getBean的方式进行Bean的获取使用了。
9. 销毁流程,1、判断是否实现了DisposableBean接口,2、调用destroyMethod方法。

三、Spring的启动的核心构建流程就是refresh方法的核心内容:
1. 首先我们会创建一个Bean工厂,类型为DefaultListableBeanFactory,然后向Bean工厂中设置一些参数(BeanPostProcessor、Aware接口的子类)等属性。
2. 通过注解或者xml加载解析Bean对象,将解析到的Bean封装成BeanDefinition,并且放到本地缓存中。
3. 实例化和调用BeanFactoryPostProcessor的扩展方法(BeanDefinitionRegistryPostProcessor),方便后续对Bean对完成具体的扩展功能。
4. 通过反射的方式将BeanDefinition对象实例化为具体的Bean对象,实例化所有剩余的Bean实例(非懒加载),完成Bean的初始化过程(Bean的生命周期)。
5. 完成容器的刷新,推送上下文刷新完毕事件(ContextRefreshedEvent)到监听器。

四、首先我们需要知道什么是循环依赖,它是指两个或者两个以上的bean(包括自己依赖自己)之间的相互依赖形成闭环。
比如beanA和beanB的相互依赖,循环依赖是在bean的创建过程中产生的,bean的创建主要是实例化和初始化(填充属性)。
1. 先创建A对象,实例化A对象,此时A对象中的b属性为空,需要填充b属性
2. 那么会到容器查找B对象,如果找到了,直接进行赋值就不存在循环依赖问题了(不通),找不到直接创建B对象
3. 实例化B对象,此时B对象中a属性为空,填充属性a
4. 从容器中查找A对象,找不到,直接创建。
以上就是形成闭环的原因。
此时,如果仔细琢磨就会发现A对象是存在的,只不过此时的A对象不是一个完整的状态,只完成了实例化但是未完成初始化,在程序调用的过程中,如果拥有了某个对象的引用是可以在后期给他完成赋值操作的,那么就可以给非完整状态的对象优先赋值,等待后续操作来完成赋值,相当于提前暴露了某个不完整对象的引用,所以解决问题的核心在于实例化和初始化分开操作,这是解决循环依赖的关键。

当所有对象都完成实例化和初始化操作之后,还要把完整的对象放到容器中,此时的容器中存在着两种对象的状态,一种是完成实例化未完成初始化、一种是完整状态,因为都在容器中,所以要使用不同的map结构来进行存储,此时就有了一级缓存和二级缓存,如果一级缓存中有了,那么二级缓存中就不会存在同名的对象,因为他们的查找顺序是1、2、3这样的方式来查找的。一级缓存中存在的是完整对象,二级缓存中存放的是非完整对象。

那么为什么需要三级缓存呢,二级缓存其实已经解决了循环依赖的问题。三级缓存的value类型是ObjectFactory,是一个函数式接口,存在的意义是保证在整个容器的运行过程中同名的bean对象只能有一个。
如果一个对象需要被代理,或者说要生成代理对象,那么要优先生成一个普通的对象。由于普通对象和代理对象是不能同时出现在容器中的,因此当一个对象需要被代理的时候,就要使用代理对象覆盖掉之前的普通对象,在实际调用过程中,是没有办法确定什么时候对象被使用,所以就要求当某个对象被调用的时候,优先判断此对象是否需要被代理类似一种回调机制的实现,因此传入Lambda表达式的时候,可以通过Lambda表达式来执行对象的覆盖过程,通过getEarlyBeanReference()方法。

Spring本身不能解决构造器依赖,可以加@Lazy注解解决,也可以自己通过@PostConstructor注解提前初始化好。

缓存的放置时间和删除时间:三级缓存:createBeanInstance之后: addSingletonFactory。 二级缓存:第一次从三级缓存确定对象是代理对象还是普通对象的时候,同时删除三级缓存getSingleton。一级缓存:生成完整对象之后放到一级缓存,删除二三级缓存:addSingleton。

五、首先要知道自动装配是什么,解决了什么问题。
​ 1、当启动springboot应用程序的时候,会先创建SpringApplication的对象,在对象的构造方法中会进行某些参数的初始化工作,最主要的是判断当前应用程序的类型以及初始化器和监听器,在这个过程中会加载整个应用程序中的spring.factories文件,将文件中的内容放到缓存对象中,方便后续获取。

​ 2、SpringApplication对象创建完成之后,开始执行run方法,来完成整个启动,启动过程中最主要的有两个方法,第一个叫做prepareContext,第二个叫做refreshContext,在这两个关键步骤中完成了自动装配的核心功能,前面的处理逻辑包含了上下文对象的创建,banner的打印,异常报告器的准备等各个准备工作,方便后续来进行调用。

​ 3、在prepareContext方法中主要完成的是对上下文对象的初始化操作,包含了属性值的设置,比如环境对象,在整个过程中有一个非常重要的方法,叫做load,load主要完成一件事,将当前启动类作为一个beanDefinition注册到registry中,方便后续在进行BeanFactoryPostProcessor调用执行的时候,找到对应的主类,来完成@SpringBootApplication、@EnableAutoConfiguration等注解的解析工作。

​ 4、在refreshContext方法中会进行整个容器的刷新过程,会调用spring中的refresh方法,refresh中有13个非常关键的方法,来完成整个spring应用程序的启动,在自动装配过程中,会调用invokeBeanFactoryPostProcessor方法,在此方法中主要是对ConfigurationClassPostProcessor类的处理,这次是BFPP的子类也是BDRPP的子类,在调用的时候会先调用BDRPP中的postProcessBeanDefinitionRegistry方法,然后调用postProcessBeanFactory方法,在执行postProcessBeanDefinitionRegistry的时候会解析处理各种注解,包含@PropertySource,@ComponentScan,@ComponentScans,@Bean,@Import等注解,最主要的是@Import注解的解析

​ 5、在解析@Import注解的时候,会有一个getImports的方法,从主类开始递归解析注解,把所有包含@Import的注解都解析到,然后在processImport方法中对Import的类进行分类,此处主要识别的是AutoConfigurationImportSelect归属于ImportSelect的子类,在后续调用过程中会调用deferredImportSelectorHandler中的process方法,来完成EnableAutoConfiguration的加载。

六、相同点:都是用来创建Bean对象的。
不同点:使用BeanFactory创建对象的时候,必须要遵循严格的生命周期流程太复杂了,如果要简单的自定义某个对象的创建,同时创建完成的对象交给spring来管理,那么就需要实现FactoryBean接口了。
FactoryBean里面有三个方法:
isSingleton:是否是单例
getObjectType:获取返回对象的类型
getObject: 自定义创建对象的过程(new,反射,动态代理)
个人理解:BeanFactory批量生产,FactoryBean是定制(比如自己想要一件喜欢的衣服,没必要走批量生产流程,私人定制更好更满意),另外FactoryBean被广泛用于Java中间件中。

七、BeanFactory:基础的IOC容器,提供完整的一套IOC基本实现。
ApplicationContext:是BeanFactory的子接口,在BeanFactory的基础上进一步扩展增强,包好了BeanFactory的所有功能,还提供了其他的高级特性,比如事件发布、国际化支持、统一资源加载策略等。一般我们都是使用ApplicationContext。

八、Spring Aop是IOC的一个扩展功能,先有IOC再有AOP,只是在整个流程中增加一个扩展点BeanPostProcessor而已。本质是通过动态代理实现的。实现步骤:
创建Aspect切面类,设置切点,然后创建Bean的时候会检查是否有增强器能作用于该Bean,比如Bean是否配置了execution表达式,如果是则将增强器作为拦截器参数,则使用JDK或者Cglib的方式生成代理对象。在执行方法的时候会走到代理类中,调用生成的字节码文件中的intercepter方法,从而方法开始执行,根据之前定义好的通知生成拦截器链,本质和拦截器类似。(从拦截器链中依次获取每一个通知开始进行执行,在执行过程中,为了方便找到下一个通知是哪个,会有一个CglibMethodInvocation的对象,找的时候是从-1的位置依次开始查找并且执行的。)

九、Spring的事务原理还没搞清楚,后续补充。

Spring事务传播特性有七种,常用的就是REQUIRED和REQUIRES_NEW

REQUIRED:Spring 默认的事务传播级别,如果上下文中已经存在事务,那么就加入到事务中执行,如果当前上下文中不存在事务,则新建事务执行。

REQUIRES_NEW:每次都会新建一个事务,如果上下文中有事务,则将上下文的事务挂起,当新建事务执行完成以后,上下文事务再恢复执行。

NESTED:嵌套事务。如果上下文中存在事务,则嵌套事务执行,如果不存在事务,则新建事务。

SUPPORTS:如果上下文存在事务,则加入到事务执行,如果没有事务,则使用非事务的方式执行。

NOT_SUPPORTED :如果上下文中存在事务,则挂起事务,执行当前逻辑,结束后恢复上下文的事务。

MANDATORY:上下文中必须要存在事务,否则就会抛出异常。

NEVER:上下文中不能存在事务,否则就会抛出异常。

十、Spring中用到的设计模式,找自己熟悉的回答:比如字体加粗的

单例模式:spring中的bean默认都是单例的

原型模式:指定作用域为prototype

工厂模式:BeanFactory

模板方法:postProcessBeanFactory、onRefresh、initPropertyValue

策略模式:BeanDefinitionReader(XMLBeanDefinitionReader、PropertiesBeanDefinitionReader)

观察者模式:spring中的事件监听使用的就是观察者模式,listener、event、multicast

适配器模式:Adapter

装饰者模式:BeanWrapper、InputSource

责任链模式:在aop的通知执行的时候会使用(过滤器、拦截器的执行) 代理模式:AOP的实现机制动态代理,cglib动态代理和jdk动态代理

委托者模式:delegate

建造者模式:以builder后缀的实现。

一事精致,便能动人。
心心在一艺,其艺必工;心心在一职,其职必举。

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
上百节课详细讲解,需要的小伙伴自行百度网盘下载,链接见附件,永久有效。 课程介绍: 01_先来看一个互联网java工程师的招聘JD 02_互联网Java工程师面试突击训练课程第一季的内容说明 03_关于互联网Java工程师面试突击训练课程的几点说明 04_体验一下面试官对于消息队列的7个连环炮 05_知其然而知其所以然:如何进行消息队列的技术选型? 06_引入消息队列之后该如何保证其高可用性? 07_我的天!我为什么在消息队列里消费到了重复的数据? 08_啥?我发到消息队列里面的数据怎么不见了? 09_我该怎么保证从消息队列里拿到的数据按顺序执行? 10_完了!生产事故!几百万消息在消息队列里积压了几个小时! 11_如果让你来开发一个消息队列中间件,你会怎么设计架构? 12_总结一下消息队列相关问题的面试技巧 13_体验一下面试官对于分布式搜索引擎的4个连环炮 14_分布式搜索引擎的架构是怎么设计的?为啥是分布式的? 15_分布式搜索引擎写入和查询的工作流程是什么样的? 16_分布式搜索引擎在几十亿数据量级的场景下如何优化查询性能? 17_你们公司生产环境的分布式搜索引擎是怎么部署的呢? 18_总结一下分布式搜索引擎相关问题的面试技巧 19_先平易近人的随口问你一句分布式缓存的第一个问题 20_来聊聊redis的线程模型吧?为啥单线程还能有很高的效率? 21_redis都有哪些数据类型?分别在哪些场景下使用比较合适呢? 22_redis的过期策略能介绍一下?要不你再手写一个LRU? 23_怎么保证redis是高并发以及高可用的? 24_怎么保证redis挂掉之后再重启数据可以进行恢复? 25_你能聊聊redis cluster集群模式的原理吗? 26_你能说说我们一般如何应对缓存雪崩以及穿透问题吗? 27_如何保证缓存与数据库双写时的数据一致性? 28_你能说说redis的并发竞争问题该如何解决吗? 29_你们公司生产环境的redis集群的部署架构是什么样的? 30_分布式缓存相关面试题的回答技巧总结 31_体验一下面试官可能会对分布式系统发起的一串连环炮 32_为什么要把系统拆分成分布式的?为啥要用dubbo? 33_dubbo的工作原理是啥?注册中心挂了可以继续通信吗? 34_dubbo都支持哪些通信协议以及序列化协议? 35_dubbo支持哪些负载均衡、高可用以及动态代理的策略? 36_SPI是啥思想?dubbo的SPI机制是怎么玩儿的? 37_基于dubbo如何做服务治理、服务降级以及重试? 38_分布式系统中接口的幂等性该如何保证?比如不能重复扣款? 39_分布式系统中的接口调用如何保证顺序性? 40_如何设计一个类似dubbo的rpc框架?架构上该如何考虑? 41_说说zookeeper一般都有哪些使用场景? 42_分布式锁是啥?对比下redis和zk两种分布式锁的优劣? 43_说说你们的分布式session方案是啥?怎么做的? 44_了解分布式事务方案吗?你们都咋做的?有啥坑? 45_说说一般如何设计一个高并发的系统架构? 46_体验一下面试官对于分库分表这个事儿的一个连环炮 47_来来来!咱们聊一下你们公司是怎么玩儿分库分表的? 48_你们当时是如何把系统不停机迁移到分库分表的? 49_好啊!那如何设计可以动态扩容缩容的分库分表方案? 50_一个关键的问题!分库分表之后全局id咋生成? 51_说说MySQL读写分离的原理?主从同步延时咋解决? 52_如何设计高可用系统架构?限流?熔断?降级?什么鬼!

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

迷梦星河

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值