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

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

前言


如果还不会 Spring源码编译 可去看看 Spring源码编译

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




工程搭建


使用 Maven 创建普通 Web 工程:

修改 pom.xml 添加依赖内容如下图:

<dependencies>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.11</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-log4j12</artifactId>
        <version>1.7.28</version>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.12</version>
    </dependency>
</dependencies>





配置 Tomcat


添加 Tomcat 的 jar 包:

点击上图中的 Add Selected 即可完成添加,添加完成了之后你在创建的时候就会出现 Servlet 选项如下图所示:

创建一个 Servlet 测试一下:

/**
 * 控制器
 *
 * @author yby6
 * @date 2023/09/29
 */
@WebServlet(name = "UserServlet", value = "/UserServlet")
public class UserServlet extends HttpServlet {
    @Override
    public void service(HttpServletRequest req, HttpServletResponse resp) {
        System.out.println("test Servlet");
    }
}

启动 Tomcat 在浏览器中访问:http://127.0.0.1:8080/UserServlet 即可测试我的测试结果如下图所示:

好了到了这里咱们的工程就已经创建完毕了,回归到我们文章的主要内容。




整体思路




解析配置

将相关配置加载进内存当中,存入定义好的数据结构,要管理哪些目标对象,通过编程语言进行解析,配置可以使用 xml,properties  或注解的方式。


定位与注册对象

查看哪些类当中标记了注解,定位到了目标对象之后,将对象注册到容器当中管理起来。


注入对象

在用户需要用到对象时,从容器中精确的获取对象,返回给用户给对应的属性进行注入(赋值)


提供通用的工具类

通过封装一些通用的工具,能够方便框架或用户方便进行操作。




创建注解


分别创建如下几个注解:

Component.java

/**
 * @author yby6
 * @program SpringPro
 * @date Created in 2023/9/30 030 9:07
 * @description
 **/
// 作用在类上
@Target(ElementType.TYPE)
// 注解的生命周期为RUNTIME,因为使用反射创建对象
@Retention(RetentionPolicy.RUNTIME)
public @interface Component {
}


Controller.java

/**
 * @author yby6
 * @program SpringPro
 * @date Created in 2023/9/30 030 9:07
 * @description
 **/
// 作用在类上
@Target(ElementType.TYPE)
// 注解的生命周期为RUNTIME,因为使用反射创建对象
@Retention(RetentionPolicy.RUNTIME)
public @interface Controller {
}


Repository.java

/**
 * @author yby6
 * @program SpringPro
 * @date Created in 2023/9/30 030 9:07
 * @description
 **/
// 作用在类上
@Target(ElementType.TYPE)
// 注解的生命周期为RUNTIME,因为使用反射创建对象
@Retention(RetentionPolicy.RUNTIME)
public @interface Repository {
}


Service.java

/**
 * @author yby6
 * @program SpringPro
 * @date Created in 2023/9/30 030 9:07
 * @description
 **/
// 作用在类上
@Target(ElementType.TYPE)
// 注解的生命周期为RUNTIME,因为使用反射创建对象
@Retention(RetentionPolicy.RUNTIME)
public @interface Service {
}




获取指定包下所有的类


指定范围,获取范围内的所有类,遍历所有类,获取被注解标记的类并加载进容器里,使用 classLoad 获取资源路径,直接使用 top.it6666 包是无法定位到对应的文件夹所在的路径,必须拿到具体路径,才能获取到该路径下所有 .class 文件。


ClassLoad 的作用:根据一个指定的类的名称,找到或者生成其对应的字节码,加载 Java 应用所需的资源:图片文件、配置文件、文件目录。




获取 URL 中文件与目录




根据文件与目录提取所有的 .class 文件


最终 ClassUtil.java 的内容如下:

/**
 * @author yby6
 * @program SpringPro
 * @date Created in 2023/9/30 030 9:24
 * @description 获取指定包下所有的类
 **/
@Slf4j
@UtilityClass
public class ClassUtil {
    public static final String FILE_PROTOCOL = "file";
    public static final String CLASS_SUFFIX = ".class";
    /**
     * 获取指定包下所有的类
     *
     * @param packageName 包名
     * @return {@link Set}<{@link Class}<{@link ?}>>
     */
    @SuppressWarnings("all")
    public Set<Class<?>> getPackageClass(String packageName) {
        // 1.获取类加载器
        ClassLoader classLoader = ClassUtil.getClassLoad();
        // 2.通过类加载器获取到加载的资源
        // 2.1把top.it6666转成top/it6666
        // 2.2通过类加载器获取加载的资源
        URL url = classLoader.getResource(packageName.replace(".", "/"));
        if (null == url) {
            log.error("{}:该包下没有任何内容", packageName);
        }
        Set<Class<?>> classSet = null;
        // 判断url是否为文件
        if (Objects.equals(FILE_PROTOCOL, url.getProtocol())) {
            // 创建集合
            classSet = new HashSet<>();
            String path = url.getPath();
            try {
                path = URLDecoder.decode(path, "utf-8");
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
            // 获取资源的实际路径
            File packageDir = new File(path);
            // 从路径当中提取Class文件,放到Set集合
            ClassUtil.getDirClass(classSet, packageDir, packageName);
        }
        return classSet;
    }
    ClassLoader getClassLoad() {
        return Thread.currentThread().getContextClassLoader();
    }
    /**
     * 根据目录提出所有的class文件
     *
     * @param classSet    Ioc容器
     * @param packageDir  文件
     * @param packageName 包名
     */
    private static void getDirClass(Set<Class<?>> classSet, File packageDir, String packageName) {
        // 如果不是一个目录直接结束
        if (!packageDir.isDirectory()) {
            return;
        }
        // 如果是目录,对目录里面的内容进行过滤
        File[] files = packageDir.listFiles(new FileFilter() {
            @Override
            public boolean accept(File file) {
                if (file.isDirectory()) {
                    return true;
                }
                String absolutePath = file.getAbsolutePath();
                // 判断absolutePath是不是以 .class 后缀结尾
                if (absolutePath.endsWith(CLASS_SUFFIX)) {
                    // 到了这里就代表是 .class 结尾的
                    addToClassSet(absolutePath);
                }
                return false;
            }
            private void addToClassSet(String absolutePath) {
                // absolutePath 有可能是:/D:/aa/aaa 或者是:D:\bb\bb 可以使用 File.separator 来统一代替
                // 把文件目录转成包的形式
                absolutePath = absolutePath.replace(File.separator, ".");
                String className = absolutePath.substring(absolutePath.indexOf(packageName));
                className = className.substring(0, className.lastIndexOf("."));
                try {
                    Class<?> targetClass = Class.forName(className);
                    classSet.add(targetClass);
                } catch (ClassNotFoundException e) {
                    e.printStackTrace();
                }
            }
        });
        if (null != files) {
            Arrays.stream(files).forEach(childFile -> ClassUtil.getDirClass(classSet, childFile, packageName));
        }
    }
}




创建 BeanContainer 容器


BeanContainer.java

/**
 * @author yby6
 * @program SpringPro
 * @date Created in 2023/10/08 008 17:24
 * @description
 **/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@SuppressWarnings("unused")
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();
        }
    }
}
相关文章
|
18小时前
|
Java Spring
Spring Boot脚手架集成校验框架
Spring Boot脚手架集成校验框架
6 0
|
1天前
|
前端开发 Java Spring
Spring Framework五大功能模块
Spring Framework五大功能模块
|
1天前
|
canal 缓存 关系型数据库
Spring Boot整合canal实现数据一致性解决方案解析-部署+实战
Spring Boot整合canal实现数据一致性解决方案解析-部署+实战
|
2天前
|
XML 人工智能 Java
Spring Bean名称生成规则(含源码解析、自定义Spring Bean名称方式)
Spring Bean名称生成规则(含源码解析、自定义Spring Bean名称方式)
|
3天前
|
安全 Java 数据库连接
[AIGC] Spring框架的基本概念和优势
[AIGC] Spring框架的基本概念和优势
|
3天前
|
Java Nacos 开发者
Java从入门到精通:4.2.1学习新技术与框架——以Spring Boot和Spring Cloud Alibaba为例
Java从入门到精通:4.2.1学习新技术与框架——以Spring Boot和Spring Cloud Alibaba为例
|
3天前
|
Dubbo Java 应用服务中间件
Java从入门到精通:3.2.2分布式与并发编程——了解分布式系统的基本概念,学习使用Dubbo、Spring Cloud等分布式框架
Java从入门到精通:3.2.2分布式与并发编程——了解分布式系统的基本概念,学习使用Dubbo、Spring Cloud等分布式框架
|
4月前
|
XML Java 数据格式
④【Spring】IOC - 基于注解方式 管理bean
④【Spring】IOC - 基于注解方式 管理bean
48 0
|
4月前
|
XML Java 数据格式
②【Spring】一文精通:IOC - 基于XML方式管理Bean
②【Spring】一文精通:IOC - 基于XML方式管理Bean
146 0
|
4月前
|
XML Java 数据格式
掌握 Spring IoC 容器与 Bean 作用域:详解 singleton 与 prototype 的使用与配置
在您的应用程序中,由 Spring IoC 容器管理的形成其核心的对象被称为 "bean"。一个 bean 是由 Spring IoC 容器实例化、组装和管理的对象
48 0

推荐镜像

更多