Summer——从头开始写一个简易的Spring框架

Summer——从头开始写一个简易的Spring框架

              

​ 参考Spring框架实现一个简易类似的Java框架。计划陆续实现IOCAOP、以及数据访问模块和事务控制模块。项目持续维护中...欢迎Star!Thanks~~~

​ 本项目参考自Spring,但又做出了自己的创新,最大的创新点在于解决循环依赖的问题,引入了自己的解决方案,具体方法可以参考更新日志中Version 0.5(Pre-release)的更新记录。

项目计划:

​ 关于对IOC和AOP功能为什么要使用(why),以及应该如何使用(how)请移步使用文档,要了解每个版本更新的内容请移步更新日志

​ 关于如何将自己的框架适配到summer,请查看如何适配summer

运行环境

JDK 8

项目主要技术栈

注解、反射

为什么要使用

IOC

​ IOC——控制反转,旨在将对象之间的依赖关系和对象的创建过程都交由一个第三方的角色来管理,这个第三方就是IOC容器。对象创建的控制权发生了反转,通过IOC我们不必再自己去控制对象如何创建。

​ 我们设想一个平时的很常见的一个设计场景,很多人开发一个项目都喜欢dao、service、controller一把梭(只是做一个引子,并无涵盖所有人或者不认同此分层设计的意思),而这里面:

  • dao(Data Access Object)意为“数据访问对象”,指的是直接操作对象,一般为直接和数据库的交互层。

  • service层表示服务层,不只对数据库的简单操作,还需要包含一些逻辑处理,比如判断要插入的数据是否合法,控制事务保持一致性等等。

  • controller层表示控制层,这层负责根据用户的业务需求调用相应服务来完成业务。

​ 以上三层相互协作,当一个用户请求发过来后,我们controller拿到用户请求,根据请求的业务需求调用不同的service来相互配合完成任务,而service又要调用相应的dao来对数据库进行数据的CRUD,以完成最终的业务需求。

​ 而上述听起来很容易的几句话,就是三层之间相互配合,但是当业务逐渐复杂起来,假如一个controller需要十几二十个service对象,而一个service又需要十几二十个dao对象,这样一个controller对象所依赖的对象是十分多的。又或是某一个对象的创建过程非常复杂,通常需要很多个步骤才能创建完成,这种情况下如果使用传统的自己new对象的方式,那对象之间的依赖关系将会十分复杂,很难管理起来。所以就有了IOC这个第三方容器来帮助管理对象之间依赖。

AOP

​ AOP——面向切面编程,不同于面向对象(OOP),AOP更加关注的是一个方法的切面。假如我们此时有一个日志记录的需求,日志需要加入到一段业务中去,如果业务开始阶段、结束阶段和抛出异常的时候要加入相应的日志记录,如果我们直接在业务方法中添加日志,那么日志和核心的业务代码就会耦合在一起,代码可读性差,不方便日后维护。AOP可以使我们无侵入地将日志的记录穿插在业务过程中。虽然日志记录的代码和核心业务的代码不在一个地方,但它们确实一起穿插执行了。

​ AOP的核心其实还是使用代理来实现,我们对一段业务函数进行代理, 在代理的类中,对它执行的开始返回异常结束阶段分别做切入,插入日志的记录,真正执行的时候我们执行的并不是执行的真实对象的方法,而是代理对象的方法,实际上被切的类在IOC中保存的并不是实际的对象,而是代理的对象。

​ 项目初期采用了JDK的动态代理来实现此功能,所以强制要求被切的方法的类需要实现一个接口。

​ ps: 2021/04/10 完成了对cglib方式的添加,现在对于实现了接口的类采用JDK的动态代理,对于没有实现接口的类采用cglib。

​ 面向切面编程有三个必需的元素:切面类(切面类中有切面方法,或者称为通知方法)、目标方法(被切的方法)、以及切入点。

如何使用

​ 仓库地址: https://github.com/vfdxvffd/Summer

​ 下载最新的jar包 ,将其导入项目中,即可使用,目录结构如下图,蓝色框内为summer的核心代码,ch包下为logback日志依赖,net.sf.cglib下为cglib动态代理的依赖,org.slf4j下为slf4j的日志门面依赖。

Version 1.0(Release)

本次更新测试了之前的版本的稳定性,以及增加了框架的扩展性,开放出别的框架适配的接口。

  • 对之前的pre-release版本进行了多次测试以确保稳定性。
  • 框架对外开放Extension接口,该接口中的方法在ioc容器构造的多个阶段进行了切入,增加了框架的可扩展性。
  • 如何将自己的框架适配到Summer,请看如何适配summer或者可以提issues

Version 0.5(Pre-release)

一次重大更新

bug描述:循环依赖的问题复现出来

​ 因为之前v0.1更新中引入的一个解决bug的方法导致了这个重大的bug,这次通过设置二级缓存来解决循环依赖的问题,具体bug的产生原因详情可见更新日志,更新日志对这次bug的出现原因以及解决方法做了详细的说明。

bug解决:

​ 针对目前掌握的代理方面的知识,对之前的做法做出一些调整。设置二级缓存,一级缓存一个(即真正的ioc容器),二级缓存两个,都是负责存放实例化但未初始化的对象,但一个是存放原对象,另一个负责存放代理对象,二级缓存的示意图如下:

将ioc容器的构造过程分为四步来进行:

  1. 遍历包,找到所有需要被IOC管理的类,封装成BeanDefinition
  2. 根据第一步获取到的BeanDefinition实例化那些单例且非延迟加载的对象,并将其加入到二级缓存的earlyRealObjects
  3. 对第二步得到的earlyRealObjects中的对象进行检查,看是否需要设置代理,如果需要则对其进行代理,并将代理对象加入到二级缓存中的earlyProxyObjects中(并不删除earlyRealObjects中对应的真正的对象)。
  4. 对第二步得到的earlyRealObjects中的对象进行注入工作(即开始进行初始化),检查每个对象的每个域,如果标注了@Autowired注解且值为null,则对其进行注入工作,现在一级缓存中查找,如果有直接取出为其注入,如果没有检查二级缓存的earlyProxyObjects,如果有则取出为其注入,如果没有则接着检查二级缓存的earlyRealObjects,找到后为其注入,此时如果还没有则说明这个域对应的bean是非单例(prototype)模式或者懒加载模式的,则为其实例化并设置代理(如果需要),并初始化,然后注入其中。如果是非ioc容器管理的域,则直接注入null,也可以考虑改为抛出异常给用户提示。

Version 0.4(Pre-release)

本次更新加入了新功能,修改了一个已知的bug

  • 本次更新引入CGLib依赖,增加动态代理的方式,对于实现了接口的方法采用JDK动态代理来实现切面功能,对于没有实现接口的类采用CGLib来实现切面。

  • 修改bug,之前版本中的判断当前类是否已经完成了实例对象全部的创建注入工作的方法,判断没有包含所有情况。

    bug描述:对于一个没有任何域需要代理的对象,进行注入工作的时候会由于没有域需要注入,从而直接判断其已经完成注入,而跳过了代理阶段。

Version 0.3(Pre-release)

  • 本次更新引入了日志依赖,增加了对ioc构造过程中的日志记录

  • 对于标注了@Aspect注解的类自动将其加入IOC容器中,不用再重复标注注解

Version 0.2(Pre-release)

本次更新加入了一些新功能,修复了一些bug

  1. 更新功能:

    • aop增加了一种切入方式,目前有以下切入方式

      @Before@AfterReturning@AfterThrowing@After

      以上对应的切入时机如下:

      try {
          @Before
          fun.invoke();
          @AfterReturning
      } catch (Throwable t) {
          @AfterThrowing
      } finally {
          @After
      }
      
    • 切面方法可以通过JoinPoint类获取被切的方法的参数、方法名、返回值类型。对于@AfterReturning的切入方式可以获取返回值,类型为Object,而@AfterThrowing可以获取抛出的异常,类型为Throwable

  2. 修复了重复代理的bug

    bug描述:当一个待注入bean中有超过一个需要注入的域(带有注解@Autowired且未完成赋值),如果对它中的方法进行切面,这时切面方法会重复执行

Version 0.1(Pre-release)

​ 本次更新主要修复了一些bug,以及优化了代码的结构

  1. 修复对于注入对象的切面方法失效的bug

    bug描述:在controller中注入service,但是如果有对于service的切面方法,则切面方法无法被调用

  2. 修复延迟加载的对象注入失败的bug

    bug描述:对于标注了延迟加载的类注入时会发生异常

  3. 修复对非单例的bean注入失败的bug

    bug描述:对于标注了非单例的类注入时会发生异常,且会调用多次构造函数的问题

  4. 增加核心代码的注释。

  5. 优化代码结构,重构了大部分冗余的代码块

  6. 抽取可重用方法。

Version 0.0(Pre-release)

  1. 完成IOC容器的初步搭建

  2. 完成AOP功能的简单使用(还需修改)

  3. 支持@Component@Autowired@Qualifier@Value@Repository@Service@Controller注解的使用

    • @Component(同@Respository、@Service、@Controller):标注在类上,将此类注册到ioc容器中
    • @Autowired:自动注入ioc容器中的对象
    • @Qualifier:自动注入ioc中对象的时候指定beanName,如不指定则按照beanType注入
    • @Value:指定将类注入到容器是基本类型(包括包装类)字段的值
  4. 支持根据beanNamebeanType获取ioc中的对象

  5. 自定义类型转化异常,@Value接受String类型,如果传入的值并不能正确转化,就抛出DataConversionException异常。

  6. 增加单例模式与非单例模式的配置注解@Scope,以及增加延迟加载的配置注解@Lazy

  7. 可以使用接口来接受IOC中返回的对象

  8. AOP可以对方法进行@Before@After@AfterThrowing的切面,需要配置方法的全方法名

  9. AOP使用JDK的动态代理,set可以不添加,内部实现是直接通过设置域的可访问属性,然后直接设置值

  10. 后续计划:

    • 支持根据xml配置ioc容器中的对象
    • 对于运行过程可能发生的异常使其尽可能可控,且明确的抛出或处理
    • 对于AOP可选择性的加入CGLIB代理
    • 对于AOP一些已注入对象的代理失效bug进行修复(已定位)
    • etc... for more...
posted @ 2021-05-19 10:01  头发是我最后的倔强  阅读(516)  评论(0编辑  收藏  举报