作者: Grey
原文地址:Spring的轻量级实现
本文是参考公众号:码农翻身 的从零开始造Spring 教程的学习笔记
源码
开发方法
- TDD【示例】
- 写一个测试用例
- 运行:失败
- 写Just enough的代码,让测试通过
- 重构代码保持测试通过,
然后循环往复。
说明
- 仅实现核心功能
- 基于spring-framework-3.2.18.RELEASE版本
- 日志和异常处理待完善
Step1 通过XML实例化一个对象
解析XML文件,拿到Bean的id和完整路径,通过反射方式实例化一个对象。
tag: step1
Step2 基础工作
- 日志支持:log4j2 + SLF4j
- 异常处理
所有异常的顶层:BeansException
Step3 封装BeanDefinition
DefaultBeanFactory中的BEAN_MAP目前只包括了beanClassName信息,后续如果要扩展其他的信息,肯定需要增加字段,所以我们需要抽象出一个接口,方便后续扩展其他的字段。
Step4 封装Resource
在BeanFactory初始化的时候,传入的是XML格式的配置信息,比如bean-v1.xml, Spring会把这个抽象成一个Resource,常见Resource有
FileSystemResource->从文件地址读配置
ClassPathResource->从classpath下读配置
BeanFactory在创建Bean的时候,只关注Resource即可。
tag: step-4-2-resource
Step5 封装XML的解析逻辑到专门的一个类中
XmlBeanDefinitionReader
用于解析XML,传入Resource,即可获取所有BeanDefinition
由于要把BeanDefinition放入BEAN_MAP中,所以XmlBeanDefinitionReader需要持有一个DefaultBeanFactory,且DefaultBeanFactory需要有注册BeanDefinition和获取BeanDefintion的能力,这样DefaultBeanFactory的职责就不单一了,所以需要抽象出一个BeanDefinitionRegistry,这个BeanDefinitionRegistry负责注册BeanDefinition和获取BeanDefintion的能力,XmlBeanDefinitionReader持有BeanDefinitionRegistry即可将解析生成的BeanDefinition注入BEAN_MAP中。
修改BeanFactoryV1Test这个测试用例,重新执行测试
tag: vstep5-final
Step6 单例多例模式的配置实现
测试:BeanFactoryV1Test:testSingletonBean,testGetPrototypeBean
解析XML的XmlBeanDefinitionReader需要增加scope的解析逻辑
BeanDefinition这个数据结构增加是否单例,是否多例的属性
DefaultBeanFactory中getBean的时候,判断是否单例,如果是单例,则复用对象,如果是多例,则new新的对象。
抽象SingletonBeanRegistry这个接口,用于注册和获取单例对象
DefaultSingletonBeanRegistry实现这个接口
tag:vstep6-scope
Step7 整合,抽象ApplicationContext
我们使用Spring的时候,一般是这样做的:
ApplicationContext ctx = new ClassPathXmlApplicationContext("mycontainer.xml");
UserService userService = (UserService) ctx.getBean("userService");
或
ApplicationContext ctx = new FileSystemApplicationContext("src\test\resources\bean-v1.xml");
UserService userService = (UserService) ctx.getBean("userService");
现在,我们需要抽象出ApplicationContext这个类来实现如上的功能
ApplicationContext
- ClassPathXmlApplicationContext(从classpath中读配置文件)
- FileSystemApplicationContext(从文件目录中读取配置文件)
- ....
tag: vstep7-applicationcontext-v1
通过观察发现,ClassPathXmlApplicationContext和FileSystemApplicationContext大部分代码都是相同的,只有在获取Resource的时候,方法不一样,所以,我们通过模板方法这个设计模式,来抽象出公有方法到一个抽象类,所有ClassPathXmlApplicationContext和FileSystemApplicationContext都实现这个抽象类。
tag: vstep7-applicationcontext-v2
Step8 注入Bean和字符串常量
形如:
<?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 id="userService" class="org.spring.service.v2.UserService">
<property name="accountDao" ref="accountDao"/>
<property name="itemDao" ref="itemDao"/>
<property name="owner" value="test"/>
<property name="version" value="2"/>
<property name="checked" value="on"/>
</bean>
<bean id="accountDao" class="org.spring.dao.v2.AccountDao">
</bean>
<bean id="itemDao" class="org.spring.dao.v2.ItemDao">
</bean>
</beans>
达到的目的就是,可以把“整型,字符串类型,简单对象类型”注入到一个Bean中。
这里我们需要解决以下几个问题:
- 把字符串转成各种各样的Value
- String->Integer/int
- String->Boolean/boolean
- ......
注:以上转换器的实现都是基于jdk中java.bean包中的PropertyEditorSupport这个类来完成的。
- CustomBooleanEditor
- CustomNumberEditor
- ....
每种类型的转换都通过类似的方式实现,然后Spring抽象出了TypeConvert这个接口,并把这些转换器加入一个特定的Map中,Map的key就是要转换的目标的类型,Value就是对应的转换器的实现类,即可实现类型转换。
- 调用Bean的setXXX方法把这些Value值set到目标Bean中
- 抽象出PropertyValue
- BeanDefiniton需要增加方法获取PropertyValue
- GenericBeanDefinition中需要增加List
- XmlBeanDefinitionReader解析XML的时候,把List
识别出来并加入BeanDefinition中(RuntimeBeanReference,TypedStringValue) - BeanDefinitionValueResolver 把对应的PropertyValue给初始化好
- **setXXX的背后实现利用的是jdk原生:java.beans.Introspector 来实现 **见(DefaultBeanFactory的populateBean方法)
tag: vstep8-inject
Step9 构造器注入
处理形如以下的配置:
<bean id="userService" class="org.spring.service.v3.UserService">
<constructor-arg ref="accountDao"/>
<constructor-arg ref="itemDao"/>
<constructor-arg value="1"/>
</bean>
<bean id="accountDao" class="org.spring.dao.v3.AccountDao">
</bean>
<bean id="itemDao" class="org.spring.dao.v3.ItemDao">
</bean>
参考测试:
ApplicationContextTestV3,在v3版本的UserService中,定义两个构造方法,需要识别出正确的构造方法。
- 和Step8中类似,抽象出ConstructorArgument用于表示一个构造函数信息,每个BeanDefinition中持有这个对象
- XmlBeanDefinitionReader需要负责解析出ConstuctorArgument(parseConstructorArgElements)
- DefaultBeanFactory通过指定构造函数来生成Bean对象(ConstructorResolver注入Bean实例到构造方法中)
注:这里指定的构造函数的查找逻辑为:解析出XML的构造函数的参数列表,和通过反射拿到对应的构造函数的参数列表进行对比(每个参数的类型和个数必须一样)
tag:vstep9-constructor
Step10 实现注解
实现两个注解:@Component @Autowired(只针对属性注入,暂时不考虑方法注入)
且需要实现如下的XML的解析:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="org.spring.service.v4,org.spring.dao.v4">
</context:component-scan>
</beans>
- 定义注解Component ,Autowired
- 需要实现的第一个功能是:给一个包名,扫描获取到这个包以及子包下面的所有Class【PackageResourceLoaderTest】包名--> *.class 【涉及一个递归调用】
- 由于第二步中的BeanDefinition不像之前的xml那样,会对Bean配置一个id,所以,这里解析出来的Bean定义需要自动生成一个BeanId(默认先取value的配置,否则就就是类名第一个字母小写,抽象BeanNameGenerator来专门对Bean定义ID),同时,Spring中单独新建了一个AnnotatedBeanDefinition接口来定义包含注解的BeanDefinition, 封装第二步中的方法+BeanId的生成到一个类中:【ClassPathBeanDefinitionScannerTest】。
- 实现了第3步以后,我们得到了对应的Class文件,我们需要通过某种方式去解析这个Class文件,拿到这个Class中的所有信息,特别是注解信息。使用ASM这个jar【ASM的原始用法见:ClassReaderTest】 *.class -> class Info
- 步骤3虽然实现了读取Class中的信息这件事。但是用ASM的原生方式解析不太方便,解析ClassMetaData和Annotation都需要定义一个Visitor,所以Spring抽象了一个接口(MetadataReader)来封装ASM的实现【MetadataReaderTest】
- 拿到Bean中的所有Field(带注解的),并把他实例化成一个对象 : class info 中的Field -> new instance()【DependencyDescriptorTest】
- 将这个对象注入目标Bean中,new Instance() ——注入——>bean 【InjectionMetadataTest】
- 处理XML解析,注入ScannerBeanDefinition
- 整合,涉及到Bean初始化和Bean的生命周期【AutowiredAnnotationProcessorTest】
tag:vstep10-annotation-final
step11 实现Aop
<context:component-scan
base-package="org.litespring.service.v5,org.litespring.dao.v5">
</context:component-scan>
<bean id="tx" class="org.litespring.tx.TransactionManager" />
<aop:config>
<aop:aspect ref="tx">
<aop:pointcut id="placeOrder"
expression="execution(* org.litespring.service.v5.*.placeOrder(..))" />
<aop:before pointcut-ref="placeOrder" method="start" />
<aop:after-returning pointcut-ref="placeOrder" method="commit" />
<aop:after-throwing pointcut-ref="placeOrder" method = "rollback"/>
</aop:aspect>
</aop:config>
- 一些术语:Joint Point, Pointcut, Advice(拦截器,Before, After, Around....)
- 为什么要用aop:日志,安全,事务 作为切面和业务代码正交
- 运行时动态生成类
- PointcutTest: 给定一个表达式,然后判断某个类的某个方法是不是匹配这个表达式【依赖AspectJ】
- Pointcut:
- MethodMatcher: 给一个method,判断是否满足指定条件
- AspectJExpressionPointcut
- MethodLocatingFactoryTest:通过Bean的名称("tx")和方法名("start")定位到这个Method,然后反射调用这个Method
- 指定指定顺序的链式调用 (Aop alliance,使用了责任链这个设计模式) ReflectiveMethodInvocationTest,需要debug查看调用链路。
-
动态代理,在一个方法前后增加一些逻辑,而不用改动原始代码。CGlibTest, 其中testFilter方法是表示支持多个aop操作,(使用)
- 普通类:CGLib
- 接口:Jdk自带
-
CglibAopProxyTest
tag: vaop-v1
- 抽象AbstractV5Test
- BeanFactoryTestV5:配置文件->Advice
- XML解析生成BeanDefinition
- aop标签的内容其实是包含在GenericDefinitionBean里面, 通过人工合成的,嵌套的Beandefinition处理
- BeandefinitionTestV5
ConfigBeanDefinitionParser.java
- BeanFactoryTestV5
嵌套Bean的处理
- ApplicationContextTest5
tag: vaop-v2
- 实现jdk动态代理
AspectJAutoProxyCreator
JdkAopProxyFactory
tag:vaop-v3