ioc是控制反转, 这是一种设计理念, 用来解决的是层和层之间, 类和类之间的耦合问题.
比如,现在有A, B两个类, 在A类中引用了B类. 那么如果有一天, B类要被替换掉, 我们会怎么办呢?如果B类被引用了100次, 我们要替换100次?
现在呢, A是直接调用B, 如果我们间接的调用B, 将B包装起来, 如果以后将B换成C, 只需要在包装类里面替换就可以了. 我们不需要修改A类. 这就是控制反转.
Spring使用了ioc, Spring.ioc(A, B) 将A和B的引用都存在ioc中, spring会帮我们维护好, 完全不用担心.
当我们在A中要使用B的时候, 使用B对应的接口, 然后使用@Autowired注解
A {
@Autowired
private IB b;
}
什么时候把B换掉了, 不痛不痒的, 只需要把新的类放到IoC中就可以了.
Spring IoC是一个容器, 在Spring Ioc中维护了许多Bean
那这些bean是如何被注册到IoC中的呢? 换句话说, 我们自定义的类, 是如何作为一个bean交给IoC容器去管理的呢?
先来回忆,我们在开发spring的时候的步骤:
第一步: 配置类. 配置类可以使用的方式通常由
1) xml配置
2) 注解配置
3) javaconfig方式配置
第二步: 加载spring上下文
1) 如果是xml, 则new ClassPathXmlApplicationContext("xml");
2) 如果是注解配置: 则new AnnotationConfigApplicationContext(config.class)
第三步: getBean()
我们会讲自定义的类, 通过xml或者注解的方式注入到ioc容器中.
在这一步, 会将xml或注解中指定的类注入到IoC容器中.
下面就来梳理一下整个过程.
第一问: 一个类要生产成一个Bean, 最重要最核心的类是什么?
是BeanFactory
第二问: BeanFactory是什么呢?
BeanFactory是Spring顶层的核心接口--使用了简单工厂模式. 通常都是根据一个名字生产一个实例, 根据传入的唯一的标志来获得bean对象, 但具体是穿入参数后创建, 还是穿入参数前创建, 这个要根据 具体情况而定, 根据名字或类型生产不同的bean.
一句话总结: BeanFactory的责任就是生产Bean
来看下面这段代码:
public static void main( String[] args ) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.getBean("***");
}
这段代码实现的功能是, 读取当前文件所在目录及其子目录中的文件, 然后获取指定名称的bean, 整个流程如下图所示:
首先, 通过ClassPathXmlApplicationContext或者AnnotationConfigApplicationContext去读取配置,
然后将其交给BeanFactory.
第三. BeanFactory调用getBean()方法, 将Bean注入到IoC容器中
我们发现, 配置的读取, 可能是xml方式, 也可能是annotation的方式, 不同的方式读取应该使用的是不同的工具. 那么这些工具读取的结果应该是统一的, 然后才能交给BeanFactory去处理.
因为在BeanFactory中是不会对这些异同点进行处理的. BeanFactory的作用只有一个, 就是个生产Bean.
我们知道,读取配置这一块, 应该会有一个不同的实现. 将xml和注解方式读取成统一的东西, 放入到beanFactory中. 这个东西是谁呢?就是BeanDefinition(Bean定义)
什么意思呢? 如下图:
看绿色框框住的部分. 这个含义是: 通过不同的工具, 可能是xmlApplicationContext, 可能是annotationApplicationContext工具 读取的配置, 最后都会构造成BeanDefinition对象. 然后将BeanDefinition传递给BeanFactory, BeanFactory统一处理BeanDefinition对象, 调用getBean()方法, 将其放入IoC容器中.
我们来举个例子, 现在有一个人, 比如说我刚买了一个房子, 我要装修. 需要一个衣柜, 这时候, 我会找到一个衣柜店. 然后告诉他我的需求, 柜子的颜色, 款式格式什么样. 然后衣柜店记录我的需求, 这个时候, 他不会自己生产, 他会通知工厂, 让工厂来生产. 工厂按照什么生产呢, 衣柜店有一个设计师, 他们的设计师. 会按照我的需求设计出一张图纸. 然后将图纸交给工厂. 工厂按照图纸要求生产Bean.
整个过程如下图:
入口是"我"
1. 我有一个需求, 打一个柜子, 找到衣柜店
2. 我告诉衣柜店我的需求, 柜子的颜色, 款式, 然后衣柜店的设计师按照我的要求 ,设计出一张图纸
3. 衣柜店将图纸给到工厂, 工厂按照图纸生产柜子
这是制造衣柜的过程. 其中在画图纸的时候, 画一张就给工厂给一张, 这样效率太低了. 我们可以画了n张, 一起给工厂. 所以, 在设计图纸这块是一个容器, 存放多张图纸
后面,如果我还想定制一个橱柜店. 那么, 就告诉设计师我的橱柜的颜色,款式, 就可以了. 流程和上面都是一样的.
整个这个过程, 就类似于我们的bean生产的过程
1. 定义了一个带有@Component注解的类, 我找到衣柜店, 衣柜店就类似于ApplicationContext.
2. 我告诉ApplicationContext我的需求, 我要懒加载@Lazy, 设置单例模式还是多例模式@Scope. 对应的就是定制柜子的颜色,款式. 然后衣柜店里的设计师BeanDefinitionRegistry根据我的需求设计出图纸, 也就是构造成BeanDefinition. 不同的BeanDefinitionRegistry设计出不同的BeanDefinition, 然后将他们都放在容器中.
3. 衣柜店ApplicationContext统一将一摞图纸BeanDefinitionMap交给工厂, 然后工厂按照要求生产Bean, 然后将生成的bean放入到IoC容器中.
这是一个带有@Component的类被加载的过程.
衣柜店要要想生意好, 那么他要去拉活呀, 所以还需要好的销售. 销售要去扫楼盘, 去联系, 哪些人有装修的需求. 挨个询问.
可是问了100个人, 可能只有10个人有装修的需求. 于是还要有一个接待, 这个接待要联系客户, 看看哪些是有意向的客户, 将其筛选出来. 然后定制家具.
这里多了两类人: 销售和接待. 具体工作如下.
销售就相当于我们的BeanDefinitionReader, 他的作用是去扫楼盘, 找到潜在客户. 对应的就是BeanDefinitionReader去读取xml配置或者Annotation注解.
xml中的配置有很多, 注解也有很多, 并不都是我们的目标. 于是有了接待
接待要去扫描所有潜在客户. 将有意向的客户扫描出来. 这就类似于我们的BeanDefinitionScanner, 去扫描潜在客户, 最后将带有@Component注解的类筛选出来
这就是后面需要定制家具的客户了
BeanDefinitionReader对应的就去读取配置类, 看看有哪些需求需要搞装修.
它本身也是一个抽象类, 可以看到他有AnnotationBeanDefinitionReader和XmlBeanDefinitionReader
我们配置了配置包, 去扫描这个包下所有的类, 然后将扫描到的所有的类交给BeanDefinitionScanner, 它会去过滤带有@Component的类.
在和上面的流程连接起来, 就是整个配置文件被加载到IoC的过程了.
1. FactoryBean的功能就是生产bean. 他生产bean是根据BeanDefinition来生产的. 所以, 一次只能生产一个
2. ApplicationContext有两种. 一种是xmlApplicationContext, 另一种是annotationApplicationContext, 他传入的参数是一个配置文件. 也就是可以加载某个目录下所有带有@Component的类
他们两个都各有使用场景. 使用ApplicationContext的居多.
另一个区别: 就是后面会说到的, ApplicationContext有两个扩展接口, 可以用来和外部集成. 比如和MyBatis集成.
不是的, 生产Bean是有一个流程的. 下面我们来看看Bean的生命周期
第一步: 实例化. bean实例化的时候从BeanDefinition中得到Bean的名字, 然后通过反射机制, 将Bean实例化. 实例化以后, 这是还只是个壳子, 里面什么都没有.
第二步: 填充属性. 经过初始化以后, bean的壳子就有了, bean里面有哪些属性呢? 在这一步填充
第三步: 初始化. 初始化的时候, 会调用initMethod()初始化方法, destory()初始化结束方法
这个时候, 类就被构造好了.
第四步: 构造好了的类, 会被放到IoC的一个Map中. Map的key是beanName, value是bean实例. 这个Map是一个单例池, 也就是我们说的一级缓存
第五步: 我们就可以通过getBean("user"), 从单例池中获取雷鸣是user的类了.
A类里面调用了B类, 所以BeanFactory在构造A的时候, 会去构造B. 然后在构造B的时候, 发现, B还依赖了A. 这样, 就是循环依赖. 这是不可以的.
Spring是如何解决循环依赖的问题的呢?
设置出口. 比如A在构造的过程中, 那么设置一个标记, 正在构造中. 然后构造B, B在构造的过程中应用了A, 这时候, 有趣构造A, 然后发现A正在构造中, 那么, 就不会再次构造A了.
后面还会详细讲解Spring是如何解决循环引用的. 这里我们需要知道的是: Spring使用的是三级缓存来解决循环引用的问题
其实, bean是存在一级缓存里面, 循环引用使用的是三级缓存来解决的. 其实, 一、二、三级缓存就是Map。
有两个非常重要的扩展接口. BeanFactoryPostProcessor(Bean工厂的后置处理器) 和 BeanDefinitionRegistryPostProcessor
这两个接口是干什么的呢?
我们在这个图里面, 看到了设计师要设计出图纸, 然后把图纸交给工厂去生产. 那么设计师设计出来的图纸, 有没有可能被修改呢?
当然是可以被修改的. 只要还没有交给工厂, 就可以修改.
public class DefinedPost implements BeanFactoryPostProcessor {
/**
* 重写Bean工厂的后置处理器
* @param beanFactory
* @throws BeansException
*/
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
// beanFactory 拿到工厂了, 就可以获取某一个Bean定义了
GenericBeanDefinition car = (GenericBeanDefinition) beanFactory.getBeanDefinition("Car");
// 拿到了car, 然后修改了Car的类名为com.example.tulingcourse.Tank. 那么后面在获取的Bean里面, 将其转换为Car, 就会报错了
car.setBeanClassName("com.example.tulingcourse.Tank");
}
}
第一步: 实现了BeanFactoryPostProcessor接口, 然后需要重写里面的方法
第二步: 我们发现重写方法直接给我们了beanFactory, bean工厂
第三步: 拿到bean工厂, 我们就可以根据名称获取BeanDefinition, 也就是bean定义了.
第四步: 我们修改了bean定义中的类名为Tank.
这时候会发生什么呢? 从bean工厂中构建的car, 取出来以后转换成Car对象, 会报错,
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Application.class);
Car car = context.getBean("car", Car.class); // 这里会报错, 因为已经被修改
System.out.println(car.getName());
}
执行流程: 当spring启动的时候, 就会去执行AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(TulingCourseApplication.class);
然后ApplicationContext回去扫描所有实现了BeanFactoryPostProcessor对象的类, 然后执行postProcessBeanFactory方法.
BeanFactoryPostProcessor被使用到的场景非常多, 在集成其他组件的时候, 比如集成mybatis
这是一个Bean定义注册的后置处理器.BeanDefinitionRegistryPostProcessor本事是实现了BeanFactoryPostProcessor 接口
我们来看个demo
public class DefinedPost implements BeanDefinitionRegistryPostProcessor {
/**
* 重写Bean工厂的后置处理器
* @param beanFactory
* @throws BeansException
*/
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
// beanFactory 拿到工厂了, 就可以获取某一个Bean定义了
GenericBeanDefinition car = (GenericBeanDefinition) beanFactory.getBeanDefinition("Car");
// 拿到了car, 然后修改了Car的类名为com.example.tulingcourse.Tank. 那么后面在获取的Bean里面, 将其转换为Car, 就会报错了
car.setBeanClassName("com.example.tulingcourse.Tank");
}
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
}
}
一个类实现了BeanDefinitionRegistryPostProcessor, 需要重写postProcessBeanDefinitionRegistry方法, 这个方法直接将BeanDefinitionRegistry就给我们了.
然后使用beanDefinitionRegistry.registerBeanDefinition(); 就可以添加图纸了
在这里可以注册新的bean, 也可以删除注册的bean. 多注册一个, bean工厂就要多构建一个.
总结:
BeanFactoryPostProcessor和BeanDefinitionRegistryPostProcessor这两个扩展类是很重要的类, 这对于向外部扩展起到了很大的的作用, 比如: 集成mybatis
BeanFactoryPostProcessor和BeanDefinitionRegistryPostProcessor是在ApplicationContext中的两个扩展接口. 这也是ApplicationContext和BeanFactory的区别之一, 因为有了这两个扩展节点, 就可以和外部做集成. 比如Mybatis集成. 比如: 扫描配置类, 就是通过 这两个扩展点的方式实现的.
这个扩展点的作用:
1. 除了IoC, 其他的扩展,比如AOP, 和MyBatis集成, 都要用到这两个扩展点. 之所以Spring能够有序不乱的和很多外部组件整合, 都是这两个扩展点的功能
除了ApplicationContext有扩展点, 在Spring IoC中的bean也有扩展点. BeanPostProcessor(Bean的后置处理器). 如果使用在getBean()之前, 那么可以阻止构建Bean, 还可以自定义构建Bean.
BeanPostProcessor使用的场景有很多. 在Bean实例化之前和之后会被调用. 在填充属性之前和之后也会被调用, 初始化之前和之后也会调用. 有些过程不只调用一次. 整个过程一共会调用9次. 在每一个过程都可以扩展Bean.
思考: Spring加入AOP是如何实现呢?
集成AOP肯定不会和IoC糅合在一块了. AOP就是通过BeanPostProcessor(Bean后置处理器)整合进来的.
AOP的实现方式有两种: 一种是CGLIB, 另一种是JDK.
假如说要进行集成, 会在那个步骤继承呢? 比如要加日志, 使用AOP的方式加. 我们通常是在初始化之后加AOP. 在这里将AOP集成进来.
如上图: 当面试的时候面试官问你, Bean的生命周期, 我们不能只说实例化-->填充属性-->初始化. 还需要说初始化的时候, 还有一些列的aware.
我们将一个类加载成Bean, 不是一步到位的,需要经历一下的过程.
1. 首先, 我们要将类加载成BeanDefinition(Bean定义)
加载成bean定义, 有以下几个步骤:
1) 使用BeanDefinitionReader加载配置类, 此时是扫描所有的xml文件或者项目中的注解. 这里面有些使我们的目标类, 有些不是
2) 使用BeanDefinitionScanner扫描出我们的目标类.
3) 使用BeanDefinitionRegistry注册bean到BeanDefinitionMap中.
2. 然后, ApplicationContext可以调用BeanFactoryPostProcessor修改bean定义, 还可以调用BeanDefinitionRegistryPostProcessor注册bean定义
3. 将BeanDefinition交给BeanFactory处理, BeanFactory调用getBean()生成Bean或者调用Bean(getBean()有两个功能).
4. 成产bean的时候, 首先会实例化, 然后填充属性(主要是读取@Autowire, @Value等注解). 在初始化Bean, 这里会调用initMethod()方法和初始化销毁方法destroy(). 初始化的时候还会调用一堆的Aware, 而且在bean生成的过程中 会有很多扩展点, 供我们去扩展.
5. 将生产出的Bean放入到Map中, map是一个一级缓存池. 后面, 我们可以通过getBean("user")从缓存池中获取bean