Sping6之IoC容器--手写IoC容器

1. 回顾Java反射

Spring框架的IOC是基于Java反射机制实现的。
Java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为Java语言的反射机制。简单来说,反射机制指的是程序在运行时能够获取自身的信息。
要想解剖一个类,必须先要获取到该类的Class对象。而剖析一个类或用反射解决具体的问题就是使用相关API**(1)java.lang.Class(2)java.lang.reflect**,所以,Class对象是反射的根源

2. 实现Spring的IoC

2.1 添加依赖以及准备测试需要的bean

<dependencies>
    <!--junit5测试-->
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter-api</artifactId>
        <version>5.3.1</version>
    </dependency>
</dependencies>

由于测试所需要的bean是controller、service、dao所以不过多赘述,只展示一个

package com.atguigu.spring.test.service.impl;

import com.atguigu.spring.core.annotation.Bean;
import com.atguigu.spring.service.UserService;

@Bean
public class UserServiceImpl implements UserService {

//    private UserDao userDao;

    @Override
    public void out() {
        //userDao.print();
        System.out.println("Service层执行结束");
    }
}

2.2 定义注解

我们通过注解的形式加载bean与实现依赖注入
Bean注解:

package com.atguigu.spring.core.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

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

依赖注入注解:

package com.atguigu.spring.core.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

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

2.3 定义Bean容器接口

package com.atguigu.spring.core;

public interface ApplicationContext {

    Object getBean(Class clazz);
}

2.4 编写注解bean容器接口实现

AnnotationApplicationContext基于注解扫描bean

package com.atguigu.spring.core;

import java.util.HashMap;

public class AnnotationApplicationContext implements ApplicationContext {

    //存储bean的容器(分析底层源码,bean其实是存在一个HashMap当中)
    private HashMap<Class, Object> beanFactory = new HashMap<>();

    @Override
    public Object getBean(Class clazz) {
        return beanFactory.get(clazz);
    }

    /**
     * 根据包扫描加载bean
     * @param basePackage
     */
    public AnnotationApplicationContext(String basePackage) {
    }
}

2.5 编写扫描bean逻辑

2.5.1 扫描bean的逻辑实现

首先分析逻辑如何实现:

  1. 创建一个存储bean的容器
  2. 实现自定义接口ApplicationContext的getBean方法
  3. 传入的basePackage样式为com.atguigu.spring6,我们需要先将·转化为\,得到com\atguigu\spring6
  4. 然后通过Thread.currentThread().getContextClassLoader().getResources(packageDirName)获取包的绝对路径
  5. 依次扫描包的绝对路径,将每个文件(或者文件夹)传入包的扫描方法loadBean(File file)
    • 内部方法的实现:
    • 1.判断当前是否为文件夹
    • 2 获取文件夹中的所有内容
    • 3 判断文件夹中是否为空,如果为空,直接返回
    • 4 如果文件夹不为空,遍历文件夹中所有内容
    • 4.1 遍历得到每个File对象,继续判断,如果还是文件夹,递归
    • 4.2 遍历得到的File不是文件夹,是文件,
    • 4.3 得到包路径 + 类名称部分-字符串截取
    • 4.4 判断当前文件类型是否为.class
    • 4.5 如果是.class类型,把路径\替换成. 把.class去掉
    • 4.6 判断类上面是否有注解 @Bean,如果有实例化过程
    • 4.7 判断是不是接口
    • 判断类上面是否有注解
    • 4.7 把对象实例化之后,放到map集合中(如果有接口则将接口放入map集合中)

2.5.2 依赖注入DI的逻辑实现

由于实例化得对象在beanFactory得map集合中

  1. 遍历beanFactory中的map集合
  2. 获取map集合每个对象(value),每个对象属性获取到
  3. 遍历得到每个对象属性数组,得到每个属性
  4. 获取对象class
  5. 获取对象属性
  6. 遍历每个对象属性,得到每个属性
  7. 判断属性上面是否有@Di注解
  8. 如果是私有属性,那么需要设置setAccessible(true)
  9. 如果有@Di注解,把对象进行设置(注入)

我们通过构造方法传入包的base路径,扫描被@Bean注解的java对象,完整代码如下:

package com.atguigu.spring.core;

import com.atguigu.spring.core.annotation.Bean;

import java.io.File;
import java.util.HashMap;

public class AnnotationApplicationContext implements ApplicationContext {

    //存储bean的容器
    private HashMap<Class, Object> beanFactory = new HashMap<>();
    private static String rootPath;

    @Override
    public Object getBean(Class clazz) {
        return beanFactory.get(clazz);
    }

    /**
     * 根据包扫描加载bean
     * @param basePackage
     */
    public AnnotationApplicationContext(String basePackage) {
       try {
            String packageDirName = basePackage.replaceAll("\\.", "\\\\");
            Enumeration<URL> dirs =Thread.currentThread().getContextClassLoader().getResources(packageDirName);
            while (dirs.hasMoreElements()) {
                URL url = dirs.nextElement();
                String filePath = URLDecoder.decode(url.getFile(),"utf-8");
                rootPath = filePath.substring(0, filePath.length()-packageDirName.length());
                loadBean(new File(filePath));
            }

        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private  void loadBean(File fileParent) {
        if (fileParent.isDirectory()) {
            File[] childrenFiles = fileParent.listFiles();
            if(childrenFiles == null || childrenFiles.length == 0){
                return;
            }
            for (File child : childrenFiles) {
                if (child.isDirectory()) {
                    //如果是个文件夹就继续调用该方法,使用了递归
                    loadBean(child);
                } else {
                    //通过文件路径转变成全类名,第一步把绝对路径部分去掉
                    String pathWithClass = child.getAbsolutePath().substring(rootPath.length() - 1);
                    //选中class文件
                    if (pathWithClass.contains(".class")) {
                        //    com.xinzhi.dao.UserDao
                        //去掉.class后缀,并且把 \ 替换成 .
                        String fullName = pathWithClass.replaceAll("\\\\", ".").replace(".class", "");
                        try {
                            Class<?> aClass = Class.forName(fullName);
                            //把非接口的类实例化放在map中
                            if(!aClass.isInterface()){
                                Bean annotation = aClass.getAnnotation(Bean.class);
                                if(annotation != null){
                                    Object instance = aClass.newInstance();
                                    //判断一下有没有接口
                                    if(aClass.getInterfaces().length > 0) {
                                        //如果有接口把接口的class当成key,实例对象当成value
                                        System.out.println("正在加载【"+ aClass.getInterfaces()[0] +"】,实例对象是:" + instance.getClass().getName());
                                        beanFactory.put(aClass.getInterfaces()[0], instance);
                                    }else{
                                        //如果有接口把自己的class当成key,实例对象当成value
                                        System.out.println("正在加载【"+ aClass.getName() +"】,实例对象是:" + instance.getClass().getName());
                                        beanFactory.put(aClass, instance);
                                    }
                                }
                            }
                        } catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
    }

}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值