在程序员的修炼过程中,挫折和永不言败是制胜之道。每一位程序员或多或少都会遇到技术瓶颈,遇到挫折时需要静下心来,不急不躁一步一步地探索。然而,仅仅依靠心态的调整是不够的,还需要结合科学的方法和持续的学习来完善这一过程。以下是我的学习心得以及手写实现一个微型 Spring 框架的过程,希望对你的突破有所帮助。
Spring 的核心思想是 控制反转(IoC) 和 依赖注入(DI) ,通过解耦组件之间的依赖关系,使得代码更加灵活和可维护。
0.项目简介
项目地址
项目介绍
项目分为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.部分原理
-
解决循环依赖
在对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); }
-
-
解析xml文件
- spring 源码中解析xml是实现了BeanDefinitionDocumentReader接口的默认实现类 可以通过parseBeanDefinitionElement方法解析单个bean标签, 也可以通过parseBeanDefinitions方法解析多个bean标签
- 这里简化实现,所有解析均在DefaultBeanDefinitionDocumentReader(BeanDefinitionDocumentReader的默认实现类)中实现。
-
解析占位符
- 用PropertyResourceConfigurer实现对@Value注解的解析填充
该类实现方法已弃用
新版本使用 org.springframework.context.support.PropertySourcesPlaceholderConfigurer ,
通过利用 org.springframework.core.env.Environment
AND org.springframework.core.env.PropertySource
机制来更灵活。
-
包扫描
- 通过扫描指定包,把带有@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.部分原理
-
把代理对象到ioc容器
容器在初始化过程中会预先实例化单例Bean,调用getBean()
- 在Spring Ioc完成代理对象target的实例化、填充、初始化。然后在初始化后置处理器中进行介入,通过BeanPostProcess会判断是否需要增强(Aop代理对象)
-
调用方法wrapIfNecessary
通过后置处理器进入AbstractAutoProxyCreator中调用wrapIfNecessary
- 1.判断是不是基础设施类(如Advice、Pointcut等),如果是直接返回Bean
- 2.而后通过getAdvicesAndAdvisorsForBean()方法获取bean的所有Advisor,其中重要的方法findCandidateAdvisors() 遍历容器中的Advisor获取所有切面,判断是否匹配细节交给AopUtil实现
- 3.找到所有Advisor之后就开始着手创建代理(createProxy),根据需要通过proxyFactory.getProxy() 创建对应的代理,赋予target等属性
-
代理调用过程
-
在调用target目标方法时,首先进入DynamicAdvisedInterceptor#intercept
- 通过调用DefaultAdvisorChainFactory#getInterceptorsAndDynamicInterceptionAdvice获取与指定配置、方法和目标类相关的拦截器和动态拦截建议,最终返回拦截器列表。
- 如果拦截器链不为空,则调用proceed(),依次执行拦截器链中的拦截器(这一步执行通知Advice)或执行目标方法
-
核心类
-
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 ## 用于执行 HandlerMethod
│ ServletPostProcessor.java
│
└─support
AbstractAnnotationConfigDispatcherServletInitializer.java ## 给用户提供接口用于 DispatcherServlet 初始化
AbstractDispatcherServletInitializer.java
2.梳理过程,各部分关系
1.初始化父子容器
由于自己写的springioc容器并不完善
webmvc模块依赖于org.springframework的web,bean,context模块
SCI:
-
SCI:即Servlet Container Initializer,Servlet容器(Tomcat)提供的初始化Servlet容器本身的接口,可替换web.xml
- 首先在spring-web的jar包中,存在一个/META-INF/services/javax.servlet.ServletContainerInitializer的文件,该文件的内容为
-
org.springframework.web.SpringServletContainerInitializer - Tomcat启动时会在classpath下查找名为org.springframework.web.SpringServletContainerInitializer的java类,并在容器启动时调用它的相关方法。
-
SpringSCI:SpringServletContainerInitializer,Srping提供的SCI的一个实现,起到中转桥接作用,桥接到 WebApplicationInitializer 接口上
- SpringServletContainerInitializer上使用了
-
@HandlesTypes(WebApplicationInitializer.class) - 表示该类只用来处理WebApplicationInitializer的具体子类。WebApplicationInitializer是Spring Web中定义的接口。
-
AbstractDispatcherServletInitializer实现接口WebApplicationInitializer,创建WebApplicationContext(也就是后面提到的子容器)
在使用Spring Boot时,不需要手动继承或配置
AbstractAnnotationConfigDispatcherServletInitializer,因为Spring Boot自动化配置(Auto Configuration)机制已经帮我们完成了这些工作。
ContextLoadListener创建了一个Spring容器作为父容器,里面主要管理Service和数据。
DispatcherServlet创建了一个Springmvc容器,作为Spring容器的子容器,管理Controller层。
子容器可以获取到父容器中的对象,Controller里面可以注入Service层级的对象。
- 在对容器初始化中会初始化其中的Bean,其中初始化WebMvcConfigurationSupport时会对其中定义的HandlerMapping,HandlerAdapter等容器Bean进行初始化
2.把@RequestMapping上的路径注册给程序并放入缓存中的
spring在启动的时候会初始化AbstractHandlerMethodMapping类,他实现了InitializingBean的接口
对HandlerMapping初始化结束之后调用afterPropertiesSet()进行对方法中的路径进行解析存储到MappingRegistry中。
3.处理接收到的请求
五.webmvc-starter
-
项目结构
└─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.收获
-
熟悉学习了spring底层处理逻辑。对spring源码进行分析解读并着手实现,梳理了我们日常使用spring的ioc容器框架、aop处理流程、mvc的处理流程。
-
学习了各种设计模式,模板方法模式(父类定义,由子类实现),工厂模式,单例模式,,适配器模式等
-
增强了日后处理异常,梳理流程,设计框架,学习源码等方面的自信心。
2.缺点与不足
- spring的ioc容器并不完善,实现了xml形式的,只简单实现注解形式
- spring中没有实现父子容器的设定
- 由于spring的ioc容器并不完善,springmvc依赖于org.springframework的ioc容器管理Beans
- jdbc模块待完善,声明式事务没有实现