前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >手写IOC

手写IOC

作者头像
一切总会归于平淡
发布2023-10-30 14:15:41
1490
发布2023-10-30 14:15:41
举报

本篇博客我们来手写一个IOC,就是模拟出IOC里边的实现过程。这过程怎么做呢?

咱们主要基于java中的反射,再加注解,来实现spring框架中IOC的这个效果。

下面我们来具体看看这个过程。首先因为这里边要用到反射,咱们把反射中的相关内容我们先做一个复习。复习之后最终让我们来手写spring IOC的这个功能。

1、回顾Java反射

java中的反射机制是什么呢?

它指的是对于任何一个类,我们都能够知道这个类里面的属性方法。

对于任何一个对象都能调它的任意方法和属性。

而这种动态获取信息以及动态调用对象方法的功能,就称为java的反射机制。

说的简单点,你要做反射,首先要得到类的卡的对象,就是咱们通俗说的字节码文件。通过字节码文件能够操作类中所有内容,包括你的属性,包括你的方法等等。这个是对于反射一个简单的概述。

自定义类

代码语言:javascript
复制
package com.jie.reflect.model;

/**
 * Car类
 * 用于反射测试
 *
 * @author 阿杰 2416338031@qq.com
 * @version 1.0
 * @date 2023/10/26 6:46
 */
public class Car {

    /**
     * 属性
     */
    private String name;
    private int age;
    private String color;

    /**
     * 无参数构造
     */
    public Car() {
    }

    /**
     * 有参数构造
     */
    public Car(String name, int age, String color) {
        this.name = name;
        this.age = age;
        this.color = color;
    }

    /**
     * 私有方法
     */
    private void run() {
        System.out.println("私有方法-run.....");
    }

    /**
     * get和set方法
     */
    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;
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

    @Override
    public String toString() {
        return "Car{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", color='" + color + '\'' +
                '}';
    }
}

这个是基本准备。下面咱们基于这个类来用一下反射中的相关内容。

1.1 获取Class对象的多种方式

第一个内容,获取Class对象的多种方式。

代码语言:javascript
复制
import com.jie.reflect.model.Car;
import org.junit.jupiter.api.Test;

/**
 * 测试反射类
 *
 * @author 阿杰 2416338031@qq.com
 * @version 1.0
 * @date 2023/10/26 6:51
 */
public class TestCar {

    //1、获取Class对象多种方式
    @Test
    public void test01() throws Exception {
        // 1.1、通过类名.class获取
        Class clazz1 = Car.class;
        
        // 1.2、通过对象.getClass()获取
        Class clazz2 = new Car().getClass();

        // 1.3、通过Class.forName("全类名")获取
        Class clazz3 = Class.forName("com.jie.reflect.model.Car");

        // 1.4、通过类加载器获取
        ClassLoader classLoader = TestCar.class.getClassLoader();
        Class clazz4 = classLoader.loadClass("com.jie.reflect.model.Car");

        //实例化
        Car car = (Car)clazz4.getConstructor().newInstance();
        System.out.println(car);
    }
}
image-20231026072644461
image-20231026072644461

1.2 获取构造方法

刚才咱们完成了第一个操作,获取class对象的多种方式演示,最终进行实例化。下面我们演示第二个内容,通过反射来获取构造方法。

代码语言:javascript
复制
 	// 2 、 获取构造方法
    @Test
    public void test02() throws Exception {
        // 2.1、获取所有的构造方法
        Class clazz = Class.forName("com.jie.reflect.model.Car");
        // getConstructors()获取所有的公有构造方法
        Constructor[] constructors = clazz.getConstructors();
        for (Constructor constructor : constructors) {
            System.out.println("方法名称:"+constructor.getName()+" 参数个数:"+constructor.getParameterCount());
        }
        System.out.println("====================================");
        // getDeclaredConstructors()获取所有的构造方法 包括私有的
        Constructor[] constructors2 = clazz.getDeclaredConstructors();
        for (Constructor constructor : constructors2) {
            System.out.println("方法名称:"+constructor.getName()+" 参数个数:"+constructor.getParameterCount());
        }

        // 2.2、获取指定有参数的构造方法构造对象
        // getConstructor 获取公有的构造方法
        Constructor constructor = clazz.getConstructor(String.class, int.class, String.class);
        Car car = (Car)constructor.newInstance("奔驰", 20, "黑色");
        System.out.println(car);

        // 2.3、获取私有的构造方法
        // getDeclaredConstructor 获取私有的构造方法
        Constructor c2 = clazz.getDeclaredConstructor(String.class, int.class, String.class);
        c2.setAccessible(true);
        Car car2 = (Car)c2.newInstance("捷达", 15, "白色");
        System.out.println(car2);
    }
image-20231026072711237
image-20231026072711237

1.3 获取属性

下面我们演示第三个获取属性

代码语言:javascript
复制
 // 3、获取属性
    @Test
    public void test03() throws Exception {
        // 3.1、获取所有的属性
        Class clazz = Class.forName("com.jie.reflect.model.Car");
        Car car = (Car) clazz.getDeclaredConstructor().newInstance();
        // getFields()获取所有的公有属性
        System.out.println("获取所有的公有属性");
        System.out.println(clazz.getFields().length);
        // 因为Car类没有公有属性,所以获取不到
//        System.out.println(clazz.getFields()[0].getName());
        System.out.println("====================================");
        // getDeclaredFields()获取所有的属性 包括私有的
        System.out.println("获取所有的属性");
        System.out.println(clazz.getDeclaredFields().length);
        Field[] declaredFields = clazz.getDeclaredFields();
        for (Field declaredField : declaredFields) {
            if (declaredField.getName().equals("name")) {
                // 私有属性需要设置访问权限
                declaredField.setAccessible(true);
                declaredField.set(car, "奔驰");
            }
            System.out.println(declaredField.getName());
            System.out.println(car);
        }

        // 3.2、获取指定的属性
        // getField 获取公有的属性
        // 因为Car类没有公有属性,所以获取不到
//        System.out.println("获取指定的公有属性");
//        System.out.println(clazz.getField("name"));
        System.out.println("====================================");
        // getDeclaredField 获取私有的属性
        System.out.println("获取指定的属性");
        System.out.println(clazz.getDeclaredField("color"));
    }
image-20231026072819638
image-20231026072819638

1.4 获取方法

然后再看第四个,就是如何来操作方法。

代码语言:javascript
复制
 // 4、获取方法
    @Test
    public void test04() throws Exception {
        // 4.1、获取所有的方法
        Class clazz = Class.forName("com.jie.reflect.model.Car");
        Car car = (Car) clazz.getDeclaredConstructor().newInstance();
        car.setName("奔驰");
        car.setAge(20);
        car.setColor("黑色");
        // getMethods()获取所有的公有方法
        System.out.println("获取所有的公有方法");
        System.out.println(clazz.getMethods().length);
        // 因为Car类没有公有方法,所以获取不到
        Method[] methods = clazz.getMethods();
        for (Method method : methods) {
            // 获取方法名称
//            System.out.println(method.getName());
            // 执行方法 toString
            if(method.getName().equals("toString")) {
                System.out.println(method.invoke(car));
            }
        }
        // 4.2 getDeclaredMethods()获取所有的方法 包括私有的
        System.out.println("获取所有的方法");
        System.out.println(clazz.getDeclaredMethods().length);
        Method[] methodsAll = clazz.getDeclaredMethods();
        for (Method m:methodsAll) {
            //执行方法 run
            if(m.getName().equals("run")) {
                m.setAccessible(true);
                m.invoke(car);
            }
        }
    }
image-20231026073423284
image-20231026073423284

2、实现Spring的IoC

我们知道,IoC(控制反转)和DI(依赖注入)是Spring里面核心的东西,那么,我们如何自己手写出这样的代码呢?下面我们就一步一步写出Spring框架最核心的部分。

2.1 搭建子模块

搭建模块:guigu-spring,搭建方式如其他spring子模块

image-20231027064621275
image-20231027064621275

2.2 准备测试需要的bean

创建UserDao接口

代码语言:javascript
复制
package com.jie.spring.dao;

/**
 * UserDao
 *
 * @author 阿杰 2416338031@qq.com
 * @date 2023/10/27 6:39
 * @version 1.0
*/
public interface UserDao {

    public void print();
}

创建UserDaoImpl实现

代码语言:javascript
复制
package com.jie.spring.dao.impl;


import com.jie.spring.dao.UserDao;

/**
 * UserDaoImpl
 *
 * @author 阿杰 2416338031@qq.com
 * @version 1.0
 * @date 2023/10/27 6:44
 */
public class UserDaoImpl implements UserDao {

    @Override
    public void print() {
        System.out.println("Dao层执行结束");
    }
}

创建UserService接口

代码语言:javascript
复制
package com.jie.spring.service;

/**
 * UserService
 *
 * @author 阿杰 2416338031@qq.com
 * @date 2023/10/27 6:45
 * @version 1.0
*/
public interface UserService {

    public void out();
}

创建UserServiceImpl实现类

代码语言:javascript
复制
package com.jie.spring.service.impl;


import com.jie.spring.service.UserService;

public class UserServiceImpl implements UserService {

    @Override
    public void out() {
        System.out.println("Service层执行结束");
    }
}

2.3 定义注解

我们通过注解的形式加载bean与实现依赖注入。

代码语言:javascript
复制
package com.jie.spring.anno;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * Bean 用于创建对象
 * 作用于类上
 * 运行时生效
 *
 * @author 阿杰 2416338031@qq.com
 * @version 1.0
 * @date 2023/10/27 6:51
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Bean {
}
代码语言:javascript
复制
package com.jie.spring.anno;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * Di 用于依赖注入
 * 作用于属性上
 * 运行时生效
 *
 * @author 阿杰 2416338031@qq.com
 * @version 1.0
 * @date 2023/10/27 6:52
 */
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Di {
}

说明:上面两个注解可以随意取名

然后我们就可以在我们的UserDaoImpl 使用我们定义的注解。

image-20231027065545362
image-20231027065545362

2.4定义bean容器接口

代码语言:javascript
复制
package com.jie.spring.bean;

/**
 *  ApplicationContext 用于获取bean
 *
 * @author 阿杰 2416338031@qq.com
 * @date 2023/10/27 7:03
 * @version 1.0
*/
public interface ApplicationContext {
    Object getBean(Class clazz);
}

2.5 编写注解bean容器接口实现

AnnotationApplicationContext基于注解扫描bean。

代码语言:javascript
复制
package com.jie.spring.bean;

import java.net.URL;
import java.net.URLDecoder;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;

/**
 * ApplicationContext 用于获取bean
 *
 * @author 阿杰 2416338031@qq.com
 * @version 1.0
 * @date 2023/10/27 7:06
 */
public class ApplicationContextImpl implements ApplicationContext {

    /**
     * 创建Map集合,放Bean对象
     */
    private Map<Class, Object> beanFactory = new HashMap<>();

    @Override
    public Object getBean(Class clazz) {
        return beanFactory.get(clazz);
    }

    /**
     * 创建有参构造器,传递包路径,设置包扫描规则
     * 扫描包路径下的所有类,判断类上是否有Bean注解,如果有,创建对象,放入Map集合
     *
     * @param basePackage 包路径
     */
    public ApplicationContextImpl(String basePackage) {
        
    }
}

2.6 编写扫描bean逻辑

我们通过构造方法传入包的base路径,扫描被@Bean注解的java对象,完整代码如下:

代码语言:javascript
复制
package com.jie.spring.bean;

import com.jie.spring.anno.Bean;

import java.io.File;
import java.net.URL;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;

/**
 * ApplicationContext 用于获取bean
 *
 * @author 阿杰 2416338031@qq.com
 * @version 1.0
 * @date 2023/10/27 7:06
 */
public class ApplicationContextImpl implements ApplicationContext {

    /**
     * 创建Map集合,放Bean对象
     */
    private static final Map<Class, Object> BEAN_FACTORY = new HashMap<>();

    private static String rootPath;

    @Override
    public Object getBean(Class clazz) {
        return BEAN_FACTORY.get(clazz);
    }

    /**
     * 创建有参构造器,传递包路径,设置包扫描规则
     * 扫描包路径下的所有类,判断类上是否有Bean注解,如果有,创建对象,放入Map集合
     *
     * @param basePackage 包路径
     */
    public ApplicationContextImpl(String basePackage) {
        try {
            // 包路径都是 com.jie.spring
            // 1 我们需要把 . 替换成 \
            String packagePath = basePackage.replaceAll("\\.", "\\\\");
            // 2 获取包的绝对路径
            Enumeration<URL> urls = Thread.currentThread().getContextClassLoader().getResources(packagePath);
            // 3 遍历包下的所有类
            while (urls.hasMoreElements()) {
                URL url = urls.nextElement();
                // 4 获取类的绝对路径
                String filePath = URLDecoder.decode(url.getPath(), StandardCharsets.UTF_8);
                // 5 获取包前面的路径部分,字符串截取
                rootPath = filePath.substring(0, filePath.length() - packagePath.length());
                // 6 调用方法,获取包下的所有类
                loadBean(new File(filePath));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    /**
     * 包扫描过程 递归
     *
     * @param file 文件
     * @return: void
     * @author 阿杰 2416338031@qq.com
     * @date: 2023/10/29 17:48
     */
    private static void loadBean(File file) throws Exception {
        // 1 判断是否是文件夹
        if (file.isDirectory()) {
            // 2 获取文件夹下的所有文件
            File[] childrenFiles = file.listFiles();

            // 3 判断文件夹里面为空,直接返回
            if (childrenFiles == null || childrenFiles.length == 0) {
                return;
            }

            // 4 如果文件夹里面有文件,遍历文件夹所有内容
            for (File childrenFile : childrenFiles) {
                // 4.1 遍历得到每个File对象,继续判断,如果还是文件夹,递归调用
                if (childrenFile.isDirectory()) {
                    // 递归调用
                    loadBean(childrenFile);
                } else {
                    // 4.2 遍历得到每个File对象不是文件夹,是文件
                    // 4.3 得到包路径+类名称部分(字符串截取过程)
                    String patgWithClass = childrenFile
                            .getAbsolutePath()
                            .substring(rootPath.length() - 1);
                    // 4.4 判断是否是class文件
                    if (patgWithClass.contains(".class")) {
                        // 4.5 如果是class文件,把路径\替换成.,把.class去掉,得到类的全限定名
                        String allName = patgWithClass
                                .replaceAll("\\\\", ".")
                                .replaceAll(".class", "");

                        // 4.6 判断类上是否有Bean注解,如果有,就进行实例化
                        // 4.6.1 获取类的字节码对象
                        Class<?> clazz = Class.forName(allName);
                        // 4.6.2 判断不是接口
                        if (!clazz.isInterface()) {
                            // 4.6.3 判断类上是否有Bean注解
                            Bean annotation = clazz.getAnnotation(Bean.class);
                            if (annotation != null) {
                                // 4.6.4 如果有,创建对象
                                Object instance = clazz.getConstructor().newInstance();
                                // 4.7 把类的全限定名和对象,放入Map集合
                                // 4.7.1 判断当前类如果实现了接口,把接口的class对象作为key
                                if (clazz.getInterfaces().length > 0) {
                                    BEAN_FACTORY.put(clazz.getInterfaces()[0], instance);
                                } else {
                                    // 4.7.2 如果没有实现接口,把当前类的class对象作为key
                                    BEAN_FACTORY.put(clazz, instance);
                                }
                            }
                        }
                    }
                }
            }
        }
    }


    public static void main(String[] args) {
        ApplicationContextImpl applicationContext = new ApplicationContextImpl("com.jie.spring");
    }
}

2.7 java类标识Bean注解

image-20231029184538323
image-20231029184538323
image-20231029184600585
image-20231029184600585

2.8 测试Bean加载

image-20231029184732111
image-20231029184732111

2.9 依赖注入实现

我们实现了Bean 的加载,现在来实现 依赖注入

image-20231029190235776
image-20231029190235776
代码语言:javascript
复制
/**
 * 属性注入
 *
 * @return: void
 * @author 阿杰 2416338031@qq.com
 * @date: 2023/10/29 18:50
 */
private void loadDi() {
    // 1 遍历BEAN_FACTORY Map集合
    Set<Map.Entry<Class, Object>> entries = BEAN_FACTORY.entrySet();
    for (Map.Entry<Class, Object> entry : entries) {
        // 2 获取map 集合每个对象(value),获取每个对象属性
        Object value = entry.getValue();
        // 获取class对象
        Class<?> clazz = value.getClass();
        // 获取每个对象属性
        Field[] declaredFields = clazz.getDeclaredFields();
        // 3 遍历得到每个对象属性数组,得到每个属性
        for (Field field : declaredFields) {
            // 4 判断属性上是否有Di注解,如果有,进行注入
            Di annotation = field.getAnnotation(Di.class);
            if (annotation != null) {
                // 如果有私有属性,需要设置属性可访问
                field.setAccessible(true);
                // 5 如果有 @Di 注解,把对象进行设置(注入)
                try {
                    field.set(value, BEAN_FACTORY.get(field.getType()));
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

2.10 测试依赖注入

image-20231029190353388
image-20231029190353388
image-20231029190339162
image-20231029190339162
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2023-10-29,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1、回顾Java反射
    • 1.1 获取Class对象的多种方式
      • 1.2 获取构造方法
        • 1.3 获取属性
          • 1.4 获取方法
          • 2、实现Spring的IoC
            • 2.1 搭建子模块
              • 2.2 准备测试需要的bean
                • 2.3 定义注解
                  • 2.4定义bean容器接口
                    • 2.5 编写注解bean容器接口实现
                      • 2.6 编写扫描bean逻辑
                        • 2.7 java类标识Bean注解
                          • 2.8 测试Bean加载
                            • 2.9 依赖注入实现
                              • 2.10 测试依赖注入
                              相关产品与服务
                              容器服务
                              腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
                              领券
                              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档