手写IOC

2023-10-30 14:15:41 浏览数 (2)

本篇博客我们来手写一个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);
    }
}

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);
    }

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"));
    }

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);
            }
        }
    }

2、实现Spring的IoC

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

2.1 搭建子模块

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

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 使用我们定义的注解。

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注解

2.8 测试Bean加载

2.9 依赖注入实现

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

代码语言: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 测试依赖注入

0 人点赞