框架源码系列四:手写Spring-配置(为什么要提供配置的方法、选择什么样的配置方式、配置方式的工作过程是怎样的、分步骤一个一个的去分析和设计)
一、为什么要提供配置的方法
经过前面的手写Spring IOC、手写Spring DI、手写Spring AOP,我们知道要创建一个bean对象,需要用户先定义好bean,然后注册到bean工厂才能创建一个bean对象。代码如下:
static PreBuildBeanFactory bf = new PreBuildBeanFactory();
GenericBeanDefinition bd = new GenericBeanDefinition();
bd.setBeanClass(ABean.class);
List<Object> args = new ArrayList<>();
args.add("abean01");
args.add(new BeanReference("cbean"));
bd.setConstructorArgumentValues(args);
bf.registerBeanDefinition("abean", bd); bd = new GenericBeanDefinition();
bd.setBeanClass(CBean.class);
args = new ArrayList<>();
args.add("cbean01");
bd.setConstructorArgumentValues(args);
bf.registerBeanDefinition("cbean", bd);
那么如果我们上面的过程换成配置的方式会是什么样的呢?
<bean id="abean" class="com.study.spring.samples.ABean">
<constructor-arg type="String" value="abean01"></constructor-arg>
<constructor-arg ref="cbean"></constructor-arg>
</bean>
<bean id="cbean" class="com.study.spring.samples.CBean">
<constructor-arg type="String" value="cbean01"></constructor-arg>
</bean>
经过上面的创建bean对象的过程由ava代码转为xml配置的方式,可以看出使用配置有如下优势:
1. 因为我们是写框架的,提供配置的方式,别人使用更简单,改动更加灵活
2. 要新增修改东西时不需要改代码
二、选择什么样的配置方式
用过Spring的朋友都知道,配置方式有两种:
1. xml
2. 注解
三、配置方式的工作过程是怎样的

四、分步骤一个一个的去分析和设计
1. 定义xml标准和注解标准

首先我们需要理清楚定义xml标准、定义注解标准的目的是什么,定义它们的目的是让用户可以使用它们去配置bean定义和标注bean定义。那么
问题1:bean定义需要指定些什么信息呢?
需要指定的信息我们可以从之前写的BeanDefinition里面看到

可以看到bean定义需要上面的这些信息
如果使用xml配置的方式,我们需要为上面的这些信息定义一个DTD(Document Type Definition)文件或者XSD(XML Schemas Definition)文件,具体实现利用了Spring提供的可扩展Schema机制实现,实现方式查看我前面的文章:
然后用户根据提供的DTD文件定义bean定义需要的信息即可:
<bean id="abean" class="com.study.spring.samples.ABean" init-method="init"
destroy-method="destroy" scope="prototype" >
<constructor-arg type="String" value="abean01"></constructor-arg>
<constructor-arg ref="cbean"></constructor-arg>
<property name="name" value="leSmall"></property>
<property name="age" value="18"></property>
</bean>
问题2:如果使用注解的方式,需要定义一些什么注解?
从需要的bean定义信息里面我们可能需要做如下的步骤:
1) 指定类
2)指定beanName
3)指定scope
4)指定工厂bean
5)指定工厂方法
6)指定init method
7)指定销毁方法
8)指定构造参数依赖
9)指定属性依赖
前面的1)到 7)我们可以定义一个注解@Component,里面持有1)到 7)需要的bean定义的信息,在创建bean定义的时候通过反射获取这些信息
package com.dn.spring.context.config.annotation; import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import com.study.spring.beans.BeanDefinition; @Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Component { String value() default ""; String name() default ""; String scope() default BeanDefinition.SCOPE_SINGLETION; String factoryMethodName() default ""; String factoryBeanName() default ""; String initMethodName() default ""; String destroyMethodName() default "";
}
注意:注解 Component里面没有定义 1)指定类 需要的元素,因为通过在类上加上@Component就能获取到类的名称了
前面的8)到 9)我们可以定义三个注解@Autowired、@Qualifier、@Value,其中@Autowired用来指定属性依赖的bean依赖或者有多个构造函数时指定使用哪个构造函数;@Qualifier用来指定bean依赖的具体bean,比如一个类有多个bean,可以指定具体的一个bean,如@Qualifier("cbean01") CBean cb;@Value用来指定属性依赖的非bean依赖。
@Autowired代码:
package com.study.spring.context.config.annotation; import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; @Target({ ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD,
ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
/**
* Declares whether the annotated dependency is required.
* <p>
* Defaults to {@code true}.
*/
boolean required() default true;
}
@Qualifier代码:
package com.study.spring.context.config.annotation; import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; @Target({ ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Qualifier { String value() default ""; }
@Value代码:
package com.study.spring.context.config.annotation; import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; @Target({ ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Value {
String value();
}
4个注解的使用示例代码:
package com.study.spring.samples; import com.study.spring.context.config.annotation.Autowired;
import com.study.spring.context.config.annotation.Component;
import com.study.spring.context.config.annotation.Qualifier;
import com.study.spring.context.config.annotation.Value; @Component(initMethodName = "init", destroyMethodName = "destroy")
public class ABean { private String name; private CBean cb; @Autowired
private DBean dbean; @Autowired
public ABean(@Value("mike") String name, @Qualifier("cbean01") CBean cb) {
super();
this.name = name;
this.cb = cb;
System.out.println("调用了含有CBean参数的构造方法");
} public ABean(String name, CCBean cb) {
super();
this.name = name;
this.cb = cb;
System.out.println("调用了含有CCBean参数的构造方法");
} public ABean(CBean cb) {
super();
this.cb = cb;
}
}
2. 用户怎么指定配置的xml文件的位置?用户怎么指定要扫描的包?
同样的我们需要分析用户指定xml配置文件的位置和指定扫描的包的目的是什么,目的是让提供方去加载xml文件和扫描包下的类,那么
问题1:怎么指定?
我们需要为用户提供一种方式
问题2:下面这些事情在哪里做好?

这里所做的事情是解析xml配置和反射获取bean定义注解、创建bean定义、向bean工厂注册bean定义,他不是bean工厂的事,我们应该单独定义接口和类完成这些事

无论是XmlApplicationContext还是AnnotationApplicationContext都要使用BeanFactory和BeanDefinitionRegistry,所以可以进一步优化类图

现在用户要使用我们的框架需要知道下面的接口和类:

那么让用户只需要知道ApplicationContext接口及其子类是否对用户更简单?
是的,这里我们可以用到前面学习的设计模式——外观模式,即提供一个新的外观,用户只需要知道要调用哪些接口和类,而不需要知道具体的实现。正确的做法是把上面类图中的BeanFactory和ApplicationContext合并到一起

3.怎么加载xml文件的配置?怎么扫描用户指定包下的类?

3.1 加载用户指定的xml配置文件
思考1:xml的来源会有多种吗?
会

那么它们的加载方式一样吗?
不一样
对于xml解析来说,从加载过程它希望获得什么?
xml解析希望获得流InputStream
我们希望能加载不同来源的xml,向解析xml配置提供统一的使用接口,那么该如何来设计接口和类呢?
让解析xml配置面向接口编程,不同的xml来源都实现该接口,提供不同的实现去加载xml配置文件最终都生产出一个InputStream

问题:这里我们定义不同的Resource类对应不同的xml来源,谁去负责分辨创建他们的对象?因为用户给定的是一个一个的字符串(这对他们来说是最简单的方式)
这个分辨字符串 ,创建对应的Resource对象的工作就是加载xml配置文件,这个事情有ApplicationContext来做。
这里就需要使用前面学过的设计模式工厂模式了:根据不同的字符串创建不同的对象
给ApplicationContext定义一个加载xml配置文件的行为ResourceLoader::

问题:怎么分辨字符串?
定义一套规则

工厂根据不同的前缀来区分,创建不同的Resource对象
3.2 注解的方式如何扫描
扫描过程如下:
到指定的包目录下找出所有的类文件(包含子孙包下的)

根据上面的扫描过程分析,我们需要定义一个正则表达式的匹配器来看扫描到的路径是否匹配用户指定的扫描包的路径:

思考:如果要扫描的是com.study下所有service包下的类,现在满足吗?
com.study/**/service/* 这种写法是ant path表达式
这时就需要在PathMatcher匹配器的基础上扩展一个ant path表达式的匹配器了:

思考:扫到了指定包下的class文件,我们最终需要的是什么?
我们最终需要的是类名,因为要拿类名去获取bean定义信息、创建bean定义、注册bean定义到bean工厂
那么这里我们需要设计什么样的接口和类呢?
在加载xml文件的时候存放扫描到的类可以吗?
不可以

思考:扫描的事情是由AnnotationApplicationContext这个类来做还是外包给其他的类来做?
外包给ClassPathBeandefinitionScanner这个类来做

说明:
扫描外包给ClassPathBeandefinitionScanner这个类来做,ClassPathBeandefinitionScanner里面的scan方法扫描完成以后得到bean定义信息,然后创建bean定义,把bean定义通过-registry : BeanDefinitionRegistry注册到bean工厂
思考:在哪里启动扫描调用ClassPathBeandefinitionScanner的scan方法?
在AnnotationApplicationContext的构造方法里面调用scan方法
4. 提供方解析xml配置文件、提供方反射获取bean定义注解

问题1:加载和扫描的输出是什么?
加载和扫描的输出是Resource

说明:
上面这张图红色部分要做的事情就是解析xml、反射获取注解,然后得到Resource读取bean定义信息,把bean定义信息注册到bean工厂里面去创建bean对象,由此我们可以做下面的设计:

说明:
BeanDefinitionReader负责解析xml、反射获取注解得到Resource,然后AbstractBeanDefinitionReader从Resource里面获取bean定义信息、创建bean定义、注册bean定义到bean工厂,具体的实现交给XmlBeanDefinitionReader和AnnotationBeanDefinitionReader这两个读取器来做
那么谁应该持有BeanDefinitionReader呢?请看下面完整的类图:

下面根据上面完整的类图开始写代码:
先定义接口,再定义抽象类、最后再定义具体类,把架子搭起来,然后再开始写具体的业务逻辑
完整代码获取地址:
https://github.com/leeSmall/FrameSourceCodeStudy/tree/master/spring-v4
框架源码系列四:手写Spring-配置(为什么要提供配置的方法、选择什么样的配置方式、配置方式的工作过程是怎样的、分步骤一个一个的去分析和设计)的更多相关文章
- Spring源码系列(四)--spring-aop是如何设计的
简介 spring-aop 用于生成动态代理类(底层是使用 JDK 动态代理或 cglib 来生成代理类),搭配 spring-bean 一起使用,可以使 AOP 更加解耦.方便.在实际项目中,spr ...
- 框架源码系列三:手写Spring AOP(AOP分析、AOP概念学习、切面实现、织入实现)
一.AOP分析 问题1:AOP是什么? Aspect Oriented Programming 面向切面编程,在不改变类的代码的情况下,对类方法进行功能增强. 问题2:我们需要做什么? 在我们的框架中 ...
- 框架源码系列二:手写Spring-IOC和Spring-DI(IOC分析、IOC设计实现、DI分析、DI实现)
一.IOC分析 1. IOC是什么? IOC:Inversion of Control控制反转,也称依赖倒置(反转) 问题:如何理解控制反转? 反转:依赖对象的获得被反转了.由自己创建,反转为从IOC ...
- 框架源码系列十二:Mybatis源码之手写Mybatis
一.需求分析 1.Mybatis是什么? 一个半自动化的orm框架(Object Relation Mapping). 2.Mybatis完成什么工作? 在面向对象编程中,我们操作的都是对象,Myba ...
- 框架源码系列十一:事务管理(Spring事务管理的特点、事务概念学习、Spring事务使用学习、Spring事务管理API学习、Spring事务源码学习)
一.Spring事务管理的特点 Spring框架为事务管理提供一套统一的抽象,带来的好处有:1. 跨不同事务API的统一的编程模型,无论你使用的是jdbc.jta.jpa.hibernate.2. 支 ...
- 框架源码系列六:Spring源码学习之Spring IOC源码学习
Spring 源码学习过程: 一.搞明白IOC能做什么,是怎么做的 1. 搞明白IOC能做什么? IOC是用为用户创建.管理实例对象的.用户需要实例对象时只需要向IOC容器获取就行了,不用自己去创建 ...
- 框架源码系列十:Spring AOP(AOP的核心概念回顾、Spring中AOP的用法、Spring AOP 源码学习)
一.AOP的核心概念回顾 https://docs.spring.io/spring/docs/5.1.3.RELEASE/spring-framework-reference/core.html#a ...
- 框架源码系列八:Spring源码学习之Spring核心工作原理(很重要)
目录:一.搞清楚ApplicationContext实例化Bean的过程二.搞清楚这个过程中涉及的核心类三.搞清楚IOC容器提供的扩展点有哪些,学会扩展四.学会IOC容器这里使用的设计模式五.搞清楚不 ...
- 框架源码系列七:Spring源码学习之BeanDefinition源码学习(BeanDefinition、Annotation 方式配置的BeanDefinition的解析)
一.BeanDefinition 1. bean定义都定义了什么? 2.BeanDefinition的继承体系 父类: AttributeAccessor: 可以在xml的bean定义里面加上DTD ...
随机推荐
- [UOJ282]长度测量鸡
思路: 数学归纳. 设最少所需刻度数为$s$,则$n和s$的关系为: $n=1,s=0;$ $n=2,s=1;$ $n=3,s=3;$ ... 观察发现$s=n(n-1)/2$,得到$sn$时,满足条 ...
- 转载 转载 转载 数组a[],a,&a之间的区别
通俗理解:内存就是公寓房间,指针就是房间的门牌号,数组就是连续的公寓房间,数组名就是这组连续房间的起始地址,也就是第一个房间的地址. 例如int a[5] a是数组名,也就是第一个房间号 & ...
- BZOJ3537 : [Usaco2014 Open]Code Breaking
考虑容斥,枚举哪些串必然出现,那么贡献为$(-1)^{选中的串数}$. 设$f[i][j]$表示$i$的子树内,$i$点往上是$j$这个串的贡献之和,那么总状态数为$O(n+m)$,用map存储$f$ ...
- 深度优先搜素之N皇后问题
#include<stdio.h>#include<malloc.h>#include<math.h>int x,a[101],book[101],count=0; ...
- CSS_常见布局
1.一列布局——常用于网站首页. html: <div class="top"></div> <div class="main"& ...
- .NET项目引用黄色小三角以及找不到依赖的解决方法
上图,问题描述: 经过查证,这个是引用的项目框架版本和本项目框架版本不一致的症状. 解决办法: 点击项目右键属性,调整版本使两个项目的版本一致 问题解决.good! ----------------- ...
- swiper默认显示三个,中间放大且显示全部图片两边显示部分图片的实现方法
本页面内容最后的红色部分有惊喜哦! 最近在做一个活动页面,要求触摸切换图片时,默认在可视区域中显示三张图片,其中中间的一张图片比其他两张都大且全部显示,而其他两张图片只显示部分即可,于是就想到了swi ...
- Uva11582 Colossal Fibonacci Numbers!(同余模定理+快速幂)
https://vjudge.net/problem/UVA-11582 首先明确,斐波那契数列在模c的前提下是有循环节的.而f[i] = f[i-1]+f[i-2](i>=2)所以只要有两个连 ...
- Docker卸载高版本重装低版本后启动提示:driver not supported
解决方法: mv /var/lib/docker /var/lib/docker.old 其实就是docker镜像文件夹目录作怪,新版本的目录无法与旧版本目录相兼容. 不过建议降级的用户这样操作: y ...
- Mac 删除/卸载 自己安装的python
官网pkg安装的python版本 第一步:删除框架 sudo rm -rf /Library/Frameworks/Python.framework/Versions/2.7 1 第二步:删除应用目录 ...
