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

本文涉及的产品
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
简介: 面试官问我咋实现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:


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





最后


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

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

相关文章
|
2天前
|
数据可视化 数据挖掘 BI
团队管理者必读:高效看板类协同软件的功能解析
在现代职场中,团队协作的效率直接影响项目成败。看板类协同软件通过可视化界面,帮助团队清晰规划任务、追踪进度,提高协作效率。本文介绍看板类软件的优势,并推荐五款优质工具:板栗看板、Trello、Monday.com、ClickUp 和 Asana,助力团队实现高效管理。
19 2
|
15天前
|
存储 安全 数据安全/隐私保护
深入解析iOS 14隐私保护功能:用户数据安全的新里程碑
随着数字时代的到来,个人隐私保护成为全球关注的焦点。苹果公司在最新的iOS 14系统中引入了一系列创新的隐私保护功能,旨在为用户提供更透明的数据使用信息和更强的控制权。本文将深入探讨iOS 14中的几项关键隐私功能,包括App跟踪透明性、简化的隐私设置以及增强的系统安全性,分析它们如何共同作用以提升用户的隐私保护水平。
51 3
|
19天前
|
前端开发 Java 开发者
Spring MVC中的请求映射:@RequestMapping注解深度解析
在Spring MVC框架中,`@RequestMapping`注解是实现请求映射的关键,它将HTTP请求映射到相应的处理器方法上。本文将深入探讨`@RequestMapping`注解的工作原理、使用方法以及最佳实践,为开发者提供一份详尽的技术干货。
59 2
|
19天前
|
前端开发 Java Spring
探索Spring MVC:@Controller注解的全面解析
在Spring MVC框架中,`@Controller`注解是构建Web应用程序的基石之一。它不仅简化了控制器的定义,还提供了一种优雅的方式来处理HTTP请求。本文将全面解析`@Controller`注解,包括其定义、用法、以及在Spring MVC中的作用。
40 2
|
19天前
|
前端开发 Java 开发者
Spring MVC中的控制器:@Controller注解全解析
在Spring MVC框架中,`@Controller`注解是构建Web应用程序控制层的核心。它不仅简化了控制器的定义,还提供了灵活的请求映射和处理机制。本文将深入探讨`@Controller`注解的用法、特点以及在实际开发中的应用。
51 0
|
3月前
|
SQL 监控 druid
springboot-druid数据源的配置方式及配置后台监控-自定义和导入stater(推荐-简单方便使用)两种方式配置druid数据源
这篇文章介绍了如何在Spring Boot项目中配置和监控Druid数据源,包括自定义配置和使用Spring Boot Starter两种方法。
|
2月前
|
人工智能 自然语言处理 前端开发
SpringBoot + 通义千问 + 自定义React组件:支持EventStream数据解析的技术实践
【10月更文挑战第7天】在现代Web开发中,集成多种技术栈以实现复杂的功能需求已成为常态。本文将详细介绍如何使用SpringBoot作为后端框架,结合阿里巴巴的通义千问(一个强大的自然语言处理服务),并通过自定义React组件来支持服务器发送事件(SSE, Server-Sent Events)的EventStream数据解析。这一组合不仅能够实现高效的实时通信,还能利用AI技术提升用户体验。
219 2
|
1天前
|
NoSQL Java Redis
Spring Boot 自动配置机制:从原理到自定义
Spring Boot 的自动配置机制通过 `spring.factories` 文件和 `@EnableAutoConfiguration` 注解,根据类路径中的依赖和条件注解自动配置所需的 Bean,大大简化了开发过程。本文深入探讨了自动配置的原理、条件化配置、自定义自动配置以及实际应用案例,帮助开发者更好地理解和利用这一强大特性。
29 14
|
23天前
|
缓存 IDE Java
SpringBoot入门(7)- 配置热部署devtools工具
SpringBoot入门(7)- 配置热部署devtools工具
41 1
SpringBoot入门(7)- 配置热部署devtools工具
|
1月前
|
缓存 IDE Java
SpringBoot入门(7)- 配置热部署devtools工具
SpringBoot入门(7)- 配置热部署devtools工具
43 2
 SpringBoot入门(7)- 配置热部署devtools工具

推荐镜像

更多