当前位置: 首页 > article >正文

Spring——原理:IoC

原理:手写IoC

Spring框架的IoC是基于java反射机制实现的,需要先回顾一下java反射。

回顾Java反射

Java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为Java语言的反射机制。简单来说,反射机制指的是程序在运行时能够获取自身的信息。

要想解剖一个类,必须先要获取到该类的Class对象。而剖析一个类或用反射解决具体的问题就是使用相关API

(1)java.lang.Class

(2)java.lang.reflect

所以,Class对象是反射的根源

自定义类

package com.ling.reflect;

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("私有方法————汽车在跑");
    }

    // 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 + '\'' +
                '}';
    }
}

编写测试类

package com.ling.reflect;

import org.junit.jupiter.api.Test;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class TestCar {
    // 1、获取Class对象多种方式
    @Test
    public void test01() throws Exception {
        // 1、类名.class
        Class c1 = Car.class;

        // 2、对象.getClass()
        Class c2 = new Car().getClass();

        // 3、Class.forName("全类名")
        Class c3 = Class.forName("com.ling.reflect.Car");

        // 实例化
        Car car = (Car) c3.getDeclaredConstructor().newInstance();
        System.out.println(car); // com.ling.reflect.Car@6b0c2d26
    }

    // 2、获取构造方法
    @Test
    public void test02() throws Exception {
        Class c = Car.class;
        // 获取所有的构造
        // getConstructors()获取所有的public修饰的构造方法
//        Constructor[] constructors = c.getConstructors();
        // getDeclaredConstructors()获取所有的构造方法
        Constructor[] constructors = c.getDeclaredConstructors();
        for (Constructor constructor : constructors) {
//            System.out.println(constructor);
            /*
            public com.ling.reflect.Car(java.lang.String,int,java.lang.String)
            public com.ling.reflect.Car()
             */
            System.out.println("方法名称:" + constructor.getName() + " 参数个数:" + constructor.getParameterCount());
            /*
            方法名称:com.ling.reflect.Car 参数个数:3
            方法名称:com.ling.reflect.Car 参数个数:0
             */
        }

        // 指定有参数构造创建对象
        // 构造方法是public修饰的
        Constructor constructor = c.getConstructor(String.class, int.class, String.class);
        Car car = (Car) constructor.newInstance("宝马", 100, "红色");
        System.out.println(car); // com.ling.reflect.Car@7bc1a03d

        // 构造方法是private修饰的
        Constructor constructor1 = c.getDeclaredConstructor(String.class, int.class, String.class);
        constructor1.setAccessible(true);
        Car car1 = (Car) constructor1.newInstance("奔驰", 200, "黑色");
        System.out.println(car1); // com.ling.reflect.Car@70b0b186
    }

    // 3、获取属性
    @Test
    public void test03() throws Exception {
        Class c = Car.class;
        Car car = (Car) c.getDeclaredConstructor().newInstance();
        // 获取所有public属性
        Field[] fields = c.getFields();
        for (Field field : fields) {
            System.out.println("(Field)属性名称:" + field.getName() + " 属性类型:" + field.getType());
        }
        // 获取所有属性
        Field[] declaredFields = c.getDeclaredFields();
        for (Field field : declaredFields) {
            if (field.getName().equals("name")) {
                field.setAccessible(true);
                field.set(car, "奔驰");
            }
            System.out.println("(DeclaredField)属性名称:" + field.getName() + " 属性类型:" + field.getType());
            System.out.println(car);
            /*
            (DeclaredField)属性名称:name 属性类型:class java.lang.String
            Car{name='奔驰', age=0, color='null'}
            (DeclaredField)属性名称:age 属性类型:int
            Car{name='奔驰', age=0, color='null'}
            (DeclaredField)属性名称:color 属性类型:class java.lang.String
            Car{name='奔驰', age=0, color='null'}
             */
        }
    }

    // 4、获取方法
    @Test
    public void test04() throws Exception {
        Car car = new Car("奔驰", 10, "黑色");
        Class c = car.getClass();
        // public方法
        Method[] methods = c.getMethods();
        for (Method method : methods) {
            System.out.println("(method)方法名称:" + method.getName() + " 方法返回值类型:" + method.getReturnType());
            if (method.getName().equals("toString")) {
                String invoke = (String) method.invoke(car);
                System.out.println(invoke); // Car{name='奔驰', age=10, color='黑色'}
            }
            /*
            (method)方法名称:getAge 方法返回值类型:int
            (method)方法名称:getColor 方法返回值类型:class java.lang.String
            (method)方法名称:setAge 方法返回值类型:void
            (method)方法名称:getName 方法返回值类型:class java.lang.String
            (method)方法名称:toString 方法返回值类型:class java.lang.String
            Car{name='奔驰', age=10, color='黑色'}
            (method)方法名称:setName 方法返回值类型:void
            (method)方法名称:setColor 方法返回值类型:void
            (method)方法名称:equals 方法返回值类型:boolean
            (method)方法名称:hashCode 方法返回值类型:int
            (method)方法名称:getClass 方法返回值类型:class java.lang.Class
            (method)方法名称:notify 方法返回值类型:void
            (method)方法名称:notifyAll 方法返回值类型:void
            (method)方法名称:wait 方法返回值类型:void
            (method)方法名称:wait 方法返回值类型:void
            (method)方法名称:wait 方法返回值类型:void
             */
        }

        // private方法
        Method[] declaredMethods = c.getDeclaredMethods();
        for (Method declaredMethod : declaredMethods) {
            System.out.println("(declaredMethod)方法名称:" + declaredMethod.getName() + " 方法返回值类型:" + declaredMethod.getReturnType());
            if (declaredMethod.getName().equals("run")) {
                declaredMethod.setAccessible(true);
                declaredMethod.invoke(car); // 私有方法————汽车在跑
            }
            /*
            (declaredMethod)方法名称:getAge 方法返回值类型:int
            (declaredMethod)方法名称:getColor 方法返回值类型:class java.lang.String
            (declaredMethod)方法名称:setAge 方法返回值类型:void
            (declaredMethod)方法名称:getName 方法返回值类型:class java.lang.String
            (declaredMethod)方法名称:run 方法返回值类型:void
            私有方法————汽车在跑
            (declaredMethod)方法名称:toString 方法返回值类型:class java.lang.String
            (declaredMethod)方法名称:setName 方法返回值类型:void
            (declaredMethod)方法名称:setColor 方法返回值类型:void
             */
        }
    }
}

实现Spring的IoC(重点)

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

①搭建子模块

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

②准备测试需要的bean

添加依赖
<!--junit-->
<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-api</artifactId>
    <version>5.8.1</version>
</dependency>
创建UserDao接口
package com.ling.dao;

public interface UserDao {
    void add();
}
创建UserDaoImpl实现
package com.ling.dao.impl;

import com.ling.annotation.Bean;
import com.ling.dao.UserDao;

@Bean
public class UserDaoImpl implements UserDao {
    @Override
    public void add() {
        System.out.println("UserDaoImpl add...");
    }
}
创建UserService接口
package com.ling.service;

public interface UserService {
    void add();
}
创建UserServiceImpl实现类
package com.ling.service.impl;

import com.ling.annotation.Bean;
import com.ling.annotation.Di;
import com.ling.dao.UserDao;
import com.ling.service.UserService;

@Bean
public class UserServiceImpl implements UserService {

    @Di
    private UserDao userDao;

    @Override
    public void add() {
        System.out.println("user service add...");
        // 调用dao的方法
        userDao.add();
    }
}

③定义注解

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

bean注解
package com.ling.annotation;

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.ling.annotation;

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 {
}

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

④定义bean容器接口

package com.ling.bean;

public interface ApplicationContext {

    Object getBean(Class clazz);
}

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

AnnotationApplicationContext基于注解扫描bean
package com.ling.bean.impl;

import java.util.HashMap;

public class AnnotationApplicationContext implements ApplicationContext {

    //存储bean的容器
    private HashMap<Class, Object> beanMap = new HashMap<>();

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

    /**
     * 根据包扫描加载bean
     * @param basePackage
     */
    public AnnotationApplicationContext(String basePackage) {
        
    }
}

⑥编写扫描bean逻辑

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

package com.ling.bean.impl;

import com.ling.annotation.Bean;
import com.ling.annotation.Di;
import com.ling.bean.ApplicationContext;

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;

/**
 * AnnotationApplicationContext 类实现了 ApplicationContext 接口,用于通过注解方式管理 Bean。
 */
public class AnnotationApplicationContext implements ApplicationContext {

    // 创建 Map 集合,用于存放 Bean 对象
    private Map<Class, Object> beanMap = new HashMap<>();
    private static String rootPath;

    /**
     * 根据包名初始化 AnnotationApplicationContext 实例,并扫描包及其子包中的类。
     *
     * @param packageName 包名
     * @throws Exception 如果扫描过程中发生异常
     */
    public AnnotationApplicationContext(String packageName) throws Exception {
        // 1. 将包名中的点(.)替换为反斜杠(\)
        String packagePath = packageName.replaceAll("\\.", "\\\\");

        // 2. 获取包的绝对路径
        Enumeration<URL> urls = Thread.currentThread().getContextClassLoader().getResources(packagePath);
//        Enumeration<URL> urls = this.getClass().getClassLoader().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());

            // 包扫描
            try {
                loadBean(new File(filePath));
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }

        // 属性注入
        loadDi();
    }

    // 进行属性的注入
    private void loadDi() throws IllegalAccessException {
        // 实例化对象在beanFactory的map集合里面
        // 1、遍历beanFactory的map集合
        Set<Map.Entry<Class, Object>> entries = beanMap.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 declaredField : declaredFields) {
                // 4、判断属性上面是否有@Di注解
                Di annotation = declaredField.getAnnotation(Di.class);
                if (annotation != null) {
                    // 如果私有属性,设置可以访问
                    declaredField.setAccessible(true);
                    // 5、如果有@Di注解,把对象进行设置(注入
                    declaredField.set(obj, beanMap.get(declaredField.getType()));
                }
            }


        }
    }

    /**
     * 包扫描过程,递归地实例化带有 @Bean 注解的类。
     *
     * @param file 当前文件或文件夹
     * @throws Exception 如果实例化过程中发生异常
     */
    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 childFile : childrenFiles) {
                // 4.1. 遍历得到每个 File 对象,继续判断,如果还是文件夹,递归
                if (childFile.isDirectory()) {
                    // 递归
                    loadBean(childFile);
                } else {
                    // 4.2. 遍历得到 File 对象不是文件夹,是文件
                    // 4.3. 得到包路径+类名称部分 ———— 字符串截取过程
                    String pathWithClass = childFile.getAbsolutePath().substring(rootPath.length() - 1);

                    // 4.4. 判断当前文件类型是否是.class
                    if (pathWithClass.endsWith(".class")) {
                        // 4.5. 如果是.class类型,把路径\替换成.  把.class去掉
                        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 = (Bean) clazz.getAnnotation(Bean.class);
                            if (annotation != null) {
                                // 4.6.4. 进行实例化
                                Object instance = clazz.getDeclaredConstructor().newInstance();
                                // 4.7. 把对象实例化之后,放到 map 集合当中
                                // 4.7.1. 判断当前类如果有接口,让接口 class 作为 map 的 key
                                if (clazz.getInterfaces().length > 0) {
                                    beanMap.put(clazz.getInterfaces()[0], instance);
                                } else {
                                    beanMap.put(clazz, instance);
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    /**
     * 根据类类型获取对应的 Bean 实例。
     *
     * @param clazz 类类型
     * @return 对应的 Bean 实例
     */
    @Override
    public Object getBean(Class clazz) {
        return beanMap.get(clazz);
    }
}

⑦java类标识Bean注解

@Bean
public class UserServiceImpl implements UserService
@Bean
public class UserDaoImpl implements UserDao 

⑧测试Bean加载

package com.ling;

import com.ling.bean.ApplicationContext;
import com.ling.bean.impl.AnnotationApplicationContext;
import com.ling.service.UserService;

public class Test {
    public static void main(String[] args) throws Exception {
        ApplicationContext context = new AnnotationApplicationContext("com.ling");
        UserService userService = (UserService) context.getBean(UserService.class);
        System.out.println(userService);
        userService.add();
    }
}

⑨依赖注入

package com.ling.service.impl;

import com.ling.annotation.Bean;
import com.ling.annotation.Di;
import com.ling.dao.UserDao;
import com.ling.service.UserService;

@Bean
public class UserServiceImpl implements UserService {

    @Di
    private UserDao userDao;

    @Override
    public void add() {
        System.out.println("user service add...");
        // 调用dao的方法
        userDao.add();
    }
}

⑩依赖注入实现

package com.ling.bean.impl;

import com.ling.annotation.Bean;
import com.ling.annotation.Di;
import com.ling.bean.ApplicationContext;

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;

/**
 * AnnotationApplicationContext 类实现了 ApplicationContext 接口,用于通过注解方式管理 Bean。
 */
public class AnnotationApplicationContext implements ApplicationContext {

    // 创建 Map 集合,用于存放 Bean 对象
    private Map<Class, Object> beanMap = new HashMap<>();
    private static String rootPath;

    /**
     * 根据包名初始化 AnnotationApplicationContext 实例,并扫描包及其子包中的类。
     *
     * @param packageName 包名
     * @throws Exception 如果扫描过程中发生异常
     */
    public AnnotationApplicationContext(String packageName) throws Exception {
        // 1. 将包名中的点(.)替换为反斜杠(\)
        String packagePath = packageName.replaceAll("\\.", "\\\\");

        // 2. 获取包的绝对路径
        Enumeration<URL> urls = Thread.currentThread().getContextClassLoader().getResources(packagePath);
//        Enumeration<URL> urls = this.getClass().getClassLoader().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());

            // 包扫描
            try {
                loadBean(new File(filePath));
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }

        // 属性注入
        loadDi();
    }

    // 进行属性的注入
    private void loadDi() throws IllegalAccessException {
        // 实例化对象在beanFactory的map集合里面
        // 1、遍历beanFactory的map集合
        Set<Map.Entry<Class, Object>> entries = beanMap.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 declaredField : declaredFields) {
                // 4、判断属性上面是否有@Di注解
                Di annotation = declaredField.getAnnotation(Di.class);
                if (annotation != null) {
                    // 如果私有属性,设置可以访问
                    declaredField.setAccessible(true);
                    // 5、如果有@Di注解,把对象进行设置(注入
                    declaredField.set(obj, beanMap.get(declaredField.getType()));
                }
            }


        }
    }

    /**
     * 包扫描过程,递归地实例化带有 @Bean 注解的类。
     *
     * @param file 当前文件或文件夹
     * @throws Exception 如果实例化过程中发生异常
     */
    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 childFile : childrenFiles) {
                // 4.1. 遍历得到每个 File 对象,继续判断,如果还是文件夹,递归
                if (childFile.isDirectory()) {
                    // 递归
                    loadBean(childFile);
                } else {
                    // 4.2. 遍历得到 File 对象不是文件夹,是文件
                    // 4.3. 得到包路径+类名称部分 ———— 字符串截取过程
                    String pathWithClass = childFile.getAbsolutePath().substring(rootPath.length() - 1);

                    // 4.4. 判断当前文件类型是否是.class
                    if (pathWithClass.endsWith(".class")) {
                        // 4.5. 如果是.class类型,把路径\替换成.  把.class去掉
                        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 = (Bean) clazz.getAnnotation(Bean.class);
                            if (annotation != null) {
                                // 4.6.4. 进行实例化
                                Object instance = clazz.getDeclaredConstructor().newInstance();
                                // 4.7. 把对象实例化之后,放到 map 集合当中
                                // 4.7.1. 判断当前类如果有接口,让接口 class 作为 map 的 key
                                if (clazz.getInterfaces().length > 0) {
                                    beanMap.put(clazz.getInterfaces()[0], instance);
                                } else {
                                    beanMap.put(clazz, instance);
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    /**
     * 根据类类型获取对应的 Bean 实例。
     *
     * @param clazz 类类型
     * @return 对应的 Bean 实例
     */
    @Override
    public Object getBean(Class clazz) {
        return beanMap.get(clazz);
    }
}

http://www.kler.cn/a/395120.html

相关文章:

  • Chromium 中chrome.webRequest扩展接口定义c++
  • nodejs利用子进程child_process执行命令及child.stdout输出数据
  • 大腾智能CAD:国产云原生三维设计新选择
  • golang自定义MarshalJSON、UnmarshalJSON 原理和技巧
  • MacPorts 中安装高/低版本软件方式,以 RabbitMQ 为例
  • 嵌入式单片机的运行方式详解
  • FileLink跨网文件安全摆渡系统——企业数据流转的安全桥梁
  • 软件工程笔记二—— 软件生存期模型
  • 服务器上安装Orcale数据库以及PL SQL工具(中文)
  • /// ts中的三斜线指令 | 前端
  • OpenJudge_ 简单英文题_04:0/1 Knapsack
  • 高级java每日一道面试题-2024年11月04日-Redis篇-Redis如何做内存优化?
  • Ubuntu 20.04 配置开发环境(持续更新)
  • MySQL中字段类型和Java对象中的数据类型对应关系
  • 【3D Slicer】的小白入门使用指南三
  • 31.校园志愿者管理系统(基于springboot和vue的Java项目)
  • 【网络安全 | 身份授权】一文讲清OAuth
  • 3. JVM 发展历程
  • 24.11.10 css
  • 初遇Python-----python/anaconda/PyCharm安装应用问题
  • 算法训练(leetcode)二刷第二十六天 | *452. 用最少数量的箭引爆气球、435. 无重叠区间、*763. 划分字母区间
  • Spring Boot与工程认证:计算机课程管理的新策略
  • 深入理解SQL中的INNER JOIN操作
  • Android 实现柱形图
  • 框架注解开发总结
  • Electron教程1-初学入门