面试突击:spring

平时工作很多概念性的东西用不到,长时间不用吧,容易忘,但面试又确实是要问,只能复习面试的时候整理下来,也是再学习一遍。

真希望能找到一份,能把学习到的诸多理论知识变成实践的工作,而不是业务复杂,但技术不复杂的工作,嗨,加油吧。

IOC

IOC全称是Inverse of Control,控制反转,也叫依赖注入

这是一种设计思想,就是原本是你手动创建类,现在让spring框架来创建和管理,spring在一个map里管理这些类,key是对象的id或者name,value是对象(BeanDefinition)

先看一下简化后的结构图:

然后简单的对着图说一下架构:

  1. BeanFactory是整个IOC的核心,现在先不说,后面会引出
  2. 我们现在springboot最常用的一般是注解式,那么我们就从注解式的注入bean说起
  3. 注解式注入bean的环境,就是图中的AnnotationConfigApplicationContext,看名字就知道
  4. 它继承了父类:GenericApplicationContext
  5. 而GenericApplicationContext的父类,是AbstractApplicationContext
  6. 这个类有两个比较重要的子类,一个是刚刚说的Generic的子类,实现注解方式注入。另一个是XML的子类,是通过XML方式来注入。
  7. AbstractApplicationContext实现了ConfigurableApplicationContext接口
  8. 而ConfigurableApplicationContext又实现了ApplicationContext接口,这个类就太熟悉了吧
  9. 而我们再看ApplicationContext,又会发现它继承了ListableBeanFactory,顾名思义,可以通过这个BeanFactory获取多个Bean
  10. 而ListableBeanFactory,继承的就是BeanFactory

现在再说一下一个加了@Bean注解的方法是如何注入类的

  1. 会先进在AnnotationConfigApplicationContext的构造函数
  2. 在这个函数里有两个主要过程,register注册和refresh初始化
  3. 在register里,把beanClass封装成了BeanDefinition
  4. 然后GenericApplicationContext的构造函数里,初始化了一个DefaultListableBeanFactory
  5. 就把刚刚封装好beanDefinition放进了初始化好的ConcurrentHashMap里,key是name(也可以是id),value就是BeanDefinition
  6. 到这一步就注册好了,注意这里还没有创建Bean对象
  7. refresh这里的工作比较多,简单的说就是先做了一些准备工作(比如准备了上下文环境context,准备了beanFactory,ConfigurableListableBeanFactory,准备BeanPostProcessor,准备监听器之类的)
  8. 然后在finishBeanFactoryInitialization根据之前注册好的beanDefinition,利用反射创建bean的实例,并初始化
  9. 然后因为这些context或者beanFactory最终都是实现了BeanFactory的接口,所以就可以通过BeanFactory里面的接口来getBean了
  10. 也因为AutowireCapableBeanFactory实现了BeanFactory,也可以@Autowire这样用了
  11. 都是从之前说的那个concurrentHashMap里面取的

AOP

一般在日志啊,事务啊,或者封装返回值的时候用到AOP,减少系统重复代码

就在希望配置的类上加注解@Aspect,在方法前执行的加@Before,方法后执行的加@AfterReturning,用@Pointcut指定影响的路径

AOP是基于动态代理,如果要代理的对象实现了接口,就走的JDK Proxy,没有实现接口的,走的Cglib

走JDK Proxy的话有的时候会有问题,所以springboot2.0以后默认都用Cglib了,用的AspectJ

因为JDK Proxy是基于接口的,代理生成的对象只能赋值给接口,如果是个实现类,比如:

1
2
@Autowired
UserServiceImpl userService;

那么就会报错

实现原理就是:

  1. 之前IOC提到了,在BeanFactory创建的时候,走Refresh方法,方法里面有一些准备工作,其中就有初始化了一些PostProcessor,如果开启了AOP,这里面就包括了AnnotationAwareAspectJAutoProxyCreator(这么长我肯定记不住,我就记ProxyCreator)
  2. 然后会在最后创建Bean对象时,会有一个拦截,看是否匹配了pointcut
  3. 如果匹配了,会生成一个代理对象,将代理对象返回给容器。这样以后getBean取的实际上就只是代理对象了。
  4. 向ProxyCreator返回真正的Bean实例,注册各种拦截器

循环依赖

循环依赖主要有两种,一种是构造器的循环依赖,一种是属性(或者@autowired)的循环依赖

通过构造器的循环依赖是没办法的,只能通过代码的方式去避免

通过属性注入导致的循环依赖,spring是通过三级缓存来避免的

三级缓存

在refrsh里面的finishBeanFactoryInitialization方法里,有个准备准备单例实例的方法preInstantiateSingletons,就是在这里实例化和初始化

这里面在getSingleton三级缓存,也就是三个map,一个对象的创建要分别经过这三个map,到二级的时候就把三级的缓存删掉,到一级到时候就把二级的缓存删掉,最后交还给容器的时候再把一级缓存删掉

  1. 三级:SingletonFactories 里面只有ObjectFactory,如果是构造器级的循环依赖,这个ObjectFactory都构建不起来,所以没法解决
  2. 二级:earlySingletonObjects 实例化了,但未初始化注入属性,但已经可以被拿去用了,解决了循环依赖的问题
  3. 一级:SingletonObjects 实例化且注入属性了

所以如果两个Bean,A、B属性级的循环依赖,在初始化的过程是这样的:

  1. A实例化后开始注入属性,也就是从二级缓存过了在准备写到一级缓存的时候,依赖了B
  2. B开始准备BeanFactory,进入了三级缓存
  3. B根据三级缓存的BeanFactory实例化了,进入了二级缓存,因为B没有开始注入属性,所以不涉及依赖
  4. A从二级缓存中取到了B,完成了注入属性,结束了A的Bean的实例化和初始化
  5. 该B初始化的时候,直接从二级缓存中取到已经实例化并初始化的earlySingletonObject
  6. 给B注入属性,因为A已经完成了,所以可以顺利完成了

事务隔离级别

和之前说的Mysql是一样的,比Mysql多一种,就是和数据库事务隔离级别保持一致

事务传播

支持当前事务:

  1. 当前存在,则加入;当前不存在,创建个新事务
  2. 当前存在,则加入;当前不存在,不以事务运行
  3. 当前存在,则加入;当前不存在,抛异常

不支持当前事务:

  1. 创建新事务,如果当前存在事务,给当前事务挂起
  2. 以非事务运行,如果当前存在事务,给当前事务挂起
  3. 以非事务运行,如果当前存在事务,抛异常

其他情况:

  1. 如果当前有事务,作为其嵌套事务;如果没有,创建新事务

自定义starter

  1. 创建名字为 xxx-spring-boot-starter 的启动器项目。
  2. 创建名字为 xxx-spring-boot-autoconfigure 的项目。
    • 编写属性绑定类 xxxProperties.
    • 编写服务类,引入 xxxProperties.
    • 编写自动配置类XXXAutoConfiguration注入配置。
    • 创建 spring.factories 文件,用于指定要自动配置的类。
  3. 启动器项目为空项目,用来引入 xxx-spring-boot-autoconfigure等其他依赖。
  4. 项目引入 starter,配置需要配置的信息。

代码样例:

https://www.cnblogs.com/niumoo/p/11775009.html

Comments

You need to set client_id and slot_id to show this AD unit. Please set it in _config.yml.