Spring Framework 源码分析(1)

基础概念

Posted by Jason Lee on 2021-03-23

概诉

Spring在java 语言层面的核心地位不用多说,Spring 三个核心思想:控制反转,依赖注入和面向切面编程,分别是

一 、Spring IOC (控制反转)

  • IOC (即 Inversion of Control)为控制反转.Spring开发的基本思想 : 采用面向接口的编程模式.框架做的越多,就越有体会接口在起中起的作用,而Spring将这一想法,开始贯彻到业务的开发中.Bean的Set方法使用接口作为参数,保证其扩展性,实现依赖关系的解耦合.所谓的控制反转,也可以理解为依赖注入,IOC容器就是具有依赖注入功能的容器,IOC容器负责实例化、定位、配置应用程序中的对象及监理这些对象间的依赖.程序将无需直接在代码中new相关对象,程序将由IOC容器进行组装.

  • 简单点说,就是将创建对象的控制权,被反转到了Spring框架上. 通常,我们实例化一个对象时,都是使用类的构造方法来new一个对象,这个过程是由我们自己来控制的,而控制反转就把new对象的工交给了Spring容器。

  • IOC主要实现方式有两种 : 依赖查找 、 依赖注入 . 依赖注入是一种更可取的方式.

  • 依赖查找,主要是容器为组件提供一个回调接口和上下文环境。这样一来,组件就必须自己使用容器提供的API来查找资源和协作对象,控制反转仅体现在那些回调方法上,容器调用这些回调方法,从而应用代码获取到资源。

  • 依赖注入,组件不做定位查询,只提供标准的Java方法让容器去决定依赖关系。容器全权负责组件的装配,把符合依赖关系的对象通过Java Bean属性或构造方法传递给需要的对象。

二 、DI (依赖注入)

由IOC容器动态将某个对象所需要的外部资源 (包括对象 、资源 、 常量 、)注入到组件(Controller 、 Service 等) 之中 . 简单说,就是IOC容器会把当前对象所需要的外部资源动态的注入给我们 .

Spring依赖注入的方式主要有四种,基于注解注入方式、set注入方式 、 构造器注入方式 、 静态工厂注入方式 . 推荐使用注解注入方式,配置扫,比较方便.

三、 Spring AOP (面向切面编程)

  • AOP (即 Aspect Oriented Programing) 称为:面向切面编程,它是一种编程思想。在程序运行的时候,动态地将代码切入到类的指定方法、指定位置上的编程思想就是面向切面的编程。 比如业务A和业务B现在需要一个相同的操作,传统方法我们可能需要在A、B中都加入相关操作代码,而应用AOP就可以只写一遍代码,A、B共用这段代码。并且,当A、B需要增加新的操作时,可以在不改动原代码的情况下,灵活添加新的业务逻辑实现。 简单来说就是“纵向重复,横向抽取”这很抽象, Filter 的思想,过滤器在解决乱码的时候不需要我们在每一个类中都写处理乱码的代码。而是直接由 Fliter 来统一处理。还有拦截器的思想,我们在 Action 中需要对参数进行校验、封装,而拦截器又是统一对参数进行校验、封装。 不管是 Fliter 还是拦截器,都是面向切面编程思想的具体应用。

  • 在实际开发中,比如商品查询、促销查询等业务,都需要记录日志、异常处理等操作,AOP把所有共用代码都剥离出来,单独放置到某个类中进行集中管理,在具体运行时,由容器进行动态织入这些公共代码。

  • Spring AOP 的具体实现 : 动态代理 和 cglib 代理。Spring 中通过代理来体现 AOP 的思想,而代理的实现又分为动态代理和 cglib 代理。动态代理要求代理类和被代理类实现同一个接口。而 cglib 是继承代理,代理对象只需继承被代理对象即可实现。在 Spring 中优先使用动态代理。

源码分析环境搭建

此章节不不在赘述,在网上一搜就能出现大把的sprign源码分析环境。Spring 源码仓库地址是:https://github.com/spring-projects/spring-framework。

核心思想分析

构建示例程序

目录结构如下:

1
2
3
4
5
6
7
8
9
├── build.gradle
└── src
├── main.java.org.springframework
│   │   ├── bean
│   │   │   └── TestBean.java
│   │   └── debug
│   │   └── SpringDebugMain.java
│   resources
   └── beans.xml
  • SpringDebugMain
1
2
3
4
5
6
7
8
public class SpringDebugMain {

public static void main(String[] args) {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:beans.xml");
TestBean testbean = ctx.getBean(TestBean.class);
System.out.println("testbean = " + testbean);
}
}
  • beanx.xml
1
2
3
4
5
6
7
8
9
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean class="org.springframework.bean.TestBean" id="testBean">
<property name="name" value="testbean1"/>
<property name="address" value="testaddress"/>
</bean>
</beans>
  • bean.java
1
2
3
4
5
6
7
package org.springframework.bean;

public class TestBean {
private String name;
private String address;
// get set to String 方法省略
}
  • build.gradle
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
plugins {
id 'java'
}

group 'org.springframework'
version '5.2.14.BUILD-SNAPSHOT'

repositories {
mavenCentral()
}

dependencies {
compile(project(":spring-context"))
testCompile group: 'junit', name: 'junit', version: '4.12'
}

前置核心概念

注意下面的所有概念均为抽象层

Resource

Java一个标准类 java.net.URL,该类在 Java SE 中的定位为统一资源定位器(Uniform Resource Locator),但是我们知道它的实现基本只限于网络形式发布的资源的查找和定位。然而,实际上资源的定义比较广泛,除了网络形式的资源,还有以二进制形式存在的、以文件形式存在的、以字节流形式存在的等等。而且它可以存在于任何场所,比如网络、文件系统、应用程序中。所以 java.net.URL 的局限性迫使 Spring 必须实现自己的资源加载策略,该资源加载策略需要满足如下要求:

  • 职能划分清楚。资源的定义和资源的加载应该要有一个清晰的界限;
  • 统一的抽象。统一的资源定义和资源加载策略。资源加载后要返回统一的抽象给客户端,客户端要对资源进行怎样的处理,应该由抽象资源接口来界定。

ResourceLoader

资源加载器,用于将文件,二进制流解析成 Resource 文件下层使用。

Environment

资源,主要是spring运行所以依赖的环境,这些环境主要是存放一下属性值或者配置。

BeanDefinitionReader

BeanDefinitionReader ,该接口的作用就是加载 Bean。在 Spring 中,Bean 一般来说都在配置文件中定义。而在配置的路径由在 web.xml 中定义。所以加载 Bean 的步骤大致就是:加载资源,通过配置文件的路径(Location)加载配置文件(Resource)解析资源,通过解析配置文件的内容得到 Bean。这个是一个扩展层,可以有多个扩展来实现,这个只是定义了抽象层接口。

BeanDefinition

Bean的定义主要由BeanDefinition来描述的。作为Spring中用于包装Bean的数据结构,里面装配了产生这个bean的所有信息,我们第IOC容器也就是 beanFactory 工厂会根据这个配置信息来生产bean。

BeanFactory

BeanFactory,以Factory结尾,表示它是一个工厂类(接口), 它负责生产和管理bean的一个工厂。在Spring中,BeanFactory是IOC容器的核心接口,它的职责包括:实例化、定位、配置应用程序中的对象及建立这些对象间的依赖。

ApplicationContext

ApplicationContext建立在BeanFactory的基础上,提供了更多面向应用的功能, 它提供了国际化支持和框架事件体系。当然本身他也将整个上述所有的关系连接起来,形成处理流,处理完成之后就会开启我们的spring容器。

我们一般称BeanFactory为IoC容器,而称ApplicationContext为应用上下文,但有时候为了行文方便,我们也将ApplicationContext称为Spring容器。
对于BeanFactory 和 ApplicationContext的用途:BeanFactory是Spring框架的基础设施,面向Spring本身
ApplicationContext面向使用Spring框架的开发者,几乎所有的应用场合都可以直接使用Application而非底层的BeanFactory.

bean产生的过程

Bean声明周期

本节我们来解决的是 bean的声明周期。 也就是上图的产出的过程。试想一下,我们如果想创建自己的java 对象,应该怎么做。

1
2
3
4
5
6
7
8
9
10
11

TestBean bean = new TestBean(); // 创建

// 赋值
bean.setName("bean Name");
bean.setAddress("address");

// 其他操作 记录对象到某个地方

// 销毁
// jvm gc 进程负责回收

这个过程包括了几个步骤

  1. 创建对象
  2. 给对象赋值
  3. 使用对象
  4. 销毁对象

这几个步骤就包括了springIOC容器对bean的管理基本步骤,当然还有一些其他额外操作比如说一下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13

public class BeanInitPostProcessor implements InitializingBean, BeanNameAware {
@Override
public void afterPropertiesSet() throws Exception {
// 这里面做一下属性赋值之后的一些工作

}
@Override
public void setBeanName(String name) {
// 这是发现接口,当我们实现Aware 接口之后,我们可以将 bean 初始化一些属性注入进来

}
}

综上所述:我们的ioc对bean有一下几个阶段

  1. 实例化 Instantiation
  2. 属性赋值 Populate
  3. 初始化 Initialization
  4. 销毁 Destruction

过程如下:实例化 -> 属性赋值 -> 初始化 -> 销毁

  • 实现了这些接口的Bean会切入到多个Bean的生命周期中。正因为如此,这些接口的功能非常强大,Spring内部扩展也经常使用这些接口,例如自动注入以及AOP的实现都和他们有关。例如:BeanPostProcessor, InstantiationAwareBeanPostProcessor吗,InstantiationAwareBeanPostProcessor作用于实例化阶段的前后,BeanPostProcessor作用于初始化阶段的前后。

  • Aware类型的接口的作用就是让我们能够拿到Spring容器中的一些资源。基本都能够见名知意,Aware之前的名字就是可以拿到什么资源,例如BeanNameAware可以拿到BeanName,以此类推。调用时机需要注意:所有的Aware方法都是在初始化阶段之前调用的!

声明周期详解

作者:大闲人柴毛毛
链接:https://www.zhihu.com/question/38597960/answer/247019950
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

实例化对象

  1. 实例化Bean对于BeanFactory容器,当客户向容器请求一个尚未初始化的bean时,或初始化bean的时候需要注入另一个尚未初始化的依赖时,容器就会调用createBean进行实例化。 对于ApplicationContext容器,当容器启动结束后,便实例化所有的bean。 容器通过获取BeanDefinition对象中的信息进行实例化。并且这一步仅仅是简单的实例化,并未进行依赖注入。 实例化对象被包装在BeanWrapper对象中,BeanWrapper提供了设置对象属性的接口,从而避免了使用反射机制设置属性。

依赖注入属性

  1. 设置对象属性(依赖注入)实例化后的对象被封装在BeanWrapper对象中,并且此时对象仍然是一个原生的状态,并没有进行依赖注入。 紧接着,Spring根据BeanDefinition中的信息进行依赖注入。 并且通过BeanWrapper提供的设置属性的接口完成依赖注入。

Aware接口

  1. 注入Aware接口紧接着,Spring会检测该对象是否实现了xxxAware接口,并将相关的xxxAware实例注入给bean。

自定义处理器

  1. BeanPostProcessor当经过上述几个步骤后,bean对象已经被正确构造,但如果你想要对象被使用前再进行一些自定义的处理,就可以通过BeanPostProcessor接口实现。 该接口提供了两个函数:postProcessBeforeInitialzation( Object bean, String beanName ) 当前正在初始化的bean对象会被传递进来,我们就可以对这个bean作任何处理。 这个函数会先于InitialzationBean执行,因此称为前置处理。 所有Aware接口的注入就是在这一步完成的。postProcessAfterInitialzation( Object bean, String beanName ) 当前正在初始化的bean对象会被传递进来,我们就可以对这个bean作任何处理。 这个函数会在InitialzationBean完成后执行,因此称为后置处理。

InitializingBean 初始化

  1. InitializingBean与init-method当BeanPostProcessor的前置处理完成后就会进入本阶段。 InitializingBean接口只有一个函数:afterPropertiesSet()这一阶段也可以在bean正式构造完成前增加我们自定义的逻辑,但它与前置处理不同,由于该函数并不会把当前bean对象传进来,因此在这一步没办法处理对象本身,只能增加一些额外的逻辑。 若要使用它,我们需要让bean实现该接口,并把要增加的逻辑写在该函数中。然后Spring会在前置处理完成后检测当前bean是否实现了该接口,并执行afterPropertiesSet函数。当然,Spring为了降低对客户代码的侵入性,给bean的配置提供了init-method属性,该属性指定了在这一阶段需要执行的函数名。Spring便会在初始化阶段执行我们设置的函数。init-method本质上仍然使用了InitializingBean接口。

对象的销毁

  1. DisposableBean和destroy-method和init-method一样,通过给destroy-method指定函数,就可以在bean销毁前执行指定的逻辑。

参考



支付宝打赏 微信打赏

赞赏一下