前言

在学习Spring框架源码时,记住一句话:源码并不难,只需要给你各种业务场景或者项目经理,你也能实现自己的Spring。虽然你的实现可能无法与开源团队相媲美,但是你肯定可以实现一个0.0.1版本。因此,初次阅读源码时,不要陷入太深的细节中。先了解大体逻辑,再仔细研读。

实现功能

本文将带领大家实现一个简易版的Spring框架,并介绍以下功能点:

  1. 了解Spring的底层源码启动过程
  2. 了解BeanDefinition的概念
  3. 了解Spring解析配置类等底层源码工作流程
  4. 了解依赖注入,Aware回调等底层源码工作流程
  5. 了解Spring AOP的底层源码工作流程

以上功能点将使我们对Spring框架的实现有所了解,但我们并不会一下子实现整个Spring框架的业务。我们将从上述功能点入手,通过手写模拟Spring框架来实现这些功能。

首先,我们像使用Spring一样,传入配置类获取applicationContext,再通过getBean方法获取具体对象。最后,我们调用方法并打印日志。如果你对这些基本流程不熟悉,可以查看我的入门系列文章:Spring入门系列:浅析知识点

详细流程如下:

  1. 解析配置类上的ComponentScan注解,获取扫描的基本路径
  2. 开始解析各个被扫描到的文件,是否是需要被Spring管理,如果是则暂存到list集合中
  3. 开始遍历被Spring管理list集合,解析各个类上的注解,比如是否是懒加载,然后将这些属性都封装到applicationContext中的以beanName为key的BeanDefineMap中
  4. 针对已经解析好的bean定义进行创建对象并实例化,并将其放入以beanName为key的singletonMap实例化缓存池中。
  5. 在实例化时,如果发现有依赖注入的对象,则将实例化缓存池中的对象存入。如果缓存池中没有该对象,则进行创建后再注入。
  6. 判断对象是否实现了各个Aware接口,如果实现,则进行回调。
  7. 判断对象是否属于增强类。在这里,我们模拟了事务注解。如果有事务注解,则创建一个代理对象,并为所有方法增加拦截器。然后将该代理对象存入单例缓存池中。

详细解析

项目结构

基本路径:com.user目录

各个注解及上下文类:config目录

需要被管理的Bean:service目录

启动类:com.user根目录

源码分析

@Component
public class UserService implements ApplicationContextAware, BeanNameAware { @AutoWired
ServiceDemo serviceDemo; private XiaoyuApplicationContext applicationContext;
private String beanName; public void test() {
serviceDemo.say();
// System.out.println(serviceDemo);
System.out.println("userService:"+applicationContext.getBean("userService"));
System.out.println("beanName:"+beanName);
} @Override
public void setApplicationContext(XiaoyuApplicationContext applicationContext) {
this.applicationContext = applicationContext;
} @Override
public void setBeanName(String beanName) {
this.beanName = beanName;
}
}

UserService类主要用于测试是否Spring已经管理了相关对象并生成了代理对象,是我们的日常业务类,我们仔细看下XiaoyuApplicationContext类,主要的流程在这边:

public class XiaoyuApplicationContext {

    //配置类
private Class config;
//初始的bean定义
private Map<String,BeanDefinition> beanDefineMap = new HashMap<>();
//单例缓存池
private Map<String,Object> singleBean = new HashMap<>(); public XiaoyuApplicationContext(Class myDemoConfigClass) {
config = myDemoConfigClass;
//解析配置类
scan(); } public void scan(){
ComponentScan declaredAnnotation = (ComponentScan) config.getDeclaredAnnotation(ComponentScan.class);
String value = declaredAnnotation.basePackages();
doScan(value);
//将bean定义Map生成具体的Bean对象
beanDefineMap.entrySet().stream().forEach(item->{
String beanName = item.getKey();
BeanDefinition beanDefinition = item.getValue();
if (!beanDefinition.isLazy() && "singleton".equals(beanDefinition.getScope())) {
Object bean = createBean(beanName);
singleBean.put(beanName,bean);
}
});
} /**
* 解析配置类
*/
private void doScan(String value) {
String path = value.replace(".","/");
//正常走文件解析
ClassLoader classLoader = this.getClass().getClassLoader();
URL resource = classLoader.getResource(path);
File file = new File(resource.getFile()); List<File> classFile = new ArrayList<>();
//简单点直接双层解析即可
if (file.isDirectory()) {
for (File f : file.listFiles()) {
if (f.isDirectory()) {
for (File f1 : f.listFiles()) {
if (!f1.isDirectory()) {
classFile.add(f1);
}
}
} else {
classFile.add(f);
}
}
}
//遍历所有解析文件
for (File cFile : classFile) {
String absolutePath = cFile.getAbsolutePath();
String className = absolutePath.substring(absolutePath.indexOf("com"), absolutePath.indexOf(".class"))
.replace("\\", "."); try {
Class<?> clazz = classLoader.loadClass(className);
//是否需要被Spring管理
if (clazz.isAnnotationPresent(Component.class)) {
//将bean上的注解封装到bean定义中
BeanDefinition beanDefinition = new BeanDefinition();
beanDefinition.setType(clazz);
beanDefinition.setLazy(clazz.isAnnotationPresent(Lazy.class));
if (clazz.isAnnotationPresent(Scope.class)) {
beanDefinition.setScope(clazz.getAnnotation(Scope.class).value());
} else {
beanDefinition.setScope("singleton");
} String beanName = clazz.getAnnotation(Component.class).value();
if (beanName.isEmpty()) {
//如果不设置beanName会默认生产唯一一个name,Spring底层也是这样做的
beanName = Introspector.decapitalize(clazz.getSimpleName());
} beanDefineMap.put(beanName, beanDefinition); }
} catch (ClassNotFoundException e) {
e.printStackTrace();
} }
} public Object createBean(String beanName){ BeanDefinition beanDefinition = beanDefineMap.get(beanName);
Class type = beanDefinition.getType(); try {
Object instance = type.newInstance();
//属性填充,依赖注入
populateBean(instance); if (instance instanceof ApplicationContextAware){
((ApplicationContextAware) instance).setApplicationContext(this);
}
if (instance instanceof BeanNameAware) {
((BeanNameAware) instance).setBeanName(beanName);
}
//是否需要AOP增强
if (type.isAnnotationPresent(Transaction.class)) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(type);
//简单的方法切面
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object proxy, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
//开启事务,关闭自动提交
System.out.println("事务已开启");
Object res = method.invoke(instance, objects);
//提交事务
System.out.println("事务已提交");
return res;
}
});
return enhancer.create();
}
return instance;
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} return null;
} private void populateBean(Object instance) {
Field[] declaredFields = instance.getClass().getDeclaredFields(); Arrays.stream(declaredFields).forEach(item->{
//寻找注入点
if (item.isAnnotationPresent(AutoWired.class)) {
Object bean = getBean(item.getName());
item.setAccessible(true);
try {
item.set(instance,bean);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}); } public Object getBean(String beanName){
if (!beanDefineMap.containsKey(beanName)) {
throw new NullPointerException();
}
if ("singleton".equals(beanDefineMap.get(beanName).getScope())) {
if (singleBean.containsKey(beanName)) {
return singleBean.get(beanName);
} else {
Object bean = createBean(beanName);
singleBean.put(beanName,bean);
return bean;
}
}
return createBean(beanName);
}
}

以上即为整个流程的基本梳理。我们在实现过程中没有涉及Bean循环依赖以及其他各种创建缓存,但Spring在实现Bean的创建过程中确实用到了各种本地缓存和同步锁(synchronized)。在学习源码时,不要太关注这些细节。首先要理解整个流程,再深入研究。

结语

最后,我们在gitee上提供了项目源码。如果需要,可以查看spring-xiaoyu。虽然我认为写一遍自己的代码更好,因为这是最简单的流程,有助于理解Spring源码。

Spring源码系列:初探底层,手写Spring的更多相关文章

  1. 《四 spring源码》利用TransactionManager手写spring的aop

    事务控制分类 编程式事务控制          自己手动控制事务,就叫做编程式事务控制. Jdbc代码: Conn.setAutoCommite(false);  // 设置手动控制事务 Hibern ...

  2. 框架源码系列三:手写Spring AOP(AOP分析、AOP概念学习、切面实现、织入实现)

    一.AOP分析 问题1:AOP是什么? Aspect Oriented Programming 面向切面编程,在不改变类的代码的情况下,对类方法进行功能增强. 问题2:我们需要做什么? 在我们的框架中 ...

  3. 框架源码系列四:手写Spring-配置(为什么要提供配置的方法、选择什么样的配置方式、配置方式的工作过程是怎样的、分步骤一个一个的去分析和设计)

    一.为什么要提供配置的方法 经过前面的手写Spring IOC.手写Spring DI.手写Spring AOP,我们知道要创建一个bean对象,需要用户先定义好bean,然后注册到bean工厂才能创 ...

  4. 框架源码系列二:手写Spring-IOC和Spring-DI(IOC分析、IOC设计实现、DI分析、DI实现)

    一.IOC分析 1. IOC是什么? IOC:Inversion of Control控制反转,也称依赖倒置(反转) 问题:如何理解控制反转? 反转:依赖对象的获得被反转了.由自己创建,反转为从IOC ...

  5. Spring源码系列 — Bean生命周期

    前言 上篇文章中介绍了Spring容器的扩展点,这个是在Bean的创建过程之前执行的逻辑.承接扩展点之后,就是Spring容器的另一个核心:Bean的生命周期过程.这个生命周期过程大致经历了一下的几个 ...

  6. Spring源码系列 — 注解原理

    前言 前文中主要介绍了Spring中处理BeanDefinition的扩展点,其中着重介绍BeanDefinitionParser方式的扩展.本篇文章承接该内容,详解Spring中如何利用BeanDe ...

  7. Spring源码系列 — BeanDefinition

    一.前言 回顾 在Spring源码系列第二篇中介绍了Environment组件,后续又介绍Spring中Resource的抽象,但是对于上下文的启动过程详解并未继续.经过一个星期的准备,梳理了Spri ...

  8. Spring源码系列(二)--bean组件的源码分析

    简介 spring-bean 组件是 Spring IoC 的核心,我们可以使用它的 beanFactory 来获取所需的对象,对象的实例化.属性装配和初始化等都可以交给 spring 来管理. 本文 ...

  9. Spring源码系列(三)--spring-aop的基础组件、架构和使用

    简介 前面已经讲完 spring-bean( 详见Spring ),这篇博客开始攻克 Spring 的另一个重要模块--spring-aop. spring-aop 可以实现动态代理(底层是使用 JD ...

  10. Spring源码系列(四)--spring-aop是如何设计的

    简介 spring-aop 用于生成动态代理类(底层是使用 JDK 动态代理或 cglib 来生成代理类),搭配 spring-bean 一起使用,可以使 AOP 更加解耦.方便.在实际项目中,spr ...

随机推荐

  1. 几行python代码完美操控手机

    最近一直成谜于python代码带来的便利,今天打算学习下用python代码来控制操作手机,首先需要利用adb,通过安卓调试桥来达到目的,其实也可以用appium来实现,不过appium多数用在自动化测 ...

  2. 【转载】Python 在已存在的excel表格中写入数据

    由于每天都要汇总日报数据,其实就是个复制粘贴的工作,将多个表的数据汇总成一个表格,工作较枯燥,于是想到用Python帮我完成这个简单又粗糙的工作.写一下我学到的几个小技巧,下次忘记怎么使用的时候,还能 ...

  3. 06 HBase安装与伪分布式配置

    1.下载压缩文件 2.解压 3.修改文件夹名 4.修改文件夹权限 5.配置环境变量 6.伪分布式配置文件 7.启动HDFS,启动Hbase 8.进入shell界面 9.停止Hbase,停止HDFS运行

  4. X射线随笔

    2023-02-27 千里马与伯乐 如果把X射线(X光)比做千里马的话,那这匹千里马的发掘,离不开伯乐的贡献.说起X射线的伯乐,就得从一个简短的故事说起: 1895年11月8日的夜晚,德国物理学家伦琴 ...

  5. axios实现无感刷新

    前言 最近在做需求的时候,涉及到登录token,产品提出一个问题:能不能让token过期时间长一点,我频繁的要去登录. 前端:后端,你能不能把token 过期时间设置的长一点. 后端:可以,但是那样做 ...

  6. RKO组——冲刺随笔(1)

    这个作业属于哪个课程 至诚软工实践F班 这个作业要求在哪里 第五次团队作业:项目冲刺 这个作业的目标 记录冲刺计划.要求包括当天会议照片.会议内容以及项目燃尽图(项目进度) 1.昨日进展 小组成员讨论 ...

  7. 油猴CSDN净化脚本

    CSDN版面越来越乱,最近还总是弹出红包雨和顶部巨大横幅,左侧也会随机出现学生认证弹窗.而且版面混乱难看,看起来非常费劲. 另外底下的推荐列表经常夹杂着CSDN文件下载的链接,下载文件又要付费,从来不 ...

  8. git练习网站(图形化版)

    https://learngitbranching.js.org/?locale=zh_CN

  9. 帝国CMS 登录后跳转ecmsadmin.php 白页 多个解决方案

    帝国CMS 6.6 版本, 运维的网站查询除了问题, 把网站数据库和整体网站拷贝到本地服务器, 配置好 e/config/config.php    和 e/class/config.php 两个目录 ...

  10. jmeter学习-性能指标、jmeter初识

    一:性能测试的指标 1. 并发/并发数/并发用户数 狭义的并发:同一时间做相同的一件事 广义的并发:同一时间做不同事情,混合场景,对服务器来说的并发 性能测试,先做简单的狭义并发,在做广义并发:先做单 ...