手写Spring框架(上)浅出

手写Spring框架

  • 准备工作
  • Spring启动和扫描逻辑实现
  • 依赖注入的实现
  • Aware回调模拟实现和初始化机制模拟实现
  • BeanPostProcessor (Bean的后置处理器) 模拟实现
  • Spring AOP 模拟实现

准备工作

  1. 准备一个空的工程
  2. 创建spring的容器类,它是Spring IOC理念的实现,负责对象的实例化、对象和对象之间依赖关系配置、对象的销毁、对外提供对象的查找等操作,对象的整个生命周期都是由容器来控制。传统使用方法是传入一个spring的配置文件或配置类根据用户的配置来创建这个容器。
package com.spring;

public class EditApplicationContext {

    //传入配置类
    private Class configClass;

    public EditApplicationContext(Class configClass) {
        this.configClass = configClass;
    }

    //定义根据别名获取类的方法
    public Object getBean(String name){
        return null;
    }
}
  1. 定义一个配置类,相当于配置文件
package com.zedit;

import com.spring.ComponentScan;

//指定包扫描路径
@ComponentScan("com.zedit.service")
public class AppConfig {
}
- 如何定义包扫描路径,编写一个注解类
@Retention(RetentionPolicy.RUNTIME)
//规定只能写在类上
@Target(ElementType.TYPE)
public @interface ComponentScan {

    //接收属性值,指定扫描路径
    String value() default "";
}
  1. 定义一个Component注解,它的作用就是将类交给spring容器,实现bean的注入
@Retention(RetentionPolicy.RUNTIME)
//规定只能写在类上
@Target(ElementType.TYPE)
public @interface Component {

    //提供默认值
    String value() default "";
}

Spring启动和扫描逻辑实现

  1. 传入配置类对于spring而言 它只需要判断配置类有没有它提供的注解,获取扫描路径值,根据路径值
  2. 通过类加载器加载目录下的类,首先获取所有文件,然后获取全限定类名
public EditApplicationContext(Class configClass) {
    this.configClass = configClass;

    //解析配置类
    //Component注解->扫描路径->扫描

    ComponentScan declaredAnnotation = (ComponentScan)configClass.getDeclaredAnnotation(ComponentScan.class);
    String path = declaredAnnotation.value();
    // 全限定类名加工成能用的路径名 "com/xuhua/service"
    path = path.replace(".", "/");

    ClassLoader classLoader = EditApplicationContext.class.getClassLoader();
    //根据AppClassLoader加载器目录获取 classPath目录下中的‘path’目录下的资源
    URL resource = classLoader.getResource(path);
    //判断是否是文件夹而不是单个文件
    if (file.isDirectory()) {
        File[] files = file.listFiles();
        for (File f : files) {
            String fileName = f.getAbsolutePath();
            // /Users/zhuxuhua/Desktop/project/spring-edit/target/classes/com/zedit/service/XxxUtils.class
            // 转换成 com.zedit.service.XxxUtils
            if (fileName.endsWith(".class")) {
                String className = fileName.substring(fileName.indexOf("com"), fileName.indexOf(".class"));
                className = className.replace("/", ".");
                try {
                	//根据全限定类名加载类
                    Class<?> clazz = classLoader.loadClass(className);
                    //判断扫描到的类是不是一个bean注解
                    if (clazz.isAnnotationPresent(Component.class)){

                    }

                } catch (ClassNotFoundException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }
}
  1. 根据@Scope 注解判断bean是单例还是原型
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Scope {
    String value();
}
  1. 定义单例池
    在这里插入图片描述
  2. 由于在使用bean和初始化bean时都要去解析bean的定义与他的注解,如果不做设计每次的解析就会显得冗余繁琐,所以spring在Context扫描阶段定义了一个BeanDefinition定义类,它记录了bean的各种信息,先将扫描到的bean填入BeanDefinitionMap随后处理单例对象

//存储单例对象的单例池
private ConcurrentHashMap<String,Object> singletonObjects = new ConcurrentHashMap<>();
//存储所有bean的定义
private ConcurrentHashMap<String,BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>();
-------
try {

   Class<?> clazz = classLoader.loadClass(className);
   if (clazz.isAnnotationPresent(Component.class)) {
       //表示当前这个类有Component注解是一个bean对象
       //解析类,判断scope注解是单例的bean还是 prototype的bean
       //每扫描到一个bean就定义一个BeanDefinition对象
       Component componentAnnotation = clazz.getDeclaredAnnotation(Component.class);
       String beanName = componentAnnotation.value();
       BeanDefinition beanDefinition = new BeanDefinition();
       
       //spring bean默认为多例模式
       beanDefinition.setScpoe("prototype");
       if (clazz.isAnnotationPresent(Scope.class)){
           Scope annotation = clazz.getAnnotation(Scope.class);
           String value = annotation.value();
           if (value.equals("singleton")){
               beanDefinition.setScpoe("singleton");
           }
       }

       beanDefinition.setClazz(clazz);
		//扫描到的所有bean都存入这个map
       beanDefinitionMap.put(beanName,beanDefinition);

   }

} catch (ClassNotFoundException e) {
   throw new RuntimeException(e);
}
  1. 扫描完后根据存储的beanDefinitionMap填入单例池
    在这里插入图片描述
  2. 获取bean方法中判断是否是单例bean,如果是直接从单例池中取,如果不是则创建bean
public Object getBean(String beanName){
    //获取bean 如果map中没有就抛出异常,说明她不是一个bean,没有被扫描到
    if (beanDefinitionMap.containsKey(beanName)){
        BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
        //判断scope值,单例直接从单例池中取
        if (beanDefinition.getScpoe().equals("singleton")){
            return singletonObjects.get(beanName);
        }else {
            //原型bean每次从新创建
            return createBean(beanDefinition);
        }
    }else {
        throw new NullPointerException();
    }

}

//用beanDefinition中的clazz信息通过反射创建bean
public Object createBean(BeanDefinition beanDefinition){
    Class clazz = beanDefinition.getClazz();
    try {
        Object instance = clazz.getDeclaredConstructor().newInstance();
        return instance;
    } catch (InstantiationException e) {
        throw new RuntimeException(e);
    } catch (IllegalAccessException e) {
        throw new RuntimeException(e);
    } catch (InvocationTargetException e) {
        throw new RuntimeException(e);
    } catch (NoSuchMethodException e) {
        throw new RuntimeException(e);
    }
}

依赖注入的实现

首先注解,能标注在成员变量上

Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Autowired {

}
@Component("userService")
@Scope("singleton")
public class UserService {

    @Autowired
    private OrderService orderService;


    public void test(){
        System.out.println(orderService);
    }
}

依赖注解的实现原理就是在启动扫描初始化阶段 spring创建bean时 给@Autowired的成员变量赋值


    //用beanDefinition中的clazz信息通过反射创建bean
    public Object createBean(BeanDefinition beanDefinition){
        Class clazz = beanDefinition.getClazz();
        try {
            Object instance = clazz.getDeclaredConstructor().newInstance();

            //依赖注入实现原理
            for (Field declaredField : clazz.getDeclaredFields()) {
                if (declaredField.isAnnotationPresent(Autowired.class)){
                    Object bean = getBean(declaredField.getName());
                    declaredField.setAccessible(true);
                    declaredField.set(instance,bean);
                }
            }

            return instance;
        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        } catch (InvocationTargetException e) {
            throw new RuntimeException(e);
        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e);
        }
    }

在这里插入图片描述

Aware回调模拟实现和初始化机制模拟实现

需要回调的实现接口方法,在初始化阶段bean的创建阶段将beanName通过反射设置值

//回调接口
public interface BeanNameAware {
    void setBeanName(String name);
}

-------

public interface InitializingBean {
    void afterPropertySet();
}


@Component("userService")
@Scope("singleton")
public class UserService implements BeanNameAware, InitializingBean {

    @Autowired
    private OrderService orderService;

    private String beanName;

    @Override
    public void setBeanName(String name) {
        beanName = name;
    }

    @Override
    public void afterPropertySet() {
        System.out.println("初始化");
    }

    //用beanDefinition中的clazz信息通过反射创建bean
    public Object createBean(String beanName,BeanDefinition beanDefinition){
        Class clazz = beanDefinition.getClazz();
        try {
            Object instance = clazz.getDeclaredConstructor().newInstance();

            //依赖注入
            for (Field declaredField : clazz.getDeclaredFields()) {
                if (declaredField.isAnnotationPresent(Autowired.class)){
                    Object bean = getBean(declaredField.getName());
                    declaredField.setAccessible(true);
                    declaredField.set(instance,bean);
                }
            }
			//aware 回调
            if (instance instanceof BeanNameAware){
                ((BeanNameAware) instance).setBeanName(beanName);
            }
			//反射调用初始化bean的方法
            if (instance instanceof InitializingBean){
                ((InitializingBean) instance).afterPropertySet();
            }
            return instance;
        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        } catch (InvocationTargetException e) {
            throw new RuntimeException(e);
        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e);
        }
    }

BeanPostProcessor (Bean的后置处理器) 模拟实现

spring的扩展机制,在bean初始化前后调用

//定义接口  有初始化前后两种操作,也可以添加更多
public interface BeanPostProcessor {

    Object postProcessorBeforeInitialization(Object bean,String beanName);

    Object postProcessorAfterInitialization(Object bean,String beanName);
}

----------------

//自定义 BeanPostProcessor 实现BeanPostProcessor接口
@Component
public class ZhuZhuBanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessorBeforeInitialization(Object bean, String beanName) {
        System.out.println("初始化前");
        //定制操作
        if (beanName.equals("userService")) {
            System.out.println("userService 初始化前");
        }
        return null;
    }

    @Override
    public Object postProcessorAfterInitialization(Object bean, String beanName) {
        System.out.println("初始化后");
        return null;
    }
}

--------

//同其他bean一样在扫描时 加载 判断是否实现了BeanPostProcessor,如果实现了就放入 专门的List存储

//scan方法中  判断此类是否实现了BeanPostProcessor,并存入list
if (BeanPostProcessor.class.isAssignableFrom(clazz)) {
    BeanPostProcessor beanPostProcessorInstance 
    = (BeanPostProcessor) clazz.getDeclaredConstructor().newInstance();
    beanPostProcessorList.add(beanPostProcessorInstance);
}

---------

//createBean方法中

//createBean 时调  初始化前调用   
for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {
    //在调用初始化方法前 重新赋值对象
    instance = beanPostProcessor.postProcessorBeforeInitialization(instance,beanName);
}

//初始化
if (instance instanceof InitializingBean){
    try {
        ((InitializingBean) instance).afterPropertySet();
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}

//createBean 时调  初始化后调用   
for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {
    //在调用初始化方法后 重新赋值对象
    instance = beanPostProcessor.postProcessorAfterInitialization(instance, beanName);
}

Spring AOP 模拟实现

使用jdk动态代理 实现

@Component("userService")
@Scope("singleton")
public class UserServiceImpl implements UserService{

    @Autowired
    private OrderService orderService;

    private String beanName;

    @Override
    public void setBeanName(String name) {
        beanName = name;
    }

    @Override
    public void afterPropertySet() {
        System.out.println("初始化");
    }

    @Override
    public void test(){
        System.out.println(orderService+"orderService test");
        System.out.println(beanName);
    }

}


--------

public interface UserService {

    void test();
}

结合 BeanPostProcessor 完成jdk动态的实现

    @Override
    public Object postProcessorAfterInitialization(Object bean, String beanName) {
        System.out.println("初始化后");
        if (beanName.equals("userService")){

            Object proxyInstance = Proxy.newProxyInstance(ZhuZhuBanPostProcessor.class.getClassLoader(), bean.getClass().getInterfaces(), new InvocationHandler() {
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    System.out.println("被代理的逻辑");

                    return method.invoke(bean,args);
                }
            });

            return proxyInstance;

        }
        return bean;
    }

被动态代理后的类,执行类中的任意方法 都会经过 jdk的代理逻辑进行增强

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/507726.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

目标检测:数据集划分 XML数据集转YOLO标签

文章目录 1、前言&#xff1a;2、生成对应的类名3、xml转为yolo的label形式4、优化代码5、划分数据集6、画目录树7、目标检测系列文章 1、前言&#xff1a; 本文演示如何划分数据集&#xff0c;以及将VOC标注的xml数据转为YOLO标注的txt格式&#xff0c;且生成classes的txt文件…

web学习笔记(五十一)

目录 1. post请求和get请求的区别 2. CORS 跨域资源共享 2.1 什么是同源 2.2 什么是同源策略 2.3 如何实现跨域资源共享 2.4 使用 cors 中间件解决跨域问题 2.5 JSONP 接口 2.6 实现 JSONP 接口的步骤 1. post请求和get请求的区别 传参方式不同&#xff1a;get请求参数…

NOSQL - Redis的简介、安装、配置和简单操作

目录 一. 知识了解 1. 关系型数据库与非关系型数据库 1.1 关系型数据库 1.2 非关系型数据库 1.3 区别 1.4 非关系型数据库产生背景 1.5 NOSQL 与 SQL的数据记录对比 2. 缓存相关知识 2.1 缓存概念 2.2 系统缓存 2.3 缓存保存位置及分层结构 二 . redis 相关知识 1.…

虚拟机下的Ubuntu系统,NAT网卡连接不上网络的问题

文章目录 解决办法1解决办法2解决办法3Ubuntu20.04桥接网卡和NAT网卡不能同时使用问题解决 本博主花了许久时间解决这个NAT网卡上网问题&#xff0c;如果你试过网上所有教程&#xff0c;检测了Windows环境和Ubuntu环境没问题&#xff0c;无法启动系统服务、ping网络失败、重置虚…

为什么感觉张宇 25 版没 24版讲得好?

很多同学反映&#xff1a;25版&#xff0c;讲得太散了, 知识点太多&#xff0c;脱离了基础班。 三个原因&#xff1a; 1. 25版改动很大&#xff0c;课程没有经过打磨&#xff1b; 2. 因为24考试难度增加&#xff0c;所以改动的总体思路是“拓宽基础”&#xff1a;即把部分强…

这些生活中常用的东西到底要怎么寄?

寄生活中这些常见的“大家伙”&#xff0c;不用发愁啦&#xff01; 看看德邦快递专业包装&#xff0c;如何保驾护航。 01、行李怎么寄&#xff1f; 如果是装有物品的行李箱&#xff1a;1.使用气泡膜包裹物品&#xff0c;轮子部位加强缓冲物防护&#xff1b; 2.放入适配纸箱&am…

Coursera自然语言处理专项课程04:Natural Language Processing with Attention Models笔记 Week01

Natural Language Processing with Attention Models Course Certificate 本文是学习这门课 Natural Language Processing with Attention Models的学习笔记&#xff0c;如有侵权&#xff0c;请联系删除。 文章目录 Natural Language Processing with Attention ModelsWeek 01…

代码随想录算法训练营第41天 | 343:整数拆分, 96:不同的二叉搜索树

Leetcode - 343&#xff1a;整数拆分 题目&#xff1a; 给定一个正整数 n &#xff0c;将其拆分为 k 个 正整数 的和&#xff08; k > 2 &#xff09;&#xff0c;并使这些整数的乘积最大化。 返回 你可以获得的最大乘积 。 示例 1: 输入: n 2 输出: 1 解释: 2 1 1, …

WinForm_初识_事件_消息提示

文章目录 WinForm开发环境的使用软件部署的架构B/S 架构应用程序C/S 架构应用程序 创建 Windows 应用程序窗口介绍查看设计窗体 Form1.cs 后台代码窗体 Form1.cs窗体的常用属性 事件驱动机制事件的应用事件的测试测试事件的级联响应常用控件的事件事件响应的公共方法 消息提示的…

入门必读!如何实现适老化设计?大广赛题目解析!

早在 2021 年 4 月工业和信息化部办公厅发布了《关于进一步落实互联网应用老化和无障碍改造专项行动的通知》。根据联合国经济和社会事务部发布的2022年世界人口展望报告&#xff0c;全球人口展望报告&#xff0c;全球人口展望报告 65 预计2022年以上人口比例将达到2022年以上年…

2021-08-06

yarn的简介&#xff1a; Yarn是facebook发布的一款取代npm的包管理工具。 yarn的特点&#xff1a; 速度超快。 Yarn 缓存了每个下载过的包&#xff0c;所以再次使用时无需重复下载。 同时利用并行下载以最大化资源利用率&#xff0c;因此安装速度更快。超级安全。 在执行代码…

哈希表(Hash Table) -- 用数组模拟--字符串前缀哈希

本文用于个人算法竞赛学习&#xff0c;仅供参考 目录 一.什么是哈希表 二.哈希函数中的取模映射 三.拉链法&#xff08;数组实现&#xff09; 四.拉链法模板 五.开放寻址法 六.开放寻址法模板 七.字符串前缀哈希 九.字符串前缀哈希 模板 十.题目 一.什么是哈希表 哈希表&…

python print用法

1.输出字符串换行 输出结果会换行&#xff0c;默认自带换行 print(111) print(0) 2.末尾插入字符串或去除换行 末尾只能插入字符串&#xff0c;不能是其他类型 print(111,end0) print(0) 3.变量&#xff0c;字符串混合输入 没有必要什么都学&#xff0c;好用的常用的学一…

基于JavaWeb SSM mybatis 私人健身房系统管理平台设计和实现以及文档报告

基于JavaWeb SSM mybatis 私人健身房系统管理平台设计和实现以及文档报告 博主介绍&#xff1a;多年java开发经验&#xff0c;专注Java开发、定制、远程、文档编写指导等,csdn特邀作者、专注于Java技术领域 作者主页 央顺技术团队 Java毕设项目精品实战案例《1000套》 欢迎点赞…

ClamAV:Linux服务器杀毒扫描工具

Clam AntiVirus&#xff08;ClamAV&#xff09;是免费而且开放源代码的防毒软件&#xff0c;软件与病毒码的更新皆由社群免费发布。ClamAV在命令行下运行&#xff0c;它不将杀毒作为主要功能&#xff0c;默认只能查出系统内的病毒&#xff0c;但是无法清除。需要用户自行对病毒…

Linux中查看文件内容的命令

文章目录 一、七类常见的Linux的文件二、显示命令三、分页显示四、显示文件前后内容五、压缩、解压缩六、补充 一、七类常见的Linux的文件 字符文件类型-普通文件&#xff0c;包括纯文本文件、二进制文件、各种压缩文件等。在find命令中&#xff0c;type 选项中用 f来表示d目录…

git学习——tags、release、drop commit

最近一直都在持续学习git相关内容&#xff0c;越来越发现git是一个十分适合大型项目和团队协作进行开发的工具&#xff0c;掌握好了对于我们参与项目维护和开发产品帮助很大&#xff0c;所以要不断持续学习git。 tags & releases tag的创建 当我们在git版本控制中遇到了…

Docker搭建LNMP环境实战(09):安装mariadb

1、编写mariadb部署配置文件 在文件夹&#xff1a;/mnt/hgfs/dockers/test_site/compose下创建文件&#xff1a;test_site_mariadb.yml&#xff0c;内容如下&#xff1a; version: "3.5" services:test_site_mariadb:container_name: test_site_mariadbimage: mari…

【Go】四、包名、访问范围控制、标识符、运算符

文章目录 1、_2、包名3、命名大小影响可访问范围4、运算符5、获取终端输入 1、_ 下划线"_"本身在Go中是一个特殊的标识符&#xff0c;称为空标识符用于忽略某个值 1&#xff09;忽略导入的没使用的包 2&#xff09;忽略某个返回值 2、包名 main包是程序的入口包&a…

【MATLAB第103期】#源码分享 | 基于MATLAB的LIME可解释性线性分类预测模型,2020b以上版本

【MATLAB第103期】#源码分享 | 基于MATLAB的LIME可解释性线性分类预测模型&#xff0c;2020b以上版本 一、模型介绍 LIME&#xff08;Local Interpretable Model-agnostic Explanations&#xff09;是一种用于解释复杂机器学习模型预测结果的算法。它由Marco Ribeiro、Sameer…