手写框架,模拟简易的SpringIOC
发布日期:2021-05-07 06:54:36 浏览次数:12 分类:原创文章

本文共 13864 字,大约阅读时间需要 46 分钟。

要实现模拟IOC,要使用过spring,明白IOC的执行流程,会使用反射等

1. 通过基于注解的IOC的执行流程整理实现思路

在这里插入图片描述

来自楠哥视频里的图

实现步骤

根据该图,我们可以将步骤分为下列几步:

① 扫描包,获取该包下所有加了Component注解的类

② 通过反射机制获取该类的Class,即是图中的原材料的组件

③ 将获取的类的Class和Component注解中的value值封装成为一个beanDefinitions对象

④ 创建一个ioc容器,以存储bean(即是beanName,和对应的实例对象)

2. 实现

(1) 创建层级目录及文件

在这里插入图片描述

说明:MyAnnotationConfigApplication 为实现springIOC中的AnnotationConfigApplication

MyBeanDefinition为实现springIOC中的BeanDefinition

MyTools为扫描包的工具类

(2) 自定义要实现的IOC注解

在这里插入图片描述

① MyComponent 注解

//自定义一个MyComponent注解@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)public @interface MyComponent {       String value() default "";}

② MyValue 注解

 //自定义Value注解@Target(ElementType.FIELD)@Retention(RetentionPolicy.RUNTIME)public @interface MyValue {       String value() default "";}

③ MyAutowired 注解

//自定义autowired注解@Target(ElementType.FIELD)@Retention(RetentionPolicy.RUNTIME)public @interface MyAutowired {   }

④ MyQualifier 注解

//自定义Qualifier注解@Target(ElementType.FIELD)@Retention(RetentionPolicy.RUNTIME)public @interface MyQualifier {       String value() default "";}

(3) MyBeanDenifition 类

@Data@AllArgsConstructor@NoArgsConstructorpublic class MyBeanDefinition {       private String beanName;    private Class beanClass;}

使用lombok,MyBeanDefinition为模拟IOC的BeanDeFinition类
成员属性说明:
beanName 为@MyComponent注解中的value值,即是bean名,如果value为空,则使用首位变小写后的类名为beanName
beanClass 为要提供bean(实例对象)的Class(原材料)

(4) MyTools 工具类

实现扫描包的功能,返回一个set集合 包含该包下所有的类

public static Set<Class<?>> getClasses(String pack) {           // 第一个class类的集合        Set<Class<?>> classes = new LinkedHashSet<Class<?>>();        // 是否循环迭代        boolean recursive = true;        // 获取包的名字 并进行替换        String packageName = pack;        String packageDirName = packageName.replace('.', '/');        // 定义一个枚举的集合 并进行循环来处理这个目录下的things        Enumeration<URL> dirs;        try {               dirs = Thread.currentThread().getContextClassLoader().getResources(packageDirName);            // 循环迭代下去            while (dirs.hasMoreElements()) {                   // 获取下一个元素                URL url = dirs.nextElement();                // 得到协议的名称                String protocol = url.getProtocol();                // 如果是以文件的形式保存在服务器上                if ("file".equals(protocol)) {                       // 获取包的物理路径                    String filePath = URLDecoder.decode(url.getFile(), "UTF-8");                    // 以文件的方式扫描整个包下的文件 并添加到集合中                    findClassesInPackageByFile(packageName, filePath, recursive, classes);                } else if ("jar".equals(protocol)) {                       // 如果是jar包文件                    // 定义一个JarFile                    System.out.println("jar类型的扫描");                    JarFile jar;                    try {                           // 获取jar                        jar = ((JarURLConnection) url.openConnection()).getJarFile();                        // 从此jar包 得到一个枚举类                        Enumeration<JarEntry> entries = jar.entries();                        findClassesInPackageByJar(packageName, entries, packageDirName, recursive, classes);                    } catch (IOException e) {                           // log.error("在扫描用户定义视图时从jar包获取文件出错");                        e.printStackTrace();                    }                }            }        } catch (IOException e) {               e.printStackTrace();        }        return classes;    }

(5) MyAnnotationConfigApplication 类 (重点)

该类的实现步骤:

① 创建一个成员变量HashMap模拟ioc容器

 // 创建一个HashMap模拟ioc容器    private Map<String,Object> ioc = new HashMap<>();

② 编写 findBeanDefinition 函数,传入包名,调用工具类,获取到所有添加了MyComponent注解的类后,将其所有类(Class原型)和beanName封装为MyBeanDefinition对象集合后返回

这一步,即实现了MyComponent注解的功能

具体解释见代码注释

public Set<MyBeanDefinition> findMyBeanDefinitions(String packageName){           // 1.获取包下的所有类        Set<Class<?>> classes = com.ruoxi.MyIOC.MyTools.getClasses(packageName); //使用工具类,用set集合获取该包名下的所有类        // 使用HashSet存储BeanDefinition        Set<MyBeanDefinition> beanDefinitions = new HashSet<>();        // 2.遍历这些类,找到添加了注解的类        Iterator<Class<?>> iterator = classes.iterator();        while(iterator.hasNext()){               Class<?> clazz = iterator.next();            // 获取注解的对象            MyComponent componentAnnotation = clazz.getAnnotation(MyComponent.class);            //如果componentAnnotation != null ,则添加了该注解            if(componentAnnotation!=null){                   // 获取注解的值                String beanName = componentAnnotation.value();                // 如果没有写beanName(即注解的值) 那么将类名的首字母变小写后作为beanName                if(beanName.equals("")) {                       String className = clazz.getSimpleName(); //获取类名                    //将类名的第一个截取变小写,然后加上后面的字符串                    beanName = className.substring(0,1).toLowerCase()+className.substring(1);                }                // 3. 将这些类封装为BeanDefinition 并存入beanDefinitions                beanDefinitions.add(new MyBeanDefinition(beanName,clazz));            }        }        return beanDefinitions;    }

③ 编写 createObjects 函数,传入上一步封装好的MyBeanDefinition对象集合,在该函数中遍历该集合,利用反射机制创建bean对象,并遍历类的各个属性,以实现MyValue注解的功能

这一步,即实现了MyValue注解的功能

具体解释见代码注释

public void createObject(Set<MyBeanDefinition> beanDefinitions){           Iterator<MyBeanDefinition> iterator = beanDefinitions.iterator();        //遍历beanDefinition集合        while (iterator.hasNext()) {               MyBeanDefinition beanDefinition = iterator.next();            Class clazz = beanDefinition.getBeanClass();            // 获取beanName            String beanName = beanDefinition.getBeanName();            try {                   // 利用反射机制创建bean对象 注意这里的是beanDefinition里的beanClass对象                Object object = clazz.getConstructor().newInstance();                //完成属性的赋值                Field[] fields = clazz.getDeclaredFields();                //遍历属性 判断是否添加了myValue注解                for (Field field : fields) {                       MyValue myValueAnnotation = field.getAnnotation(MyValue.class);                    if(myValueAnnotation != null){                           //获取注解的值                        String value = myValueAnnotation.value();                        //通过截取属性名 拼接为方法名                        String fieldName = field.getName();                        String methodName = "set"+fieldName.substring(0,1).toUpperCase()+fieldName.substring(1);                        //得到设置属性的方法                        Method method = clazz.getMethod(methodName,field.getType());                        //完成数据类型转换                        Object val = null;                        //将value转化为对应的属性类型,并给属性赋值                        switch (field.getType().getName()){    //这里只列举了部分类型                            case "java.lang.Integer":                                val = Integer.parseInt(value);                                break;                            case "java.lang.String":                                val = value;                                break;                            case "java.lang.Float":                                val = Float.parseFloat(value);                                break;                            case  "java.lang.Double":                                val= Double.parseDouble(value);                                break;                        }                        method.invoke(object,val); //执行方法,给属性赋值                    }                }                // 将该对象和beanName存入ioc容器                ioc.put(beanName,object);            } catch (InstantiationException e) {                   e.printStackTrace();            } catch (IllegalAccessException e) {                   e.printStackTrace();            } catch (InvocationTargetException e) {                   e.printStackTrace();            } catch (NoSuchMethodException e) {                   e.printStackTrace();            }        }    }

④ 编写getBean函数,通过传入beanName名取得bean

 // 通过beanName获取bean    public Object getBean(String beanName){           return ioc.get(beanName);    }

⑤ 编写 autowireObject 函数,还是传入BeanDefinition的Set集合,遍历Set中的Class中的属性,查询是否有添加MyAutowired或MyQualifier注解,如果有,则实现对应属性(内部类)注入bean的功能

这一步,即实现了MyAutowired和MyQualifier注解的功能

具体解释见代码注释

//实现autowire和MyQualifier注解的功能    public void autowireObject(Set<MyBeanDefinition> beanDefinitions){           Iterator<MyBeanDefinition> iterator = beanDefinitions.iterator();        while (iterator.hasNext()) {               MyBeanDefinition beanDefinition = iterator.next();            //创建beanDefinition中的beanClass对象            Class clazz = beanDefinition.getBeanClass();            //遍历属性,查找是否有添加 MyAutowired或MyQualifier注解            Field[] fields = clazz.getDeclaredFields();            for (Field field : fields) {                   MyAutowired myAutowired = field.getAnnotation(MyAutowired.class);                if(myAutowired!=null){                       MyQualifier myQualifier = field.getAnnotation(MyQualifier.class);                    //如果有MyQualifier注解,则为ByName方式 否则为ByType方式                    if(myQualifier!=null){                           try {                               //通过beanName获取bean对象 这里即是内部类的对象                            String beanName = myQualifier.value();                            Object bean = getBean(beanName);                            String fieldName = field.getName();                            String methodName = "set"+ fieldName.substring(0,1).toUpperCase()+ fieldName.substring(1);                            Method method = clazz.getMethod(methodName,field.getType());                            //取出对象(bean)                            //beanDefinition.getBeanName()即是当前的beanName                            Object object = getBean(beanDefinition.getBeanName());                            //执行method 将bean注入到object中,这里即是将对象的bean赋值给内部类                            method.invoke(object,bean);                        } catch (NoSuchMethodException e) {                               e.printStackTrace();                        } catch (IllegalAccessException e) {                               e.printStackTrace();                        } catch (InvocationTargetException e) {                               e.printStackTrace();                        }                    }                    else {    //当只有autowired注解时 通过byType方式获取bean                        Class fieldClass = field.getType();                        // 通过反射获取指定的注解                        MyComponent fieldClassAnnotation = (MyComponent) fieldClass.getDeclaredAnnotation(MyComponent.class);                        //如果属性的类也添加了MyComponent注解 则获取属性的Class的MyComponent注解的值 并注入属性的值                        if(fieldClassAnnotation != null){                               String filedBeanName = fieldClassAnnotation.value();                            if(filedBeanName.equals("")){    //如果属性的Component注解没有赋值,则将首字母变小写后成为beanName                                String className = fieldClass.getSimpleName(); //获取属性类名                                //将类名的第一个截取变小写,然后加上后面的字符串                                filedBeanName = className.substring(0,1).toLowerCase()+className.substring(1);                            }                            // 获取该属性的bean                            Object fieldBean = getBean(filedBeanName);                            try                            {                                   // 拼接字符串得到set属性的方法                                String fieldName = fieldClass.getSimpleName();                                String setFiledMethodName = "set"+fieldName.substring(0,1).toUpperCase()+fieldName.substring(1);                                Method method = clazz.getMethod(setFiledMethodName,field.getType());                                //取出当前beanDefinition的类对象bean                                //beanDefinition.getBeanName()即是当前的beanName                                Object object = getBean(beanDefinition.getBeanName());                                method.invoke(object,fieldBean);                            }                             catch (InvocationTargetException e) {                                   e.printStackTrace();                            } catch (NoSuchMethodException e) {                                   e.printStackTrace();                            } catch (IllegalAccessException e) {                                   e.printStackTrace();                            }                        }                    }                }            }        }    }

⑥ 编写MyAnnotationConfigAppliaction类的有参构造函数,并使用上述所有的函数来实现ioc的功能

public MyAnnotationConfigApplicationContext(String packageName){           //遍历包,找到目标类(原材料)        Set<MyBeanDefinition> beanDefinitions = findMyBeanDefinitions(packageName);                //根据原材料创建bean        createObject(beanDefinitions);        //判断是否有MyAutowired或MyQualifier注解,如有则对应实现功能        autowireObject(beanDefinitions);    }

至此,模拟SpringIOC的功能已大致完成,接下来是测试

3. 测试

(1) 创建俩个类并加上自定义注解

① TestIOC.class

@Data@AllArgsConstructor@NoArgsConstructor@MyComponentpublic class TestIOC {       @MyValue("123")    private Integer id;    @MyValue("小明")    private String name;    @MyAutowired//    @MyQualifier("zi")    private Zi zi;}

② Zi.class

@Data@NoArgsConstructor@AllArgsConstructor@MyComponentpublic class Zi {       @MyValue("123456")    private Integer Ziid;    @MyValue("打工人")    private String Ziname;}

(2) 在main方法中进行测试,打印输出getBean(“testIOC”)

① 不在TestIOC类中给内部类Zi属性添加MyQualifier注解,只单用MyAutowired注解

    @MyAutowired//    @MyQualifier("zi")    private Zi zi;
public class Test {       public static void main(String[] args) {           MyAnnotationConfigApplicationContext myAnnotationConfigApplication = new MyAnnotationConfigApplicationContext("com.ruoxi.entity");        System.out.println(myAnnotationConfigApplication.getBean("testIOC"));    }}

在这里插入图片描述
通过打印结果可以看见,成功取出了名为"testIOC"的bean,且各个属性均已赋值,说明@MyComponent注解生效,@MyValue注解也已生效

同时内部类zi也被注入了值,说明Zi类的bean也注入,且TestIOC类的成员变量Zi被注入了这个bean值,说明@MyAutowired注解生效

② 在TestIOC类中给内部类Zi属性添加上MyQualifier注解

 @MyAutowired    @MyQualifier("zi")    private Zi zi;

在这里插入图片描述
同样成功打印,说明MyQualifier注解也生效

至此,模拟SpringIOC已完成

上一篇:Idiot 的间谍网络
下一篇:一个小程序以理解控制反转

发表评论

最新留言

感谢大佬
[***.8.128.20]2025年04月13日 17时14分29秒