一.识点串讲,后续章节进行深入分析
内容:
- Bean的生命周期底层原理
- 依赖注入底层实现原理
- 初始化底层原理
- 推断构造方法,底层实现原理
- AOP底层实现原理
- Spring事务底仓原理
1.1 Bean的生命周期
先看一段下面代码
ClassPathXmlApplication context = new ClassPathXmlApplicationContext("spring.xml");
UserService userService = context.getBean("userService");
userService.test();
这三行代码做了什么呢?
- 第一行代码,会构造一个ClassPathXmlApplicationContext对象, ClassPathXmlApplicationContext,调用该构造方法除开会实例化得到 一个对象,还会做哪些事情?
- 第二行代码,会调用ClassPathXmlApplicationContext的getBean方法,会得到 一个UserService对象,getBean()是如何实现的?返回的UserService对象和我们自 己直接new的UserService对象有区别吗?
- 第三行代码,就是简单的调用UserService的test()方法。
对于上的三行代码,如果要用的话就得这么写,如果你要用Spring,你就得这么写。就像你要用Mybatis,你就得写各种 Mapper接口。 但是用ClassPathXmlApplicationCotext其实已经过时了,在新版的Spring MVC和SpringBoot的底层主要用的都是AnnotationConfigAppilcationApplicationContext.
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
UserService userservice = (UserService)context.getBean("userService");
userServcie.test();
可以看到AnnotationConfigApplicationContext的用法和 ClassPathXmlApplicationContext是非常类似的,只不过需要传入的是一个class,而不是 一个xml文件。 而AppConfig.class和spring.xml一样,表示Spring的配置,比如可以指定扫描路径,可以 直接定义Bean,比如: spring.xml中的内容为:
<context:component-scan base-package = "com.ruozhuo"/>
<bean id = "userService" class = "com.ruozhuo.service.UserService"
AppConfig里面的内容为:
@ComponentScan("com.ruozhuo")
public class AppConfig{
@Bean
public UserService userService(){
return new UserService;
}
}
spring.xml和AppConfig的本质是一样的 目前我们基本很少使用上面两种方式来用spring,而是使用SpringMVC,或是SpringBoot,但是他们都是基于上面的方式进行的,都是需要在内部构建一个ApplicationContext的,只不过:
- Spring MVC创建的是XmlWebApplicationContext,和 ClassPathXmlApplicationContext类似,都是基于XML配置的
- Spring Boot创建的是AnnotationConfigApplicationContext 因为AnnotationConfigApplicationContext是比较重要的,并且 AnnotationConfigApplicationContext和ClassPathXmlApplicationContext大部分底层 都是共同的,后续会着重介绍AnnotationConfigApplicationContext的底层实现
二.Spring中如何创建一个对象
不管是AnnotationConfigApplicationContext还是ClassPathXmlAplicationContext,都可以简单的理解为就是用来创建Java对象的,比如调用getBean()就会去创建对象(此处不严谨,getBean可能也不会去创 建对象,后续详解)
在Java语言中,肯定是根据某个类来创建一个对象的。我们在看一下实例代码:
AnnotationConfigApplicationContext context =
new
AnnotationConfigApplicationContext(AppConfig.class);
UserService userService = (UserService) context.getBean("userService");
userService.test();
当我们调用context.getBean("userService")时,就会去创建一个对象,但是getBean方法 内部怎么知道"userService"对应的是UserService类呢? 所以,我们就可以分析出来,在调用AnnotationConfigApplicationContext的构造方法 时,也就是第一行代码,会去做一些事情:
1. 解析AppConfig.class,得到扫描路径
2. 遍历扫描路径下的所有Java类,如果发现某个类上存在@Component、 @Service等注解,那么Spring就把这个类记录下来,存在一个Map中,比如 Map<String,Class>(实际上,Spring源码中确实存在类似的这么一个Map,叫 做BeanDefinitionMap,后续会讲到)
3. Spring会根据某个规则生成当前类对应的beanName,作为key存入Map,当前 类作为value
总结: 扫描到注解:@Component注解、@Service注解、@Bean注解等,根据一定的规则生成当前类对应的beanName-----即对象名字---》作为key存入Map中,当前类作为value.这样在调用context.getBean("userService")时候,就可以根据userService(key)映射到(valeu)UserService类,-----紧接着根据UserService类去创建对象。
三.Bean的创建过程
那么Spring到底是如何来创建一个Bean的呢,这个就是Bean创建的生命周期,大致过程如下:
-
利用类的构造方法来实例化得到一个对象
有多方法----spring会进行选择,这个叫做推断构造方法。
-
得到对象之后会判断是否有被@Autowired注解修饰的属性,这些属性找出来并由Spring进行行赋值-----这个过程叫做依赖注入。
-
依赖注入后spring会判断该对象是否实现了BeanNameAware接口、BeanClassLoaderAware接口、BeanFactoryAware接口,如果实现了,就表示当前对象必须分别实现接口中定义的方法
- BeanNameAware接口-----setBeanName(设置对象名字)
- BeanFactoryAware接口-----setBeanClassLoader(设置对象类加载器)
- BeanFactoryAware------setBeanFactory(设置对象工厂)
Spring会调用这些方法,并传入相应的参数**(Aware回调**
-
Aware回调后----即设置完,Spring会判断该对象中是否存在某个方法被@PostConstruct注解了,如果存在Spring会调用当前对象的此方法**(初始化前)**
-
紧接着,Spring会判断该对象是否实现了InitiailizingBean接口,如果实现了就表示当前对象必须实现该接口中的“afterPropertiesSet()”方法,那Spring就会调用当 前对象中的afterPropertiesSet()方法(初始化)属性赋值之后。
-
最后,Spring会判断当前对象需不需要进行AOP,如果不需要那么Bean就创建完 了,如果需要进行AOP,则会进行动态代理并生成一个代理对象做为Bean(初始化 后) 通过最后一步,我们可以发现,当Spring根据UserService类来创建一个Bean时:
如果不用进行AOP,那么Bean就是UserService类的构造方法所得到的对象。
如果需要进行AOP,那么Bean就是UserService的代理类所实例化得到的对象,而 不是UserService本身所得到的对象。
Bean对象创建出来后: 1. 如果当前的Bean是单例Bean------会把Bean对象存入一个Map<String,Object>,Map的key为beanName,valu为Bean对象。这样下次getBean时就可 以直接从Map中拿到对应的Bean对象了。(实际上,在Spring源码中,这个Map就 是单例池) 2. 如果当前Bean是原型Bean,那么后续没有其他动作,不会存入一个Map,下次 getBean时会再次执行上述创建过程,得到一个新的Bean对象。
推断构造方法
Spring在基于某个类生成Bean的过程中,需要利用该类的构造方法来实例化得到一个对 象,但是如果一个类存在多个构造方法,Spring会使用哪个呢?
Spring的判断逻辑如下:
-
如果一个类只存在一个构造方法,不管该构造方法是无参构造方法,还是有参构造 方法,Spring都会用这个构造方法
-
如果一个类存在多个构造方法
a. 这些构造方法中,存在一个无参的构造方法,那么Spring就会用 这个无参的构造方法
b. 这些构造方法中,不存在一个无参的构造方法,那么Spring就会 报错 Spring的设计思想是这样的:
-
如果一个类只有一个构造方法,那么没得选择,只能用这个构造方法
-
如果一个类存在多个构造方法,Spring不知道如何选择,就会看是否有无参的构 造方法,因为无参构造方法本身表示了一种默认的意义
-
不过如果某个构造方法上加了@Autowired注解,那就表示程序员告诉Spring就用这个加了注解的方法,那Spring就会用这个加了@Autowired注解构造方法了 需要重视的是
-
如果Spring选择了一个有参的构造方法,Spring在调用这个有参构造方法 时,需要传入参数,那这个参数是怎么来的呢?
Spring会根据入参的类型和入参的名字去Spring中找Bean对象(以单例Bean为例, Spring会从单例池那个Map中去找):
a. 先根据入参类型找,如果只找到一个,那就直接用来作为入参
b. 如果根据类型找到多个,则再根据入参名字来确定唯一一个
c. 最终如果没有找到,则会报错,无法创建当前Bean对象 确定用哪个构造方法,确定入参的Bean对象,这个过程就叫做推断构造方法。
-
AOP的大致流程
AOP就是进行动态代理,在创建Bena的过程中,Spring会在最后一步判断当前正在创建的这个Bean是不是需要进行AOP,如果需要就会进行动态代理。
如何判断当前Bean是否需要动态代理:
1. 找出所有的切面Bean
2. 遍历切面中的每一个方法,看是否写了@Before,@After注解
3. 如果写了,则判断所对应的@PointCut是否和当前的Bean对象的类匹配
4. 如果匹配则表示当前Bean对象有匹配的PointCut,表示需要进行AOP