Spring入门系列:浅析知识点

2023-04-11 00:00:00 创建 对象 方法 注解 构造

前言

讲解Spring之前,我们首先梳理下Spring有哪些知识点可以进行入手源码分析,比如:

  1. Spring IOC依赖注入
  2. Spring AOP切面编程
  3. Spring Bean的声明周期底层原理
  4. Spring 初始化底层原理
  5. Spring Transaction事务底层原理

Hello World

通过这些知识点,后续我们慢慢在深入Spring的使用及原理剖析,为了更好地理解Spring,我们需要先了解一个简单的示例——Hello World。在学习任何框架和语言之前,Hello World都是必不可少的。

//在以前大家都是spring.xml进行注入bean后供Spring框架解析
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
UserService userService = (UserService) context.getBean("userService");
userService.test();

spring.xml中的内容为:

<context:component-scan base-package="com.zhouyu"/>
<bean id="userService" class="com.zhouyu.service.UserService"/>

如果对上面的代码或者xml形式很陌生,再看下面一种代码,也是目前流行的一种形式

//通过我们的配置类进行注入bean并解析
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MySpringConfig.class);
//ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
UserService userService = (UserService) context.getBean("userService");
userService.test()

MySpringConfig中的内容为:

@ComponentScan("com.xiaoyu")
public class MySpringConfig {
	@Bean
	public UserService userService(){
		return new UserService();
	}
}

相信很多人都会对上面的代码不陌生,那我们来看下这三行代码都做了那些工作:

  1. 构造一个ClassPathXmlApplicationContext类解析配置文件 或者AnnotationConfigApplicationContext类 解析配置类,那么调用该构造方法除开会实例化得到一个对象,还会做哪些事情?
  2. 从context中获取一个名字为"userService"的userService对象,那么为什么输入一个字符串就可以得到对象呢,好像跟Map<String,Object>有些类似,getBean()又是如何实现的?返回的UserService对象和我们自己直接new的UserService对象有区别吗?
  3. 通过获取到的UserService对象调用test方法,这个不难理解。

虽然我们目前很少直接使用这种方式来使用Spring,而是使用Spring MVC或者Spring Boot,但是它们都是基于上面这种方式的,都需要在内部去创建一个
ApplicationContext的,只不过:

  1. Spring MVC创建的是XmlWebApplicationContext,和ClassPathXmlApplicationContext类似,都是基于XML配置的
  2. Spring Boot创建的是AnnotationConfigApplicationContext

下面我们将重点讲解Spring对bean的创建,也就是大家常说的Bean的生命周期。虽然我们只是入门级别的学习,但我们仍将深入探讨流程,但不会涉及到具体的源码分析。在后续的源码分析系列中,我们将着重分析每一个知识点。

Bean的创建过程

那么,Spring是如何创建一个Bean的呢?这就是Bean的生命周期。其大体过程如下:

  1. 首先当我们的对象类被配置类使用@Bean又或者被包路径扫描到将会进入创建Bean的流程
  2. Spring会使用对象构造器进行实例化创建个对象
  3. 开始解析对象的属性是否有被@autowired注解修饰,如果有会从Spring的单例池中获取对象并进行注入就是属性赋值(依赖注入)
  4. 依赖注入后会判断对象时否实现了各种Aware接口,如:BeanNameAware接口、 BeanClassLoaderAware接口、BeanFactoryAware接口等,并自动调用其中的需要被实现的方法(Aware回调)
  5. 回调方法实现后,Spring会判断是否有包含了@postconstruct注解方法,如果有会进行调用此方法(实例化前)
  6. Spring判断对象是否实现了InitializingBean接口,实例化对象就必须调用afterPropertiesSet()方法,也是Spring帮调用的(初始化)
  7. 后,Spring会判断我们的对象是否需要进行AOP,如果不需要那么Bean到此就完事,如果需要,Spring还会自动动态代理并生成一个代理对象做为Bean(初始化后)

通过以上步骤,我们可以了解到创建的对象可能存在两种结果。如果不需要AOP,Bean就是通过构造方法创建的对象;如果需要AOP,Bean就是代理类所实例化得到的对象,而不是构造方法所得到的对象。

Bean创建后:

  1. 如果当前Bean是单例的(默认),Spring在初始化Bean之后,会将当前已经初始化之后的对象放入Spring管理的单例缓存池中(Map结构),这样如果其他对象需要注入这个对象时,会直接从单例缓存池中取出来,进行属性注入。
  2. 如果当前Bean是多例的(即被@Scope("prototype")注解修饰),每次获取对象时,或者被其他对象引用时都会重新走一遍创建Bean的逻辑来创建一个新对象。这意味着,每次获取该Bean时都会创建一个新的实例,而不是从缓存池中获取已有的实例。因此,多例Bean的对象是不共享的,每个对象都是独立的。

构造器的初始化

在Spring中,每个对象都会有默认的构造器。但是在实际业务中,有时候会存在多个构造器的情况。那么,Spring如何去选择使用哪个构造器去创建对象呢?
Spring的判断逻辑如下:

  1. 如果当前bean有且仅有一个的构造器,那么直接使用这个构造器创建对象。不管它是有参还是无参。
  2. 如果存在多个构造器,Spring会从中选择一个无参构造器进行创建对象,如果没有无参构造器,那么直接报错。

Spring的设计思想是这样的:

  1. 如果只有一个构造器,那么没有选择,只能使用这个构造器

  2. 如果有多个,只选择没有入参的构造器,因为无参构造方法本身表示了一种默认的意义

  3. 还要一种就是使用了@Autowired注解修饰,那么就表示人工干预 了Spring选择的权利,直接选择程序员指定的构造器,如果有参,里面的参数bean对象(单例)会从单例缓存池中取。

    a.先按照bean类型进行查找,如果只找到一个实例,那么直接注入。
    b.如果找到多个实例,那么会进行匹配入参name名字来确定唯*一一个实例。
    c如果没有找到,则会报错,无法创建当前Bean对象

综上所述,Spring会根据Bean的构造器情况进行选择,如果需要人工干预,可以使用@Autowired注解修饰。

AOP的大致流程

在创建对象时,Spring会判断当前对象是否需要进行AOP代理。为了确定当前Bean对象是否需要代理,大致流程如下:

  1. Spring启动时寻找所有使用@AspectJ注解的切面Bean对象
  2. 搜索切面bean的各个方法是否有包含了@Before、@After、@Around注解。
  3. 检查当前Bean或方法是否符合我们编写的Pointcut切面条件。
  4. Spring创建代理对象通常采用cglib代理,JDK代理是另一种模式。后续我们会详细分析此逻辑。

具体流程如下:

  1. 首先当前bean被确认为需要进行代理,将会生成BeanProxy对象,而不是我们构造出来的Bean。
  2. 当前BeanProxy会重写符合切面方法的方法
  3. 当前BeanProxy被依赖注入时,给其他Bean的属性赋值时也会是代理对象。
  4. BeanProxy代理对象都有一个target属性,用于注入被代理对象,即我们正常构造并进行属性注入的Bean对象
  5. 当BeanProxy的切面方法被调用时,首先调用我们编写的切面逻辑,然后调用被代理对象的方法(即我们编写的业务方法)。这里也不太准确,具体得看你写的什么样的切面注解,不过大致都是这样来调用的链路。

Spring事务

一谈到事务,大家首先想到的肯定是@Transaction注解,而这种注解也会被Spring创建对象时检测到,然后会生成代理对象,这种方式其实工作中用到的也特别多,用起来也特别爽,那我们也大概讲下事务的逻辑处理吧,其大致流程如下:

  1. 首先如果当前Bean有@Transaction注解,那么当前bean会成为代理对象。
  2. 当被调用到具体的方法时。Spring则会利用事务管理器TransactionManage获取一个数据库连接。
  3. 直接将当前数据库连接的自动提交关闭,autocommit=false。
  4. 开始执行我们的业务方法,执行业务SQL操作。
  5. 如果执行完方法后,没有异常则提交事务,如果出现异常则进行回滚。

相关文章