目录
主要从3方面进行解析
Bean与BeanDefinition
容器初始化主要做的事情(主要脉络)
BeanFactory
ApplicationContext
模板方法模式
Resource、ResourceLoader、容器之间的关系
BeanDefinitionReader
BeanDefinition的注册
小结
-
主要从3方面进行解析
- 解析配置
- 定位与注册对象
- 注入对象
-
Bean与BeanDefinition
- Bean是Spring的一等公民
- Bean的本质就是java对象,只是这个对象的生命周期由容器来管理
- 不需要为了创建Bean而在原来的java类上添加任何额外的限制(低侵入)
- 对java对象的控制方式体现在配置上
- Bean是一个由Spring IoC容器实例化、组装和管理的对象
- Bean并不是程序员编辑的,而是程序运行时,由Spring通过反射生成的
- 什么是BeanDefinition
- 根据配置,生成用来描述Bean的BeanDefinition
- BeanDefinition 是定义 Bean 的配置元信息接口
- 包含:
- Bean 的类名
- 设置父 bean 名称、是否为 primary
- Bean 行为配置信息,作用域、自动绑定模式、生命周期回调、延迟加载、初始方法、销毁方法等
- Bean 之间的依赖设置,dependencies
- 构造参数、属性设置
- 例:
- 作用范围scope(@Scope)
- 懒加载lazy-init(@Lazy):决定Bean实例是否延迟加载
- true:在使用bean实例的时候才会将bean实例创建出来
- 首选primary(@Primary) :设置为true的bean会是优先的实现类
- 当一个接口有多个实现类的时候,加了@Primary的bean会是优先的实现类
- factory-bean和factory-method(@Configuration和@Bean)
- factory-bean:工厂bean的名称
- factory-method:工厂方法的名称
- main执行,主要是从容器里面调用getBean,传入Bean的id,来获取实例对象
- 不同的BeanId创建的实例都是不同的
- 不管是xml还是注解的方式,bean对象都会被容器定位读取到内存,之后解析成一个个的beanDefinition实例注册到容器,整个过程发生在容器的初始化过程中
-
容器初始化主要做的事情(主要脉络)
- Spring的Bean的继承关系不是通过extends和implements实现的,而是设置parent属性
- BeanDefinition:描述某个Bean实例的配置信息(延时加载,scope...)
- AttributeAccessor:定义了最基本的对任意对象的元数据的修改或者获取方式,主要用于获取BeanDefinition的属性,并对这些属性进行操作
- BeanMetadataElement:用来传输可配置的元对象 ,主要用于返回BeanDefinition这个class对象本身
- AbstractBeanDefinition:定义了共有的构造函数,子类就可以基于构造函数给属性赋值,其次定义了一些通用属性的get和set方法,方便给通用的属性赋值,还提供了公用的工具方法
- RootBeanDefinition:不能作为其他类的子类,通常用于在运行时接受多个BeanDefinition合并起来的信息;能接受具有继承关系的两个BeanDefinition的属性,承接两者合并在一起的除了parent属性之外的属性
- GenericBeanDefinition:是通用的BeanDefinition实现,具有parentName属性,方便程序在运行时设置parentBeanDefinition
-
BeanFactory
- BeanFactory与FactoryBean有什么区别?
- BeanFactory是Spring容器的根接口,定义了Bean工厂的最基本的功能特性(比如根据BeanName获取Bean实例等)
- 使用作管理Bean的容器,Spring中生成的Bean都是由这个接口的实现类管理的
- FactoryBean也是接口,基于接口里面的getObject方法,用户可以生成一套复杂的逻辑来生成Bean
- 它本质也是一个Bean,但他并不是注入到某个地方例如Service之类
- 它的作用是用来生成普通的bean的,实现这个接口之后,Spring容器在初始化时会把实现了这个接口的bean取出来,使用Bean里面的getObject方法来生成我们想要的bean
- 例子:
- 配置bean,并测试得到bean实例
- 发现创建的并不是UserFactoryBean的实例,而是user实例
- 说明直接调用UserFactoryBean的getBean方法,它会默认调用里面的getObject方法,返回创建的user实例
- 那怎么获取UserFactoryBean的实例呢?
- 只需要在前面添加一个&即可
- BeanFactory的重要方法:
- 简单容器
- 接口主要用来描述容器具有的功能,实现类实现功能
- ListableBeanFactory:批量列出工厂生产的实例的信息(beanName)
-
ApplicationContext
- 术语补充
- 组件扫描:自动发现应用容器中需要创建的Bean
- 自动装配:自动满足Bean之间的依赖(对被注解Autowired注解标记的成员变量进行依赖注入)
- BeanFactory面向的是Spring自身,而ApplocationContext面向的是开发者
- 好比Spring容器是一辆汽车,BeanFactory是一个汽车的发动机,而ApplicationContext则是一辆完整的汽车
- ApplicationContext:应用上下文,继承BeanFactory接口,它是Spring的一个更高级的容器,提供了更多的有用的功能
- 国际化(MessageSource)
- 访问资源,如URL和文件(ResourceLoader)
- 载入多个(有继承关系)上下文 ,使得每一个上下文都专注于一个特定的层次,比如应用的web层
- 消息发送、响应机制(ApplicationEventPublisher)
- AOP(拦截器)
- 因为他继承了多个接口所以拥有更多的功能
- EnvironmentCapable:可以通过EnvironmentCapable里面的Environment getEnvironment();获取web.xml里面contextConfigLocation信息,根据它的值去加载Spring的所有的配置文件
- ListableBeanFactory: 通过列表的方式管理bean
- HierarchicalBeanFactory: 实现多层级的容器来实现对每一层bean的管理
- ResourcePatternResolver: 加载资源文件
- MessageSource: 管理message,进而实现国际化的功能
- ApplicationEventPublisher:具备事件发布的能力,(容器在启动的时候会发布一些listener,用来监听发布的事件)监听机制
- ApplicationContext常用容器
- 传统的基于XML配置的经典容器
- FileSystemXmlApplicationContext:从文件系统加载配置
- ClassPathXmlApplicationContext:从classpath加载配置
- XmIWebApplicationContext:用于Web应用程序的容器
- 目前比较流行的容器
- AnnotationConfigServletWebServerApplicationContext:在SpringBoot的Boot模块下
- AnnotationConfigReactiveWebServerApplicationContext:用来满足响应式的需求
- AnnotationConfigApplicationContext:对于普通的非web应用常用
- ApplicationContext方法都是以get开头,都是只读的,需要使用子接口来实现ApplicationContext可配置的能力
- 子接口ConfigurableApplicationContext:
- 里面提供了一些方法来配置ApplicationContext,启动、刷新、关闭应用上下文的能力
- 重新启动容器,清除缓存,重新装载类信息
- AbstractApplicationContext:
- 实现了ApplicationContext里面简单不易动的部分:容器工厂的处理,事件的发送广播、监听器的注册、容器初始化操作refresh方法、getBean方法
- refresh()是Spring最核心的方法,在SpringApplication.run(args)的时候执行,是一个同步同步方法,用synchronized关键字来实现
- refresh()大致功能
- 容器初始化、配置解析
- BeanFactoryPostProcessor和BeanPostProcessor的注册和激活
- 国际化配置
-
模板方法模式
- 围绕抽象类,实现通用逻辑,定义模板结构,部分逻辑由子类实现
- 基于继承的,会准备一个抽象类,将部分逻辑以具体方法和具体逻辑实现,然后定义一个模板结构,将剩下的具体内容延迟到子类去实现
- 声明一些抽象方法迫使子类实现剩下的逻辑
- 复用: 将相同逻辑的代码在父类中复用,将具体实现下沉到子类
- 反向控制: 通过父类调用子类的操作,通过对子类具体的实现扩展出不同的行为,以此来实现反向控制
- 通过子类扩展来实现定制化的行为,符合开闭原则
- 模板方法:定义了整个方法需要实现的业务的骨架
- 具体方法:一些确定不变的逻辑,父类直接实现
- 钩子:不是由子类来直接调用而是在特定条件发生时由抽象类的调用方来调用,以用于对发生的事件进行响应
- 钩子就是供子类灵活变通的钥匙
- 例如:去KTV唱歌,服务生帮忙打开音响,结束后客户付钱是统一必有的操作,所以在父类中实现,点歌需要看用户的需求,所以设置为抽象方法交给子类实现,但是会不会额外消费子类就看情况而定,选择性实现,定义为钩子方法
- refresh()方法就是一个模板方法,主要定义了容器启动时需要做的事情,其中方法:
-
Resource、ResourceLoader、容器之间的关系
- java中资源会被抽象成url,解析url的protocol处理不同协议资源
- 而Spring将物理资源抽象为Resource
- Resource
- 一个接口,定义了资源的基本操作
- InputStreamSource:只有一个方法,获取资源流
- Resource家族:
- 针对不同的资源有不同类的实现;每个实现类代表资源的访问策略
- EncodedResource:对资源文件的编码处理
- AbstractResource:对Resource方法的大部分默认公共实现;若想自定义Resource可继承它,覆盖相应方法即可
- ServletContextResource:访问web容器中的上下文资源而实现的,支持以流、url的形式访问,还可以从jar包中访问资源
- ClassPathResource:访问类加载路径下的资源;可自动搜索WEB-INF/classes下的资源
- FileSystemResource:访问文件系统资源;java提供的File类也可实现
- 会根据资源地址自动选择正确的Resource
- 强大的加载资源的方式
- 自动识别"classpath:"、”file:" 等资源地址前缀
- 支持自动解析Ant风格带通配符的资源地址
- Ant
- 路径匹配表达式,用来对URI进行匹配;类似于正则表达式,只不过正则表达式表示的范围比较广,Ant只适用于路径匹配
- ? 匹配任何单字符
- *匹配0或者任意数量的字符
- **匹配0或者更多的目录
- 例子:
- Resourceloader
- 实现不同的Resource加载策略,按需返回特定类型的Resource
- 是一个接口,根据路径获取资源;可以是classPath或者file等
- DefaultResourceLoader
- 简单工厂模式侧重的是:返回创建出的对象,用户不了解对象本身,相当于黑盒
- 但是策略模式要求用户了解策略本身,即针对什么样的资源使用什么样的Resource加载
- 因为Resourceloader里面的方法只能获取一个resource实例,因此又来了一个接口ResourcePatternResolver
- 实现类PathMatchingResourcePatternResolver去实现,同时还支持Ant路径风格模式
- Spring提供了Resource和ResourceLoader来统一抽象整个资源及其定位,使得资源与资源的定位有了更加清晰的界限,并且有DefaultResourceLoader使得自定义实现更加清晰和方便
- 发现ApplicationContext继承了 ResourcePatternResolver 那就间接继承了ResourceLoader
- 所以任何的ApplicationContext的实现都可以看做 ResourcePatternResolver 或ResourceLoader的实例
- 整个ApplicationContext 的实现类完全可以支持ResourcePatternResolver 、ResourceLoader,这也是高级容器为什么支持统一加载资源的原因
- 在容器读取配置时,委派给了PathMatchingResourcePatternResolver以及DefaultResourceLoader来执行
-
BeanDefinitionReader
- 它是ResourceLoader的使用者,是资源加载利器的使用者
- 它利用ResourceLoader、ResourcePatternResolver,将配置信息解析成一个个BeanDefinition,并借助BeanDefinitionRegistry将BeanDefinition注册到容器里
- 作用:
- 定义了一系列加载BeanDefinition的接口,针对单个或者多个配置文件的加载,或者单个resource实例或者多个resource实例的加载,最终目的将配置文件的配置转换成一个个的BeanDefinition
- 体系结构:
- AbstractBeanDefinitionReader
- 实现了BeanDefinition的公共逻辑
- 如果是ResourcePatternResolver的实例,则代表需要加载多个资源
- 不管是加载单个还是多个资源,最后都会调用loadBeanDefinitions方法做进一步加载
- loadBeanDefinitions():主要根据用户提供的资源加载器的类型来判断加载单个或多个资源
- 实现类是针对不同的资源类型做定义的
- XmlBeanDefinitionReader
- 传入指定xml文件的路径,XmlBeanDefinitionReader 调用它的父类ResourceLoader根据传入的路径返回resoure实例,它再去调用loadBeanDefinitions方法去执行
-
BeanDefinition的注册
- 方法:
- 1---向注册表中注册一个新的BeanDefinition实例
- 2---移除注册表中已存在的实例
- 3---从注册表中取得指定的BeanDefinition实例
- 4---判断BeanDefinition实例是否在注册表中(是否注册)
- DefaultListableBeanFactory实现了 BeanDefinitionRegistry接口
- 同时里面定义的beanDefinitionMap将注册的beanName为key,注册实例为value存里面
- 配置到读取到加载到解析
- 主要的逻辑就是将解析的beanDefinition的实例给注册到DefaultListableBeanFactory容器(里面的beanDefinitionMap)中
- 注解容器是先于xml文件创建出来的,为什么要提前创建出来呢?
- 因为他会提前创建出一些系统内置的beanDefinition实例,所以就需要提前在构造函数中创建DefaultListableBeanFactory实例,以提供对系统内置的beanDefinition的注册
- 先加载内置的beanDefinition实例,再加载entrance(被Component放到容器里的bean)
- 再加载其他被注解(Controller、Service....)修饰的类
- 在容器刷新的时候被注册进来,和xml配置一样都是在容器刷新的时候被注册进来
- BeanDefinitionRegistryPostProcessor的beanDefinition会被优先执行,普通的BeanFactoryPostProcessor会被延后执行
- 执行BeanDefinitionRegistryPostProcessor会调用invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry)
- 执行完之后才会调用invokeBeanFactoryPostProcessors(registryProcessors, beanFactory);去执行普通的BeanFactoryPostProcessor
-
小结
- Spring会依据BeanDefinition创建Bean
- DefaultListableBeanFactory:主要负责对BeanDefinition的注册
- 容器刷新时的共性:都使用了AbstractApplicationContext里面的refresh()
- 配置资源在Spring里面会被转换成一个个不同的Resource对象实例,这离不开ResourceLoader的支持
- 有了ResourcePatternResolver使得Spring可以根据配置资源的ur路径选择合适的resource进行包装,体现了策略模式
- 有了好的利器,BeanDefinitionReader进行使用
- xml资源被XmlBeanDefinitionReader解析成Document对象,再委托给BeanDefinitionDocumentReader解析成一个个GenericBeanDefinition实例,再将其注册到DefaultListableBeanFactory内置容器中
- 不同于xml,xml所有的beanDefinition实例都是在refresh()方法中刷新时注册的,而注解分为三类BeanDefinition的注册
- 第一类:容器内部设置的BeanDefinition实例,在容器的构造函数一经调用就被注册到内置容器中
- 第二类:用户自定义的带有@Configuration的类,在容器的构造函数中调用register()方法时被注册
- 第三类:常规的BeanDefinition则是在refresh()方法里的容器级别后置处理器被调用时,进行注册的
- AnnotatedBeanDefinitionReader:负责对上述三种类型的BeanDefinition进行解析,并将其注册到DefaultListableBeanFactory容器中