Spring
Bean 生命周期
实例化 Instantiation
属性赋值 Populate
初始化 Initialization
销毁 Destruction
Bean 加载过程
以 ClassPathXmlApplicationContext 的启动为例,展开大致的分析:
调用 AbstractApplicationContext 的 refresh 方法,完成内部容器的加载,初始化,其中会调用 finishBeanFactoryInitialization 方法来初始化所有的 singleton bean
再调用 DefaultListableBeanFactory 的 preInstantiateSingletons 方法,其中重点是
getBean
getBean 本身调用的是 AbstractBeanFactory 的 doGetBean 方法,该方法我们仅仅分析获取 singleton 的相关逻辑步骤如下:
(1)先从缓存中获取 singleton bean(可能是 eagerly 类型的)
(2)如果获取的是 null,则需要进行创建,先加载 dependsOn 的 bean
(3)如果需要获取的是 singleton bean,则调用 DefaultSingletonBeanRegistry 的 getSingleton 方法
getSingleton 会先对要创建的 bean 进行标记,然后调用传入的 singletonFactory 的 getObject 方法获取 bean,最后将新创建的 bean 放进缓存中
singletonFactory 实际是一个 ObjectFactory 的子类,其中的 getObject 方法调用的 AbstractAutowireCapableBeanFactory 的 createBean 方法,该方法的核心是 doCreateBean
doCreateBean 内部是 bean 创建的核心,包含了实例化,属性赋值,初始化,下面也大致分析下流程:
(1)createBeanInstance 方法创建 bean 实例,其方法内部会根据构造器类型选择如何进行创建 bean
(2)如果支持循环依赖,就会使用 DefaultSingletonBeanRegistry 的 addSingletonFactory 更新缓存
(3)populateBean 方法根据 bean 名称或类型进行依赖注入,其中会再调getBean
方法获取依赖 bean
(4)initializeBean 方法会对 bean 实例进行初始化,会调用对应的 BeanPostProcessors 的方法
经过以上大致的步骤,bean 基本就加载完成了。
Bean 循环依赖
循环依赖其实就是循环引用,也就是两个或则两个以上的 bean 互相持有对方,最终形成闭环。
Spring 中循环依赖场景有:
(1)构造器的循环依赖
(2)field 属性的循环依赖。
检测循环依赖相对比较容易,Bean 在创建的时候可以给该 Bean 进行标记,如果递归调用回来发现正在创建中的话,即说明了循环依赖了。
Spring 怎么解决循环依赖
Spring 是通过递归的方式获取目标 bean 及其所依赖的 bean 的;
Spring 实例化一个 bean 的时候,是分两步进行的,首先实例化目标 bean,然后为其注入属性。
Spring 为了解决单例的循环依赖问题,使用了三级缓存。
这三级缓存分别指:
singletonFactories : 单例对象工厂的 cache
earlySingletonObjects :提前暴光的单例对象的 Cache
singletonObjects:单例对象的 cache
创建 bean 的时候,首先是从 cache 中获取这个单例的 bean,这个缓存就是 singletonObjects。主要调用方法就就是:
isSingletonCurrentlyInCreation()判断当前单例 bean 是否正在创建中,也就是没有初始化完成
allowEarlyReference 是否允许从 singletonFactories 中通过 getObject 拿到对象
分析 getSingleton 的整个过程:
Spring 首先从一级缓存 singletonObjects 中获取。
如果获取不到,并且对象正在创建中,就再从二级缓存 earlySingletonObjects 中获取。
如果还是获取不到且允许 singletonFactories 通过 getObject()获取,就从三级缓存 singletonFactory.getObject(三级缓存)获取,如果获取到了则移除该 singletonFactory,并将 bean 放入 earlySingletonObjects 中。其实也就是从三级缓存移动到了二级缓存。
实际上,Spring 解决循环依赖的诀窍就在于 singletonFactories 这个三级 cache。
当使用 createBeanInstance 方法创建 bean 的实例(调用了构造器,还未发生属性赋值以及初始化)后,会进一步调用 addSingletonFactory 方法更新缓存(会将对应的 singleton bean 的 ObjectFactory 存放起来),所以 Spring 此时将这个对象提前曝光出来让其他的 bean 引用。
Spring 动态代理
JDK 反射动态代理
(一)实现原理
JDK
的动态代理是基于反射实现。JDK
通过反射,生成一个代理类,这个代理类实现了原来那个类的全部接口,并对接口中定义的所有方法进行了代理。当我们通过代理对象执行原来那个类的方法时,代理类底层会通过反射机制,回调我们实现的InvocationHandler
接口的invoke
方法。这就是JDK
动态代理大致的实现方式。并且这个代理类是 Proxy 类的子类。
(二)优点
JDK
动态代理是JDK
原生的,不需要任何依赖即可使用;通过反射机制生成代理类的速度要比
CGLib
操作字节码生成代理类的速度更快;
(三)缺点
如果要使用
JDK
动态代理,被代理的类必须实现了接口,否则无法代理;JDK
动态代理无法为没有在接口中定义的方法实现代理,假设我们有一个实现了接口的类,我们为它的一个不属于接口中的方法配置了切面,Spring
仍然会使用JDK
的动态代理,但是由于配置了切面的方法不属于接口,为这个方法配置的切面将不会被织入。JDK
动态代理执行代理方法时,需要通过反射机制进行回调,此时方法执行的效率比较低;
CGLib 动态代理
(一)实现原理
CGLib
实现动态代理的原理是,底层采用了ASM
字节码生成框架,直接对需要代理的类的字节码进行操作,生成这个类的一个子类,并重写了类的所有可以重写的方法,在重写的过程中,将我们定义的额外的逻辑(简单理解为Spring
中的切面)织入到方法中,对方法进行了增强。而通过字节码操作生成的代理类,和我们自己编写并编译后的类没有太大区别。
(二)优点
使用
CGLib
代理的类,不需要实现接口,因为CGLib
生成的代理类是直接继承自需要被代理的类;CGLib
生成的代理类是原来那个类的子类,这就意味着这个代理类可以为原来那个类中,所有能够被子类重写的方法进行代理;CGLib
生成的代理类,和我们自己编写并编译的类没有太大区别,对方法的调用和直接调用普通类的方式一致,所以CGLib
执行代理方法的效率要高于JDK
的动态代理;
(三)缺点
由于
CGLib
的代理类使用的是继承,这也就意味着如果需要被代理的类是一个final
类,则无法使用CGLib
代理;由于
CGLib
实现代理方法的方式是重写父类的方法,所以无法对final
方法,或者private
方法进行代理,因为子类无法重写这些方法;CGLib
生成代理类的方式是通过操作字节码,这种方式生成代理类的速度要比JDK
通过反射生成代理类的速度更慢;
SpringMVC 架构
第一步:发起请求到前端控制器(DispatcherServlet)
第二步:前端控制器请求 HandlerMapping 查找 Handler
第三步:处理器映射器 HandlerMapping 向前端控制器返回 Handler
第四步:前端控制器调用处理器适配器去执行 Handler
第五步:处理器适配器去执行 Handler
第六步:Handler 执行完成给适配器返回 ModelAndView
第七步:处理器适配器向前端控制器返回 ModelAndView
第八步:前端控制器请求视图解析器去进行视图解析
第九步:视图解析器向前端控制器返回 View
第十步:前端控制器进行视图渲染
第十一步:前端控制器向用户响应结果
Springboot Starter
SpringBoot 拥有很多方便使用的 starter(Spring 提供的 starter 命名规范 spring-boot-starter-xxx.jar,第三方提供的 starter 命名规范 xxx-spring-boot-starter.jar),比如 spring-boot-starter-log4j、mybatis-spring-boot-starter.jar 等,各自都代表了一个相对完整的功能模块。
SpringBoot-starter 是一个集成接合器,完成两件事:
引入模块所需的相关 jar 包
自动配置各自模块所需的属性
评论