手动实现 SpringBoot 中的 IoC(控制反转)

1. 什么是Spring Boot IOC

IOC(Inversion of Control)是指将控制反转,以前我们的程序里需要自己去 new 对象,通过依赖注入的方式,我们可以告诉Spring Boot框架需要哪些类,框架会根据您的要求自动将对象装配到相应位置,我们不需要关心对象的创建和依赖关系,让 Spring Boot 的控制流程来管理,这就是IOC的精髓。

2. 手动创建对象不行吗?

在一个程序中,不同的对象之间经常需要相互协作,多个对象之间相互依赖。如果我们在代码中手动创建这些对象,即使用 new 关键字进行实例化,那么在对象之间产生依赖关系时就需要我们手动进行对象的传递和管理,这样代码会变得非常繁琐,可读性和可维护性都会大大降低。

而IoC(Inverse of Control)控制反转是一种解决这类问题的设计模式,就是将对象实例的创建、依赖关系的管理和生命周期托管给 IoC 容器,由容器自动进行管理和注入。Spring Boot 的 IoC 容器会自动创建对象实例,并在需要的时候自动注入依赖关系。这样我们就可以避免手动创建对象,也不需要考虑对象之间的依赖关系,代码量可以大大减少,程序的可读性和可维护性也会更高。

简单来说,使用 Spring Boot 的 IoC 容器可以让我们专注于业务逻辑的实现,而无需关注对象的创建和依赖关系的管理,使得开发变得更加高效、简单和可维护。

3. 基于 Java 基本库实现 IoC

首先,我们定义一个 @Component 注解和 @Autowired 注解:

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Autowired {
}

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Component {
}

接着定义 IoC 容器类 SimpleIoc,用于维护对象映射关系和创建实例:

package com.example.demo;

import java.lang.reflect.Field;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

public class SimpleIoc {
    private final Map<Class<?>, Object> beanMap = new ConcurrentHashMap<>();

    public void scan(String packageName) throws Exception {
        Set<Class<?>> classes = ClassUtils.getClasses(packageName);
        for (Class<?> cls : classes) {
            if (cls.isAnnotationPresent(Component.class)) {
                register(cls);
            }
        }
    }

    public void register(Class<?> clazz) throws Exception {
        for (Class<?> intf : clazz.getInterfaces()) {
            beanMap.put(intf, createBean(clazz));
        }
    }

    private Object createBean(Class<?> clazz) throws Exception {
        // 如果已经注册了该类,直接返回
        Object bean = beanMap.get(clazz);
        if (bean != null) {
            return bean;
        }
        bean = clazz.newInstance();
        beanMap.put(clazz, bean);
        // 查找该类的带有 @Autowired 注解的属性,并注入依赖
        for (Field field : clazz.getDeclaredFields()) {
            if (field.isAnnotationPresent(Autowired.class)) {
                Class<?> fieldType = field.getType();
                Object dependency = beanMap.get(fieldType);
                if (dependency == null) {
                    dependency = createBean(fieldType);
                }
                field.setAccessible(true);
                field.set(bean, dependency);
            }
        }
        return bean;
    }

    public <T> T getBean(Class<?> clazz) {
        return (T)  beanMap.get(clazz);
    }
}

对于 scan 方法,我们使用 getClasses 方法从指定包中获取所有的 Class 对象,并对每一个带有 @Component 注解的类进行注册。当注册某个 Class 对象时,我们使用反射创建它的实例对象并将其放入 beanMap 中,并使用循环遍历其所有字段,如果有带有 @Autowired 注解的字段,就通过 getBean 方法获取它所要依赖的对象,并将其注入到当前对象中。getBean 方法根据传入的 Class 对象从 beanMap 中获取相应的实例,如果不存在就抛出异常。


接下来定义 UserRepository 接口和它的实现类 UserRepositoryImpl:

interface UserRepository {
    String getInfo();
}

@Component
class UserRepositoryImpl implements UserRepository {
    @Override
    public String getInfo() {
        return "Hello, World!";
    }
}

然后定义 UserService 接口和它的实现类 UserServiceImpl:

public interface UserService {
    String getInfo();
}

@Component
public class UserServiceImpl implements UserService {
    @Autowired
    private UserRepository userRepository;

    @Override
    public String getInfo() {
        return userRepository.getInfo();
    }
}

在上面的代码中,UserServiceImpl 类中使用了带有 @Autowired 注解的 userRepository 字段用于注入依赖,并实现了 getInfo 方法。


以下是简单实现的 ClassUtils 工具类代码,用于从指定包名中获取所有的 Class 对象

import java.io.File;
import java.net.URL;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set;

public class ClassUtils {
    public static Set<Class<?>> getClasses(String packageName) throws Exception {
        Set<Class<?>> classes = new HashSet<>();
        String path = packageName.replace(".", "/");
        Enumeration<URL> resources = Thread.currentThread().getContextClassLoader().getResources(path);
        while (resources.hasMoreElements()) {
            URL resource = resources.nextElement();
            if (resource != null) {
                String protocol = resource.getProtocol();
                if ("file".equals(protocol)) {
                    classes.addAll(findClassesByFile(new File(resource.toURI()), packageName));
                }
            }
        }
        return classes;
    }

    private static Set<Class<?>> findClassesByFile(File parent, String packageName) throws Exception {
        Set<Class<?>> classes = new HashSet<>();
        if (!parent.isDirectory()) {
            return classes;
        }
        File[] files = parent.listFiles(file -> file.isDirectory() || file.getName().endsWith(".class"));
        for (File file : files) {
            String fileName = file.getName();
            if (file.isDirectory()) {
                classes.addAll(findClassesByFile(file, packageName + "." + fileName));
            } else {
                String className = packageName + "." + fileName.substring(0, fileName.length() - 6);
                classes.add(Class.forName(className));
            }
        }
        return classes;
    }
}

最后,我们定义一个简单的测试类:

public class Main {
    public static void main(String[] args) throws Exception {
        SimpleIoc ioc = new SimpleIoc();
        ioc.scan("com.example.demo");

        UserService userService = ioc.getBean(UserService.class);
        System.out.println(userService.getInfo());
    }
}

4. 总结

  1. @Component 注解:该注解用于标记需要被 IoC 容器所管理的类,被标记的类会自动被扫描并加入到 IoC 容器中进行管理。

  2. @Autowired 注解:该注解用于标记需要进行依赖注入的属性,被标记的属性会自动被 IoC 容器所管理的对象所注入。

  3. SimpleIoc 类:该类是一个简单的 IoC 容器实现,主要实现了对象的创建和注入,并且能够自动扫描标记了 @Component 注解的类,并将其加入到容器中进行管理。

  4. UserRepository 接口及其实现类:模拟了一个 DAO 层的接口,实现了简单的业务逻辑。

  5. UserService 接口及其实现类:模拟了一个 Service 层的接口,依赖于 UserRepository 接口,并实现了相应的业务逻辑。

  6. Main 类:程序的入口类,使用 SimpleIoc 容器创建 UserService 对象并调用其方法。

此示例程序通过简单的实现了 IoC 的基本原理和使用方法,它可以帮助我们更好地理解 IoC 的概念和实现方式。在实际开发中,Spring Boot 的 IoC 容器使用更加自动化和方便,可扩展性也非常好。

PS:你以为IoC容器多么复杂,其实就是个Map。^_^

关注微信公众号:小虎哥的技术博客

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小虎哥的技术博客

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值