Q: IOC 和 AOP
spring 要控制和管理bean,就要从bean的配置,实例化,设置属性,初始化,这一套的实现以及操作bean就是控制反转 IOC。 Aop是切面编程,对局部功能的增强。
Spring想要通过IOC来控制反转去创建bean,就需要从XML或注解文件中通过loader读取为 resource,再通过 bean define reader读取为 bean definition,保存在registry。再通过 stractegy策略来实例化,再使用bean wrapper填充属性,最后初始化完成bean的创建。在实例化和初始化的前后,肯定,是会有生命周期的回调。 InstantationAwareBeanPostProcessor用在实例化前后,beanPostProcessor作用于初始化阶段的前后。
Aop面向切面编程主要是使用aspectJ实现,并且引入了连接点,置入等概念。但它的实现主要还是用动态代理, 优先使用cjlib,可以改为JDK实现。
Q:如何解决循环依赖问题?
引入了AOP动态代理之后,循环依赖的解决方式会更加复杂。
就要用到三级缓存才能解决,一级缓存是单例成品的容器。singletonObjects,二级缓存是lazy懒加载的半成品容器earlySingletonObjects,三级缓存是工厂池singletonFactories。
如在创建A过程中,引用B,发现B对A是有引用的。
首先,A先从一级,二级,三级缓存依次找bean,如果3个缓存没有,会在创建一个FactoryA缓存在第3级的工厂池中,然后A要填充属性时,发现需要创建B。
于是,又先创建FactoryB到第3级工厂池,接着B填充属性,需要A,就把FactoryA的半成品Bean或Aop代理对象移动到2级缓存,这里看到,当需要Aop时,代理对象是在2级缓存之前暴露的;接着,把A半成品提供给B注入,在B创建完毕后,B的成品放入1级缓存,并删除掉B的3级缓存。
回到A,A此时拿到了B的成品注入,完全创建后,A的成品放入1级缓存,删除A的1级和2级缓存。
Q:为什么需要三级缓存? 不是二级缓存?
只要两个缓存确实可以做到解决循环依赖的问题,但是有一种情况,就是这个Bean需要AOP切面代理,如果加上AOP,两级缓存是无法解决的。如果不存在Aop的话,可以直接用二级缓存,因为Aop操作,不管是jdk动态代理还是cglib动态代理,都会创建一个bean代理,这个代理bean和原始目标bean属于不同的对象,有不同的内存空间。首先要知道正常流程中Spring希望AOP代理对象的创建是在Bean初始化的后置处理器中,而循坏依赖中会把aop代理对象的创建提前至二级缓存之前完成,下面说明为什么要在循坏依赖中提前完成aop,因为如果没有提前完成aop,那么B对象依赖的是A对象的原始bean,而后期A完成Aop后使用的是代理bean,这里肯定会有问题!所以必须要在循环依赖中提前创建好代理bean,并放到二级缓存中,同时删除三级缓存,避免再一次创建另一个代理bean。
3级缓存的value类型是ObjectFactory,是一个函数接口,它能生产两种产品:单例的原始半成品Bean和Bean代理对象,当需要AOP且循环依赖时才会产出Bean代理对象,其存在的意义是保证在整个容器的加载过程中同名的bean对象只能有一个,不能同时使用原始bean和代理bean。在对象A需要被对象B引用注入时,我们不想每次都从3级缓存的工厂中给我产生一个新的A的bean代理对象提供给B,与其走这些复杂流程,不如借助一个2级缓存来保存产生出来的代理对象,需要就从2级缓存获取。2级缓存主要想存入代理bean,为了统一,也会存入原始半成品Bean。
Q:为什么Bean代理对象,不一开始放进3级缓存,然后不要2级缓存?
可以但没必要,bean的加载不一定需要Aop,不一定有循环依赖,所以bean代理对象的创建非必须,3级缓存的意义,更偏向于指对原始Bean的缓存,只是为了适应也能生产Bean代理对象,才缓存ObjectFactory。不让3级缓存去存Bean代理对象的理由,首先,保证对象beanName在整个容器的加载过程中只能有一个,也就是说,不能同时使用原始bean和代理bean,其次,就是想实现懒加载代理Bean,需要代理Bean才创建,不需要就不创建不缓存。
Spring希望AOP代理对象的创建是在Bean初始化的后置处理器中。
但是,有循环依赖+AOP的情况下,因为Bean代理对象的产生流程被提前暴露在3级缓存之后、2级缓存之前,这样,2级缓存就可以统一了单例的半成品Bean和Bean代理对象的获取,已保证AOP代理对象产生流程只走一次(走多次也没关系,因为内部还有earlyProxyReferences单例缓存),当然了,AOP代理对象流程在Bean初始化的后置处理器中依旧还是会走一次的;
2级缓存的存在,避免循环依赖中再次通过工厂获取bean这一复杂流程,提升加载效率;因为从3级缓存获取对象时需要每次都通过工厂去拿,需要遍历所有的后置处理器、判断是否创建代理对象,而判断是否创建代理对象本身也是一个复杂耗时的过程;
Q:(问题与上题差不多)三级缓存为什么要使用工厂而不是直接使用引用?换而言之,为什么需要这个三级缓存,直接通过二级缓存暴露一个引用不行吗?
答:这个工厂的目的在于延迟对实例化阶段生成的对象的代理,只有真正发生循环依赖的时候,才去提前生成代理对象,否则只会创建一个工厂并将其放入到三级缓存中,但是不会去通过这个工厂去真正创建对象
Q:A能不能去掉2级缓存,然后代理对象放到1级缓存中,B直接引用1级缓存的A代理对象?
1级缓存放的是已经初始化完毕的 Bean,要知道 A依赖了B,A准备引用属性B时,这时候,A还没有初始化完毕,此时放到1级缓存,未成品被当成成品暴露,是有问题的。
Q:B中提前注入了一个没有经过初始化的A类型对象不会有问题吗?
答:不会。虽然在创建B时会提前给B注入了一个还未初始化的A对象,但是在创建A的流程中一直使用的是注入到B中的A对象的引用,之后会根据这个引用对A进行初始化,所以这是没有问题的。
Q:初始化的时候是对A对象本身进行一些初始化操作,而容器中以及注入到B中的都是代理对象,这样不会有问题吗?
答:不会,这是因为不管是cglib代理还是jdk动态代理生成的代理类,内部都持有一个目标类的引用,当调用代理对象的方法时,实际会去调用目标对象的方法,A完成初始化相当于代理对象自身也完成了初始化
(其实初始化A,是对exposedObject初始化吧,上述情况,它也是代理对象吧,所谓A对象本身,就是在代理对象的目标对象??这是个人想法)
Q:三级缓存比二级缓存提高了bean加载效率了吗?
没有,只是加载顺序有些差异。
earlyProxyReferences缓存相对于一般的普通类是不起作用的,所有的类在doCreateBean方法中的applyBeanPostProcessorsAfterInitialization方法(用来判断是否需要创建aop的方法)中去earlyProxyReferences缓存中找有没有这个beanname的bean,但是并不是所有的类的bean都会在earlyProxyReferences这个缓存中,因为只有当的doCreateBean这个方法中addSingletonFactory(把创建当前bean的lamda表达式放进三级缓存)执行之后,有某个地方调用了getSingleton去缓存中再次获取当前这个bean的时候,当前的这个bean才会被放进earlyProxyReferences缓存作为一个备份,那么什么地方有可能去getSingleton获取当前的bean呢,按目前的知识储备来讲,似乎只有 当前bean被循环引用 了。。。
所以earlyProxyReferences这个缓存时为了解决,当当前的bean被循环引用的时候,避免重复判断aop流程的(wrapIfNecessary)
面试官:”为什么要使用三级缓存呢?二级缓存能解决循环依赖吗?“
简答:如果要使用二级缓存解决循环依赖,意味着所有Bean在实例化后就要完成AOP代理,这样违背了Spring设计的原则,Spring在设计之初就是通过AnnotationAwareAspectJAutoProxyCreator这个后置处理器来在Bean生命周期的最后一步来完成AOP代理,而不是在实例化后就立马进行AOP代理。
引用链接:https://zhuanlan.zhihu.com/p/157314153
SpringMVC流程
用户发起请求,被前端控制器DispatcherServlet拦截,然后调用处理器映射器HandlerMapping,根据URL去获得一个处理链Handler ExecutionChain,里边包括了拦截器Interceptor和我们写的Controller,通过层层拦截校验最终获得处理器适配器HandlerAdatper,同时真正执行处理器Hander(Controller),处理业务后,返回一个ModelAndView对象给前端控制器,里边包含数据和跳转的路径。然后前端控制器会选择一个合适的视图解析器ViewReslover去进行一个视图的渲染。
这里边,可以内部转发和重定向,可以处理文件上传和处理普通HTTP请求。
这里边,拦截器使用了责任链模式,handler的处理与视图的解析使用了适配器模式,利用反射处理请求入参和返回结果视图。
这里边,对于@ResponseBody,是在RequestMappingHandlerAdapter中设置了messageConverters的逻辑
,根据HTTP头的Accept信息,或默认JSON,通过HttpMessageConverters对消息对象的转化。
Spring容器启动流程
部署一个web应用在web容器中,它会提供一个全局的上下文环境,这个上下文就是ServletContext,它为后面的IoC容器提供宿主环境,当web容器启动的时候,会执行web.xml中的ContextLoaderListener监听器初始化contextInitialized方法,调用父类的initWebApplicationContext方法,这个方法里面执行了三个任务:1.创建WebApplicationContext容器,2.加载context-param中spring配置文件,3.初始化配置文件并且创建配置文件中的bean。监听器初始化完毕后,开始初始化web.xml中配置的servlet ,用DispatcherServlet举例,它是一个前端控制器,用来转发、匹配、处理每个servlet 请求。DispatcherServlet上下文在初始化的时候会建立自己的上下文,先从ServletContext 中获取之前的WebApplicationContext作为自己上下文的父类上下文,有了这个父类上下文之后,再初始化自己持有的上下文,创建springmvc相关的bean,初始化处理器映射、视图解析等等,初始化完后,spring把Servlet的相关的属性作为属性key,存到servletcontext中,方便后面使用。这样每个Servlet 都持有自己的上下文,拥有自己独立的bean 空间,各个servlet 共享相同的bean,也就是根上下文定义的那些bean。web容器停止时候会执行ContextLoaderListener的contextDestroyed方法销毁context容器。
SpringBoot启动流程
1、new了一个SpringApplication对象,使用SPI机制扫描spring.factories文件,加载所有的初始化器和监听器。
2、调用run() 方法,准备上下文环境,创建上下文对象,根据环境insert组件,比如说autowired,configuration等 。
3,最后刷新环境,启动spring容器和serverlet容器。
Springboot自动装配原理
主要是SPI机制。Springboot启动时会扫描Import注解找到Selector类,调用selectImports方法,读取所有spring.factories配置文件,根据文件中定义的自动配置类路径,按需将Bean加载到Spring容器中。
主要依赖类上的@ springbootapplication注解,它由3个分注解组成,@ComponentScan 扫描当前包和子包,@SpringBootConfiguration 代表当前是一个配置类,第三个数自动装配的核心,@EnableAutoConfiguration ,有两个@Import注解实现,第一个批量注册Bean,第二个是扫描系统中所有spring.factories拿到配置类,根据@Condition 配置是否生效。
Spring 事务传播机制
Spring有4个事务机制,它们的区别体现在嵌套时的不同传播策略:
对于无事务,要么创建事务,要么不创建,要么抛出异常;
对于一个创建事务,有嵌套时加入策略,嵌套时挂起并新建事务,嵌套时挂起并非事务运行,嵌套时抛出异常,嵌套时savePoint机制。
Mybatis 原理
读取mybatis-config,xml配置文件,加载Mapper.xml映射文件,里面放置了很多SQL语句,然后通过构造一个单例的会话工厂SqlSessionFactory,开启一个会话对象SqlSession,会话有API执行SQL语句,通过执行器Executor真正去与数据库通信,输入和查询结果通过MappedStatement交互。
Q:Spring 用到了哪些设计模式?
1、简单工厂模式:BeanFactory
就是简单工厂模式的体现,根据传入一个唯一标识来获得 Bean 对象。
2、工厂方法模式:FactoryBean
就是典型的工厂方法模式。spring在使用getBean()
调用获得该bean时,会自动调用该bean的getObject()
方法。每个 Bean 都会对应一个 FactoryBean
,如 SqlSessionFactory
对应 SqlSessionFactoryBean
。
3、单例模式:一个类仅有一个实例,提供一个访问它的全局访问点。Spring 创建 Bean 实例默认是单例的。
4、适配器模式:SpringMVC中的适配器HandlerAdatper
。由于应用会有多个Controller实现,如果需要直接调用Controller方法,那么需要先判断是由哪一个Controller处理请求,然后调用相应的方法。当增加新的 Controller,需要修改原来的逻辑,违反了开闭原则(对修改关闭,对扩展开放)。
为此,Spring提供了一个适配器接口,每一种 Controller 对应一种 HandlerAdapter
实现类,当请求过来,SpringMVC会调用getHandler()
获取相应的Controller,然后获取该Controller对应的 HandlerAdapter
,最后调用HandlerAdapter
的handle()
方法处理请求,实际上调用的是Controller的handleRequest()
。每次添加新的 Controller 时,只需要增加一个适配器类就可以,无需修改原有的逻辑。
常用的处理器适配器:SimpleControllerHandlerAdapter
,HttpRequestHandlerAdapter
,AnnotationMethodHandlerAdapter
。
5、代理模式:spring 的 aop 使用了动态代理,有两种方式JdkDynamicAopProxy
和Cglib2AopProxy
。
6、观察者模式:spring 中 observer 模式常用的地方是 listener 的实现,如ApplicationListener
。
7、模板模式: Spring 中 jdbcTemplate
、hibernateTemplate
等,就使用到了模板模式。
依赖注入方式
构造器注入,构造器注入会先初始化其依赖对象,而set注入是后初始化其依赖对象;
字段注入
Setter注入
@Autowired是Spring的注解,Autowired默认先按byType,如果发现找到多个bean,则,又按照byName方式比对,如果还有多个,则报出异常;@Resource 是JDK1.6支持的注解,默认按照名称(Byname)进行装配,而@Autowired还要结合@Qualifier注解来使用,且@Resource是jdk的注释,可与Spring解耦。