框架源码系列四:手写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 ...
随机推荐
- 潭州课堂25班:Ph201805201 django 项目 第四十三课 后台 用户管理前后功能实现 (课堂笔记)
用户的展示,编辑,删除, 把用户显示出来,用户名,员工(是,否), 超级用户(是, 否) 活跃状态,(非活跃示为删除) 在前台要显示该用户所属的用户组,在前台代码中是调用类的属性,所以在 user 的 ...
- Markdown使用简介 及 学习资源整理
Markdown资源整理 官网 http://daringfireball.net/projects/markdown/ http://jgm.github.io/stmd/spec.html htt ...
- [USACO18DEC]The Cow Gathering
Description: 给定一棵树,每次删去叶子,有m个限制,分别为(a,b)表示a需要比b先删,为每个点能否成为最后被删的点 Hint: \(n,m \le 10^5\) Solution: 手模 ...
- PAT Basic 1026
1026 程序运行时间 (15 分) 要获得一个 C 语言程序的运行时间,常用的方法是调用头文件 time.h,其中提供了 clock() 函数,可以捕捉从程序开始运行到 clock() 被调用时所耗 ...
- 【重要】将项目发布到Maven中央库
http://www.ruanyifeng.com/blog/2013/07/gpg.html
- 【JavaScript从入门到精通】第四课初探JavaScript魅力-04
第四课初探JavaScript魅力-04 style与className 之前我们已经讲过,style用于在JS里控制元素的样式,通过style可以选中元素的各种css属性.此外,我们也提到过,JS用 ...
- 【Geek软技能】程序员,为什么写不好一份简历?
一份好简历会是一份好工作的开端. 为什么?沧海也会遗珠 简历,是如此重要,它是获得一份满意工作的敲门砖,但不同的简历敲门的声响可不同. 但很多时候简历给人的感觉也似乎微不足道,因为没有人会真正细致 ...
- android学习十二(android的Content Provider(内容提供器)的使用)
文件存储和SharePreference存储以及数据存储一般为了安全,最好用于当前应用程序中訪问和存储数据.内容提供器(Content Provider)主要用于在不同的应用程序之间实现数据共享的功能 ...
- Ubuntu apt-get彻底卸载软件包【转】
原文地址:https://blog.csdn.net/get_set/article/details/51276609 最近对ubuntu卸载参数的详细程度了解不够:转载已了解查用. 如果你关注搜索到 ...
- 单片机成长之路(51基础篇) - 004 STC89C52MCU 软件实现系统复位
用户应用程序在运行过程中,有时会有特殊需求,需要实现单片机系统复位(热启动之一),传统的8051单片机由于硬件上未支持此功能,用户必须用软件模拟实现,实现起来较麻烦.STC单片机增加了相应的硬件功能, ...
