Spring 原理
目录
Bean 的作用域
概念
Bean 的作用域
Bean 的生命周期
源码阅读
Spring Boot 自动装配
Spring 加载 Bean
SpringBoot 源码
@EnableAutoConfiguration
1. @Import{Auto ConfigurationImportSelector.class}
2. @AutoConfigurationPackage
Spring 三级缓存
Spring 解决循环依赖
Spring MVC 流程
完!
Bean 的作用域
概念
在 Spring IoC&DI 中,研究了 Spring 帮我们管理对象的方法
1. 通过 @Controller,@Service,@Respository,@Component,@Configuration,@Bean 注解来声明一个 Bean 对象
2. 通过 @ApplicationContext 和 @BeanFactory 注解来获取对象
3. 通过 @Autowired / @Resource / @Qualifire / Setter 方法 / 构造方法为应用程序注入所依赖的 Bean 对象
发现输出的 Bean 对象的地址值是一致的,说明每次从 Spring 容器中取出来的 Bean 对象是同一个,即单例模式。默认情况下,Spring 容器中的 bean 中都是单例的,这种行为模式,我们称之为 Bean 的作用域。
Bean 的作用域指 Bean 在 Spring 框架中的某种行为模式。
比如单例作用域:表示该 Bean 对象,在整个 Spring 中就只有一份,是全局共享的, 当卡他人修改了这个值之后,另一个人收到的就是修改的值。
dog1 和 dog2 是同一个对象,dog2 拿到了 dog1 设置的值。
Bean 的作用域
在 Spring 中支持 6 中作用域,后 4 种在 Spring MVC 种才起作用
单例作用域中,多次访问的时候,得到的都是同一个对象,并且 @Autowired 和 applicationContext.getBean() 也是一个对象
多例作用域中,每次获取的对象都不一样。(@Autowired 注入的对象在 Spring
容器启动的时候,就已经注入了,所以多次请求的时候也不会发生变化)
请求作用域中,@Autowired 和 applicationContext.getBean() 也是同一个对象,但是每次请求的时候,都会重新创建对象
会话作用域中,在一个 session 中,多次请求,获得的对象都是同一个对象。
但是,当我们换另一个浏览器斤西瓜访问的时候,就会重现创建一个对象(换了浏览器就换了一个 Session)
Application 作用域中,在一个应用中,多次访问都是同一个对象。
在一个容器中 ApplicationContext 可以有多个。
Application scope 是针对于整个 web 容器来说的,bean 的作用域是 ServletContext 级别的,Application scope 和 singleten 有点类似,区别在于,Application scope 是 ServlectContext,singleton 是一个 ApplicationContext 的单例。
Bean 的生命周期
生命周期指的就是一个对象从创建到销毁的全过程。
Bean 的生命周期可以分为五个部分:
1. 实例化(为 Bean 分配内存空间)
2. 属性赋值(Bean 注入和赋值,例如 @Autowired)
3. 初始化:
3.1:执行各种通知(Aware 接口)(没有方法,仅用于标记 Bean 需要感知容器资源),常见的有 BeanNameAware:让 Bean 知道自己在容器中的名字,BeanFactoryAware:让 Bean 拿到容器的 BeanFactory,ApplicationContextAware:让 Bean 拿到容器的 ApplicationContext(即容器主动给 Bean 发消息,把容器的资源告诉 Bean)
3.2:执行初始化方法,Bean 完成基础的 实例化 + 属性赋值后,Spring 就会调用开发者自定义的方法,让 Bean 完成额外的初始化工作
4. 使用 Bean
5. 销毁 Bean
源码阅读
创建 Bean 的代码入口在 AbstractAutowireCapableBeanFactory#createBean
doCreateBean 方法:
initializeBean:
调用三个Bean开头的Aware方法
invokeInitMethod:
通过反射调用开发者自定义的初始化方法
Spring Boot 自动装配
SpringBoot 的自动装配,指的是当 Spring 容器启动后,一些配置类,Bean 对象,就自动存储在了 IoC 容器中,不需要我们手动声明。
即,SpringBoot 是如何将依赖 jar 包中的配置类以及 Bean 加载到 Spring IoC 容器中。
Spring 加载 Bean
我们之前在使用第三方的 jar 包的时候,都是直接导包引入即可使用,但如果我们在自己的项目下创建不同的目录来模拟第三方的代码引入,就会发生报错。
原因:Spring 通过五大注解和 @Bean 注解可以帮助我们把 Bean 加载到 SpringIoC 容器中,但有个前提是,这些注解类必须和 SpringBoot 的启动类在同一个目录下。
但第三方的 jar 包也并不在启动类的目录系,为什么 Spring 能够帮我们管理呢?
解决办法:
1. ComponentScan 组件扫描
2. Import 导入(使用 @Import 导入的类会被 Spring 加载到 IoC 容器中)
使用 @ComponentScan 注解,可以指定 Spring 的扫描路径
但试想,如果我们大量引入第三方依赖,就需要大量的扫描注解,极其繁琐。
@Import
1. 导入类
2. 导入 ImportSelector 接口实现类
这种导入类的方式和扫描一样,也很繁琐。
这种方法先将一些第三方方法在 MyImportSelector 中进行导入,然后一个 Import 导入项目启动类即可。
但还是有一个缺点:使用者需要知道第三方以来中有那些 bean 对象或配置类,容易忽略。
但是,可以让第三方依赖的开发者完成这件事。
Spring 采用的就是这种方式
SpringBoot 源码
SpringBoot 底层是如何实现,一切来源与 SpringBoot 的启动类开始
@SpringBootApplication 标注的类,就是 SpringBoot 项目的启动类
SpringBootApplication 是一个组合注解
1. 元注解:JDK 中提供了 4 个标准的用来对注解类型再进行注解的注解类,我们称为元注解。
@Target 描述注解的使用范围
@Retention:描述注解保留的时间范围
@Documented:描述再使用 javadoc 工具为类生成帮助文档时是否保留注解信息
@Inherited 使被它修饰的注解具有继承性
2. @SpringBootConfiguration:里面就是 @Configuration,标注当前类为配置类
3. @EnableAutoConfiguration:开启自动装配,后面我们详细了解
4. @ComponentScan:包扫描。如果没有定义特定的包,就从声明该注解类的包开始扫描,这也是为什么 SpringBoot 项目声明的注解类必须要在启动类目录下的原因(excludeFilters:自定义过滤器,用于排除一些类)
@EnableAutoConfiguration
这个注解包含两部分:
1. @Import{Auto ConfigurationImportSelector.class}
即是我们上面将的方法,使用 @Import,导入实现了 ImportSelector 接口的实现类
ImportSelector 方法调用了 getAutoConfigurationEntry 方法
getAutoConfigurationEntry 方法调用了 getCandidateConfigrations 方法
getCandidateConfigurations 方法中,获取所有基于
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.impots文件
META-INF/spring.factories 文件中配置类的集合
在引入的起步依赖中,通常都包含这两个文件
在这里面,就包含了很多第三方依赖的配置文件
2. @AutoConfigurationPackage
这个注解主要是导入一个配置文件 AutoConfigurationPackages.Registrar.class
Registrar实现了 ImportBeanDefinitionRegistrar 类,就可以被注解@Import导⼊到spring 容器里.
(String[])(new PackageImports(metadata)).getPackageNames().toArray(new String[0]) :当前启动类所在的包名.
即 @AutoConfigurationPackage 的作用就是将启动类所在的包下面的所有的组件都扫描注册到 Spring 容器中
SpringBoot 自动装配原理流程图如下:
当 SpringBoot 程序启动的时候,会加载配置文件中所定义的配置类,通过 @Import 注解将这些注解全部加载到 Spring 的 IoC 容器中管理。
Spring 三级缓存
Spring 是如何解决循环依赖问题的 ==》 三级缓存
什么是循环依赖?
举个栗子:A 对象依赖了 B 对象,但是 B 对象同时也依赖了 A 对象
回顾 Bean 的生命周期:
上面前四步执行完毕后,才算一个初始化完毕的 Bean,也就是 Spring 容器中完整的 Bean 对象
Spring 容器中保存 Bean,使用缓存的方式:使用 Map<String,Object> 结构,key 为 Bean 的名称,value 为 Bean 的对象。需要的时候直接从缓存中获取。
如果出现 A,B 互相依赖的情况:
容器中没有 A,会实例化 A 对象
在实例化 A 的时候,需要装配 B 对象,发现 B 在容器中没有,则先实例化 B
实例化 B 对象
在实例化 B 的时候,需要装配 A 对象,发现 A 在容器中没有,则对 A 进行实例化
重复了~~~ 开始套娃~~
注意:由于 Spring 支持构造方法注入,属性注入,Setter 注入,所以,不能简单的先把所有的对象都实例化放到缓存中,然后执行初始化。
原因:虽然可以先全都实例化,获取所有对象的引诱,但对象中的属性都是 null,当我们执行初始化的时候可能会出现空指针异常。
即,Spring 其实只能解决部分循环依赖问题。
Spring 解决循环依赖
Spring 使用三级缓存的机制来解决循环依赖的问题。
单例模式中 A B 循环依赖执行流程如下: