面试突击:spring
平时工作很多概念性的东西用不到,长时间不用吧,容易忘,但面试又确实是要问,只能复习面试的时候整理下来,也是再学习一遍。
真希望能找到一份,能把学习到的诸多理论知识变成实践的工作,而不是业务复杂,但技术不复杂的工作,嗨,加油吧。
IOC
IOC全称是Inverse of Control,控制反转,也叫依赖注入
这是一种设计思想,就是原本是你手动创建类,现在让spring框架来创建和管理,spring在一个map里管理这些类,key是对象的id或者name,value是对象(BeanDefinition)
先看一下简化后的结构图:
然后简单的对着图说一下架构:
- BeanFactory是整个IOC的核心,现在先不说,后面会引出
- 我们现在springboot最常用的一般是注解式,那么我们就从注解式的注入bean说起
- 注解式注入bean的环境,就是图中的AnnotationConfigApplicationContext,看名字就知道
- 它继承了父类:GenericApplicationContext
- 而GenericApplicationContext的父类,是AbstractApplicationContext
- 这个类有两个比较重要的子类,一个是刚刚说的Generic的子类,实现注解方式注入。另一个是XML的子类,是通过XML方式来注入。
- AbstractApplicationContext实现了ConfigurableApplicationContext接口
- 而ConfigurableApplicationContext又实现了ApplicationContext接口,这个类就太熟悉了吧
- 而我们再看ApplicationContext,又会发现它继承了ListableBeanFactory,顾名思义,可以通过这个BeanFactory获取多个Bean
- 而ListableBeanFactory,继承的就是BeanFactory
现在再说一下一个加了@Bean注解的方法是如何注入类的
- 会先进在AnnotationConfigApplicationContext的构造函数
- 在这个函数里有两个主要过程,register注册和refresh初始化
- 在register里,把beanClass封装成了BeanDefinition
- 然后GenericApplicationContext的构造函数里,初始化了一个DefaultListableBeanFactory
- 就把刚刚封装好beanDefinition放进了初始化好的ConcurrentHashMap里,key是name(也可以是id),value就是BeanDefinition
- 到这一步就注册好了,注意这里还没有创建Bean对象
- refresh这里的工作比较多,简单的说就是先做了一些准备工作(比如准备了上下文环境context,准备了beanFactory,ConfigurableListableBeanFactory,准备BeanPostProcessor,准备监听器之类的)
- 然后在finishBeanFactoryInitialization根据之前注册好的beanDefinition,利用反射创建bean的实例,并初始化
- 然后因为这些context或者beanFactory最终都是实现了BeanFactory的接口,所以就可以通过BeanFactory里面的接口来getBean了
- 也因为AutowireCapableBeanFactory实现了BeanFactory,也可以@Autowire这样用了
- 都是从之前说的那个concurrentHashMap里面取的
AOP
一般在日志啊,事务啊,或者封装返回值的时候用到AOP,减少系统重复代码
就在希望配置的类上加注解@Aspect,在方法前执行的加@Before,方法后执行的加@AfterReturning,用@Pointcut指定影响的路径
AOP是基于动态代理,如果要代理的对象实现了接口,就走的JDK Proxy,没有实现接口的,走的Cglib
走JDK Proxy的话有的时候会有问题,所以springboot2.0以后默认都用Cglib了,用的AspectJ
因为JDK Proxy是基于接口的,代理生成的对象只能赋值给接口,如果是个实现类,比如:
1 |
|
那么就会报错
实现原理就是:
- 之前IOC提到了,在BeanFactory创建的时候,走Refresh方法,方法里面有一些准备工作,其中就有初始化了一些PostProcessor,如果开启了AOP,这里面就包括了AnnotationAwareAspectJAutoProxyCreator(这么长我肯定记不住,我就记ProxyCreator)
- 然后会在最后创建Bean对象时,会有一个拦截,看是否匹配了pointcut
- 如果匹配了,会生成一个代理对象,将代理对象返回给容器。这样以后getBean取的实际上就只是代理对象了。
- 向ProxyCreator返回真正的Bean实例,注册各种拦截器
循环依赖
循环依赖主要有两种,一种是构造器的循环依赖,一种是属性(或者@autowired)的循环依赖
通过构造器的循环依赖是没办法的,只能通过代码的方式去避免
通过属性注入导致的循环依赖,spring是通过三级缓存来避免的
三级缓存
在refrsh里面的finishBeanFactoryInitialization方法里,有个准备准备单例实例的方法preInstantiateSingletons,就是在这里实例化和初始化
这里面在getSingleton三级缓存,也就是三个map,一个对象的创建要分别经过这三个map,到二级的时候就把三级的缓存删掉,到一级到时候就把二级的缓存删掉,最后交还给容器的时候再把一级缓存删掉
- 三级:SingletonFactories 里面只有ObjectFactory,如果是构造器级的循环依赖,这个ObjectFactory都构建不起来,所以没法解决
- 二级:earlySingletonObjects 实例化了,但未初始化注入属性,但已经可以被拿去用了,解决了循环依赖的问题
- 一级:SingletonObjects 实例化且注入属性了
所以如果两个Bean,A、B属性级的循环依赖,在初始化的过程是这样的:
- A实例化后开始注入属性,也就是从二级缓存过了在准备写到一级缓存的时候,依赖了B
- B开始准备BeanFactory,进入了三级缓存
- B根据三级缓存的BeanFactory实例化了,进入了二级缓存,因为B没有开始注入属性,所以不涉及依赖
- A从二级缓存中取到了B,完成了注入属性,结束了A的Bean的实例化和初始化
- 该B初始化的时候,直接从二级缓存中取到已经实例化并初始化的earlySingletonObject
- 给B注入属性,因为A已经完成了,所以可以顺利完成了
事务隔离级别
和之前说的Mysql是一样的,比Mysql多一种,就是和数据库事务隔离级别保持一致
事务传播
支持当前事务:
- 当前存在,则加入;当前不存在,创建个新事务
- 当前存在,则加入;当前不存在,不以事务运行
- 当前存在,则加入;当前不存在,抛异常
不支持当前事务:
- 创建新事务,如果当前存在事务,给当前事务挂起
- 以非事务运行,如果当前存在事务,给当前事务挂起
- 以非事务运行,如果当前存在事务,抛异常
其他情况:
- 如果当前有事务,作为其嵌套事务;如果没有,创建新事务
自定义starter
- 创建名字为
xxx-spring-boot-starter
的启动器项目。- 创建名字为
xxx-spring-boot-autoconfigure
的项目。
- 编写属性绑定类
xxxProperties
.- 编写服务类,引入
xxxProperties
.- 编写自动配置类
XXXAutoConfiguration
注入配置。- 创建
spring.factories
文件,用于指定要自动配置的类。- 启动器项目为空项目,用来引入
xxx-spring-boot-autoconfigure
等其他依赖。- 项目引入
starter
,配置需要配置的信息。
代码样例: