理解Spring原理 - 手写IOC和DI

2023-09-21 09:28:52

本文完整资源包,懒得一步步操作的同学可以移步下载:
CSDN资源-手写IOC和DI完整代码下载

回顾Java反射

我们都知道,Spring框架的IOC是基于Java的反射机制实现的,下面我们先回顾一下Java反射:
Java反射机制是在运行状态中,对于任意类,都能够知道这个类的属性和方法;对于任何一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象的方式称为Java语言的反射机制。简单来说,反射机制指的是程序在运行时能够获取自身的信息。
想要解剖一个类,必须先要获取到该类的Class对象。而剖析一个类或用反射解决具体的问题就是使用相关应用程序接口(1)java.lang.Class(2)java.lang.reflect,所以,Class对象是反射的根源。

自定义User类:

User类中包括三个私有属性、set get方法、一个无参构造方法、一个有参构造方法和一个私有普通方法run

package user;
/**
 * @author Sean Zhang
 * @date 2023/9/20 23:37
 */
public class User {
    private int id;
    private String name;
    private int age;
    public User() {
    }
    public User(int id, String name, int age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }
    private void run() {
        System.out.println("私有方法run...");
    }
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
}

自定义UserTest类:

UserTest主要测试一下问题:

  1. 获取Class的多种方式
  2. 获取构造方法
  3. 获取属性
  4. 获取方法
package user;
import org.junit.jupiter.api.Test;
/**
 * @author Sean Zhang
 * @date 2023/9/20 23:45
 */
public class UserTest {
    // 1. 获取Class的多种方式
    @Test
    public void testClass() {
    }
    // 2. 获取构造方法
    @Test
    public void testConstructor() {
    }
    // 3. 获取属性
    @Test
    public void testProperty() {
    }
    // 4. 获取方法
    @Test
    public void testMethod() {
    }
}

测试1:获取Class的多种方式testClass

获取Class的三种方式

  1. 类名.class
  2. 对象.getClass()
  3. Class.forName("全路径")
public void testClass() throws ClassNotFoundException {
    // 1. `类名.class`
    Class<User> userClass1 = User.class;
    // 2. `对象.getClass()`
    Class<? extends User> userClass2 = new User().getClass();
    // 3. `Class.forName("全路径")`
    Class<?> userClass3 = Class.forName("user.User");
}

注:获取Class对象之后,对对象实例化:

User user = userClass1.getDeclaredConstructor().newInstance();

测试2:获取构造方法testConstructor

获取构造方法testConstructor步骤

  1. 获取Class
  2. 获取所有构造方法
 public void testConstructor() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        Class<User> userClass = User.class;
        Constructor<?>[] constructors = userClass.getDeclaredConstructors();
        for (Constructor<?> constructor : constructors) {
            System.out.println("方法名称:" + constructor.getName() + "参数个数:" + constructor.getParameterCount());
        }
        Constructor<?>[] publicConstructors = userClass.getConstructors();
        //构造创建对象
        Constructor<User> constructor = userClass.getDeclaredConstructor(int.class, String.class, int.class);
        //私有的要设置允许
        constructor.setAccessible(true);
        User user = constructor.newInstance(1, "张三", 18);
    }

注:仅仅获得public构造方法:

Constructor<?>[] publicConstructors = userClass.getConstructors();

测试3:获取属性testProperty

 public void testProperty() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        Class<User> userClass = User.class;
        User user = userClass.getDeclaredConstructor().newInstance();
        Field[] declaredFields = userClass.getDeclaredFields();
        for (Field field : declaredFields) {
            if (field.getName().equals("name")) {
                //私有的属性要设置允许
                field.setAccessible(true);
                field.set(user, "李四");
            }
            System.out.println(field.getName());
        }
    }

注:私有的属性要设置允许访问:

field.setAccessible(true);

测试4:获取方法testMethod

  public void testMethod() throws InvocationTargetException, IllegalAccessException {
        User user = new User(2, "王五", 20);
        Class<? extends User> clazz = user.getClass();
        Method[] methods = clazz.getDeclaredMethods();
        for (Method method : methods) {
            if (method.getName().equals("run")) {
                method.setAccessible(true);
                Object invoke = method.invoke(user);
                System.out.println("run执行了" + invoke);
            }
        }
    }

注:私有的方法要设置允许访问:

method.setAccessible(true);

实现Spring的IOC

我们知道,IOC和DI是Spring里面最核心的东西,,下面我们一步步写出这两个模块。

步骤:

  1. 创建测试类service和dao
  2. 手写两个注解:@Bean 创建对象 @DI 属性注入
  3. 创建Bean容器接口ApplicationContext定义方法,返回对象
  4. 实现bean容器接口
    a. 返回对象
    b. 根据包规则加载bean

步骤1:手写两个注解:@Bean 创建对象 @DI 属性注入:

package com.hand.anno;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Bean {
}
package com.hand.anno;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Di {
}

步骤2:创建测试类service和dao:

service和dao类上面标注注解@Bean,具体@Bean实现控制反转后面在ApplicationContext处理
service对dao的依赖要标注注解@DI,具体@DI实现依赖注入后面在ApplicationContext处理

dao:

package com.hand.dao;
public interface UserDao {
    void add();
}
package com.hand.dao.impl;
import com.hand.MyLog;
import com.hand.anno.Bean;
import com.hand.dao.UserDao;
@Bean
public class UserDaoImpl implements UserDao {
    @Override
    public void add() {
        MyLog.logger.info("dao add ...");
    }
}

service:

package com.hand.service;
public interface UserService {
    void add();
}
package com.hand.service.impl;
import com.hand.MyLog;
import com.hand.anno.Bean;
import com.hand.anno.Di;
import com.hand.dao.UserDao;
import com.hand.service.UserService;
@Bean
public class UserServiceImpl implements UserService {
    @Di
    private UserDao userDao;
    @Override
    public void add() {
        MyLog.logger.info("service add ...");
        userDao.add();
    }
}

步骤3:创建接口类ApplicationContext及其实现类AnnoApplicationContext:

主要是使用反射进行对象创建和依赖注入

package com.hand.bean;
public interface ApplicationContext {
    Object getBean(Class clazz);
}
package com.hand.bean;
import com.hand.MyLog;
import com.hand.anno.Bean;
import com.hand.anno.Di;
import java.io.File;
import java.lang.reflect.Field;
import java.net.URL;
import java.net.URLDecoder;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
public class AnnoApplicationContext implements ApplicationContext {
    private String rootPath = null;
    private Map<Class, Object> beanFactory = new HashMap<>();
    @Override
    public Object getBean(Class clazz) {
        return beanFactory.get(clazz);
    }
    public AnnoApplicationContext(String packageStr) {
        try {
            String packagePath = packageStr.replaceAll("\\.", "\\\\");
            Enumeration<URL> resources = Thread.currentThread().getContextClassLoader().getResources(packagePath);
            while (resources.hasMoreElements()) {
                URL url = resources.nextElement();
                String filePath = URLDecoder.decode(url.getFile(), "utf-8");
//                MyLog.logger.info(filePath);
                rootPath = filePath.substring(0, filePath.length() - packagePath.length());
                loadBean(new File(filePath));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        loadDi();
    }

    /**
     * 对象加入容器
     */
    private void loadBean(File file) throws Exception {
        if (file.isDirectory()) {
            File[] childFiles = file.listFiles();
            if (childFiles == null || childFiles.length == 0) {
                return;
            }
            for (File childFile : childFiles) {
                if (childFile.isDirectory()) {
                    loadBean(childFile);
                    continue;
                }
                String pathWhitClass = childFile.getAbsolutePath().substring(rootPath.length() - 1);
                if (!pathWhitClass.contains(".class")) {
                    continue;
                }
                String allName = pathWhitClass.replaceAll("\\\\", ".").replace(".class", "");
                MyLog.logger.info("类:" + allName);
                Class<?> clazz = Class.forName(allName);
                if (clazz.isInterface()) {
                    continue;
                }
                Bean bean = clazz.getAnnotation(Bean.class);
                if (bean != null) {
                    Object instance = clazz.getConstructor().newInstance();
                    if (clazz.getInterfaces().length > 0) {
                        beanFactory.put(clazz.getInterfaces()[0], instance);
                    } else {
                        beanFactory.put(clazz, instance);
                    }
                }
            }
        }
    }

    /**
     * 依赖注入
     */
    private void loadDi() {
        for (Object obj : beanFactory.values()) {
            Class<?> clazz = obj.getClass();
            Field[] declaredFields = clazz.getDeclaredFields();
            for (Field field : declaredFields) {
                Di di = field.getAnnotation(Di.class);
                if (di == null) {
                    continue;
                }
                field.setAccessible(true);
                try {
                    field.set(obj, beanFactory.get(field.getType()));
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

步骤4:测试:

package com.hand;
import com.hand.bean.AnnoApplicationContext;
import com.hand.service.UserService;
public class Ioc {
    public static void main(String[] args) {
        AnnoApplicationContext context = new AnnoApplicationContext("com.hand");
        UserService userService = (UserService) context.getBean(UserService.class);
        userService.add();
    }
}
更多推荐

【cmake开发(6)】Cmakelists 使用 gcc/g++指定版本,find_package 基本原理

我们在《【cmake开发(5)】cmake设置常规变量、环境变量、内置变量;cmake带参数编译和-D选项;c++源码通过-D选项的宏定义进行条件编译》中提到了变量。文章目录回顾一、指定编译器二、find_package基本原理2.1如果不使用find_package2.2如果使用find_package2.3find

Dubbo学习(二)——dubbo环境搭建

文章目录dubbo核心简介SpringCloud与Dubbo的区别Dubbo的架构说明dubbo和Feign远程调用的差异共同点:区别:基于SpringBoot开发微服务应用项目介绍1.启动注册中心2.新建一个springboot项目3.添加Maven依赖4.定义服务接口5.定义服务端的实现实现接口配置服务端Yaml配

搭建Android自动化python+appium环境

一.需要软件JDK:JAVA安装后配置JDK环境SDK:SDK下载后配置adb环境Python:pyhton语言Pycharm:python脚本编译工具Appium-python-client:pyhton中的库Appium客户端二.搭建步骤1.配置JDK环境①.下载安装java:https://www.oracle.

【vue2第十八章】VueRouter 路由嵌套 与 keep-alive缓存组件(activated,deactivated)

VueRouter路由嵌套在使用vue开发中,可能会碰到使用多层级别的路由。比如:其中就包含了两个主要页面,首页,详情,但是首页的下面又包含了列表,喜欢,收藏,我的四个子路由。此时就需要配置子路由通过使用children:importVuefrom'vue'importAppfrom'./App.vue'importM

【数据结构】单值二叉树 & 相同的树 & 翻转二叉树(五)

目录一,单值二叉树题目详情:解法:父子比较法解题思路:思路实现:源代码:二,相同的树题目详情:解法:比较法解题思路:思路实现:源代码:三,翻转二叉树解法:替换法解题思路:思路实现:源代码:一,单值二叉树题目详情:如果二叉树每个节点都具有相同的值,那么该二叉树就是单值二叉树;只有给定的树是单值二叉树时返回true;否则返

小白的入门二叉树(C语言实现)

前言:二叉树属于数据结构的一个重要组成部分,很多小白可能被其复杂的外表所吓退,但我要告诉你的是“世上无难事,只怕有心人”,我将认真的对待这篇博客,我相信只要大家敢于思考,肯定会有所收获的,当我们攀过一座山,回头看去,可能当初畏惧的大山也不过如此。目录前言:一,树的基本知识1树的概念2,树相关概念二,二叉树的基本知识1,

黄金代理如何选择平台?窍门在这儿

作为一个黄金代理平台,什么才是最重要的呢?笔者认为以下三个方面是最重要的,一个是资质,第二个是口碑,第三个是平台的软件。这三者是成为黄金代理要考虑的最重要的三个因素,也直接关系大黄金代理的职业生涯能不能持续,能不能实现可持续发展。首先来看第一个重要的因素,这个就是现货黄金平台,这是重中之重。现货黄金平台是黄金代理的重要

网站有反爬机制就爬不了数据?那是你不会【反】反爬

目录前言一、什么是代理IP二、使用代理IP反反爬1.获取代理IP2.设置代理IP3.验证代理IP4.设置代理池5.定时更新代理IP三、反反爬案例1.分析目标网站2.爬取目标网站四、总结前言爬虫技术的不断发展,使得许多网站都采取了反爬机制,以保护自己的数据和用户隐私。常见的反爬手段包括设置验证码、IP封锁、限制访问频率等

腾讯面试题:无网络环境,如何部署Docker镜像?

亲爱的小伙伴们,大家好!我是小米,很高兴再次和大家见面。今天,我要和大家聊聊一个特别有趣的话题——腾讯面试题:无网络环境,如何部署Docker镜像?这可是一个技术含量颇高的问题哦!废话不多说,让我们开始吧!什么是Docker在深入探讨如何在无网络环境下部署Docker镜像之前,我们首先来了解一下Docker是什么。Do

Harmony 开始支持 Flutter ,聊聊 Harmony 和 Flutter 之间的因果

相信大家都已经听说过,明年的HarmonyNext版本将正式剥离AOSP支持,基于这个话题我已经做过一期问题汇总,当时在现有App如何兼容HarmonyNext问题上提到过:华为内部也主导适配目前的主流跨平台方案,主动提供反向适配支持,估计后面就会有类似Flutterforharmony的社区支持。没想到HDC大会才刚

【笔记】ubuntu 20.04 + mongodb 4.4.14定时增量备份脚本

环境ubuntu20.04mongodb4.4.14还没实际使用(20230922)后续到10月底如果有问题会修改原理只会在有新增数据时生成新的备份日期目录备份恢复时,如果恢复的数据库未删除,则会覆盖数据准备准备一个文件夹,用于存放脚本增量备份脚本mongodb_incremental_backup.sh#!/bin/

热文推荐