《精通Spring4.x》阅读笔记(一)- SpringIoC读这一篇就够了

文末福利:扫描文末二维码,回复关键字"SpringIoC"获取总结的完整思维导图。

Spring4.x-IoC

IoC基本概念

IoC(Inverse of Control 控制反转):接口实现类的选择控制权,从调用类中移除,转交给第三方决定,即由Spring容器借由Bean配置来进行控制。

IoC的概念不太直观,后来有人提出DI的概念用来代替IoC。

DI(Dependency Injection 依赖注入):调用类对某一接口的实现类的依赖关系由第三方(容器或协作类)注入,以移除调用类对某一接口实现类的依赖。

IoC的类型有:

  1. 构造函数注入
  2. 属性注入
  3. 接口注入(效果与属性注入类似,不推荐使用)

Spring支持构造函数注入和属性注入。

IoC的实现方式–反射

Java语言允许通过程序化的方式间接对Class进行操作。Class文件由类装载器装载后,在JVM中将形成描述Class结构的元信息对象,通过该元信息对象可以获知Class的结构信息。为使用程序化的方式操作Class对象开辟了途径。Java反射涉及到的主要类如下:

ClassLoader

类装载器:负责寻找类的字节码文件并构造出类在JVM内部表示对象的组件

类装载步骤:

  1. 装载:查找和导入Class文件
  2. 链接
    ① 校验:检查载入Class文件数据的正确性
    ② 准备:给类的静态变量分配存储空间
    ③ 解析(可选):将符号引用转换成直接引用
  3. 初始化:对类的静态变量、静态代码块执行初始化工作

JVM运行时产生3个ClassLoader:根装载器、ExtClassLoader、AppClassLoader
根装载器不是ClassLoader的子类,它是用C++编写,负责装载JRE核心类库:rt.jar、charsets.jar等
ExtClassLoader、AppClassLoader都是ClassLoader的子类。Ext负责装载JRE扩展目录ext中的JAR类包,AppClassLoader负责装载Classpath路径下的类包。

扫描二维码关注公众号,回复: 10986640 查看本文章

三个装载器存在父子层级关系,默认情况下使用AppClassLoader装载应用程序中的类。JVM装载类时的全盘负责委托机制(/双亲委派机制):

  • 该类所依赖及引用的类也由这个ClassLoader载入(除非显式使用其他ClassLoader);(全盘负责)
  • 先委托父装载器寻找目标类,找不到的情况下从自己的类路径中查找并装载目标类。(委托机制)

主要方法:

  1. defineClass(String name, byte[] b, int off, int len):将类文件的字节数组转换成JVM内部的java.lang.Class对象。字节数组可以从本地文件系统、远程网络获取。参数name为字节数组对应的全限定类名。该方法由JVM装载类时调用构造Class对象
  2. getParent() 获取装载器的父装载器。除根装载器,所有类装载器有且仅有一个父装载器。ExtClassLoader的父装载器是根装载器,非Java语言编写,所以调用该方法时返回null;
  3. loadClass(name)、loadClass(name, resolve):通过全限定类名装载类;
  4. findSystemClass(name):从本地文件系统载入Class文件;
  5. findLoadedClass(name):查看ClassLoader是否已经装载某个类,已装入会返回Class对象,否则返回null。

Class文件、类装载器、Class类对象之间的引用关系如图:
类加载机制

主要的反射类

  1. Constructor:构造函数反射类,可通过Class对象的getContructor(s)方法获得,其主要的方法是newInstance可创建类对象实例,相当于new关键字。
  2. Method:类方法的反射类,可通过Class对象的getDeclaredMethod(s)方法获得,其主要的方法是invoke执行指定对象的这个方法。同时,可通过其他方法获得该Method的返回、参数类型、异常、注解信息
  3. Field:类成员变量反射类,可通过Class对象的getDeclaredField(s)方法获得,其主要方法是set为指定对象域设置值,基本类型域通过setInt/setBoolean等设置值。

资源访问与加载

Spring通过访问配置文件,或者读取配置类进行容器的初始化、启动、刷新等操作,资源访问作为Spring框架的底层基础实施,具体是怎样实现的呢?提供了哪些扩展的方式?下面具体来看……

Spring设计了Resource接口,为应用提供了更强的底层资源访问能力。通过Spring接口,可以将Spring的配置信息放置到任何地方,比如数据库、LDAP等。

Resource在Spring框架中起着不可或缺的作用,Spring使用Resource装载各种资源,包括配置文件资源、国际化属性文件资源等。具体实现类如下图:

资源实现类

资源实现类体系结构

  1. WritableResource:字接口类定义了可写资源相关方法,其实现类有FileSystemResource、PathResource。

  2. ClassPathResource:类路径下的资源,资源以类路径的方式表示。

  3. UrlResource:封装了java.net.URL,能够访问通过URL表示的资源,如文件系统资源、HTTP资源等。

  4. ServletContextResource:为访问Web容器上下文中资源定义的类。以Web应用根路径的方式加载资源。

  5. ByteArrayResource:二进制数组表示的资源。

  6. InputStreamResource:以输入流返回表示。

  7. FileSystemResource:文件系统资源。

  8. PathResource:表示任何通过URL、Path、系统文件路径表示的资源。

资源加载

有了针对资源的表达方式,加载资源上需要由资源的路径地址以及资源加载器实现,可以将资源文件看作代加工的物料,加载器看作加工处理器,Spring的体系中随处可见这样的设计思路。

资源表达式
不需要显示指定具体Resource实现类,通过地址自动识别,支持的以下地址及对应的实现类:
file:/xxx、http://xxx、ftp://xxx 使用UrlResource装载资源;classpath:/xxx使用ClassPathResource装载;没有前缀则使用ApplicationContext具体实现类对应类型的Resource。

“classpath:” 还有一种"classpath*:"的前缀,表示在多个包名中进行匹配,否则仅匹配第一个出现的包名,对于分模块打包需要加载多个配置文件的情况下适用。

Ant风格支持的匹配符:

  • ?:匹配文件名中任意一个字符。
  • *:匹配文件名中任意多个字符。
  • **:匹配多层目录

资源加载器
资源加载器类结构

ResourceLoader接口仅支持带资源类型前缀的表达式,ResourcePatternResolver扩展ResourceLoader接口,支持Ant风格资源路径表达式。PathMatchingResourcePatternResolver是Spring提供的标准实现。

Spring容器-框架级接口

我们平常所说的Spring容器具体在Spring框架中,指代的是BeanFactory、ApplicationContext以及在Web环境中使用的WebApplicationContext三个容器级别的接口,在启动Spring程序时,也是通过其中某一个接口进行容器的启动工作。那这三个接口具体是什么含义呢,他们之间又有什么相同点和区别呢,他们具体又有哪些实现类供我们实际使用呢?

BeanFactory

BeanFactory可看作是Spring内部的基础设施,是对容器的抽象,定义了Bean工厂的基本方法,供Spring内部使用;ApplicationContext在BeanFactory的基础上,提供了更丰富的功能支持,比如国际化支持和框架事件体系等。

对于Spring的使用者,我们一般使用ApplicationContext,而非底层的BeanFactory。为了更好的理解Spring框架,我们还是有必要理解BeanFactory的设计思路的,BeanFactory中定义个主要方法为getBean(beanName),通过bean名称获得bean,通过其他接口扩展BeanFactory的功能,具体如下:

BeanFactory类继承体系

  1. ListableBeanFactory 定义访问容器中基本信息的方法:查看个数、获取某一类型Bean的配置名、容器中是否包含某一Bean等。
  2. HierarchicalBeanFactory 父子级联IoC容器接口,子容器可以通过该接口方法访问父容器,而父容器无法访问子容器。子容器可以拥有一个和父容器id相同的Bean。增强了Spring框架的扩展性和灵活性,第三方可以通过编程方式添加子容器,提供一些额外功能。比如:SpringMVC中,展现层Bean位于一个子容器,可访问业务层和持久才能够的bean,反过来却无法访问。
  3. ConfigurableBeanFactory 重要接口,增强可定制性。包括:设置类装载器、属性编辑器、容器初始化后置处理器等方法。
  4. AutowireCapableBeanFactory 定义将容器中的bean按某种规则(名字或类型匹配)进行自动装配的方法
  5. SingletonBeanRegistry 允许运行期向容器注册单实例Bean的方法
  6. BeanDefinitionRegistry 提供向容器手工注册BeanDefinition方法。(每一个节点元素对应Spring容器中的BeanDefinition对象表示)

BeanFactory常用实现类为DefaultListableBeanFactory,配合使用XmlBeanDefinitionReader读取配置信息,启动容器:

public class BeanFactoryTest {

    public static void main(String[] args) {
        ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        Resource resource = resolver.getResource("classpath:com/smart/beanfactory/beans.xml");

        DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
        XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(bf);
        reader.loadBeanDefinitions(resource);

        Car car = bf.getBean("car", Car.class);
        car.introduce();

    }
}

ApplicationContext

ApplicationContext扩展自BeanFactory的子接口,同时继承了容器事件、国际化消息访问相关的接口,功能更加丰富,具体继承体系如下图:
在这里插入图片描述

  • ApplicationEventPublisher 让容器拥有发布应用上下文事件的功能,包括启动、关闭事件等。实现了ApplicationListener事件监听接口的Bean可以接收到容器事件,并对事件进行响应处理。AbstractApplicationContext中存在一个ApplicationEventMulticaster,负责保存所有监听器,以便容器产生上下文事件时通知这些事件监听器
  • MessageSource 为应用提供i18n国际化消息访问的功能
  • ResourcePatternResolver 其实现类都实现了类似于PathMatchingResourcePatternResolver的功能,可通过带前缀的Ant风格资源文件路径装载Spring的配置文件。
  • LifeCycle start、stop两方法,用于控制异步处理过程。ApplicationContext会将start/stop信息传递给容器中所有实现了该接口的Bean,以达到控制JMX、任务调度等目的。

实现类

ApplicationContext主要的实现类有ClassPathXmlApplicationContext、FileSystemXmlApplicationContext、AnnotationConfigApplicationContext、GenericGroovyApplicationContext。

AnnotationConfigApplicationContext为基于类注解的配置方式提供专门的实现类;GenericGroovyApplicationContext是为Groovy DSL配置方式提供的专门的实现类

对比BeanFactory

  • BeanFactory初始化时并未实例化Bean,直到第一次访问某个Bean时才实例化目标Bean,存在“第一次惩罚”的问题,使用ApplicationContext启动容器时就会加载所有singleton bean,因此启动会稍慢一些,但带来的好处是及早发现潜在的配置问题,提升运行时效率;
  • ApplicationContext能够自动识别出配置文件中定义的BeanPostProcessor、InstantiationAwareBeanPostProcessor和BeanFactoryPostProcessor(统称为后处理器),并自动将他们注册到应用上下文中,而BeanFactory需要手动调用方法注册。

WebApplicationContext

专门为Web应用准备,除了prototype、singleton,另外为Bean新增了三个作用域:request、session、global session。类继承体系为:
WebApplicationContext类继承体系

Spring的Web应用上下文与Web容器可以实现互相访问:
Spring与Web应用上下文融合

WebApplicationContext的初始化方式区别于BeanFactory和ApplicationContext,需要配置在Web容器启动的基础上进行启动,具体是配置ContextLoaderServlet或ContextLoaderListener完成容器的初始化工作。

Bean生命周期

在了解了Spring容器核心接口的情况下,继续讨论被Spring容器管理的Bean,具体会经过哪些阶段完成初始化,以及销毁的过程又是怎样?

Spring初始化Bean经过了若干步骤,具体流程如下图所示:
Bean生命周期

① 若容器注册了InstantiationAwareBeanPostProcessor接口,则调用其postProcessBeforeInstantiation方法。
② 根据配置调用类的构造函数或工厂方法实例化Bean。
③ 与步骤①对应,调用其xxxAfterInstantiation方法。
④ 与步骤①对应,设置属性值前,调用其xxxPropertyValues方法。
⑤ 调用Bean的Setter方法,设置Bean属性值。
⑥ 若Bean实现了BeanNameAware接口,调用其setBeanName方法,设置BeanName到Bean中。
⑦ 与步骤⑥类似,BeanFactoryAware接口,设置BeanFactory。
⑧ 若BeanFactory装配了BeanPostProcessor实现类,则调用postProcessBeforeInitialization方法。
⑨ 若Bean实现了InitializingBean接口,则调用其afterPropertiesSet方法。
⑩ 执行节点指定的init-method或注解了@PostConstruct的方法。
⑪ 与步骤⑧对应,执行其xxxAfterxxx方法。
⑫ 将bean返回给调用者,对于单实例bean进行缓存,prototype作用域的Bean容器不再管理其生命周期。
⑬ 容器关闭时,对于实现了DisposableBean接口的单实例Bean,调用其destroy方法。
⑭ 调用单实例节点指定的destroy-method或注解了@PreDestroy的方法。

图中带☆标记的方法称为后处理器,附属于容器级别的Bean,影响是全局性的,同时也可以注册多个Bean,分步骤处理,需实现Ordered接口

阶段划分

按流程划分

可以看到Bean的声明周期过程阶段很多,不容易理解,可将整个过程划分为几类阶段:

  1. 实例化及其周边:包括步骤①至④,涉及到两个类——InstantiationAwareBeanPostProcessor和要实例化的Bean类,经此产生待设置属性值的半成品Bean;
  2. 属性设置、对象初始化及其周边:包含步骤⑤至⑪,设置各种属性值(包括Bean的成员变量以及BeanName可选、BeanFactory可选),执行对象初始化方法
  3. 销毁bean前处理:包括步骤⑬和⑭,进行销毁Bean之前的清理工作。

按生命周期级别划分
Bean生命周期

  1. Bean自身内部方法:包括构造函数、属性设置方法、初始化方法和destroy-method方法。
  2. Bean级生命周期接口方法:包括BeanNameAware、BeanFactoryAware、InitializingBean和DisposableBean接口定义的方法。主要解决个性化处理的问题。
  3. 容器级声明周期接口方法:包括InstantiationAwareBeanPostProcessor、BeanPostProcessor接口方法。主要解决容器中某些Bean共性化处理的问题。(后工厂处理器方法也属于容器级别,在加载配置后执行一次)

一般,为了实现与Spring框架的解耦,我们不需要关注Bean级生命周期接口及相关方法,使用init-method和destroy-method指定初始化、销毁前的清理动作能达到和实现接口同样的效果。但BeanPostProcesor不同,它以插件的形式为容器提供了对Bean进行后续加工处理的切入点,同时不需要Bean实现接口,在此基础上,能够统一扩展Spring的功能。

ApplicationContext管理的Bean生命周期与BeanFactory类似,不同之处在于增加了装配容器上下文的ApplicationContextAware接口执行,以及在启动容器时执行的一些后工厂处理器方法,通过实现BeanFactoryPostProcessor接口实现,Spring框架提供的后工厂处理器有CustomEditorConfigurer、PropertyPlaceholderConfigurer等。

配置Bean

XML Schema的配置方式

xmls对文档所引用的命名空间进行声明

  • 默认命名空间
  • 标准。。。。
  • 自定义。。。。
    命名空间的定义分为两个步骤:第一步指定命名空间的名称;第二步指定命名空间的Schema文档格式文件的位置,用空格或回车换行进行分隔。
  1. xmls:aop=“http://www.springframework.org/schema/aop”
  2. http://www.springframework.org/schema/aop xxx.xsd

Bean基本配置

  • id(Optional)可指定多个名字,逗号、分号、空格分隔,不允许多个相同的id
  • name(Optional)支持特殊字符,可指定多个,允许相同name(返回bean时返回后面声明的bean)
  • class(未指定id和name时,使用全限定类名为名称。出现多个时,第一个为全限定类名,第二个其后缀为 #1、第三个后缀为#2,匿名bean:为外层bean提供注入值使用)

命名:id需要满足XML命名规范,name不限制特殊字符;id和name都可以指定多个名字;id在配置中唯一,name可以有相同的,getBean返回后面声明的Bean;尽量使用id。

依赖注入

  • 属性注入
    要求Bean提供默认构造函数、Setter方法
    JavaBean关于属性命名的特殊规范:变量前两个字母要么全部大写,要不全部小写。
  • 构造函数注入
    按类型匹配入参
    按索引匹配入参
    联合使用类型和索引匹配入参
    自身类型反射匹配入参
    循环依赖问题
  • 工厂方法注入
    非静态工厂方法:factory-bean factory-method,需要声明工厂类Bean
    静态工厂方法:class属性指定工厂类,factory-method指定工厂方法。

注入方式选择?
构造方法

  • 可以保证一些重要属性在Bean实例化时就设置好,避免因为一些重要属性没有提供导致无用Bean的情况
  • 不需要为每个属性提供Setter方法,减少类的方法个数
  • 更好的封装类变量,不需要提供Setter方法,避免错误调用

属性注入

  • 属性众多,构造方法签名过长,可读性差
  • 构造方法灵活性不够,属性可选时需传null
  • 多个构造器配置时需要考虑匹配歧义的问题,配置相对复杂
  • 构造方法不利于类的继承和扩展,子类需引用父类复杂构造方法
  • 构造方法注入有时会造成循环依赖的问题。

注入参数

  1. 字面值
    基本数据类型、字符串(可自定义编辑器),property标签及value子标签
  2. 引用其他bean
    property标签及ref子标签,ref标签的bean指定bean,parent属性引用父容器中的bean
  3. 内部bean
  4. null值
  5. 级联属性
  6. 集合类型
    List、Set、Map、Properties、强类型集合、通过父子bean实现集合合并

简化配置方式

  • 字面值属性 proverty/constructor-arg/map-entry value
  • 引用对象属性
  • 使用p命名空间
    定义集合类型的Bean,使用util命名空间

自动配置autowire

  • byName
  • byType
  • constructor
  • autodetect

方法注入

  • lookup方法注入
    通过一个singleton Bean获取一个prototype
  • 方法替换
    MethodReplacer接口

Bean之间关系

  • 继承
  • 依赖
  • 引用

Bean作用域

  1. singleton
  2. prototype
  3. Web应用环境相关
  • request
  • session
  • global session

使用注解

  • @Autowired(required)
    标注属性
    标注方法
    集合类标注
  • @Qualifier指定名称
  • @Lazy
  • 支持标准注解:@Resource、@Inject
    Resource按名称匹配注入Bean,默认用变量名或方法名注入
    Inject按类型,但没有required属性
  • @scope
  • @PostConstruct和@PreDestroy(可多个)

基于Java类的配置

@Configuration
@Bean

混合配置

  • xml引用Java类配置,通过context:component-scan实现
  • Java类配置引用xml,通过@ImportResource实现

Spring容器工作机制

在讨论Bean生命周期的时候,涉及到了Spring容器的很多接口及组件,这些是何时实例化的?Spring容器启动时到底经过了哪些步骤?Bean的初始化过程和容器启动过程是怎样衔接的?这些问题从AbstractApplicationContext的refresh方法中窥探一二。

Spring容器工作机制

上图整理了Spring容器启动时经历的各个阶段,从配置文件一个个节点到容器中完备的Bean实例,可以分为以下几个大的阶段:

  1. 配置文件加工,产出为注册于BeanDefinitionRegistry中的BeanDefinition成品组件;
  2. 注册特殊Bean,基于BeanDefinition,可识别出特殊的一些Bean,包括后处理器、国际化、事件监听相关Bean等;
  3. 初始化除了Lazy标记的所有单实例Bean;
  4. 发布上下文刷新事件。

《精通Spring4.x》书中对于BeanDefinition、属性编辑器、工厂后处理器、InstantiationStrategy、BeanWapper等组件有详细的介绍,在图中进行了简短的梳理,有兴趣的可以找书来读一读。

小结

本文根据《精通Spring4.x 企业应用开发实战》,总结了SpringIoC的方方面面,从IoC的基本概念出发,介绍SpringIoC的底层Java基础技术,即反射,介绍了配置Bean的方式以及Spring容器的工作机制及Bean的生命周期阶段,希望对读者了解Spring容器、Bean相关配置、内部工作机制有所帮助。

扫描文末二维码,回复关键字"SpringIoC"获取总结的完整思维导图:
我的公众号

发布了159 篇原创文章 · 获赞 225 · 访问量 21万+

猜你喜欢

转载自blog.csdn.net/lyg673770712/article/details/105641619