面试官问我咋实现Spring框架IOC和DI好吧打趴下,深度解析手动实现Spring框架的IOC与DI功能2

简介: 面试官问我咋实现Spring框架IOC和DI好吧打趴下,深度解析手动实现Spring框架的IOC与DI功能2

定义相关属性扫描所有 Bean



修改 BeanContainer.java

/**
 * @author yby6
 * @program SpringPro
 * @date Created in 2023/10/08 008 17:24
 * @description
 **/
@Slf4j
@SuppressWarnings("unused")
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class BeanContainer {
    public static BeanContainer getInstance() {
        return ContainerHolder.HOLDER.instance;
    }
    /**
     * 容器枚举
     *
     * @author yby6
     * @date 2023/10/08
     */
    private enum ContainerHolder {
        /**
         * 持有人
         */
        HOLDER;
        /**
         * 实例
         */
        private final BeanContainer instance;
        /**
         * 容器
         */
        ContainerHolder() {
            instance = new BeanContainer();
        }
    }
    /**
     * bean注释
     */
    private final List<Class<? extends Annotation>> beanAnnotation =
            Arrays.asList(Component.class, Controller.class, Service.class, Repository.class);
    /**
     * bean映射
     */
    private final Map<Class<?>, Object> beanMap = new ConcurrentHashMap<>();
    /**
     * 容器实例的个数
     *
     * @return int
     */
    public int size() {
        return beanMap.size();
    }
    /**
     * 加载状态
     */
    private boolean loaded = false;
    /**
     * 容器是否被加载过
     *
     * @return boolean
     */
    public boolean isLoaded() {
        return loaded;
    }
    /**
     * 加载bean
     *
     * @param packageName 包名
     */
    public synchronized void loadBeans(String packageName) {
        // 判断容器是否被加载过
        if (isLoaded()) {
            log.warn("容器已经被加载!");
            return;
        }
        // 1.获取该包下所有的字节码
        Set<Class<?>> packageClass = ClassUtil.getPackageClass(packageName);
        if (null == packageClass || packageClass.isEmpty()) {
            log.warn("get null from packageName:{}", packageName);
            return;
        }
        // 2.遍历所有的字节码对象
        packageClass.forEach(clazz -> beanAnnotation.forEach(annotation -> {
            // 2.1.判断当前字节码上是否有指定的注解
            if (clazz.isAnnotationPresent(annotation)) {
                // 创建对象存放到集合当中
                beanMap.put(clazz, BeanContainer.newInstance(clazz, false));
            }
        }));
        loaded = true;
    }
    private static <T> T newInstance(Class<?> clazz, boolean accessible) {
        Constructor<?> declaredConstructor;
        try {
            declaredConstructor = clazz.getDeclaredConstructor();
            declaredConstructor.setAccessible(accessible);
            return (T) declaredConstructor.newInstance();
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("创建实例失败!");
        }
    }
}


改造一下我们之前的测试类代码移动目录位置,移动到 test 包当中如下,顺便改一下我们自定义的 IOC 与 DI 功能代码存放包的位置:


/**
 * @author yby6
 * @program SpringPro
 * @date Created in 2023/9/30 030 14:36
 * @description
 **/
public class MyTest {
    private static BeanContainer beanContainer;
    @BeforeAll
    public static void init() {
        beanContainer = BeanContainer.getInstance();
    }
    @Test
    @SuppressWarnings("all")
    void testClassUtil() {
        ClassUtil.getPackageClass("top.it6666").forEach(System.out::println);
    }
    @Test
    void testBeanContainer() {
        Assertions.assertFalse(beanContainer.isLoaded());
        beanContainer.loadBeans("top.it6666");
        Assertions.assertEquals(1, beanContainer.size());
        Assertions.assertTrue(beanContainer.isLoaded());
    }
}

创建一个 MyController.java 然后在该类上添加 @Controller 注解导包是我们自己定义的那个注解:


然后运行测试类即可查看结果:




容器相关操作


增加删除操作,可以通过 class 获取对应的实例。获取所有的 key 和所有的 value。通过注解来获取被注解标注的 class。通过传入接口,来获取接口实现类的 class 对象。


/**
 * 添加bean
 *
 * @param clazz clazz
 * @param obj   obj
 * @return {@link Object}
 */
public Object addBean(Class<?> clazz, Object obj) {
    return beanMap.put(clazz, obj);
}
/**
 * 删除bean
 *
 * @param clazz clazz
 * @return {@link Object}
 */
public Object removeBean(Class<?> clazz) {
    return beanMap.remove(clazz);
}
/**
 * 获取Bean
 *
 * @param clazz clazz
 * @return {@link Object}
 */
public Object getBean(Class<?> clazz) {
    return beanMap.get(clazz);
}
/**
 * 获取容器所有的classes
 *
 * @return {@link Set}<{@link Class}<{@link ?}>>
 */
public Set<Class<?>> getClasses() {
    return beanMap.keySet();
}
/**
 * 获取容器所有的Bean实例
 *
 * @return {@link Set}<{@link Object}>
 */
public Set<Object> getBeans() {
    return new HashSet<>(beanMap.values());
}


添加根据指定注解获取该注解标记的 Bean 的 Class 集合:

/**
 * 根据指定注解获取该注解标记的 Bean 的 Class 集合
 *
 * @param annotation 注解
 * @return {@link Set}<{@link Class}<{@link ?}>>
 */
public Set<Class<?>> getClassesByAnnotation(Class<? extends Annotation> annotation) {
    // 1.获取容器当中所有的class
    Set<Class<?>> classes = this.getClasses();
    if (null == classes || classes.isEmpty()) {
        log.info("没有任何信息!");
        return null;
    }
    Set<Class<?>> classSet = new HashSet<>();
    classes.forEach(clazz -> {
        if (clazz.isAnnotationPresent(annotation)) {
            // 保存到集合当中
            classSet.add(clazz);
        }
    });
    return classSet.size() > 0 ? classSet : null;
}


添加根据传入的接口,到容器当中找对应的实现类或子类:

/**
 * 根据传入的接口,到容器当中找对应的实现类或子类
 *
 * @param interfaceOrClass 接口或类
 * @return {@link Set}<{@link Class}<{@link ?}>>
 */
public Set<Class<?>> getClassBySuper(Class<?> interfaceOrClass) {
    // 1.获取容器当中所有的class
    Set<Class<?>> classes = getClasses();
    if (null == classes || classes.isEmpty()) {
        log.info("没有任何信息!");
        return null;
    }
    Set<Class<?>> classSet = new HashSet<>();
    classes.forEach(clazz -> {
        // 判断当前字节码是否为指定字节码的子类或接口并且指定的字节码不等于本身字节码
        if (interfaceOrClass.isAssignableFrom(clazz) && !Objects.equals(clazz, interfaceOrClass)) {
            classSet.add(clazz);
        }
    });
    return classSet.size() > 0 ? classSet : null;
}





依赖注入


定义相关的注解标签,实现创建被注解标记的成员变量实例,并将其注入到成员变量里,依赖注入使用:

创建 Autowired.java

/**
 * @author yby6
 * @program SpringPro
 * @date Created in 2023/10/25 025 9:01
 * @description
 **/
/*
及作用在成员变量上
* */
@Target(ElementType.FIELD)
/*
保留在运行时
* */
@Retention(RetentionPolicy.RUNTIME)
public @interface Autowired {
    String value() default "";
}


依赖注入实现思路:





依赖注入整体实现


DependencyInjector.java

/**
 * 依赖注入
 *
 * @author yby6
 * @date 2023/10/25
 */
@Slf4j
public class DependencyInjector {
    /**
     * Bean容器
     */
    private final BeanContainer beanContainer;
    /**
     * 依赖注入,构造器
     */
    public DependencyInjector() {
        beanContainer = BeanContainer.getInstance();
    }
    /**
     * 执行Ioc操作
     */
    public void doIoc() {
        // 1.遍历Bean容器中所有的Class对象
        beanContainer.getClasses().forEach(clazz -> {
            // 2.遍历Class对象的所有成员变量
            Field[] declaredFields = clazz.getDeclaredFields();
            if (declaredFields.length == 0) {
                // 当前没有成员变量,继续查找下一个
                return;
            }
            // 有成员变量
            Arrays.stream(declaredFields).forEach(field -> {
                // 3.找出被Autowired标记的成员变量
                if (field.isAnnotationPresent(Autowired.class)) {
                    // 获取注解实例
                    // 被 Autowired注解标注
                    // 4.获取这些成员变量的类型
                    Class<?> fieldType = field.getType();
                    // 5.获取成员变量的类型在容器里对应的实例
                    Object instanceObj = this.getFieldInstance(fieldType, field.getAnnotation(Autowired.class).value());
                    // 6.通过反射将对象的成员变量实例进行赋值操作
                    if (null == instanceObj) {
                        throw new RuntimeException("成员属性注入失败:" + fieldType.getName());
                    } else {
                        // 获取当前类实例
                        // 给当前类实例成员变量赋值
                        this.setField(field, beanContainer.getBean(clazz), instanceObj, true);
                    }
                }
            });
        });
    }
    /**
     * 根据Class在BeanContainer里获取其实例或者实现类
     *
     * @param fieldClass     成员变量类型
     * @param autowiredValue autowired注解的内容
     * @return {@link Object}
     */
    private Object getFieldInstance(Class<?> fieldClass, String autowiredValue) {
        Object fieldValue = beanContainer.getBean(fieldClass);
        // 字段是实现类,直接返回
        if (null != fieldValue) {
            return fieldValue;
        } else {
            // 字段是接口
            Class<?> implClass = this.getImplClass(fieldClass, autowiredValue);
            if (implClass != null) {
                return beanContainer.getBean(implClass);
            } else {
                return null;
            }
        }
    }
    /**
     * 得到impl类
     *
     * @param fieldClass     接口
     * @param autowiredValue autowired的价值
     * @return {@link Class}<{@link ?}>
     */
    private Class<?> getImplClass(Class<?> fieldClass, String autowiredValue) {
        //从容器当中获取该接口的实现类
        Set<Class<?>> classSet = beanContainer.getClassBySuper(fieldClass);
        if (null != classSet && !classSet.isEmpty()) {
            if (Objects.equals(autowiredValue, "")) {
                if (classSet.size() == 1) {
                    return classSet.iterator().next();
                } else {
                    // 如果有多个实现类
                    throw new RuntimeException("有多个实现类,请指定具体的实现类名称:" + fieldClass.getName());
                }
            } else {
                // 注解当中有设置值
                for (Class<?> clazz : classSet) {
                    if (autowiredValue.equals(clazz.getSimpleName())) {
                        return clazz;
                    }
                }
            }
        }
        return null;
    }
    /**
     * 设置字段
     *
     * @param field        字段
     * @param targetObject 目标对象
     * @param obj          obj
     * @param accessible   是否可访问
     */
    private void setField(Field field, Object targetObject, Object obj, boolean accessible) {
        field.setAccessible(accessible);
        try {
            field.set(targetObject, obj);
        } catch (IllegalAccessException e) {
            log.error("setField error:" + e);
            throw new RuntimeException(e);
        }
    }
}




测试依赖注入


创建 Service

IUserService.java

/**
 * @author yby6
 * @program SpringPro
 * @date Created in 2023/10/25 025 10:03
 * @description
 **/
public interface IUserService {
    /**
     * 显示
     */
    void show();
}


IUserServiceImpl.java

/**
 * @author yby6
 * @program SpringPro
 * @date Created in 2023/10/25 025 10:04
 * @description
 **/
@Service
public class IUserServiceImpl implements IUserService {
    @Override
    public void show() {
        System.out.println("my is IUserServiceImpl");
    }
}


修改 MyController.java

/**
 * @author yby6
 * @program SpringPro
 * @date Created in 2023/10/09 009 11:31
 * @description
 **/
@Controller
public class MyController {
    @Autowired
    private IUserService iUserService;
    public void testShow() {
        this.iUserService.show();
    }
}


修改 MyTest.java 当前 IUserService 只有一个实现类所以 @Autowired 是可以直接标记即可不用指定名称,在 MyTest 添加 testIoc 方法:

@Test
void testIoc() {
    beanContainer.loadBeans("top.it6666");
    MyController myController = (MyController) beanContainer.getBean(MyController.class);
    DependencyInjector dependencyInjector = new DependencyInjector();
    dependencyInjector.doIoc();
    myController.testShow();
}


运行结果如下:


如上是只有一个实现类,紧接着在来新建一个实现类,在 impl 包当中创建 ITaoBaoServiceImpl.java:

/**
 * @author yby6
 * @program SpringPro
 * @date Created in 2023/10/25 025 10:42
 * @description
 **/
@Service
public class ITaoBaoServiceImpl implements IUserService {
    @Override
    public void show() {
        System.out.println("my is ITaoBaoServiceImpl");
    }
}


再次运行测试类代码结果如下图:


修改 MyController.java:


再次运行测试类代码运行结果如下图所示:





最后


本期结束咱们下次再见👋~

🌊 关注我不迷路,如果本篇文章对你有所帮助,或者你有什么疑问,欢迎在评论区留言,我一般看到都会回复的。大家点赞支持一下哟~ 💗

相关文章
|
7月前
|
存储 缓存 NoSQL
Redis常见面试题全解析
Redis面试高频考点全解析:从过期删除、内存淘汰策略,到缓存雪崩、击穿、穿透及BigKey问题,深入原理与实战解决方案,助你轻松应对技术挑战,提升系统性能与稳定性。(238字)
|
9月前
|
存储 安全 测试技术
Python面试题精选及解析
本文详解Python面试中的六大道经典问题,涵盖列表与元组区别、深浅拷贝、`__new__`与`__init__`、GIL影响、协程原理及可变与不可变类型,助你提升逻辑思维与问题解决能力,全面备战Python技术面试。
522 1
|
7月前
|
监控 Java 关系型数据库
面试性能测试总被刷?学员真实遇到的高频问题全解析!
面试常被性能测试题难住?其实考的不是工具,而是分析思维。从脚本编写到瓶颈定位,企业更看重系统理解与实战能力。本文拆解高频面试题,揭示背后考察逻辑,并通过真实项目训练,帮你构建性能测试完整知识体系,实现从“会操作”到“能解决问题”的跨越。
|
7月前
|
XML Java 测试技术
《深入理解Spring》:IoC容器核心原理与实战
Spring IoC通过控制反转与依赖注入实现对象间的解耦,由容器统一管理Bean的生命周期与依赖关系。支持XML、注解和Java配置三种方式,结合作用域、条件化配置与循环依赖处理等机制,提升应用的可维护性与可测试性,是现代Java开发的核心基石。
|
7月前
|
监控
新功能上线:云解析DNS-重点域名监控功能发布
新功能上线:云解析DNS-重点域名监控功能发布
|
11月前
|
存储 安全 Java
2025 最新史上最全 Java 面试题独家整理带详细答案及解析
本文从Java基础、面向对象、多线程与并发等方面详细解析常见面试题及答案,并结合实际应用帮助理解。内容涵盖基本数据类型、自动装箱拆箱、String类区别,面向对象三大特性(封装、继承、多态),线程创建与安全问题解决方法,以及集合框架如ArrayList与LinkedList的对比和HashMap工作原理。适合准备面试或深入学习Java的开发者参考。附代码获取链接:[点此下载](https://pan.quark.cn/s/14fcf913bae6)。
5980 50
|
11月前
|
NoSQL Java 微服务
2025 年最新 Java 面试从基础到微服务实战指南全解析
《Java面试实战指南:高并发与微服务架构解析》 本文针对Java开发者提供2025版面试技术要点,涵盖高并发电商系统设计、微服务架构实现及性能优化方案。核心内容包括:1)基于Spring Cloud和云原生技术的系统架构设计;2)JWT认证、Seata分布式事务等核心模块代码实现;3)数据库查询优化与高并发处理方案,响应时间从500ms优化至80ms;4)微服务调用可靠性保障方案。文章通过实战案例展现Java最新技术栈(Java 17/Spring Boot 3.2)的应用.
918 9
|
11月前
|
缓存 NoSQL Java
Java Redis 面试题集锦 常见高频面试题目及解析
本文总结了Redis在Java中的核心面试题,包括数据类型操作、单线程高性能原理、键过期策略及分布式锁实现等关键内容。通过Jedis代码示例展示了String、List等数据类型的操作方法,讲解了惰性删除和定期删除相结合的过期策略,并提供了Spring Boot配置Redis过期时间的方案。文章还探讨了缓存穿透、雪崩等问题解决方案,以及基于Redis的分布式锁实现,帮助开发者全面掌握Redis在Java应用中的实践要点。
599 6
|
11月前
|
缓存 算法 NoSQL
校招 Java 面试高频常见知识点深度解析与实战案例详细分享
《2025校招Java面试核心指南》总结了Java技术栈的最新考点,涵盖基础语法、并发编程和云原生技术三大维度: 现代Java特性:重点解析Java 17密封类、Record类型及响应式Stream API,通过电商案例演示函数式数据处理 并发革命:对比传统线程池与Java 21虚拟线程,详解Reactor模式在秒杀系统中的应用及背压机制 云原生实践:提供Spring Boot容器化部署方案,分析Spring WebFlux响应式编程和Redis Cluster缓存策略。
317 1
|
11月前
|
XML 人工智能 Java
Spring IOC 到底是什么?
IOC(控制反转)是一种设计思想,主要用于解耦代码,简化依赖管理。其核心是将对象的创建和管理交给容器处理,而非由程序直接硬编码实现。通过IOC,开发者无需手动new对象,而是由框架负责实例化、装配和管理依赖对象。常见应用如Spring框架中的BeanFactory和ApplicationContext,它们实现了依赖注入和动态管理功能,提升了代码的灵活性与可维护性。
261 1

热门文章

最新文章

推荐镜像

更多
  • DNS