• Spring的轻量级实现


    作者: Grey

    原文地址:Spring的轻量级实现

    本文是参考公众号:码农翻身 的从零开始造Spring 教程的学习笔记

    源码

    github

    开发方法

    1. 写一个测试用例
    2. 运行:失败
    3. 写Just enough的代码,让测试通过
    4. 重构代码保持测试通过,

    然后循环往复。

    说明

    • 仅实现核心功能
    • 基于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中。
    这里我们需要解决以下几个问题:

    1. 把字符串转成各种各样的Value
      1. String->Integer/int
      2. String->Boolean/boolean
      3. ......

    注:以上转换器的实现都是基于jdk中java.bean包中的PropertyEditorSupport这个类来完成的。

    • CustomBooleanEditor
    • CustomNumberEditor
    • ....

    每种类型的转换都通过类似的方式实现,然后Spring抽象出了TypeConvert这个接口,并把这些转换器加入一个特定的Map中,Map的key就是要转换的目标的类型,Value就是对应的转换器的实现类,即可实现类型转换。

    1. 调用Bean的setXXX方法把这些Value值set到目标Bean中
      1. 抽象出PropertyValue
      2. BeanDefiniton需要增加方法获取PropertyValue
      3. GenericBeanDefinition中需要增加List
      4. XmlBeanDefinitionReader解析XML的时候,把List识别出来并加入BeanDefinition中(RuntimeBeanReference,TypedStringValue)
      5. BeanDefinitionValueResolver 把对应的PropertyValue给初始化好
      6. **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中,定义两个构造方法,需要识别出正确的构造方法。

    1. 和Step8中类似,抽象出ConstructorArgument用于表示一个构造函数信息,每个BeanDefinition中持有这个对象
    2. XmlBeanDefinitionReader需要负责解析出ConstuctorArgument(parseConstructorArgElements)
    3. 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>
    
    1. 定义注解Component ,Autowired
    2. 需要实现的第一个功能是:给一个包名,扫描获取到这个包以及子包下面的所有Class【PackageResourceLoaderTest】包名--> *.class 【涉及一个递归调用】
    3. 由于第二步中的BeanDefinition不像之前的xml那样,会对Bean配置一个id,所以,这里解析出来的Bean定义需要自动生成一个BeanId(默认先取value的配置,否则就就是类名第一个字母小写,抽象BeanNameGenerator来专门对Bean定义ID),同时,Spring中单独新建了一个AnnotatedBeanDefinition接口来定义包含注解的BeanDefinition, 封装第二步中的方法+BeanId的生成到一个类中:【ClassPathBeanDefinitionScannerTest】。
    4. 实现了第3步以后,我们得到了对应的Class文件,我们需要通过某种方式去解析这个Class文件,拿到这个Class中的所有信息,特别是注解信息。使用ASM这个jar【ASM的原始用法见:ClassReaderTest】 *.class -> class Info
    5. 步骤3虽然实现了读取Class中的信息这件事。但是用ASM的原生方式解析不太方便,解析ClassMetaData和Annotation都需要定义一个Visitor,所以Spring抽象了一个接口(MetadataReader)来封装ASM的实现【MetadataReaderTest】
    6. 拿到Bean中的所有Field(带注解的),并把他实例化成一个对象 : class info 中的Field -> new instance()【DependencyDescriptorTest】
    7. 将这个对象注入目标Bean中,new Instance() ——注入——>bean 【InjectionMetadataTest】
    8. 处理XML解析,注入ScannerBeanDefinition
    9. 整合,涉及到Bean初始化和Bean的生命周期【AutowiredAnnotationProcessorTest】

    image.png
    image.png
    image.png
    image.png
    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>
    

    image.pngimage.png

    1. 一些术语:Joint Point, Pointcut, Advice(拦截器,Before, After, Around....)
    2. 为什么要用aop:日志,安全,事务 作为切面和业务代码正交
    3. 运行时动态生成类

    image.png

    1. PointcutTest: 给定一个表达式,然后判断某个类的某个方法是不是匹配这个表达式【依赖AspectJ】
      1. Pointcut:
      2. MethodMatcher: 给一个method,判断是否满足指定条件
      3. AspectJExpressionPointcut
    2. MethodLocatingFactoryTest:通过Bean的名称("tx")和方法名("start")定位到这个Method,然后反射调用这个Method
    3. 指定指定顺序的链式调用 (Aop alliance,使用了责任链这个设计模式) ReflectiveMethodInvocationTest,需要debug查看调用链路。

    image.png

    1. 动态代理,在一个方法前后增加一些逻辑,而不用改动原始代码。CGlibTest, 其中testFilter方法是表示支持多个aop操作,(使用)

      1. 普通类:CGLib
      2. 接口:Jdk自带
    2. CglibAopProxyTest

    image.png

    tag: vaop-v1

    1. 抽象AbstractV5Test
    2. BeanFactoryTestV5:配置文件->Advice
    3. XML解析生成BeanDefinition
      1. aop标签的内容其实是包含在GenericDefinitionBean里面, 通过人工合成的,嵌套的Beandefinition处理
    4. BeandefinitionTestV5

    ConfigBeanDefinitionParser.java
    image.png
    image.png
    image.png

    1. BeanFactoryTestV5

    嵌套Bean的处理

    1. ApplicationContextTest5

    tag: vaop-v2

    1. 实现jdk动态代理

    AspectJAutoProxyCreator
    JdkAopProxyFactory

    tag:vaop-v3
    image.png

  • 相关阅读:
    关于第三方库
    一些css属性,抄自某个大神忘记谁了,不好意思
    10.使用express模拟数据服务器
    9.text-shadow
    8.css背景图案
    7.一个抄来圆形菜单
    6.文字闪烁效果
    5.偶然看到的一个css加载动画
    4.怎样使用css实现一个切角效果
    3.写一个简单的弹出菜单
  • 原文地址:https://www.cnblogs.com/greyzeng/p/14706549.html
Copyright © 2020-2023  润新知