面试官:我看到你的简历写着熟悉Spring
面试官:要不你来讲讲Spring 的IOC和AOP你是怎么理解的呗?
候选者:嗯嗯,IOC和AOP是Spring非常核心的知识点
候选者:我就先来讲讲Spring IOC?
面试官:嗯
候选者:我个人理解下:Spring IOC 解决的是对象管理和对象依赖的问题。
候选者:本来是我们自己手动new出来的对象,现在则把对象交给Spring的IOC容器管理
候选者:IOC容器可以理解为一个对象工厂,我们都把该对象交给工厂,工厂管理这些对象的创建以及依赖关系
候选者:等我们需要用对象的时候,从工厂里边获取就好了
面试官:嗯,说起IOC,就可以在网上或书籍经常看到的两个概念
候选者:哦,你说的就是「控制反转
」和「注入依赖
」吧?
面试官:你怎么还抢答的咯…那你顺便说说你对这两个概念的理解呗?
候选者:我认为「控制反转」指的就是:把原有自己掌控的事交给别人去处理
候选者:它更多的是一种思想或者可以理解为设计模式
候选者:比如:本来由我们自己new出来的对象,现在交由IOC容器,把对象的控制权交给它方了
候选者:而「依赖注入」在我的理解下,它其实是「控制反转」的实现方式
候选者:对象无需自行创建或者管理它的依赖关系,依赖关系将被「自动注入」到需要它们的对象当中去
面试官:嗯,那我想问问,用Spring IOC有什么好处吗?
面试官:或者换个问法:本来我可以new出来的对象,为什么我要交由Spring IOC容器 管理呢?
候选者:主要的好处在于「将对象集中统一管理」并且「降低耦合度」
候选者:如果面试官理解了「工厂模式」,那就知道为什么我们不直接new对象
面试官:好家伙,不行,这答案我观众不满意!
候选者:要说理由的话,可以举很多例子,比如说:
候选者:我用Spring IOC 可以方便 单元测试、对象创建复杂、对象依赖复杂、单例等等的,什么都可以交给Spring IOC
候选者:理论上自己new出来的都可以解决上面的问题,Spring在各种场景组合下有可能不是最优解
候选者:但new出来的你要自己管理,可能你得自己写工厂,得实现一大套的东西才能满足需求
候选者:写着写着有可能还是Spring的那一套
候选者:但现在Spring现在已经帮你实现了啊!
候选者:如果项目里的对象都是就new下就完事了,没有多个实现类,那没事,不用Spring也没啥问题
候选者:并且Spring核心不仅仅IOC啊,除了把对象创建出来,还有一整套的Bean生命周期管理
候选者:比如说你要实现对象增强,AOP不就有了吗?不然你还得自己创建代理..
面试官:好好好
面试官:但我看这届观众好像还是不太满意?
候选者:不,他们已经满意了。
面试官:那你继续来聊下Spring AOP呗?
候选者:Spring AOP 解决的是 非业务代码抽取的问题
候选者:AOP 底层的技术是动态代理,在Spring内实现依赖的是BeanPostProcessor
候选者:比如我们需要在方法上注入些「重复性」的非业务代码,就可以利用Spring AOP
候选者:所谓的「面向切面编程」在我理解下其实就是在方法前后增加非业务代码
面试官:那你在工作中实际用到过AOP去优化你的代码吗?
候选者:有的。当时我用AOP来对我们公司现有的监控客户端进行封装
候选者:一个系统离不开监控,监控基本的指标有QPS、RT、ERROR等等
候选者:对外暴露的监控客户端只能在代码里写对应的上报信息(灵活,但会与业务代码掺杂在一起)
候选者:于是我利用注解+AOP的方式封装了一把,只要方法/类上带有我自定义的注解
候选者:方法被调用时,就会上报AQS、RT等信息
候选者:实现了非业务代码与业务代码分离的效果(:
面试官:你们项目一般是怎么把对象交给IOC容器管理的?
面试官:换个问法:一般是怎么定义Bean的?
候选者:Spring提供了4种方式,分别是:
候选者:1):注解 2):XML 3):JavaConfig 4):基于Groovy DSL配置
候选者:一般项目我们用注解或XML比较多,少部分用JavaConfig
候选者:日常写业务代码一般用注解来定义各种对象,责任链这种一般配置在XML,「注解」解决不了的就用JavaConfig
候选者:总体而言,还是得看项目的代码风格吧(:
候选者:反正就是定义元数据,能给到Spring解析就好了
面试官:嗯,了解。
面试官:要不来聊聊你使用Spring的感受?
候选者:嗯嗯..
候选者:当我还是初学Spring的时候,我觉得Spring很麻烦,需要有一大堆的配置信息才能跑起来
候选者:光是搭建环境就需要耗费我好长的时间
候选者:毕竟版本冲突,依赖冲突什么的就可能一个下午就过去了
候选者:但毕竟一个系统环境只搭一次嘛,所以还好
候选者:(后来用上了SpringBoot这又更方便了)
面试官:嗯…
候选者:话说回来,IOC和AOP在工作用的时候还是很爽的
候选者:毕竟搞个注解什么的,配置下就可以把对象交给Spring管理了
候选者:配合Spring的生态,@Transactional注解什么的,都好用得飞起
候选者:不过,Spring给我们封装得太好了
候选者:经常就会有奇奇怪怪的”bug“出现,也踩过很多的坑了
候选者:Bean经常没办法创建成功,导致项目启动失败
候选者:对象的循环依赖问题…
候选者:同一个接口,多个实现,识别不出我要创建哪个对象…
候选者:为什么catch了异常,Spring事务为什么还会自动回滚
候选者:等等等…..
候选者:总的来说,Spring给我们封装了一个很好的环境,实现对我们屏蔽了
候选者:但是如果理解不深的话,很有可能就会触发各种bug
面试官:了解
面试官:今天要不来聊聊Spring对Bean的生命周期管理?
候选者:嗯,没问题的。
候选者:很早之前我就看过源码,但Spring源码的实现类都太长了
候选者:我也记不得很清楚某些实现类的名字,要不我大概来说下流程?
面试官:没事,你开始吧
候选者:首先要知道的是
候选者:普通Java对象和Spring所管理的Bean实例化的过程是有些区别的
候选者:在普通Java环境下创建对象简要的步骤可以分为:
候选者:1):java源码被编译为被编译为class文件
候选者:2):等到类需要被初始化时(比如说new、反射等)
候选者:3):class文件被虚拟机通过类加载器加载到JVM
候选者:4):初始化对象供我们使用
候选者:简单来说,可以理解为它是用Class对象作为「模板」进而创建出具体的实例
候选者:而Spring所管理的Bean不同的是,除了Class对象之外,还会使用BeanDefinition的实例来描述对象的信息
候选者:比如说,我们可以在Spring所管理的Bean有一系列的描述:@Scope、@Lazy、@DependsOn等等
候选者:可以理解为:Class只描述了类的信息,而BeanDefinition描述了对象的信息
面试官:嗯,这我大致了解你的意思了。
面试官:你就是想告诉我,Spring有BeanDefinition来存储着我们日常给Spring Bean定义的元数据(@Scope、@Lazy、@DependsOn等等),对吧?
候选者:不愧是你
面试官:赶紧的,继续吧
候选者:Spring在启动的时候需要「扫描」在XML/注解/JavaConfig 中需要被Spring管理的Bean信息
候选者:随后,会将这些信息封装成BeanDefinition,最后会把这些信息放到一个beanDefinitionMap中
候选者:我记得这个Map的key应该是beanName,value则是BeanDefinition对象
候选者:到这里其实就是把定义的元数据加载起来,目前真实对象还没实例化
候选者:接着会遍历这个beanDefinitionMap,执行BeanFactoryPostProcessor这个Bean工厂后置处理器的逻辑
候选者:比如说,我们平时定义的占位符信息,就是通过BeanFactoryPostProcessor的子类PropertyPlaceholderConfigurer进行注入进去
候选者:当然了,这里我们也可以自定义BeanFactoryPostProcessor来对我们定义好的Bean元数据进行获取或者修改
候选者:只是一般我们不会这样干,实际上也很有少的使用场景
面试官:嗯….
候选者:BeanFactoryPostProcessor后置处理器执行完了以后,就到了实例化对象啦
候选者:在Spring里边是通过反射来实现的,一般情况下会通过反射选择合适的构造器来把对象实例化
候选者:但这里把对象实例化,只是把对象给创建出来,而对象具体的属性是还没注入的。
候选者:比如我的对象是UserService,而UserService对象依赖着SendService对象,这时候的SendService还是null的
候选者:所以,下一步就是把对象的相关属性给注入(:
候选者:相关属性注入完之后,往下接着就是初始化的工作了
候选者:首先判断该Bean是否实现了Aware相关的接口,如果存在则填充相关的资源
候选者:比如我这边在项目用到的:我希望通过代码程序的方式去获取指定的Spring Bean
候选者:我们这边会抽取成一个工具类,去实现ApplicationContextAware接口,来获取ApplicationContext对象进而获取Spring Bean
候选者:Aware相关的接口处理完之后,就会到BeanPostProcessor后置处理器啦
候选者:BeanPostProcessor后置处理器有两个方法,一个是before,一个是after(那肯定是before先执行、after后执行)
候选者:这个BeanPostProcessor后置处理器是AOP实现的关键(关键子类AnnotationAwareAspectJAutoProxyCreator)
候选者:所以,执行完Aware相关的接口就会执行BeanPostProcessor相关子类的before方法
候选者:BeanPostProcessor相关子类的before方法执行完,则执行init相关的方法,比如说@PostConstruct、实现了InitializingBean接口、定义的init-method方法
候选者:当时我还去官网去看他们的被调用「执行顺序」分别是:@PostConstruct、实现了InitializingBean接口以及init-method方法
候选者:这些都是Spring给我们的「扩展」,像@PostConstruct我就经常用到
候选者:比如说:对象实例化后,我要做些初始化的相关工作或者就启个线程去Kafka拉取数据
候选者:等到init方法执行完之后,就会执行BeanPostProcessor的after方法
候选者:基本重要的流程已经走完了,我们就可以获取到对象去使用了
候选者:销毁的时候就看有没有配置相关的destroy方法,执行就完事了
面试官:嗯,了解,但我的观众好像不太满意,总感觉少了些什么。
面试官:你看过Spring是怎么解决循环依赖的吗?
面试官:如果现在有个A对象,它的属性是B对象,而B对象的属性也是A对象
面试官:说白了就是A依赖B,而B又依赖A,Spring是怎么做的?
候选者:嗯,这块我也是看过的,其实也是在Spring的生命周期里面嘛
候选者:从上面我们可以知道,对象属性的注入在对象实例化之后的嘛。
候选者:它的大致过程是这样的:
候选者:首先A对象实例化,然后对属性进行注入,发现依赖B对象
候选者:B对象此时还没创建出来,所以转头去实例化B对象
候选者:B对象实例化之后,发现需要依赖A对象,那A对象已经实例化了嘛,所以B对象最终能完成创建
候选者:B对象返回到A对象的属性注入的方法上,A对象最终完成创建
候选者:上面就是大致的过程;
面试官:听起来你还会原理哦?
候选者:Absolutely
候选者:至于原理,其实就是用到了三级的缓存
候选者:所谓的三级缓存其实就是三个Map…首先明确一定,我对这里的三级缓存定义是这样的:
候选者:singletonObjects(一级,日常实际获取Bean的地方);
候选者:earlySingletonObjects(二级,还没进行属性注入,由三级缓存放进来);
候选者:singletonFactories(三级,Value是一个对象工厂);
候选者:再回到刚才讲述的过程中,A对象实例化之后,属性注入之前,其实会把A对象放入三级缓存中
候选者:key是BeanName,Value是ObjectFactory
候选者:等到A对象属性注入时,发现依赖B,又去实例化B时
候选者:B属性注入需要去获取A对象,这里就是从三级缓存里拿出ObjectFactory,从ObjectFactory得到对应的Bean(就是对象A)
候选者:把三级缓存的A记录给干掉,然后放到二级缓存中
候选者:显然,二级缓存存储的key是BeanName,value就是Bean(这里的Bean还没做完属性注入相关的工作)
候选者:等到完全初始化之后,就会把二级缓存给remove掉,塞到一级缓存中
候选者:我们自己去getBean的时候,实际上拿到的是一级缓存的
候选者:大致的过程就是这样
面试官:那我想问一下,为什么是三级缓存?
候选者:首先从第三级缓存说起(就是key是BeanName,Value为ObjectFactory)
候选者:我们的对象是单例的,有可能A对象依赖的B对象是有AOP的(B对象需要代理)
候选者:假设没有第三级缓存,只有第二级缓存(Value存对象,而不是工厂对象)
候选者:那如果有AOP的情况下,岂不是在存入第二级缓存之前都需要先去做AOP代理?这不合适嘛
候选者:这里肯定是需要考虑代理的情况的,比如A对象是一个被AOP增量的对象,B依赖A时,得到的A肯定是代理对象的
候选者:所以,三级缓存的Value是ObjectFactory,可以从里边拿到代理对象
候选者:而二级缓存存在的必要就是为了性能,从三级缓存的工厂里创建出对象,再扔到二级缓存(这样就不用每次都要从工厂里拿)
候选者:应该很好懂吧?
面试官:确实(:
候选者:我稍微总结一下今天的内容吧
候选者:怕你的观众说不满意,那我就没有赞了,没有赞我就很难受
候选者:首先是Spring Bean的生命周期过程,Spring使用BeanDefinition来装载着我们给Bean定义的元数据
候选者:实例化Bean的时候实际上就是遍历BeanDefinitionMap
候选者:Spring的Bean实例化和属性赋值是分开两步来做的
候选者:在Spring Bean的生命周期,Spring预留了很多的hook给我们去扩展
候选者:1):Bean实例化之前有BeanFactoryPostProcessor
候选者:2):Bean实例化之后,初始化时,有相关的Aware接口供我们去拿到Context相关信息
候选者:3):环绕着初始化阶段,有BeanPostProcessor(AOP的关键)
候选者:4):在初始化阶段,有各种的init方法供我们去自定义
候选者:而循环依赖的解决主要通过三级的缓存
候选者:在实例化后,会把自己扔到三级缓存(此时的key是BeanName,Value是ObjectFactory)
本文系作者在时代Java发表,未经许可,不得转载。
如有侵权,请联系nowjava@qq.com删除。