Spring--手写一个简易的IoC容器,附思路原理

依赖注入 DI(Dependecy Injection)

一个Java对象依赖别的对象,一种依赖关系,如service依赖dao,只声明对象不赋值均为null,通过注入自动装配对象实例

控制反转 IoC(Inversion of Control)

不需要控制所有依赖和装配的进行,有容器会自动进行装配

下面的案例,简单的实现IoC容器,可帮助理解依赖注入和控制反转

(在这个demo中是基于maven搭建的,使用了springframework的Autowired,但不重要,自己生成一个注解替换即可)

简单的讲讲实现的流程

  1. resource目录下新建一个properties配置文件,用来配置需要自动装配的bean服务和对应限定全类名,它可能长这样:

2. 在需要自动装配的对象使用对应的注解, 如在UserService类中依赖了UserDao对象,添加一个@Autowired注解(是不是Autowired不重要,注解可自行替换)

public class UserService {
    @Autowired 
    private UserDao userDao;

    public User getCurrentLoginUser() {
        return userDao.getUserById(1);
    }
}
复制代码
  1. (以下代码贴在最后)启动容器时,用new Properties("[你的配置文件路径]")读取配置文件的内容,获取到的是配置的服务名和其限定全类名
  2. 声明一个Map用于存储即将要通过反射生成的对象实例,遍历properties对象,通过Class.forName("全类名").getConstructor().newInstance()获取对象实例,再通过map.put(服务名,对象实例)存储
  3. 遍历Map,获取反射生成的对象的class对象,调用其getDeclaredFields()方法拿到全部字段,筛选出字段含有@Autowired注解的字段,并返回一个Field对象数组
  4. 遍历这个Field对象数组,拿到字段的名字,并将private的属性设置成可访问状态,调用field.setAccessible(true)后,调用field.set()传入字段对应的对象实例和要赋值的对象,要赋值的对象从Map.get(fieldName)获取
  5. 依赖注入的过程到此结束
  6. 可以通过暴漏一个Api叫getBean的方法,传入bean的名字,返回Map.get([name])来获取Bean实例

(需要通过约束属性名的方式进行注入,即属性名要和配置文件中的一致,否则注入时会找不到Map中对应的实例,属性也会为null)

核心代码

UserService中依赖UserDao,使用注解标识

public class UserService {
    @Autowired
    private UserDao userDao;

    public User getCurrentLoginUser() {
        // getUserById模拟的返回用户对象
        return userDao.getUserById(1);
    }
}
复制代码

UserDao

public class UserDao {
    public User getUserById(Integer id) {
        System.out.println("返回了一个用户");
        return new User(id, "user" + id);
    }
}
复制代码

容器代码

import org.springframework.beans.factory.annotation.Autowired;

import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.*;
import java.util.stream.Collectors;

public class MyIoCContainer {
    private Map<String, Object> beansCache = new HashMap<>();

    public static void main(String[] args) {
        // 创建一个容器并启动
        MyIoCContainer container = new MyIoCContainer();
        container.start();
        // 通过getBean获取实例,并调用其方法
        UserService userService = (UserService) container.getBean("userService");
        userService.getCurrentLoginUser();
    }

    // 启动该容器
    public void start() {
        Properties properties = new Properties();
        try {
            properties.load(MyIoCContainer.class.getResourceAsStream("/beans.properties"));
            properties.forEach((propertyName, propertyClassName) -> {
                try {
                    beansCache.put((String) propertyName, 
                            Class.forName((String) propertyClassName).getConstructor().newInstance());
                } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException | ClassNotFoundException e) {
                    e.printStackTrace();
                }
            });
        } catch (IOException e) {
            e.printStackTrace();
        }
        // 使用lambdab表达式简化代码
        beansCache.forEach((beanName, beanInstance) -> dependencyInjection(beanInstance, beansCache));
    }

    // 实现依赖注入
    private void dependencyInjection(Object beanInstance, Map<String, Object> beansCache) {
        // Stream结合lambda表达式
        // 得到所有带@Autowired注解的字段
        List<Field> fieldList = Arrays.stream(beanInstance.getClass().getDeclaredFields())
            .filter(field -> field.getAnnotation(Autowired.class) != null)
            .collect(Collectors.toList());
        
        // 自动装配对应的对象实例
        fieldList.forEach(field -> {
            String fieldName = field.getName();
            field.setAccessible(true);
            try {
                field.set(beanInstance, beansCache.get(fieldName));
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        });
    }

    // 从容器中获取一个bean
    public Object getBean(String beanName) {
        return beansCache.get(beanName);
    }
}
复制代码

UserService中,只声明了userDao属性而没赋值,通过打断点,可以观察到userDao对象成功注入了,如果有多个地方的userDao使用注解注入,则可在调试中观察到得到的是同一个对象实例

userDao的方法也正常执行,打印输出并会返回一个mock的User对象

Spring的核心实现会比这个复杂的多,但本质上都是通过配置-反射-自动装配的流程处理程序,并不是要手写出一个Spring框架,而是通过手写类似的模式来加深对Spring的理解。


链接:https://juejin.cn/post/7031775073384005645
 

全部评论

相关推荐

昨天 21:59
门头沟学院 Java
点赞 评论 收藏
分享
05-12 17:28
已编辑
门头沟学院 硬件开发
ldf李鑫:不说公司名祝你以后天天遇到这样的公司
点赞 评论 收藏
分享
求面试求offer啊啊啊啊:1600一个月?
点赞 评论 收藏
分享
关于我大学本科四年,想了很多,但还是不知道该怎么动笔&nbsp;“大学四年,是我从懵懂少年走向职场青年的转折期。这一路跌跌撞撞,有迷茫,有遗憾,也有成长和决心。”&nbsp;大一刚进来时仍然有高中那股学习劲,经常一个人去图书馆学高等数学,但后面劲头一过便开始在宿舍开启躺平生活(现在想想那段时间真的很爽,无忧无虑)。由于大一担任班干部,所以经常要跟其他班的班干部交流,在此期间认识了隔壁班的一位女生,短发而很可爱,因为很多团建还有比赛都是我们两班一起参加的,而且我和她都是负责人,所以交集很多,后面慢慢地彼此对产生了好感,所以在大一刚开学的2个月后,我们在一起了,彼此之前都是初恋。但当时我真的是太太太直男了,对感情的想...
真烦好烦真烦:骗哥们可以,别把你自己也骗到了就行。哥们被你骗了真无所谓的,打个哈哈就过了。但希望你打完这段话后擦一下眼角,别让眼泪掉在手机屏幕上了就行。你说的这些话,哥们信一下也是没什么的。还能让你有个心里安慰,但这种话说出来骗骗兄弟就差不多得了,哥们信你一下也不会少块肉,但是你别搞得自己也当真了就行。哥们被你骗一下是真无所谓的,兄弟笑笑也就过去了。真不是哥们想要破你防,你擦擦眼泪好好想想,除了兄弟谁还会信你这些话?
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务