• 理解Spring原理 - 手写IOC和DI


    本文完整资源包,懒得一步步操作的同学可以移步下载:
    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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    自定义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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    测试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");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

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

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

    测试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);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

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

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

    测试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());
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

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

    field.setAccessible(true);
    
    • 1

    测试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);
                }
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

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

    method.setAccessible(true);
    
    • 1

    实现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 {
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    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 {
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

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

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

    dao:

    package com.hand.dao;
    public interface UserDao {
        void add();
    }
    
    • 1
    • 2
    • 3
    • 4
    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 ...");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    service:

    package com.hand.service;
    public interface UserService {
        void add();
    }
    
    • 1
    • 2
    • 3
    • 4
    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();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

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

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

    package com.hand.bean;
    public interface ApplicationContext {
        Object getBean(Class clazz);
    }
    
    • 1
    • 2
    • 3
    • 4
    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();
                    }
                }
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95

    步骤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();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
  • 相关阅读:
    暗流涌动的智能家居,和顺势已飞的三翼鸟
    synchronized原理剖析
    【数据结构初阶】七、非线性表里的二叉树(堆的实现 -- C语言顺序结构)
    判断一个矩阵是另一个矩阵的子矩阵C++
    不清楚用电脑怎么图片转文字?来看看这三个方法吧
    树莓派高级开发------总线地址、物理地址和虚拟地址的认识
    DS1339C串行实时时钟-国产兼容RS4C1339
    “高级小程序开发指南“
    怎么把视频转换成音频
    ubuntu基础操作(1)-个人笔记
  • 原文地址:https://blog.csdn.net/Ureliable/article/details/133107200