通过手写代码,写出spring框架最核心的部分
之前在JavaWeb中的学习里一段初步手写IoC可供参考和跳转初步学习

实现过程

  1. 创建子模块()引入依赖
  2. 创建测试类和接口(service dao)
  3. 创建(手写)两个注解,实现IoC功能
  4. @Bean 创建对象
  5. @Di 属性注入
  6. 创建Bean容器接口 ApplicationContext ,定义方法,返回对象
  7. 实现Bean容器接口,实现类中功能
  8. 返回对象
  9. 根据包的扫描规则,加载bean

扫描规则:
比如包是com.atguigu
扫描com.atguigu这个包,和他的子包里面的所有类,扫描这个上面是否有@Bean注解,如果有,那就把这个类通过反射进行实例化


准备工作

简单来说就是通过手写代码的方式实现两个注解(对应第三步)
1.png
创建两个注解类,配置方式如下
2.png
创建bean容器接口并实现(第四步)
3.png

实现容器接口(第五步:实现第一个注解)

@Bean创建对象
我们创建一个集合,重写接口中的方法,通过getBean方法,在集合中获取,返回我们需要的对应类的对象。
在有参数的构造当中,我们需要编写对应的扫描规则。
首先我们需要获取对应包的绝对路径,同时添加一个简化路径的变量rootPath方便后面扫描,根据路径添加一个扫描包的方法 loadBean 随后我们具体实现扫描包的方法。

public class AnnotationApplicationContext implements ApplicationContext {

    //创建map集合,放bean实例对象
    private Map<Class, Object> beanFactory = new HashMap<>();

    //简化路径
    private static String rootPath;

    //此方法用于返回我们创建的对象
    @Override
    public Object getBean(Class clazz) {
        //根据key返回对象
        return beanFactory.get(clazz);
    }

    //设置包的扫描规则
    //当前包及其子包里面,哪个类有@Bean注解,把这个类通过反射进行实例化
    //创建有参数构造,传递包的路径basePackage,设置扫描规则
    //public AnnotationApplicationContext(String basePackage) throws IOException {
    public static void pathdemo1(String basePackage) {
        try {
            //设置包的扫描规则 com.atguigu.spring6
            //找到这个包所在的路径,找到绝对路径,看一下文件夹中是否有子文件夹和文件
            //1.把.替换成\
            String packagePath = basePackage.replaceAll("\\.", "\\\\");

            //2.获取包的绝对路径
            Enumeration<URL> urls = Thread.currentThread().getContextClassLoader().getResources(packagePath);
            while (urls.hasMoreElements()) {
                //遍历,得到里面的每一个具体值
                URL url = urls.nextElement();
                //转码
                String filePath = URLDecoder.decode(url.getFile(), "utf-8");

                //获取包前面的路径部分,用作字符串截取
                rootPath = filePath.substring(0, filePath.length() - packagePath.length());

                //根据路径进行包扫描过程
                loadBean(new File(filePath));

            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }


    }

    public static void main(String[] args) {
        pathdemo1("com.atguigu");

    }
}

@Bean创建对象

实现loadBean方法的大致思路如下

    //包扫描过程,实例化
    private static void loadBean(File file) {
        //1.判断当前文件是否文件夹

        //2.是文件夹,获取文件夹中里面的所有内容

        //3.判断文件夹里面内容为空,直接返回


        //4.如果文件夹里面不为空,遍历文件夹里面的所有内容

        //4.1遍历得到每部分内容file对象,继续判断,如果还是文件夹,递归


        //4.2如果遍历得到的file对象,不是一个文件夹,是文件
        
        //4.3得到包路径 + 类名称部分 --->字符串截取


        //4.4判断当前文件的类型是否是.class类型

        //是,反射

        //4.5如果是.class类型,把路径中的\替换成.  把.class去掉
        //com.atguigu.service.UserServiceImpl

        
        //4.6判断类上面是否有注解@Bean,如果有,进行实例化过程

        //4.7把对象实例化之后,放到Map集合中beanFactory去
        
        //4.7.1判断当前类有接口,让接口作为key
        
        //4.7.2判断当前类没有接口,让自己作为key

        

    }

实现包扫描过程,用反射进行实例化,按照步骤完成。

    //包扫描过程,实例化
    private 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 child : childrenFiles) {
                //4.1遍历得到每部分内容file对象,继续判断,如果还是文件夹,递归
                if (child.isDirectory()) {
                    //递归的方式
                    loadBean(child);
                } else {
                    //4.2如果遍历得到的file对象,不是一个文件夹,是文件
                    //4.3得到包路径 + 类名称部分 --->字符串截取

                    String pathWithClass = child.getAbsolutePath().substring(rootPath.length() - 1);

                    //4.4判断当前文件的类型是否是.class类型
                    if (pathWithClass.contains(".class")) {

                        //4.5如果是.class类型,把路径中的\替换成.  把.class去掉
                        //com.atguigu.service.UserServiceImpl
                        String allName = pathWithClass.replaceAll("\\\\", ".")
                                .replace(".class", "");

                        //4.6判断类上面是否有注解@Bean,如果有,进行实例化过程
                        //4.6.1得到了类的全路径,获取类的class对象
                        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.getDeclaredConstructor().newInstance();
                                //4.7把对象实例化之后,放到Map集合中beanFactory去
                                //4.7.1判断当前类有接口,让接口class作为key
                                if (clazz.getInterfaces().length > 0) {
                                    beanFactory.put(clazz.getInterfaces()[0], instance);

                                } else {
                                    //4.7.2判断当前类没有接口,让自己class作为key
                                    beanFactory.put(clazz, instance);
                                }


                            }
                        }
                    }
                }
            }
        }
    }

最终源码,实现@Bean创建对象 + 扫描

package com.atguigu.bean;

import com.atguigu.anno.Bean;

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

public class AnnotationApplicationContext implements ApplicationContext {

    //创建map集合,放bean实例对象

    private Map<Class, Object> beanFactory = new HashMap<>();

    //简化路径
    private static String rootPath;


    //此方法用于返回我们创建的对象
    @Override
    public Object getBean(Class clazz) {
        //根据key返回对象
        return beanFactory.get(clazz);
    }

    //设置包的扫描规则
    //当前包及其子包里面,哪个类有@Bean注解,把这个类通过反射进行实例化
    //创建有参数构造,传递包的路径basePackage,设置扫描规则
    public AnnotationApplicationContext(String basePackage) {
        try {
            //设置包的扫描规则 com.atguigu.spring6
            //找到这个包所在的路径,找到绝对路径,看一下文件夹中是否有子文件夹和文件
            //1.把.替换成\
            String packagePath = basePackage.replaceAll("\\.", "\\\\");

            //2.获取包的绝对路径
            Enumeration<URL> urls = Thread.currentThread().getContextClassLoader().getResources(packagePath);
            while (urls.hasMoreElements()) {
                //遍历,得到里面的每一个具体值
                URL url = urls.nextElement();
                //转码
                String filePath = URLDecoder.decode(url.getFile(), "utf-8");

                //获取包前面的路径部分,用作字符串截取

                rootPath = filePath.substring(0, filePath.length() - packagePath.length());


                //根据路径进行包扫描过程
                loadBean(new File(filePath));

            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }


    }


    //包扫描过程,实例化
    private 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 child : childrenFiles) {
                //4.1遍历得到每部分内容file对象,继续判断,如果还是文件夹,递归
                if (child.isDirectory()) {
                    //递归的方式
                    loadBean(child);
                } else {
                    //4.2如果遍历得到的file对象,不是一个文件夹,是文件
                    //4.3得到包路径 + 类名称部分 --->字符串截取

                    String pathWithClass = child.getAbsolutePath().substring(rootPath.length() - 1);

                    //4.4判断当前文件的类型是否是.class类型
                    if (pathWithClass.contains(".class")) {

                        //4.5如果是.class类型,把路径中的\替换成.  把.class去掉
                        //com.atguigu.service.UserServiceImpl
                        String allName = pathWithClass.replaceAll("\\\\", ".")
                                .replace(".class", "");

                        //4.6判断类上面是否有注解@Bean,如果有,进行实例化过程
                        //4.6.1得到了类的全路径,获取类的class对象
                        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.getDeclaredConstructor().newInstance();
                                //4.7把对象实例化之后,放到Map集合中beanFactory去
                                //4.7.1判断当前类有接口,让接口class作为key
                                if (clazz.getInterfaces().length > 0) {
                                    beanFactory.put(clazz.getInterfaces()[0], instance);

                                } else {
                                    //4.7.2判断当前类没有接口,让自己class作为key
                                    beanFactory.put(clazz, instance);
                                }


                            }
                        }
                    }
                }
            }
        }
    }
    
}

浅浅的测试一下。
4.png


@Bi属性注入

进行@Di注解的注入属性实现,添加一个方法loadDi()

    //进行属性的注入
    private void loadDi() {
        //实例化对象在beanFactory的map集合中里面
        //1.遍历beanFactory的map集合


        //2.获取map集合的每个对象(value),每个对象中的属性获取到


        //3.遍历得到的每个对象属性的数组,得到里面的每个属性


        //4.判断属性上面是否有@Di注解
        //如果私有属性,设置可以设置值


        //5.如果有@Di注解,把对象进行设置(注入)



    }

根据注解完成对应的核心实现。

    //进行属性的注入
    private void loadDi() {
        //实例化对象在beanFactory的map集合中里面
        //1.遍历beanFactory的map集合

        Set<Map.Entry<Class, Object>> entries = beanFactory.entrySet();
        for (Map.Entry<Class, Object> entry : entries) {
            //2.获取map集合的每个对象(value),每个对象中的属性获取到
            Object obj = entry.getValue();

            //获取对象的class
            Class<?> clazz = obj.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(obj,beanFactory.get(field.getType()));
                        //表示向obj设置值
                        //field.getType()获得变量的类型,通过map容器找到该类的一个实例对象注入。
                    } catch (IllegalAccessException e) {
                        throw new RuntimeException(e);
                    }


                }

            }

        }
        

    }

测试一下
5.png


最终IoC核心源码

package com.atguigu.bean;

import com.atguigu.anno.Bean;
import com.atguigu.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;
import java.util.Set;

public class AnnotationApplicationContext implements ApplicationContext {

    //创建map集合,放bean实例对象

    private Map<Class, Object> beanFactory = new HashMap<>();

    //简化路径
    private static String rootPath;


    //此方法用于返回我们创建的对象
    @Override
    public Object getBean(Class clazz) {
        //根据key返回对象
        return beanFactory.get(clazz);
    }

    //设置包的扫描规则
    //当前包及其子包里面,哪个类有@Bean注解,把这个类通过反射进行实例化
    //创建有参数构造,传递包的路径basePackage,设置扫描规则
    public AnnotationApplicationContext(String basePackage) {
        try {
            //设置包的扫描规则 com.atguigu.spring6
            //找到这个包所在的路径,找到绝对路径,看一下文件夹中是否有子文件夹和文件
            //1.把.替换成\
            String packagePath = basePackage.replaceAll("\\.", "\\\\");

            //2.获取包的绝对路径
            Enumeration<URL> urls = Thread.currentThread().getContextClassLoader().getResources(packagePath);
            while (urls.hasMoreElements()) {
                //遍历,得到里面的每一个具体值
                URL url = urls.nextElement();
                //转码
                String filePath = URLDecoder.decode(url.getFile(), "utf-8");

                //获取包前面的路径部分,用作字符串截取

                rootPath = filePath.substring(0, filePath.length() - packagePath.length());


                //根据路径进行包扫描过程
                loadBean(new File(filePath));

            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        //完成属性的注入
        loadDi();


    }


    //包扫描过程,实例化
    private 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 child : childrenFiles) {
                //4.1遍历得到每部分内容file对象,继续判断,如果还是文件夹,递归
                if (child.isDirectory()) {
                    //递归的方式
                    loadBean(child);
                } else {
                    //4.2如果遍历得到的file对象,不是一个文件夹,是文件
                    //4.3得到包路径 + 类名称部分 --->字符串截取

                    String pathWithClass = child.getAbsolutePath().substring(rootPath.length() - 1);

                    //4.4判断当前文件的类型是否是.class类型
                    if (pathWithClass.contains(".class")) {

                        //4.5如果是.class类型,把路径中的\替换成.  把.class去掉
                        //com.atguigu.service.UserServiceImpl
                        String allName = pathWithClass.replaceAll("\\\\", ".")
                                .replace(".class", "");

                        //4.6判断类上面是否有注解@Bean,如果有,进行实例化过程
                        //4.6.1得到了类的全路径,获取类的class对象
                        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.getDeclaredConstructor().newInstance();
                                //4.7把对象实例化之后,放到Map集合中beanFactory去
                                //4.7.1判断当前类有接口,让接口class作为key
                                if (clazz.getInterfaces().length > 0) {
                                    beanFactory.put(clazz.getInterfaces()[0], instance);

                                } else {
                                    //4.7.2判断当前类没有接口,让自己class作为key
                                    beanFactory.put(clazz, instance);
                                }


                            }
                        }
                    }
                }
            }
        }
    }

    //进行属性的注入
    private void loadDi() {
        //实例化对象在beanFactory的map集合中里面
        //1.遍历beanFactory的map集合

        Set<Map.Entry<Class, Object>> entries = beanFactory.entrySet();
        for (Map.Entry<Class, Object> entry : entries) {
            //2.获取map集合的每个对象(value),每个对象中的属性获取到
            Object obj = entry.getValue();

            //获取对象的class
            Class<?> clazz = obj.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(obj,beanFactory.get(field.getType()));
                        //表示向obj设置值
                        //field.getType()获得变量的类型,通过map容器找到该类的一个实例对象注入。
                    } catch (IllegalAccessException e) {
                        throw new RuntimeException(e);
                    }


                }

            }

        }
        
    }
    

}

我书写这篇文章的初衷就是总结学习的进度,遗忘之际可以拿出来翻看,如有不对的地方还望指正,多多海涵。