一、概述
很多人认为Spring中亮点是AOP与IOC,其实这个理解是非常片面的,除了暴露你是一个小白以外什么也说明不了;在Spring中最大的亮点是整个Spring的生态;也就是基于这个生态,才可以让Spring集成其他资源(MyBatis等)的时候可以有序不乱的没有问题的管控及执行,这些都必须要归功于Spring的生态;
AOP与IOC的区别
- AOP(Aspect Oriented Programming):面向切面编程,是一个概念,是一个理论的东西,需要其他外部的实现;可以理解成一种解耦抽象的方式,且 :指的是横切逻辑,原有业务逻辑代码不动,只能操作横切逻辑代码,所以面向横切逻辑;面 :横切逻辑代码往往要影响的是很多个方法,每个方法如同一个点,多个点构成一个面。这里有一个面的概念
- IOC(Inverse of Control):是控制反转的意思,是一种模式,是一种设计思想,依赖注入(DI)是IOC最常见以及最合理的实现方式;
在Spring中所有资源都是依赖于IOC,即:所有类及资源的加载都是基于IOC容器来管理的;另外在源码中可以看到都是基于IOC的基础上进行的拓展;除了IOC以外的所有资源都是通过拓展功能进行装载及管理的;
二、Spring体系结构
下图基于Spring4.X;在Spring5之后官方推荐使用SpringBoot来构建;
Core Container:是核心容器,IOC容器就属于这个核心容器;
- **Beans:**用于管理Bean的维护管理;
- **Core:**核心工具;
- **Context:**上下文;
- **SpEL:**EL表达式处理;
AOP与Aspects:在Spring中AOP仅仅是引用了Aspects的注解及表达式,并没有完全地使用Aspects;
三、IOC
是控制反转,是一个设计理念,也称为:依赖倒置,用于解决层与层之间或者类与类之间的耦问题;
在传统开发中假设有A、B两个类,其中B依赖A,在使用的时候必须先要创建一个A的实例,其中要考虑创建A的各种条件及因素(创建、初始化、销毁、变更等);而在控制反转中,是将需求方进行统一管理,当B需要A的时候只需要说一声“我要A”就会有人主动把初始化好的A交到你的手中;对于B而言直接使用A中的API即可;
上面的例子,可能一般人不能明白,这里转换一下,换一个普通人可以理解的例子:
董事长依赖总经理、总经理依赖部门经理、部门经理依赖员工;如果“员工离职了”、“部门经理叛逃了”老板怎么办?那么把关系颠倒过来:“员工受部门经理管理,部门经理受总经理管理,总经理受董事长管理”;这样一来是不是解决了问题了呢;
在Spring中实现了一个容器(其实就是一个Map对象,Key:Bean的名称;Value:为Bean的定义),这个容器中装载了所有的Bean对象,在需要使用地方只需要向容器中索取即可,由依赖注入(DI)来解决创建、初始化、销毁、变更等问题;
四、委托管理
如何将自己定义的Bean委托交给IOC来进行管理呢?方法归纳如下:
1、通过XML配置
final ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("【xml文件路径】");
final Object bean = classPathXmlApplicationContext.getBean("【定义的Bean名称】");
复制代码
2、通过注解方式
final AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(Config.class);
final Object bean = annotationConfigApplicationContext.getBean("【定义的Bean名称】");
复制代码
通过上面的两种方法可以将配置的类委托给IOC来进行管理;
注:在SpringBoot中直接通过Main方法可以自动扫描Main所在路径下(包含所有子路径下)的所有类;
结合上述内容,可以轻松地把需要的对象委托给IOC容器进行管理,那么委托给IOC容器的对象是如何进行处理的呢;这里需要了解一个Spring的重要知识点:BeanFactory(Bean工厂)
4.1)简单的示例
- 创建一个简单的接口;public interface IBean { void testDemoFunction1(); }
- 创建一个实现类public class TestBean implements IBean {@Override public void testDemoFunction1(){ System.out.println("testDemoFunction1"); } 复制代码}
- 创建一个配置类,注意@Configuration与@Bean注解,当然你也可以@ComponentScan等注解用于描述一个将被扫描的路径;@Configuration public class TestConfig { @Bean public IBean testBean(){ return new TestBean(); } }
- 创建启动类,运行看效果;public class TestMain { public static void main(String[] values){ final AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(TestConfig.class); final IBean testBean = (IBean)annotationConfigApplicationContext.getBean("testBean"); testBean.testDemoFunction1(); } }---------------最终打印结果--------------------- testDemoFunction1
4.2)简单的源码跟踪
从4.1中可以看到通过getBean方法可以直接获取到我们定义的Bean对象;那么他是如何实现的呢?进入getBean的源码中看看
public Object getBean(String name) throws BeansException {
//啥也不说,先来一个断言,看看当前是否是活跃的,可用的;
this.assertBeanFactoryActive();
/*
* 下面的代码可以看出这个方法,其实是一个门面包装方法,他自己本身不进行任何处理,而是直接交给另外一个对象进行处理;
* 是直接调用的this.getBeanFactory()中的getBean方法来实现的
* 这里先买个关子:这个getBean可以获取Bean也可以生产Bean,在以后有机会挑战源码的时候再说吧
*/
return this.getBeanFactory().getBean(name);
}
//这里getBeanFactory是获取的一个ConfigurableListableBeanFactory对象
public abstract ConfigurableListableBeanFactory getBeanFactory() throws IllegalStateException;
复制代码
来看看这个是ConfigurableListableBeanFactory个什么东西
很明显这类是BeanFactory的接口抽象;
从这个UML图中可以看出DefaultListableBeanFactory中仅仅是实现了ConfigurableListableBeanFactory,但是没有实现超类接口中的getBean方法,具体的getBean方法是在AbstractBeanFactory中实现的;
/*
* 在AbstractBeanFactory中是直接调用的doGetBean方法进行的处理
* 这里只能说一句,我真的很菜,真的看了很久没有看懂这个doGetBean中的具体实现;
* 欢迎高手指教;
*/
@Override
public Object getBean(String name) throws BeansException {
return doGetBean(name, null, null, false);
}
复制代码
4.3)BeanFactory是什么
BeanFactory是Spring对Bean管理的顶层核心接口,使用了简单工厂模式,负责生产Bean;
4.4)Bean的装配
上面说了一堆,都是说得怎么获取到Bean,那么这些Bean是什么时候及怎么放进去的呢?在最前面有过介绍Bean的装载是由多种形式的,有XML(ClassPathXmlApplicationContext)、也有注解(AnnotationConfigApplicationContext)等多种方式,如果一种方式写一种加载模式,Spring的开发人员肯定要哭了,这么多的方式是通过什么办法统计进行处理的呢?
先来看看下面的一个例子:
业务人员拿到了某小区的销售信息,查询到张先生与李先生都购买了这个小区的房子,装修公司的销售找到了张先生与李先生,张先生需要装修而李先生不需要装修,销售带张先生先来到装修公司,张先生提出自己的需求,装修公司的设计师根据张三的需求出装修设计图纸,然后装修公司再找到工厂去下单生产,最后在张三的房子中安装完成装修;
在这个例子中翻译过来:
- **张先生与装修:**可以看作2个类,张三需要依赖装修才能入住;
- **装修公司:**可以看作上下文对象(ApplicationContext),由装修公司进行统一的管理;
- **业务人员:**可以看作Bean定义读取对象(BeanDefinitionReader),用于加载配置信息;
- **销售:**可以看作Bean定义扫描对象(BeanDefinitionSacnner),用于扫描所有对象;用于处理配置中的信息,确定哪些需要装载到容器,哪些不需要;
- **设计师的图纸:**可以看作Bean定义对象(BeanDefinition),这里包含了Bean的所有描述信息,为工厂的生产提供依据;
- **工厂:**可以看作BeanFactory用于生产装修需要的东西;
装修公司的业务人员拿到楼盘购买信息,销售人员根据业务人员提供的业主信息把所有业主的联系方式都找出来,最终找到了张先生,邀请张先生到装修公司来洽谈业务,装修公司根据张先生的装修的风格(是否单例)、灯具要求(是否懒加载)、墙面需求、地板需求等多方面的需要进行设计;设计好的图纸交给工厂,工厂根据设计的图纸进行生产;虽然张先生对房屋整装的需求很多,但最终都变成了可量化的图纸,而工厂不关心张先生还是李先生,只根据图纸进行生产,根据多源同根的原则解决了问题;
在Spring中也是如此操作及处理的,不管外部的源是什么,最终都会变成一个统一的Bean定义对象(BeanDefinition),最后Bean工厂(BeanFactory)根据这个Bean定义(BeanDefinition)进行生产;大致的流程为:读取配置——>筛选对象——>生成Bean定义——>注册Bean定义——>生产Bean;
对于外部配置而言,不管外面是什么样的场景,最终都是转换为对应的BeanDefinitionReader;
- **AnnotatedBeanDefinitionReader:**用于读取注解的Bean定义配置;
- **XmlBeanDefinitionReader:**用于读取XML的Bean定义配置;
在AbstractBeanDefinition中可以看到定义了如下几个关键的属性
/**
* 用于保存bean组件的class对象
*/
@Nullable
private volatile Object beanClass;
/**
* bean的作用范围
* 默认是singleton,prototype
*/
@Nullable
private String scope = SCOPE_DEFAULT;
/**
* 判断当前的bean是不是抽象的
*/
private boolean abstractFlag = false;
/**
* 判断当前的bean是不是懒加载的
*/
private boolean lazyInit = false;
/**
* 默认的注入模型是0不支持外部注入
*/
private int autowireMode = AUTOWIRE_NO;
/**
* 默认的依赖检查模式
*/
private int dependencyCheck = DEPENDENCY_CHECK_NONE;
/**
* 当前的bean创建 必须要要依赖哪个bean先被创建
*/
@Nullable
private String[] dependsOn;
/**
*设置为false的时候,这样容器在自动装配对象的时候,会
* 不考虑当前的bean(他不会被看作为其他bean的依赖的bean,但是他依赖其他的bean是能够被自动装配进来的)
*/
private boolean autowireCandidate = true;
/**
*当发生自动装配的时候,假如某个bean会发现多个,那么标注了primary 为true的首先被注入
*/
private boolean primary = false;
复制代码
BeanDefinition也是Spring中的一个重要的核心接口,封装了生产Bean的一切信息;BeanDefinition是通过BeanDefinitionRegistry进行装载的;BeanDefinitionRegistry的装载方法如下:
/**
* 注册一个Bean定义
* 支持RootBeanDefinition与ChildBeanDefinition
* @param beanName 需要注册的bean实例名称
* @param beanDefinition 需要注册的bean的定义
* @throws BeanDefinitionStoreException 如果注册的Bean无效或已经存在则抛出这个异常
* @see GenericBeanDefinition
* @see RootBeanDefinition
* @see ChildBeanDefinition
*/
void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
throws BeanDefinitionStoreException;
复制代码
总结:一个类要变成一个Bean,首先要把类变成Bean定义,需要经过:ApplicationContext读取配置类——>根据配置类扫描满足加载条件的类——>将满足条件的类变成Bean定义(BeanDefinition)——>将Bing定义进行注册——>由BeanFactory进行生产——>最后变成我们的Bean;并且ApplicationContext可以调用BeanFactoryPostProcessor修改Bean定义,也可以调用BeanDefinitionRegistryPostProcessor来注册Bean定义;
五、Bean的生命周期
上面已经把我们的Bean装载到IOC中了,那么一个Bean的周期是什么样的?需要经过哪些步骤?带着这些问题我们继续往下看;
5.1)实例化
一般情况下是使用反射的方式进行处理,但是需要注意的是,这里仅仅只是Bean对象的实例化,Bean中的资源还没有解析(例如:@Autowired这些注解类型的资源),这个时候的Bean仅仅只是一个空壳而已;
还可以通过工厂方法来进行处理,我们在配置类中使用@Bean注解标识的方法就是一个工厂方法;
反射是由Spring自身的机制完成实例化的,而工厂方法是用户自己完成的,通俗的讲反射是Spring通过自己的算法来实现的(也就是上面说的没看明白的那个方法),而工厂方法是用户自己控制new方法来完成的,用户拥有初始化的完全掌控权;
我们常用的@Service、@RestController、@Component等都是通过反射的方式来进行实例化的;
反射没有工厂灵活
5.2)注入填充属性
在这一步里才是真正的解析@Autowired、@Value等资源,开始将对象的资源进行注入;
在填充的时候会出现循环依赖问题,即:BeanA中@Autowired BeanB,BeanB中@Autowired BeanA。BeanA依赖BeanB,BeanB依赖BeanA,这个时候一个闭环就出现了,这就是循环依赖问题产生的原因,目前Spring已经解决了这个问题,是采用三级缓存来解决的,貌似是采用的标识方法来解决的,当初始化BeanA的时候标识为正在创建,当创建BeanB的时候发现依赖的BeanA被标识为正在创建,那么就会直接退出,从而解决了循环依赖的问题,这部分内容在后面的章节中再进行详细的阐述吧;
5.3)初始化
这一步是对Bean的属性进行初始化处理,例如调用我们定义的initMethod、destroy等方法;到了这里Bean基本上是一个可用状态了;注意,这里也是AOP接入的地方;
5.4)装入缓存
这里会把初始化好的Bean装载到一个Map中(Key:Bean的名称;Value:Bean的实例),这个Map就是Spring中大名鼎鼎的“单例缓存池”,也是我们常说的一级缓存,在调用getBean的时候都是从这里面获取的;
注意:到这里一个Bean的生命周期基本完成,但是不完整,因为在Bean初始化阶段还会调用很多(一堆)Bean声明周期回调方法,例如:BeanNameAware、BeanClassLoaderAware、BeanFactoryAware等等等等等等等等等等,总之就是疯狂的各种各样的一堆Aware;通过这些接口可以获取到很多有用的资源,例如:Bean的名字、Bean的类加载器
* <p>Bean factory implementations should support the standard bean lifecycle interfaces
* as far as possible. The full set of initialization methods and their standard order is:
* <ol>
* <li>BeanNameAware's {@code setBeanName}
* <li>BeanClassLoaderAware's {@code setBeanClassLoader}
* <li>BeanFactoryAware's {@code setBeanFactory}
* <li>EnvironmentAware's {@code setEnvironment}
* <li>EmbeddedValueResolverAware's {@code setEmbeddedValueResolver}
* <li>ResourceLoaderAware's {@code setResourceLoader}
* (only applicable when running in an application context)
* <li>ApplicationEventPublisherAware's {@code setApplicationEventPublisher}
* (only applicable when running in an application context)
* <li>MessageSourceAware's {@code setMessageSource}
* (only applicable when running in an application context)
* <li>ApplicationContextAware's {@code setApplicationContext}
* (only applicable when running in an application context)
* <li>ServletContextAware's {@code setServletContext}
* (only applicable when running in a web application context)
* <li>{@code postProcessBeforeInitialization} methods of BeanPostProcessors
* <li>InitializingBean's {@code afterPropertiesSet}
* <li>a custom init-method definition
* <li>{@code postProcessAfterInitialization} methods of BeanPostProcessors
* </ol>
复制代码
原文链接:https://juejin.cn/post/6931996590664220679
如果觉得本文对你有帮助,可以关注一下我公众号,回复关键字【面试】即可得到一份Java核心知识点整理与一份面试大礼包!另有更多技术干货文章以及相关资料共享,大家一起学习进步!