核心解释
从IoC、AOP、事务管理、JDBC、模板集成(简化开发)、源码方面进行解释
@Transactional
就能进行事务声明缺点:
@Autowired
来注入。IoC,Inverse of Control,控制反转。IoC是一种设计思想,你需要什么类型的对象(POJO),就将设计好的创建模板(XML配置文件或者注解)交给Spring IoC容器,在需要使用到的时候由Spring IoC容器创建这个对象(Bean)。在这个过程中,对象的创建由依赖于它的程序主动创建变成了Spring IoC容器来创建,控制权发生反转,所以叫做控制反转。
DI,Dependency Injection,依赖注入。DI是一种行为,组件之间依赖关系由容器在运行期决定,容器动态地将某个依赖关系注入到组件之中。整个依赖的过程,应用程序依赖于IoC容器,需要IoC容器来提供对象所需要的外部资源;注入就是IoC容器将对象所需要的外部资源注入到对象中,某个对象所需要的外部资源包括对象、资源、常量数据等。DI主要是通过反射实现的。
底层是通过工厂+反射实现的。简单工厂是一种设计模式,通过传入一个标识然后交由工厂类创建出所需要的对象。在没有利用反射时,传入的是一个对象的名称,工厂的的设计原则也更加的复杂。引入反射之后可以直接传入类路径名,之后动态地生成一个对象。
@Component
注解:该注解等价于 @Service
、@Controller
、@Repository
注解。这种方式使用的时候需要配置扫描包
,是通过反射的形式来利用类创建Bean对象。@Bean
注解:该注解通常使用在一个方法上,使用这个注解区别于@Component
注解可以控制Bean的实例化过程。@Import
注解:有三种方式可以创建Bean对象。Spring IoC的创建,首先会实例化一个ApplicationContext对象。Spring IoC的加载分为四个形态:【可以类比于工厂拿着设计图纸参数去生产产品的过程】
概念态需要调用一个Bean工厂的后置处理器invokeBeanFactoryPostProcessors,提供扩展点操作Bean定义。这个扩展点既对内扩展也对外扩展,然后通过这个扩展注册成为一个定义态。简易化的过程就是:扫描src下的com.company.moduleName路径下的所有类,判断是否存在@Component注解,之后将符合条件的类封装成BeanDefinition。
定义态就是Bean已经被封装成BeanDefinition的状态,这个状态下包含Bean的许多信息,比如scope、dependsOn、lazyInit、className、beanClass等。成为定义态之后需要判断是否符合初始化标准:比如是否是单例的,是否懒加载,是否是抽象的。符合标准就会直接进入实例化阶段。实例化成为早期暴露的Bean之后,就进入纯静态了。
纯静态之后的主要工作就是属性赋值,是DI的一种实现。属性赋值之后就进入初始化阶段,这个阶段会进行AOP的一个使用。这个步骤完成之后,就判断Bean的类型回调Aware接口,调用生命周期回调方法;如果需要代理就实现代理。在这之后就会将Bean添加进一个Map
中。这个Map就是BeanDefinitionMap,作用就是缓存好实例化的Bean对象,把它存放在单例池中,Bean的创建就完成了,Bean就存放在Spring Ioc容器中。
成熟态使用的时候就直接从IoC容器中获取所需要的Bean即可。至此IoC加载完成。
BeanDefinition主要用来存储Bean的定义信息,用来决定Bean的生产方式。
和
配置来定义依赖,这些配置将始终覆盖自动注入Bean装配就是将对象注入IoC容器,这个过程就是Bean的装配。
创建应用对象之间协作关系的行为通常称为装配(wiring),这也是依赖注入(DI)的本质。
Spring一般通过两个角度来自动化装配Bean:
第一种方式,使用构造器,构造过程是Spring来控制的,我们只是配置了一些Bean的定义信息。后面三种方法,Bean的构造过程都是可控的,虽然编写上稍微复杂,但使用上更加灵活。
单例Bean采用了单例模式,也就是这个类只能创建一个实例。单例模式中,将构造方法进行了私有化,而且单例类必须自己给自己提供这个唯一的实例,而且必须给所有其他实例提供这一对象。
由于不会每次都创建新的对象,所以有下面这些优点:
首先来简略地描述下Bean的加载过程:
假设是以JavaConfig的方式来创建Bean对象:@Bean
指令调用之后,会生成一个AnnotationConfigApplicationContext容器,之后解析配置类,注册成为一个BeanDefinitionMap,然后根据这个Map,由BeanFactory调用getBean方法,生成Bean对象。
那么BeanDefinition的加载,主要就是解析我们所需要传入的配置信息,然后将这些属性信息封装成一个BeanDefinition对象。顺序如下:
1、读取配置:BeanDefinitionReader
2、解析Config:@Bean @Import @Component…
3、配置类的解析器ConfigurationClassParser
4、扫描:ClassPathBeanDefinitionSacnner#doScan
5、根据包路径找到所有的.class文件判断类是不是标准@Component注解
6、排除接口是不是抽象类
7、注册BeanDefintion
在Bean的产生过程中,如果Bean已经被实例化,但是还没有被注入属性值和初始化,这个Bean就是不完整的,对应于纯静态。
假设现在存在多个线程,当第一个线程以微弱的优势将Bean创建之后存放到L3缓存中,但是还没有进行赋值,这个时候被第二个线程直接从三级缓存中获取到这个没有赋值的Bean,就造成了获取到的Bean不完整的情况。
Spring是通过双重检查锁DCL解决这个问题的。
双重检查锁是使用在单例模式中的,简单的DCL如下所示:
public class Singleton {
private volatile static Singleton singleton;
private Singleton (){
}
public static Singleton getSingleton() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
第一个线程在获取Bean的时候,会调用getSingleton方法来创建Bean。从这个时间点开始一直到创建完成,整个过程都会加锁。一级缓存只会存完整的Bean,创建的时候是在二三级缓存中进行的,二三级缓存在创建的过程中是加锁状态的,创建完成之后会返回一级缓存并清理二三级缓存。
整个过程一级缓存不加锁,第二个线程先访问一次一级缓存,如果没有创建完毕,那么一级缓存中是不会存在Bean的。二三级缓存此时加锁状态,线程就是阻塞的。
第一个线程创建完成之后二三级缓存锁释放并清理二三级缓存,线程二如果此时访问二三级缓存会发现是空状态。此时如果第二个线程直接创建,那么可能造成资源的浪费与单例获取出现问题,此时会进行二次检查一级缓存,会发现一级缓存中存在Bean,直接返回即可。
整个过程中不对一级缓存加锁,是为了提高性能,避免已经创建好的Bean阻塞等待。
后面三个注解都是调用的@Component注解,实际上都是@Component这一个注解。加上后三者注解是为了标识三层架构,提高代码的可读性。
循环依赖:简单分为三种:自身依赖自身;A依赖B,B又依赖A;三者及以上构成闭环的依赖关系。
参考文档:Spring 是如何解决循环依赖的? - 知乎 (zhihu.com)
spring的循环依赖_wojiao228925661的博客-CSDN博客_spring的循环依赖
Spring是通过三级缓存解决循环依赖的,简单来说就是三个Map。解决循环依赖的关键就是一定要有一个缓存保存早期对象,形成一个循环的出口。
A对应testService1,B对应于testService2。那么这个过程就是:
查缓存是在doGetBean方法中进行的,装配属性发现依赖关系是在populateBean方法中进行的。doGetBean方法由getBean方法调用。
二级缓存是为了避免实现了AOP的类重复创建动态代理。三级缓存中使用的是函数式接口,不会立即调用。使用二级缓存,就会避免在循环依赖中重复创建动态代理,这与普通的Bean初始化产生区分。【普通Bean在进行实例化创建,三级缓存中进行;循环依赖的Bean,创建循环依赖Bean的时候,三级缓存会被依赖的对象在创建的时候删除,避免了在三级缓存中创建动态代理(第二条)】
没有发生循环依赖的正常Bean的生命周期中,应该是在初始化的时候创建的动态代理。而由于发生循环依赖,是在第二次创建A的时候才会创建动态代理。
三级缓存的作用:①一级缓存存储完整的Bean;②二级缓存避免重复创建动态代理;③存放ObjectFactory对象,主要调用工厂产生早期Bean。
@Lazy
解决构造函数的循环依赖
ClassPathXmlApplicationContext(".xml")
AnnotationConfigApplicationContext(Config.class)
两者在实现的时候一般都使用多态的方式创建,利用共同的接口ApplicationContext
以多态的方式创建出不同的容器,之后使用ClassPathXmlApplicationContext
和AnnotationConfigApplicationContext
去调用不同的加载配置类的方法,解析配置信息,之后封装成BeanDefinition
对象。
加载配置注解容器的时候,AnnotationConfigApplicationContext
的过程:①读取配置类:使用AnnotatedBeanDefinitionReader
类的this.reader.register(annotatedClasses);
方法;②解析配置类:使用BeanDefinitionRegistryPostProcess
类,调用ConfigurationClassParser
配置类解析器,解析各种注解如@Bean @Component等,注册为BeanDefinition
对象。
加载xml配置文件的时候,ClassPathXmlApplicationContext
的过程:①加载xml配置文件:读取xml配置文件使用XmlBeanDefinitionReader
类来对配置文件进行读取操作,使用AbstractXmlApplicationContext#loadBeanDefinitions()
方法加载BeanDefinition
的所需信息;②解析配置文件:使用LoadBeanDifinition
和DefaultBeanDefinitionDocumentReader
来解析
和
等配置标签,注册为BeanDefinition
。
①基于XML文件的配置方式:从Spring诞生就有的方式,使用applicationContext.xml文件和
标签
②基于注解的配置方式:使用@Component注解标识该类是要注入到IoC容器的类,并且在applicationContext.xml文件中创建
标注需要扫描的包路径。需要注入的时候,使用@Autowired
注解完成注入。该方式在Spring 2.5版本之后开始支持。
③基于Java的配置:JavaConfig方式,诞生于Spring 3.0方式之后。使用@Configuration
和 @Bean
注解完成配置。
首先,对于prototype的Bean,Spring只负责在使用的时候加载多例的Bean,之后就交给客户端代码管理。对于singleton的Bean,Spring负责跟踪整个Bean的生命周期。
Bean的生命周期:指的是Bean从创建到销毁的整个过程。主要分为四个阶段:实例化、属性赋值、初始化、销毁。
setBeanName()
方法setBeanFactory()
方法setApplicationContext()
方法afterPropertiesSet()
方法,调用定制的初始化方法,调用BeanPostProcessor的后初始化方法。(调用初始化生命周期回调,有三种方式,此处是其一)初始化生命周期回调另外两种方式:①XML文件中指定
;②用注解@PostConstructor实现初始化生命周期回调Dispoable
接口的destroy()
方法
@PreDestory
创造销毁前置方法