自行编写IOC

温馨提示:完整代码已上传至(https://github.com/Monkey-yc/IOC),欢迎访问。

近期学习Spring框架,其核心技术乃AOP(见上篇)和IOC。前段时间经过画图和自己尝试实现AOP,发现加深了不少理解,因此尝试着编写了IOC,在此分享心得:

IOC,翻译过来大家把他叫控制反转,那么究竟是控制什么,什么被反转。我的理解如此:先举个例子:双十一快到了,大家都在网上买东西,一般买东西都是下单后卖家从本地发快递,一般需要三到七天,就算顺丰航空速递,也不能改变多大。但是京东在全国各大城市建了一个仓库,他把货物预先存在仓库中,而这个仓库负责周边地区的货物供应,一般离得不是很远的一天就能送到,而同城的当天到达。

IOC就是这个意思:大家都知道成员需要赋值,平时都是在使用的时候再给成员进行赋值。而IOC就像这个仓库,他把你加了注解的方法都会产生对象,存在这个仓库里边,在你用的时候他直接注入,由此大大方便了用户,也提高了效率。

根据我的理解,画了思维结构图如下(图丑,说明问题即可):
IOC思维导图图中画虚线的是假调用,真正的调用是将BeanDepot类中的Map得到对象,注入到该成员的。

下面来说说核心类的实现:
首先需要说明三个注解, 这里注解的命名模仿了Spring:

@Component:此注解添加给类,表明该类中有需要注入的成员或需要扫描的方法。说白了就是这个类里边有成员加了@Autowried注解或者有方法加了@Bean方法。

@Bean:这个注解是给方法加的,加了这个注解的方法表示需要被扫描且执行产生对象存进IOC仓库中以备注入时使用。

@Autowried:这个注解是给成员加的,加了这个注解表明这个成员的值会被从IOC容器自动注入,无需初始化即可使用。

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

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

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

下面先来说说BeanFactory这个类,该类的作用就是生成对象的一个工厂,通过包扫描,扫描带有@Component注解的类,自动给其生成对象,并存储到BeanDepot类的仓库;

因为在实际操作中,类都是封装好的,你不可能去添加注解生成对象,
因此需要写一些带有@Bean注解的方法,通过反射执行带有@Bean注解的方法生成对象
存储到BeanDepot类的仓库中以备注入。

这是包扫描的方法:

public static void scanPackage(String packageName) {
        BeanDepot beanDepot = new BeanDepot();

        new ScannerPackage() {
            @Override
            public void dealClass(Class<?> klass) {
                if (!klass.isAnnotationPresent(Component.class)) {
                    return;
                }

                try {
                    createBean(beanDepot, klass, null);
                    Object object = beanDepot.getYcBean(klass).getObject();
                    Method[] methods = klass.getMethods();
                    for (Method method : methods) {
                        if (!method.isAnnotationPresent(Bean.class)) {
                            continue;
                        }
                        invokeBeanMethod(beanDepot, method, object);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }

            }
        }.scanPackage(packageName);
    }

这是将生成的对象存入到仓库的方法:

 //将产生的Object存储进Beandepot仓库
    private static void createBean(BeanDepot beanDepot, Class<?> klass, Object object)
            throws Exception{
        if (object != null) {
            beanDepot.addBeanMap(klass, object);
        } else {
            beanDepot.addBeanMap(klass, klass.newInstance());
        }
    }

在仓库中以类名为键,以YcBean为值存储对象。下面来说一说这个YcBean这个类。
这个类虽然很简单,但是很重要。他的两个成员,一个就是生成的对象,另一个是一个Boolean类型的,可以说这个boolean类型的成员是该类的一个属性,用来判断该类是否已经被注入了成员的值。将这个属性和生成的对象一起打包封装成一个类,装在仓库中的Map中,可以在注入时调出这个属性判断该类是否已经注入过了,如果注入过了,那就不再进行注入了,如果没有注入,那么继续注入。由此实现了依赖循环的注入。

public class YcBean {
    private Object object;
    private Boolean isinjection;

    YcBean() {
        isinjection = false;
    }

    Object getObject() {

        return object;
    }

    void setObject(Object object) {
        this.object = object;
    }

    Boolean getisInjection() {

        return isinjection;
    }

    void setisInjection(Boolean isinjection) {
        this.isinjection = isinjection;
    }

}

下面再来看看IOC容器,BeanDepot这个仓库:这个仓库中以Map来存储类名和相对应得YcBean。
通过工厂中的包扫描来添加这个Map。仓库中还给了添加和删除的方法来操作Map。最重要的是实现了注入,根据传递过来的类寻找加了@Autowried注解得成员,如果没有找到有加了@Autowried注解的方法,就说明这个类没有成员需要注入,直接返回。
遇到需要注入的类,将该类对应的是否注入属性设置为true,下次如果在执行到这个类,可以直接使用而不用再注入。
处理完上述事情,就可以来处理带有@Autowried注解的成员了。取得这个成员的类型,看这个类中有没有需要注入的成员,如果有,应该先处理这个成员的类型的类,然而该类中需要处理的成员的类还有成员需要注入呢?很明显此处应使用递归解决。判断如果成员的类中有需要注入的成员,那就在此调用注入的方法,将该成员的类型及其对象传过去。
然处理完成员类的注入后,就可以给该成员注入值了,将该成员类型的对象注入到该成员的对象,即可大功告成。
然后在用户调用的时候,从Map中取出该类对应得YcBean,再从YcBean中取出判断是否注入过得属性值,如果未true,则该类已经被注入过了,直接返回。如果为false,就执行注入的方法给该成员进行赋值,给外界提供了一个得到值得接口。

下面是注入的方法:

private static void injection(Class<?> klass, Object object) {
        Field[] fields = klass.getDeclaredFields();
        for (Field field : fields) {
            if (!field.isAnnotationPresent(Autowired.class)) {
                return;
            }
            //表示该类已被注入过;
            beanMap.get(klass.getName()).setisInjection(true);

            Class<?> fieldType = field.getType();
            YcBean fieldTypeBean = beanMap.get(fieldType.getName());
            Object fieldobject = fieldTypeBean.getObject();
            //如果成员的类类型需要注入,即成员的类型在被加载时发现他的类中需要注入另一个
            //成员,其才能被成功加载。同理,在加载其成员的类的成员时,成员的成员也需要注入成员
            // 故此处应使用递归处理。
            if(!fieldTypeBean.getisInjection()) {
                injection(fieldType, fieldobject);
            }
            field.setAccessible(true);
            try {
                field.set(object, fieldobject);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }

        }
    }

给外界提供赋值的接口:

public static Object getBean(Class<?> klass) {
        YcBean ycBean = beanMap.get(klass.getName());
        if(!ycBean.getisInjection()) {
            injection(klass, ycBean.getObject());
        }

        return ycBean.getObject();
    }

我给StudentDao类直接赋了值,接下来就测试一下
赋值的StudentDao:

public StudentModel getStuById(String id) {
        StudentModel stu = new StudentModel();
        stu.setId("12138");
        stu.setName("张三");
        stu.setPasswd("123456");

        return stu;
    }

测试类如下:

public class Test {
    public static void main(String[] args) {
        BeanFactory.scanPackage("com.yc.ioc.student");

        StudentAction studentAction = (StudentAction) BeanDepot.getBean(StudentAction.class);
        StudentModel student = studentAction.getStudent("12138");

        System.out.println(student);

    }
}

可以对照上边的图好好推敲一下这个测试,StudentAction类调用了getStudent()方法,在此方法内部,使用studentDao调用了Dao的方法getStuById()方法,但是studentDao并没有初始化赋值,而是通过IOC容器给注入的值,输出如下:
在这里插入图片描述

还有依赖循环的测试在此就不多说了,原理上边已经说清楚了,有兴趣的朋友可以去GitHub。

大概核心内容就这么多,我将我编写IOC时认为重要的地方也都在上文做了详细说明。源代码见文首温馨提示。也希望您留下宝贵的意见,共同进步!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值