自己实现简易版Ioc容器

一,前言

最近了解了一下Spring的源码,为了加固对Spring源码的认识。
想手写一个Spring的Ioc容器。

基本思路

  1. 自定义注解类
  • MyComponent:注解在类上
  • MyAutowired:注解在成员变量
  1. 加载指定路径下的所有类:通过指定路径+全类名加载
  2. 实例化并放入BeanFactory中;对于那些有MyComponent注解的类,做实例化
  3. 完成依赖注入;遍历BeanFactory中的bean,对于Filed上有MyAutoWired注解的属性,完成依赖注入
  4. 测试:检验Bean是否实例化成功且完成了依赖注入;遍历BeanFactory,执行所有bean都有的某个方法

二,实现

我们需要自定义两个注解:

  • MyComponent:标志这个类需要加入bean管理
  • MyAutowired:用于依赖注入

MyComponent

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

MyAutowired

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

重点来了,我们需要写一个类来实现ioc容器的功能 —> MyIOC

目标:需要扫描某个路径所有的class文件,获取都所有类的Class对象,有注解的则加入到ioc容器中,同时还要实现依赖注入

package ioc;

import java.io.File;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;


public class MyIOC {

    // beanFactory 要声明为类变量,因为它不能被GC回收:
    private static HashMap<String, Object> beanFactory = new HashMap<>();

    /**
     * 随着MyIOC类被加载到内存进行初始化,就会执行其静态代码块
     * @param args
     */
    static {
        init();

    }

    /**
     * 获取BeanFactory
     * @return
     */
    public static HashMap<String, Object> getBeanFactory() {
        return beanFactory;

    }

    /**
     * 根据全类名更新BeanFactory中的bean
     * @param typeName
     * @param proxyInstance
     */
    public static void updateBeanFromBeanFactory(String typeName, Object proxyInstance) {
        beanFactory.put(typeName, proxyInstance);

    }

    /**
     * 通过全类名获得对应的实例
     * @param completeClassName
     * @return
     */
    public static Object getBean(String completeClassName) {
        return beanFactory.get(completeClassName);

    }


    public static void init() {
        HashMap<String, Class> loadedClazzList;//<全类名, Class对象>
        try {
            //1.加载指定的类
//            File file = new File("E:\\ioc");//!!!这里写死了路径不合适,可以做改进

            //上面的可以改进一下,我们知道在spring中有一个主启动类,我们要想让某个类加入ioc容器,除了要使用相应注解外,
            //还需要把这个类放在主启动类“后面”才能被spring扫描到。至于为什么,我想底层有一段类似这样的代码
            String path = MyIOC.class.getResource("/").getPath(); //获取类路径,/D:/IDEA/stady/target/classes/
            String name = MyIOC.class.getPackage().getName(); //获取当前类所在的包名
            File file = new File(path+name);


            loadedClazzList = loadAllClazz(file); //加载当前目录下的所有类,获取他们的Class对象

            //2.实例化并放入IOC容器中:对于那些有注解的类,做实例化
            newInstance(loadedClazzList);

            // 3.完成依赖注入
            autoWired();

            // 4.测试:找到beanFactory中的某个bean,并执行其某个方法 ===> 这里有个问题,只能执行指定的方法,所以beanFactory中的所有bean都得有这个方法,这里先这么做了,但这明显不合理。
            // test();

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /*
     * 描述: 测试方法,调用所有bean的test方法
     * @param:
     * @return: void
     * @date: 2021/1/24 23:25
     */
    public static void test() throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
        for (Map.Entry<String, Object> entry : beanFactory.entrySet()) {
            Method method = entry.getValue().getClass().getMethod("test");
            method.invoke(entry.getValue());

        }

    }

    /**
     * 对BeanFactory中管理的所有bean完成依赖注入。
     * 交给IOC容器管理的类,需要注入成员变量,如果该成员变量是自定义的类,该类也是需要交给IOC容器管理的。
     *
     * @throws IllegalAccessException
     * @throws IllegalArgumentException
     * @throws MalformedURLException
     * @throws ClassNotFoundException
     */
    public static void autoWired() throws IllegalArgumentException, IllegalAccessException, ClassNotFoundException, MalformedURLException {
        for (Map.Entry<String, Object> entry : beanFactory.entrySet()) {

            Field[] fields = entry.getValue().getClass().getDeclaredFields();//!!!getFields():只能获取到运行时类中及其父类中声明为public的属性;getDeclaredFields():获取运行时类本身声明的所有的属性
            for (Field field : fields) {
                Annotation[] annotations = field.getAnnotations();
                for (int i = 0; i < annotations.length; i++) {
                    if (annotations[i].annotationType() == MyAutowired.class) {
                        //从beanFactory中找到相应的bean,赋值给该成员变量,以完成依赖注入。
                        Object object = beanFactory.get(field.getType().getTypeName());
                        //System.out.println(field.getType().getTypeName());//MyIOCAndMyAop.bean.Student
                        //通过Field(反射)为成员变量赋值:
                        field.setAccessible(true);
                        field.set(entry.getValue(), object);
                    }
                }
            }
        }
    }

    /**
     * 实例化: 放到loadedClazzlist集合中的Class对象都是需要做实例化的(加了@MyComponent注解的类)
     */
    public static void newInstance(HashMap<String, Class> loadedClazzList) throws InstantiationException, IllegalAccessException, ClassNotFoundException, MalformedURLException {
        for (Map.Entry<String, Class> entry : loadedClazzList.entrySet()) {
            beanFactory.put(entry.getKey(), entry.getValue().newInstance()); //注意,没有无参构造会报错
        }
    }

    /**
     * 加载指定路径下的类。
     * 类加载:javase/src/classLoader/a01helloworld/A03GetExtClassLoader
     *
     * @return 类加载器加载进来的指定路径下的所有Class对象
     * @throws IllegalAccessException
     * @throws InstantiationException
     */
    public static HashMap<String, Class> loadAllClazz(File file) throws ClassNotFoundException, MalformedURLException, InstantiationException, IllegalAccessException {
        //用于存放类加载器加载进来的Class对象<全类名, Class对象>
        HashMap<String, Class> loadedClazzList = new HashMap<>();

        URL[] urls = new URL[]{file.toURI().toURL()};

        URLClassLoader classLoader = new URLClassLoader(urls); //用这个类加载器加载这个包下的所有类

        ArrayList<String> allCompleteClassName = getAllCompleteClassName(file);
        
        for (String element : allCompleteClassName) {
            Class<?> clazz = classLoader.loadClass(element);

//            也可以使用Class<?> clazz = Class.forName(element),因为element是全限定类名了
//            区别: loadClass() 方法加载类不会对其进行初始化,而通过Class.forName() 方法加载类会对其进行初始化。
//            Class<?> clazz = Class.forName(element);
            Annotation[] annotations = clazz.getAnnotations();// !!!拿到Class对象的时候,就进行筛选出有注解的Class再放到容器中,而不是把指定路径下的所有类都加载进来。
            for (int i = 0; i < annotations.length; i++) {
                if (annotations[i].annotationType() == MyComponent.class) {
                    loadedClazzList.put(element, clazz);//得到各个类对象了
                }
            }
        }
        return loadedClazzList;
    }

    /**
     * 得到allNeedLoadClassFiles中所有要加载的class文件的全类名
     */
    private static ArrayList<String> getAllCompleteClassName(File file) {
        // 所有要加载的class的全类名,如:classLoader.a02myclassloader.bean.Bean
        ArrayList<String> completeClassNames = new ArrayList<>();
        // 用于存放指定路径下所有要加载的class文件
        ArrayList<File> allNeedLoadClassFiles = new ArrayList<File>();

        getAllNeedLoadClassFile(file, allNeedLoadClassFiles);
        String classpath = MyIOC.class.getResource("/").getPath();
        for (File element : allNeedLoadClassFiles) {
            String s = element.getPath();

            if (s.endsWith(".class")) {
                //把 D:\work\code\20170401\search-class\target\classes\com\baibin\search\a\A.class 这样的绝对路径转换为全类名com.baibin.search.a.A
                s = s.replace(classpath.replace("/","\\").replaceFirst("\\\\",""),"").replace("\\",".").replace(".class","");
                completeClassNames.add(s);
            }
        }
        return completeClassNames;

    }

    /**
     * 通过递归获取指定路径下所有要加载的class文件
     * 递归:javase/src/recursion/A_PrintFolder
     * @param file
     */
    private static ArrayList<File> getAllNeedLoadClassFile(File file, ArrayList<File> allNeedLoadClassFiles) {
        if (!file.exists()) {//!!!这里要多一层判断
            return allNeedLoadClassFiles;
        }
        if (file.isDirectory()) {//是文件夹
            File[] listFiles = file.listFiles();
            for (File element : listFiles) {
                getAllNeedLoadClassFile(element, allNeedLoadClassFiles);
            }
        } else {//是文件
            allNeedLoadClassFiles.add(file);
        }
        return allNeedLoadClassFiles;
    }

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值