手写框架,模拟简易的SpringIOC

6 篇文章 0 订阅

要实现模拟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
@NoArgsConstructor
public 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
@MyComponent
public class TestIOC {
    @MyValue("123")
    private Integer id;
    @MyValue("小明")
    private String name;
    @MyAutowired
//    @MyQualifier("zi")
    private Zi zi;
}

② Zi.class

@Data
@NoArgsConstructor
@AllArgsConstructor
@MyComponent
public 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已完成

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值