手写实现微型spring框架

200 阅读11分钟

在程序员的修炼过程中,挫折和永不言败是制胜之道。每一位程序员或多或少都会遇到技术瓶颈,遇到挫折时需要静下心来,不急不躁一步一步地探索。然而,仅仅依靠心态的调整是不够的,还需要结合科学的方法和持续的学习来完善这一过程。以下是我的学习心得以及手写实现一个微型 Spring 框架的过程,希望对你的突破有所帮助。

Spring 的核心思想是 控制反转(IoC)  和 依赖注入(DI) ,通过解耦组件之间的依赖关系,使得代码更加灵活和可维护。

0.项目简介

项目地址

github.com/Yeaury/work…

项目介绍

项目分为spring模块和springmvc模块

Spring模块

包括ioc模块,aop模块,jdbc模块(没有完善)

实现了xml注册Bean的部分,注解和xml上逻辑大体相同(就简单实现注解的)

  • xml依赖于XmlBeanDefinitionReader实现注册Bean
  • 注解主要依赖于ClassPathBeanDefinitionScanner实现扫描注册Bean(简单实现)
Springmvc模块

由于自己实现的ioc没有实现注解,mvc模块依赖于org.springframework中的spring的ioc和web模块

  • springmvc-starter

一.Ioc模块

1..项目结构

beans:

├─beans
│  │  BeansException.java
│  │  PropertyValue.java                          ## bean属性信息
│  │  PropertyValues.java                         ## 用于保存单个 Bean 属性的信息和值的对象。
│  │  
│  ├─factory
│  │  │  Aware.java                               ## 用于感知容器         
│  │  │  BeanFactory.java
│  │  │  BeanFactoryAware.java
│  │  │  BeanNameAware.java
│  │  │  HierarchicalBeanFactory.java             ## beanFactory接口的扩展接口
│  │  │  InitializingBean.java                    ## 为bean提供了属性初始化后的处理方法
│  │  │  ListableBeanFactory.java                 ## beanFactory接口的扩展接口
│  │  │  ObjectFactory.java                       ## 函数式接口,,对象的创建方式完全由调用方或者子类实现
│  │  │  PropertyResourceConfigurer.java          ## 属性文件占位符替换
│  │  │  
│  |  ├─annotation
│  |  │      Autowired.java
│     │      AutowiredAnnotationBeanPostProcessor.java      ## 对@Autowire和@Value注解进行处理
│     │      Value.java
│     │      
│  │  ├─config
│  │  │      AutowireCapableBeanFactory.java       
│  │  │      BeanDefinition.java                  
│  │  │      BeanFactoryPostProcessor.java         
│  │  │      BeanPostProcessor.java               
│  │  │      BeanReference.java                              ## 对 Bean 名称的逻辑引用(用在属性注入Bean里面)
│  │  │      ConfigurableBeanFactory.java                     
│  │  │      ConfigurableListableBeanFactory.java             
│  │  │      InstantiationAwareBeanPostProcessor.java         
│  │  │      SingletonBeanRegistry.java                      ## 单例Bean的注册创建 接口
│  │  │      
│  │  ├─support
│  │  │  │  AbstractAutowireCapableBeanFactory.java          ## 创建并获取Bean的核心实现类
│  │  │  │  AbstractBeanFactory.java                         ## ioc容器的抽象实现类
│  │  │  │  DefaultListableBeanFactory.java                  ## BeanFactory的核心实现
│  │  │  │  
│  │  │  ├─beanDefinition
│  │  │  │      AbstractBeanDefinition.java                  ## BeanDefinition的核心实现类
│  │  │  │      AbstractBeanDefinitionReader.java            ## BeanDefinitionRegistry接口,加载BeanDefinition的核心实现类
│  │  │  │      BeanDefinitionReader.java                    ## 接口
│  │  │  │      BeanDefinitionRegistry.java                  ## 接口
│  │  │  │      DefaultSingletonBeanRegistry.java            ## 默认的SingletonBeanRegistry接口实现类, 其中设置有三级缓存处理逻辑
│  │  │  │      RootBeanDefinition.java                      
│  │  │  │      
│  │  │  └─beanDefinitionInStantiationStrategy
│  │  │          InstantiationStrategy.java
│  │  │          SimpleInstantiationStrategy.java            ## 简单的实例化策略
│  │  │          
│  │  └─xml
|  |          BeanDefinitionDocumentReader.java              ## 解析xml
│  |          DefaultBeanDefinitionDocumentReader.java       ## 解析xml的默认实现类
│  │          XmlBeanDefinitionReader.java                   ## 解析xml中定义的Bean
│  │          

context:

├─context
│  │  ApplicationContext.java                                 ## 主容器接口
│  │  ApplicationContextAware.java                            ## 注入Applicationcontext容器
│  │  ApplicationEvent.java                                   ## Application事件定义接口
│  │  ApplicationEventPublisher.java                          ## 时间发布接口
│  │  ApplicationListener.java
│  │  ConfigurableApplicationContext.java
│  │  
│  ├─annotation
│  │      Bean.java                                           ## (未实现)
│  │      ClassPathBeanDefinitionScanner.java                 
│  │      ClassPathScanningCandidateComponentProvider.java    ## package-scan的支持类
│  │      ComponentScan.java                                  ## (未实现)
│  │      ComponentScans.java                                 ## (未实现)
│  │      Lazy.java                                           ## (未实现)
│  │      Scope.java                                          ## (未实现)
│  ├─event
│  │      AbstractApplicationEventMulticaster.java            ## 提供基本的侦听器注册工具。
│  │      ApplicationContextEvent.java
│  │      ApplicationEventMulticaster.java                    ## 事件多播接口
│  │      ContextClosedEvent.java                             ## 容器自带的容器关闭事件
│  │      ContextRefreshedEvent.java                          ## 容器自带的容器刷新事件
│  │      ContextStartedEvent.java                            ## 容器自带的容器开始事件
│  │      ContextStoppedEvent.java                            ## 容器自带的容器停止事件
│  │      SimpleApplicationEventMulticaster.java              ## 将所有事件多播到所有已注册的侦听器
│  │      
│  └─support
│          AbstractApplicationContext.java                          ## Context核心实现
│          AbstractRefreshableApplicationContext.java               ## 实现刷新容器,创建beanFactory并加载BeanDefinition
│          AbstractRefreshableConfigApplicationContext.java         
│          AbstractXmlApplicationContext.java                       ## 实现调用加载BeanDefinition的方法
│          ApplicationContextAwareProcessor.java                    ## 实现Aware,注入ApplicationContext
│          ClassPathXmlApplicationContext.java                      ## 加载xml文件

2.流程梳理

1.加载并注册BeanDefinitions

主要是在初始化刷新容器的前面部分进行,把要注册的Bean先加载成BeanDefinition

2.Spring中预先加载单例Bean(包括getBean())

会在容器刷新的最后,把可以预先加载的单例bean进行加载获得,

也会在获取Bean的时候调用

3.部分原理

  1. 解决循环依赖

在对Bean的属性填充populateBean()

循环依赖的解决:三级缓存

如果不实现Aop功能,二级缓存是足够解决循环依赖的

  • 在DefaultSingletonBeanRegistry设置有三个map用于实现三级缓存,解决循环依赖问题

    • singletonObjects 第一级缓存,用于保存实例化、注入、初始化完成的bean实例
    • earlySingletonObjects 第二级缓存,存放原始的 bean 对象(尚未填充属性),用于解决循环依赖
    • singletonFactories 第三级缓存,用于保存bean创建工厂,以便于后面扩展有机会创建代理对象。
    • 其中第三级缓存中添加ObjectFactory对象, 对象可能需要经过Aop,通过getEarlyBeanReference方法获取代理对象

  • 用提前暴露的方法避免循环依赖

    • 源码中的实现在BeanDefinitionParserDelegate#parsePropertyValue():

    •         public static final String REF_ATTRIBUTE = "ref";
              //  ....
              boolean hasRefAttribute = ele.hasAttribute(REF_ATTRIBUTE);
              //  ....
              //  如果又依赖于其他Bean,首先生成BeanReference
              if (hasRefAttribute) {
              //  ....
                  RuntimeBeanReference ref = new RuntimeBeanReference(refName);
                  return ref;
              }
      
    • 手写源码简单实现在DefaultBeanDefinitionDocumentReader# registerBeanDefinitions():

    •         public static final String REF_ATTRIBUTE = "ref";
              //   ....
              String propertyValueAttribute = property.attributeValue(VALUE_ATTRIBUTE);
              //   ....
              Object value = propertyValueAttribute;
              if (StrUtil.isNotEmpty(propertyRefAttribute)) {
                  value = new BeanReference(propertyRefAttribute);
              }
      
  1. 解析xml文件

  • spring 源码中解析xml是实现了BeanDefinitionDocumentReader接口的默认实现类 可以通过parseBeanDefinitionElement方法解析单个bean标签, 也可以通过parseBeanDefinitions方法解析多个bean标签
  • 这里简化实现,所有解析均在DefaultBeanDefinitionDocumentReader(BeanDefinitionDocumentReader的默认实现类)中实现。
  1. 解析占位符

  • 用PropertyResourceConfigurer实现对@Value注解的解析填充

该类实现方法已弃用

新版本使用 org.springframework.context.support.PropertySourcesPlaceholderConfigurer ,

通过利用 org.springframework.core.env.Environment

AND org.springframework.core.env.PropertySource

机制来更灵活。

  1. 包扫描

  • 通过扫描指定包,把带有@Component注解的类注册到ioc中作为Bean
ClassPathBeanDefinitionScanner #doScan----->
ClassPathScanningCandidateComponentProvider #findCandidateComponents         ##查找指定基础包路径下带有Component注解的类

5. ### Bean 的生命周期

时序图中也有流程

二.Aop模块

1.项目结构

├─aop
│  │  Advisor.java                                 ## Advisor是Pointcut和Advice的组合
│  │  AdvisorChainFactory.java                     ## 获取拦截器和动态拦截建议接口
│  │  ClassFilter.java
│  │  DefaultAdvisorChainFactory.java              ## AdvisorChainFactory的默认实现类
│  │  Pointcut.java                                ## 切点
│  │  PointcutAdvisor.java
│  │  ReflectiveMethodInvocation.java              ## 反射式方法调用,主要用来调用拦截器
│  │  
│  ├─adapter                                       ## 两种Aop方式的适配器 用来调用拦截器方法
│  │      AfterReturningAdviceInterceptor.java
│  │      MethodBeforeAdviceInterceptor.java
│  │      
│  ├─advice
│  │      AfterAdvice.java
│  │      AfterReturningAdvice.java                ## 在方法返回后调用的
│  │      BeforeAdvice.java
│  │      MethodBeforeAdvice.java                  ## 在方法执行前调用的
│  │      
│  ├─advised
│  │      AdvisedSupport.java
│  │      ProxyFactory.java                        ## 根据限定条件创建相应的 AopProxy
│  │      
│  ├─aspectj
│  │      AspectJExpressionPointcut.java           ## AspectJ切点 Pointcut的实现类
│  │      AspectJExpressionPointcutAdvisor.java    ## 存放Pointcut和Advice PointcutAdvisor的实现类
│  │      
│  ├─autoproxy
│  │      AbstractAutoProxyCreator.java            ## Aop程序的入口以及主要实现
│  │      DefaultAdvisorAutoProxyCreator.java      ## 主要实现查找目标源相关的 advice和 advisors
│  │      
│  ├─proxy
│  │      AopProxy.java                            ## 两种代理方式 默认使用 CglibAopProxy
│  │      CglibAopProxy.java                       ## cglib创建出来的代理对象是被代理对象的子类
│  │      JdkDynamicAopProxy.java                  ## jdk创建出的代理对象和目标源同一个接口的实现类
│  │      
│  ├─support
│  │      AopUtils.java
│  │      
│  └─target
│          MethodMatcher.java                      ## 判断方法是否符合定义的切入点(pointcut)的条件
│          TargetSource.java                       ## 被代理的目标对象

2.Aop代理注册流程

在对Bean实例化之前首先会判断是否需要增强,进入Aop流程

最终返回一个增强之后的代理对象

3.部分原理

  1. 把代理对象到ioc容器

容器在初始化过程中会预先实例化单例Bean,调用getBean()

  • 在Spring Ioc完成代理对象target的实例化、填充、初始化。然后在初始化后置处理器中进行介入,通过BeanPostProcess会判断是否需要增强(Aop代理对象)
  1. 调用方法wrapIfNecessary

通过后置处理器进入AbstractAutoProxyCreator中调用wrapIfNecessary

  • 1.判断是不是基础设施类(如Advice、Pointcut等),如果是直接返回Bean
  • 2.而后通过getAdvicesAndAdvisorsForBean()方法获取bean的所有Advisor,其中重要的方法findCandidateAdvisors() 遍历容器中的Advisor获取所有切面,判断是否匹配细节交给AopUtil实现
  • 3.找到所有Advisor之后就开始着手创建代理(createProxy),根据需要通过proxyFactory.getProxy() 创建对应的代理,赋予target等属性
  1. 代理调用过程

  • 在调用target目标方法时,首先进入DynamicAdvisedInterceptor#intercept

    • 通过调用DefaultAdvisorChainFactory#getInterceptorsAndDynamicInterceptionAdvice获取与指定配置、方法和目标类相关的拦截器和动态拦截建议,最终返回拦截器列表。
    • 如果拦截器链不为空,则调用proceed(),依次执行拦截器链中的拦截器(这一步执行通知Advice)或执行目标方法
  1. 核心类

  • AspectJExpressionPointcut

    • AspectJ 切点,实现Pointcut, ClassFilter, MethodMatcher接口
    • 判断给定的类是否与切点表达式匹配。
    • 用于根据 AspectJ 切点表达式创建一个新的 AspectJExpressionPointcut 实例。
  • AspectJExpressionPointcutAdvisor

    • Advisor是Pointcut和Advice的组合
    • 专门处理基于AspectJ的通知+切点的
  • AbstractAutoProxyCreator

    • Aop创建动态代理的入口及主要执行
    • 根据需要包装给定的bean,如果bean满足AOP切点条件,则返回代理对象,否则返回原bean。
    • 默认子类DefaultAdvisorAutoProxyCreator用于实现模板方法getAdvicesAndAdvisorsForBean(),获取所有适用于当前Bean 的 Advisors
  • DefaultAdvisorChainFactory

    • 实现AdvisorChainFactory接口
    • 通过方法getInterceptorsAndDynamicInterceptionAdvice(),获取与指定配置、方法和目标类相关的拦截器和动态拦截建议。
  • MethodInterceptor(org.aopalliance.intercept.MethodInterceptor)

    • 主要实现了两个方法拦截器

    • AfterReturningAdviceInterceptor#invoke()

    • MethodBeforeAdviceInterceptor#invoke()

三.Jdbc模块

JdbcTemplate继承了JdbcAccessor和接口JdbcOperation。

JdbcAccessor主要是对DataSource进行管理和配置。

JdbcOperation主要是通过JDBC操作数据库的基本操作方法。

四.Mvc模块

1.项目结构

├─annotation
│      EnableWebMvc.java                       ## 实现启动mvc的自定义注解
│      
├─config
│      DelegatingWebMvcConfiguration.java      ## 委派 Web MVC 配置InterceptorRegistration.java            ## 封装拦截器信息InterceptorRegistry.java                ## 注册用户定义的拦截器WebMvcConfigurationSupport.java         ## 帮助MVC完成配置WebMvcConfigurer.java                   ## MVC核心配置接口WebMvcConfigurerComposite.java          ## 配置的整合
│      
├─convert
│      ConvertComposite.java                   ## 转换器的整合ConvertHandler.java
│      
├─exception
│      MvcException.java
│      
├─http
│      HttpMethod.java                         
│      
├─resolver                                                       
│      DefaultHandlerExceptionResolver.java             ## 默认异常解析器ExceptionHandlerExceptionResolver.java           ## 异常解析器HandlerMethodArgumentResolverComposite.java      ## 方法参数解析接口实现类(对解析器的整合)HandlerMethodReturnValueHandlerComposite.java    ## 方法返回值接口实现类(对解析器的整合)PathVariableMapMethodArgumentResolver.java       ## 处理 @PathVariable注解修饰的且为map参数PathVariableMethodArgumentResolver.java          ## 处理 @PathVariable注解修饰的且不为map参数RequestHeaderMapMethodArgumentResolver.java      ## 处理 @RequestHeader注解修饰的且为map参数RequestHeaderMethodArgumentResolver.java         ## 处理 @RequestHeader注解修饰的且不为map参数RequestParamMapMethodArgumentResolver.java       ## 处理 @RequestParam注解修饰的且为map参数RequestParamMethodArgumentResolver.java          ## 处理 @RequestParam注解修饰的且不为map参数RequestRequestBodyMethodArgumentResolver.java    ## 处理添加了@RequestBody 注解的参数。RequestResponseBodyMethodReturnValueHandler.java ## 处理添加了@ResponseBody 注解的参数。ServletRequestMethodArgumentResolver.java        ## 解析 servlet 支持的与请求相关的方法参数ServletResponseMethodArgumentResolver.java       ##处理 ServletResponse、OutputStream 以及 Writer 类型的参数
│      
├─servlet
│  │  ControllerAdviceBean.java                    ## 注解 @ControllerAdvice的映射Bean
│  │  DispatcherServlet.java                       ## 用于HTTP请求处理程序/控制器的中央调度器
│  │  DispatcherServlet.properties                 ## 用于加载默认的 HandlerMapping,HandlerAdapter 等组件
│  │  FrameworkServlet.java                        ## 接收各类请求,执行 doService
│  │  HandlerAdapter.java                          ## HandlerAdapter接口 处理请求的工具
│  │  HandlerExceptionResolver.java                ## HandlerExceptionResolver接口
│  |  HandlerMethodReturnValueHandler.java         ## 返回值处理器 接口
│  |  HandlerMethodArgumentResolver.java           ## 对请求中的参数进行解析的 接口
│  │  HandlerInterceptor.java                      ## HandlerInterceptor接口 为用户提供拦截器接口
│  │  HandlerMapping.java                          ## HandlerMapping接口 将不同的请求映射到不同的Handler
│  │  HttpServletBean.java                         
│  │  
│  ├─adapter
│  │      RequestMappingHandlerAdapter.java        ## HandlerAdapter的核心实现类
│  │      
│  └─handler
│          AbstractHandlerMapping.java             
│          AbstractHandlerMethodMapping.java       
│          BeanNameUrlHandlerMapping.java
│          ExceptionHandlerMethod.java
│          HandlerExecutionChain.java              ## 处理程序执行链HandlerMethod.java
│          MappedInterceptor.java                  ## 对 HandlerInterceptor 实现类进行了封装RequestMappingHandlerMapping.java       ## HandlerMapping 核心实现类ServletInvocableMethod.java             ## 用于执行 HandlerMethodServletPostProcessor.java
│          
└─support
        AbstractAnnotationConfigDispatcherServletInitializer.java      ## 给用户提供接口用于 DispatcherServlet 初始化
        AbstractDispatcherServletInitializer.java                      

2.梳理过程,各部分关系

1.初始化父子容器

由于自己写的springioc容器并不完善

webmvc模块依赖于org.springframework的web,bean,context模块

SCI:

  1. SCI:即Servlet Container Initializer,Servlet容器(Tomcat)提供的初始化Servlet容器本身的接口,可替换web.xml

    1. 首先在spring-web的jar包中,存在一个/META-INF/services/javax.servlet.ServletContainerInitializer的文件,该文件的内容为
    2.     org.springframework.web.SpringServletContainerInitializer
      
    3.   Tomcat启动时会在classpath下查找名为org.springframework.web.SpringServletContainerInitializer的java类,并在容器启动时调用它的相关方法。
  2. SpringSCI:SpringServletContainerInitializer,Srping提供的SCI的一个实现,起到中转桥接作用,桥接到 WebApplicationInitializer 接口上

    1. SpringServletContainerInitializer上使用了
    2.     @HandlesTypes(WebApplicationInitializer.class)
      
    3.   表示该类只用来处理WebApplicationInitializer的具体子类。WebApplicationInitializer是Spring Web中定义的接口。
  3. AbstractDispatcherServletInitializer实现接口WebApplicationInitializer,创建WebApplicationContext(也就是后面提到的子容器)

在使用Spring Boot时,不需要手动继承或配置AbstractAnnotationConfigDispatcherServletInitializer,因为Spring Boot自动化配置(Auto Configuration)机制已经帮我们完成了这些工作。

ContextLoadListener创建了一个Spring容器作为父容器,里面主要管理Service和数据。

DispatcherServlet创建了一个Springmvc容器,作为Spring容器的子容器,管理Controller层。

子容器可以获取到父容器中的对象,Controller里面可以注入Service层级的对象。

  • 在对容器初始化中会初始化其中的Bean,其中初始化WebMvcConfigurationSupport时会对其中定义的HandlerMappingHandlerAdapter等容器Bean进行初始化

2.把@RequestMapping上的路径注册给程序并放入缓存中的

spring在启动的时候会初始化AbstractHandlerMethodMapping类,他实现了InitializingBean的接口

对HandlerMapping初始化结束之后调用afterPropertiesSet()进行对方法中的路径进行解析存储到MappingRegistry中。

3.处理接收到的请求

五.webmvc-starter

  1. 项目结构

└─main
    ├─java
    │  └─com
    │      └─zhang
    │              MvcAutoConfiguration.java         ## 自动装配类
    │              
    └─resources
        └─META-INF
                spring.factories                          

2. ## 依赖

<dependencies>

    <!-- 用来打包starter -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>

    <!-- 手写springmvc -->
    <dependency>
        <groupId>com.zhang</groupId>
        <artifactId>springmvc-07-27</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </dependency>

</dependencies>

六.总结

1.收获

  1. 熟悉学习了spring底层处理逻辑。对spring源码进行分析解读并着手实现,梳理了我们日常使用spring的ioc容器框架、aop处理流程、mvc的处理流程。

  2. 学习了各种设计模式,模板方法模式(父类定义,由子类实现),工厂模式,单例模式,,适配器模式等

  3. 增强了日后处理异常,梳理流程,设计框架,学习源码等方面的自信心。

2.缺点与不足

  1. spring的ioc容器并不完善,实现了xml形式的,只简单实现注解形式
  2. spring中没有实现父子容器的设定
  3. 由于spring的ioc容器并不完善,springmvc依赖于org.springframework的ioc容器管理Beans
  4. jdbc模块待完善,声明式事务没有实现