欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

利用递归,反射,注解等,手写Spring Ioc和Di 底层(分分钟喷倒面试官)了解一下

程序员文章站 2023-11-09 21:03:34
再我们现在项目中Spring框架是目前各大公司必不可少的技术,而大家都知道去怎么使用Spring ,但是有很多人都不知道SpringIoc底层是如何工作的,而一个开发人员知道他的源码,底层工作原理,对于我们对项目的理解是有非常大的帮助的,有可能工作了两三年的中级工程师,乃至四五年的,只知其然,却不知 ......

   

    再我们现在项目中spring框架是目前各大公司必不可少的技术,而大家都知道去怎么使用spring ,但是有很多人都不知道springioc底层是如何工作的,而一个开发人员知道他的源码,底层工作原理,对于我们对项目的理解是有非常大的帮助的,有可能工作了两三年的中级工程师,乃至四五年的,只知其然,却不知其所以然。我的一个盆友,今年年初以实习生的身份去北京面试 ,面试官让我的朋友说spring源码,作为一个实习生,就要去知道spring的源码。虽然我们可以不用知道,也可以做项目,但他会成为我们面试结果的绊脚石,

而各个公司面试喜欢提问都是spring的原理,底层,源码。而看了今天的文章,再去面试我们就不用怕了。

当面试官问我们,了解spring吗,我们回答,了解而且对ioc还很深入,这时候,面试官的兴趣一下子就被你勾引了,心想,一个小菜鸟居然敢说听深入,他必会让你讲,殊不知他已经上当了,这时候我们给他来一手手写springioc和di的工作原理,而把这些都写完,解释完,怎么的也得半个多小时过去了,而面试官不会去花太多时间去面试一个人,甚至有可能你把这个问题说完,随便问几句,直接就录用你了,(因为面试官都有可能都知道他底层的工作原理)这样即使面试官百分之八十就会高看我们一眼,起始的薪资呢,也不会低。

进入正题:一张图就可以看出我们的整体结构。是用mvc模式,去引出我们的底层代码。

               

  spring ioc                                                       利用递归,反射,注解等,手写Spring Ioc和Di 底层(分分钟喷倒面试官)了解一下

首先创建pojo,dao层,service层和controller层

pojo:定义两个属性,加上getset和tostring方法

package cn.com.wx.pojo.user;

public class user {
     private integer id;
     private string name;
    public integer getid() {
        return id;
    }
    public void setid(integer id) {
        this.id = id;
    }
    public string getname() {
        return name;
    }
    public void setname(string name) {
        this.name = name;
    }
    @override
    public string tostring() {
        return "user [id=" + id + ", name=" + name + "]";
    }
     
}

 

dao层:这里我们要模仿数据库的层:写一个selectone的方法,用以调用

package cn.com.wx.dao;

import cn.com.wx.pojo.user.user;

public class userdao {
    public user selectone(integer id,string name) {
        user user =new user();
        user.setid(id);
        user.setname(name);
        return user;
        
    }
}

 

最后加上service层和controller层

package cn.com.wx.service;

import cn.com.wx.dao.userdao;
import cn.com.wx.pojo.user.user;

public class userservice {
    private userdao userdao;

       public void setuserdao(userdao userdao) {
           this.userdao = userdao;
      }

public user get(integer id,string name) {
        
        user user=userdao.selectone(id, name);
        return user;
        
    }
}

 

package cn.com.wx.controller;

import cn.com.wx.service.userservice;
import cn.com.wx.pojo.user.user;

public class usercontroller {
    private userservice userservice ;

       public void setuserservice(userservice userservice) {
           this.userservice = userservice;
       }

public user get(integer id, string name) {
        user user =userservice.get(id, name);
        return user;
        
        
    }
}

 

 

接下里我们写容器的创建过程,遍历目录,获取所有的class文件,这里我们使用io和文件api,利用递归的方式把文件拿出来,

package cn.com.wx.files;

import java.io.file;
import java.util.list;

public class junitfile {
    public static list<string> getfilename(string dir, list<string> list) {
        file file = new file(dir);//拿到目录
        file[] f = file.listfiles();//封装在一个数组里面
        for (file name : f) {
            if(name.isdirectory()) {//api isdirectory:判断是否为文件,如果是文件夹,证明文件夹里边还有文件,利用递归的方式进入文件夹
                getfilename(name.getabsolutepath(), list);
            }else {
                //不是文件夹就是文件,把文件的路径放到集合中
                list.add(name.getabsolutepath());
            }
        }

        return list;
    }
}

 

这时我们创建一个测试类来验证下我们的文件是否拿到

package cn.com.wx.test;

import java.util.arraylist;
import java.util.list;

import cn.com.wx.files.junitfile;

public class test {
    public static void main(string[] args) {

        list<string> list = new arraylist<string>();// 创建集合用于接受junnitfile的返回值
        // 这个路径是我们项目的路径,一定要根据自己工作空间建的项目的目录
        string dir = "c:\\users\\wangxiao\\eclipse-workspace\\bokespringioc\\bin";
        junitfile.getfilename(dir, list);
        for (string string : list) {
            system.out.println(string);
        }
    }
}

 

输出结果:这样我们bin目录下的所有的class文件就都拿到了

c:\users\wangxiao\eclipse-workspace\bokespringioc\bin\cn\com\wx\controller\usercontroller.class
c:\users\wangxiao\eclipse-workspace\bokespringioc\bin\cn\com\wx\dao\userdao.class
c:\users\wangxiao\eclipse-workspace\bokespringioc\bin\cn\com\wx\files\junitfile.class
c:\users\wangxiao\eclipse-workspace\bokespringioc\bin\cn\com\wx\pojo\user\user.class
c:\users\wangxiao\eclipse-workspace\bokespringioc\bin\cn\com\wx\service\userservice.class
c:\users\wangxiao\eclipse-workspace\bokespringioc\bin\cn\com\wx\test\test.class

 

这时候拿一个路径过来分析:

c:\users\wangxiao\eclipse-workspace\bokespringioc\bin\cn\com\wx\test\test.class
我们整整需要的是包名加上文件名: cn\com\wx\test\test
这样就需要我们干掉bin目录之前的目录和类的后缀.class
这个就非常简单了,我们可以使用基础的api去截取字符串:
package cn.com.wx.files;

import java.util.list;

public class covers {
    // c:\users\wangxiao\eclipse-workspace\bokespringioc\bin\cn\com\wx\test\test.class
    // c:\users\wangxiao\eclipse-workspace\bokespringioc\bin\

    // 看着上边的目录我们来分析问题:首选我们已经把标准目录拿到了,放到一个list集合中,集合作为参数传到我的getscanname方法中
    // 这样我只需要一个需要截取的部分的字符串,这里我用scandir来表示
    public static list<string> getscanname(string scandir, list<string> list) {
        // scandir=c:\users\wangxiao\eclipse-workspace\bokespringioc\bin\

        // 下一步分析:假定我们截取完字符串,那我们是不是还要把它放回去,放回去就要知道我们的角标,而普通的foreach循环满足不了,我们用普通的for循环来替代
        for (int i = 0; i < list.size(); i++) {
            string strname = list.get(i);// 逐个去拿他的目录
            // 先替换虽有的斜杠
            strname = strname.replace("\\", "/");
            scandir = scandir.replace("\\", "/");
            // 干掉scandir部分
            strname = strname.replace(scandir, "");
            // 从后边拿到点出现的位置
            int pos = strname.lastindexof(".");
            strname = strname.substring(0, pos);
            // 这样我们就拿到了这样的格式
            // cn/com/wx/test/test
            // 最后再把斜杠替换成点
            strname = strname.replace("/", ".");
            // 最后再放回集合中
            list.set(i, strname);
        }
        return list;

    }
}

 

然后我们在来测试:还是用刚才的测试类:

package cn.com.wx.test;

import java.util.arraylist;
import java.util.list;

import cn.com.wx.files.covers;
import cn.com.wx.files.junitfile;

public class test {
    public static void main(string[] args) {

        list<string> list = new arraylist<string>();// 创建集合用于接受junnitfile的返回值
        // 这个路径是我们项目的路径,一定要根据自己工作空间建的项目的目录
        string dir = "c:\\users\\wangxiao\\eclipse-workspace\\bokespringioc\\bin\\";
        junitfile.getfilename(dir, list);
        for (string string : list) {
            system.out.println(string);
        }
        covers.getscanname(dir, list);
        for (string string : list) {
            system.out.println(string);
        }
        
    }
}

 

运行结果:

c:\users\wangxiao\eclipse-workspace\bokespringioc\bin\cn\com\wx\controller\usercontroller.class
c:\users\wangxiao\eclipse-workspace\bokespringioc\bin\cn\com\wx\dao\userdao.class
c:\users\wangxiao\eclipse-workspace\bokespringioc\bin\cn\com\wx\files\covers.class
c:\users\wangxiao\eclipse-workspace\bokespringioc\bin\cn\com\wx\files\junitfile.class
c:\users\wangxiao\eclipse-workspace\bokespringioc\bin\cn\com\wx\pojo\user\user.class
c:\users\wangxiao\eclipse-workspace\bokespringioc\bin\cn\com\wx\service\userservice.class
c:\users\wangxiao\eclipse-workspace\bokespringioc\bin\cn\com\wx\test\test.class
cn.com.wx.controller.usercontroller
cn.com.wx.dao.userdao
cn.com.wx.files.covers
cn.com.wx.files.junitfile
cn.com.wx.pojo.user.user
cn.com.wx.service.userservice
cn.com.wx.test.test

 

这里我们有一处出现了硬编码拿就是测试类:我门把路径写死了,而我们的spring底层肯定不会出现这种情况,这样就用我们的api来替代他,让他动态的获取路径

package cn.com.wx;

public class runapp {
    public static void main(string[] args) {
        //获取主路径(bin之前)
        // string dir=runapp.class.getresource("/").getpath()
        //      /c:/users/wangxiao/eclipse-workspace/bokespringioc/bin/
        //上面是正常的获取主路径但是获取的路径前面会有一个斜杠,所以我们要加上一个substring的api去掉斜杠
        
        //主路径
        string dir=runapp.class.getresource("/").getpath().substring(1);
        system.out.println(dir);
        
        //包路径
        string str=runapp.class.getpackage().getname();

        system.out.println(str);//cn.com.wx  输出的结果,分割是用点分割的,所有要替换成斜杠
        
        //最后在于我们的主路径拼接
       string scandir=dir+str.replace(".", "/");
       system.out.println(scandir);
        
    }
}

 

运行结果:

c:/users/wangxiao/eclipse-workspace/bokespringioc/bin/
cn.com.wx
c:/users/wangxiao/eclipse-workspace/bokespringioc/bin/cn/com/wx

 

这样我们的动态获取路径的方法就实现了,正常我们开发一般都是面向接口的开发:所有要把我们得test方法改成面向接口的格式:创建接口:beanfactory,写两个方法,让test类去实现接口,runapp方法去调用接口

package cn.com.wx.test;

import java.util.list;

public interface beanfactory {
    public list<string> fild(string dir, list<string> list) ;
    
    public void cover(string scandir, list<string> list);
}

 

修改test类,实现beanfactory接口

package cn.com.wx.test;

import java.util.arraylist;
import java.util.list;

import cn.com.wx.files.covers;
import cn.com.wx.files.junitfile;

public class test implements beanfactory {

    list<string> list = new arraylist<string>();// 创建集合用于接受junnitfile的返回值
    // 这个路径是我们项目的路径,一定要根据自己工作空间建的项目的目录
    // string dir = "c:\\users\\wangxiao\\eclipse-workspace\\bokespringioc\\bin\\";

    public list<string> fild(string dir, list<string> list) {
        list<string> dirlist = junitfile.getfilename(dir, list);
        return dirlist;
    }

    public void cover(string scandir, list<string> list) {
        list<string> coverlist = covers.getscanname(scandir, list);
        
    }

}

 

runapp调用:

package cn.com.wx;

import java.util.arraylist;
import java.util.list;

import cn.com.wx.test.beanfactory;
import cn.com.wx.test.test;

public class runapp {
    public static void main(string[] args) {
        //获取主路径(bin之前)
        // string dir=runapp.class.getresource("/").getpath()
        //      /c:/users/wangxiao/eclipse-workspace/bokespringioc/bin/
        //上面是正常的获取主路径但是获取的路径前面会有一个斜杠,所以我们要加上一个substring的api去掉斜杠
        
        //主路径
        string dir=runapp.class.getresource("/").getpath().substring(1);
        //system.out.println(dir);
        
        //包路径
        string str=runapp.class.getpackage().getname();

       // system.out.println(str);//cn.com.wx  输出的结果,分割是用点分割的,所有要替换成斜杠
        
        //最后在于我们的主路径拼接
       string scandir=dir+str.replace(".", "/");
       // system.out.println(scandir);
       list<string> list =new arraylist<string>();
       
       beanfactory context =new test();
       context.fild(scandir, list);
       context.cover(dir, list);
       for (string string : list) {
        system.out.println(string);
    }
       
       
        
    }
}

 

输出结果:起始正题的思想并没有变,只是我们把开发的过程变成了面向接口的开发:并且将来我们要在在test方法中建立反射,而获取的结果包名点类名:就是我们所说的全局限定名

cn.com.wx.controller.usercontroller
cn.com.wx.dao.userdao
cn.com.wx.files.covers
cn.com.wx.files.junitfile
cn.com.wx.pojo.user.user
cn.com.wx.runapp
cn.com.wx.service.userservice
cn.com.wx.test.beanfactory
cn.com.wx.test.test

 

在我们spring使用的过程中是使用注解的开发方式,而我们的底层也是注解的方式,用注解去标识三个层:控制层业务层持久层,所以就要建立三个注解去标识他们:

controller注解

package cn.com.wx.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 service {

}

 

package cn.com.wx.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 controller {

}

 

package cn.com.wx.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 repository {

}

 

并在每一层的类上面去引用注解:

package cn.com.wx.service;

import cn.com.wx.annotation.service;
import cn.com.wx.dao.userdao;
import cn.com.wx.pojo.user.user;
@service//加入注解
public class userservice {
    private userdao userdao;
    
    public user get(integer id,string name) {
        
        user user=userdao.selectone(id, name);
        return user;
        
    }
}

 

package cn.com.wx.controller;

import cn.com.wx.annotation.controller;
import cn.com.wx.service.userservice;
import cn.com.wx.pojo.user.user;
@controller//加入注解
public class usercontroller {
    private userservice userservice ;
    public user get(integer id, string name) {
        user user =userservice.get(id, name);
        return user;
        
        
    }
}

 

package cn.com.wx.dao;

import cn.com.wx.annotation.repository;
import cn.com.wx.pojo.user.user;
@repository//加入注解
public class userdao {
    public user selectone(integer id,string name) {
        user user =new user();
        user.setid(id);
        user.setname(name);
        return user;
        
    }
}

 

然后通过全局限定名,利用反射class.forname方法创建类。遍历这些文件,判断其上面有无@controller注解或者@service注解,如果没有继续循环,如果有其一,或者@controller或者@service就去根据反射获取它的类上注解来判断。对他创建对象,利用反射clazz.newinstance的方法创建对象实例,把它暂存到一个容器中,而容器容器实际是一个map集合,map集合有key,有valuekey就是类的全路径,value就是对象实例。

这样做有什么好处,对象创建直接就可以通过包扫描机制在类调用之前(初始化阶段)全都放入容器,创建好。

在需要用的时候,直接从容器中获取。对象都创建好了,性能高,代码少。

下面我们在cover方法中建立反射:

package cn.com.wx.test;

import java.util.arraylist;
import java.util.hashmap;
import java.util.list;
import java.util.map;

import cn.com.wx.annotation.controller;
import cn.com.wx.annotation.repository;
import cn.com.wx.annotation.service;
import cn.com.wx.files.covers;
import cn.com.wx.files.junitfile;

public class test implements beanfactory {
    //创建容器
    private static final map<string, object> beans =new hashmap<string, object>();

    list<string> list = new arraylist<string>();// 创建集合用于接受junnitfile的返回值
    // 这个路径是我们项目的路径,一定要根据自己工作空间建的项目的目录
    // string dir = "c:\\users\\wangxiao\\eclipse-workspace\\bokespringioc\\bin\\";

    public list<string> fild(string dir, list<string> list) {
        list<string> dirlist = junitfile.getfilename(dir, list);
        return dirlist;
    }

    public void cover(string scandir, list<string> list) throws exception {
        list<string> coverlist = covers.getscanname(scandir, list);
        for (string classname : coverlist) {
            class<?> clazz = class.forname(classname);// 需要抛异常,记得把接口和实现的方法都抛异常

            // 通过反射拿到注解标识的类名
            controller controller = clazz.getannotation(controller.class);
            service service = clazz.getannotation(service.class);
            repository repository = clazz.getannotation(repository.class);
            // 判断如果拿到了注解类,就不会是空值,所以这里加上判断
            if (controller != null || service != null || repository != null) {
                //一旦我们拿到注解类,就要创建实例对象
                object obj = clazz.newinstance();
                beans.put(classname, obj);
                
            }

        }
        for (string key : beans.keyset()) {
            system.out.println(beans.get(key));
        }

    }

}

 

执行runapp:输出结果为:可以看见前三个输出就是我们获取的对象

cn.com.wx.service.userservice@45ee12a7
cn.com.wx.controller.usercontroller@330bedb4
cn.com.wx.dao.userdao@2503dbd3
cn.com.wx.annotation.controller
cn.com.wx.annotation.repository
cn.com.wx.annotation.service
cn.com.wx.controller.usercontroller
cn.com.wx.dao.userdao
cn.com.wx.files.covers
cn.com.wx.files.junitfile
cn.com.wx.pojo.user.user
cn.com.wx.runapp
cn.com.wx.service.userservice
cn.com.wx.test.beanfactory
cn.com.wx.test.test

 这时候我们放进容器的是class的路径对象,我们定义的时候是定义成 private userservice userservice的模式,而我们当控制层去调用业务成和业务层调用持久层的时候是拿的usersevice实例对象,这时候需求来了,我们要吧包名去掉,把首字母变成小写;这时候我们就需要写一个方法,把

反射拿到的类名转换成容器正真需要的格式:在covers类写一个方法 

package cn.com.wx.files;

import java.util.list;

public class covers {
    // c:\users\wangxiao\eclipse-workspace\bokespringioc\bin\cn\com\wx\test\test.class
    // c:\users\wangxiao\eclipse-workspace\bokespringioc\bin\

    // 看着上边的目录我们来分析问题:首选我们已经把标准目录拿到了,放到一个list集合中,集合作为参数传到我的getscanname方法中
    // 这样我只需要一个需要截取的部分的字符串,这里我用scandir来表示
    public static list<string> getscanname(string scandir, list<string> list) {
        // scandir=c:\users\wangxiao\eclipse-workspace\bokespringioc\bin\

        // 下一步分析:假定我们截取完字符串,那我们是不是还要把它放回去,放回去就要知道我们的角标,而普通的foreach循环满足不了,我们用普通的for循环来替代
        for (int i = 0; i < list.size(); i++) {
            string strname = list.get(i);// 逐个去拿他的目录
            // 先替换虽有的斜杠
            strname = strname.replace("\\", "/");
            scandir = scandir.replace("\\", "/");
            // 干掉scandir部分
            strname = strname.replace(scandir, "");
            // 从后边拿到点出现的位置
            int pos = strname.lastindexof(".");
            strname = strname.substring(0, pos);
            // 这样我们就拿到了这样的格式
            // cn/com/wx/test/test
            // 最后再把斜杠替换成点
            strname = strname.replace("/", ".");
            // 最后再放回集合中
            list.set(i, strname);
        }
        return list;
    }

    public static string getbeanname(string classname) {
        // 拿去点的最后边的位置加上一
        int pos = classname.lastindexof(".") + 1;
        classname = classname.substring(pos);
        
        //把第一个字母变成小写,并且拼接字符串
        classname =classname.tolowercase().charat(0)+classname.substring(1);

        return classname;

    }
}

 

 之后我们就需要修改反射的内容,把之前放入容器得classname转成beanname格式:

                                                                                                                                                                                                                                下面是test类的反射变化

public void cover(string scandir, list<string> list) throws exception {
        list<string> coverlist = covers.getscanname(scandir, list);
        for (string classname : coverlist) {
            class<?> clazz = class.forname(classname);// 需要抛异常,记得把接口和实现的方法都抛异常

            // 通过反射拿到注解标识的类名
            controller controller = clazz.getannotation(controller.class);
            service service = clazz.getannotation(service.class);
            repository repository = clazz.getannotation(repository.class);
            // 判断如果拿到了注解类,就不会是空值,所以这里加上判断
            string beanname="";//这里我是方便看,把classname转成beanname
            if (controller != null || service != null || repository != null) {
                //一旦我们拿到注解类,就要创建实例对象
                object obj = clazz.newinstance();
                beanname=covers.getbeanname(classname);
                //把转换好的beanname放进容器
                beans.put(beanname, obj);
            }

        }
        for (string key : beans.keyset()) {
            system.out.println(beans.get(key));
        }

    }

 

 这时候只要我们需要容器中key值就可以去取容器中查找,看有没有我需要的实例名称,而容器中value值就是实例名称对应的对象实例,这样,以后我们在使用的时候就不要new了,我们直接从容器中获取就可以了:

首先在我们的接口方法中加上我们的方法:

package cn.com.wx.test;

import java.util.list;

public interface beanfactory {
    public list<string> fild(string dir, list<string> list) ;
    
    public void cover(string scandir, list<string> list) throws exception;
    
    //从容器中调用实例对象
    public<t> t getbean(string beanname);
}

 

package cn.com.wx.test;

import java.util.arraylist;
import java.util.hashmap;
import java.util.list;
import java.util.map;

import cn.com.wx.annotation.controller;
import cn.com.wx.annotation.repository;
import cn.com.wx.annotation.service;
import cn.com.wx.files.covers;
import cn.com.wx.files.junitfile;

public class test implements beanfactory {
    //创建容器
    private static final map<string, object> beans =new hashmap<string, object>();

    list<string> list = new arraylist<string>();// 创建集合用于接受junnitfile的返回值
    // 这个路径是我们项目的路径,一定要根据自己工作空间建的项目的目录
    // string dir = "c:\\users\\wangxiao\\eclipse-workspace\\bokespringioc\\bin\\";

    public list<string> fild(string dir, list<string> list) {
        list<string> dirlist = junitfile.getfilename(dir, list);
        return dirlist;
    }

    public void cover(string scandir, list<string> list) throws exception {
        list<string> coverlist = covers.getscanname(scandir, list);
        for (string classname : coverlist) {
            class<?> clazz = class.forname(classname);// 需要抛异常,记得把接口和实现的方法都抛异常

            // 通过反射拿到注解标识的类名
            controller controller = clazz.getannotation(controller.class);
            service service = clazz.getannotation(service.class);
            repository repository = clazz.getannotation(repository.class);
            // 判断如果拿到了注解类,就不会是空值,所以这里加上判断
            string beanname="";//这里我是方便看,把classname转成beanname
            if (controller != null || service != null || repository != null) {
                //一旦我们拿到注解类,就要创建实例对象
                object obj = clazz.newinstance();
                beanname=covers.getbeanname(classname);
                //把转换好的beanname放进容器
                beans.put(beanname, obj);
            }

        }
        for (string key : beans.keyset()) {
            system.out.println(beans.get(key));
        }

    }
    
    //<t>代表后面出现t就是泛型,t代表返回值
    //也称为泛型方法
    public<t> t getbean(string beanname) {
        return (t) beans.get(beanname);
        
    }

}

 

 这样我们就可以在runapp方法中去测试,看我们能不能找到实例对象:在最下面测试我们的方法

       userservice us =context.getbean("userservice");
       system.out.println("这是从容器中拿到的"+us);
       

 

 输出结果:可以看见我们的实例对象已经放到容器中了:这样我们ioc的底层就实现了。

cn.com.wx.controller.usercontroller@45ee12a7
cn.com.wx.dao.userdao@330bedb4
cn.com.wx.service.userservice@2503dbd3
这是从容器中拿到的cn.com.wx.service.userservice@2503dbd3

 

 这样我们就可以通过三个层调用方法了:在runapp下面加上测试

        usercontroller uc = context.getbean("usercontroller");
        userservice us = context.getbean("userservice");
        userdao ud = context.getbean("userdao");

        uc.setuserservice(us);
        us.setuserdao(ud);
        user user = uc.get(5,"马云");
        system.out.println(user);

 

 我们看下输出结果:

cn.com.wx.controller.usercontroller@45ee12a7
cn.com.wx.dao.userdao@330bedb4
cn.com.wx.service.userservice@2503dbd3
user [id=5, name=马云]

 

 di(依赖注入)

   uc.setuserservice(us);
        us.setuserdao(ud);

注意我上边写的代码,我是在usercontroller和userservice类中定义了set方法,然后这里才可以调用,继而把参数传过去,其一:这是没有太多的属性,其二:开发过程麻烦,
一不小心忘记写了哪一步,就会报空指针异常。那么近引进我们得di(依赖注入),也叫自动驻入,我认为自动注入这个名字更能体现他,让我们的di去帮我们实现注入(也就是去set)
他,那么我们的usercontroller和usereservice类就不用写set方法了,直接一个注解就搞定,实现的时候,我们也不用去调用了set了。

              网上大部分人说ioc是概念而di才是真正的去实现ioc,但是我认为这种说话是不正确的,我认为di是ioc的重要实现功能,是在有ioc的前提下,有容器,有属性

之后di再去实现,di它实现了对象之间关系的绑定,判断什么类需要进行诸如,通过我们的@autowired注解识别,然后从容器中拿到对象。

      首先我们来定义注解@autowired来定义di:

package cn.com.wx.annotation;

import java.lang.annotation.elementtype;
import java.lang.annotation.retention;
import java.lang.annotation.retentionpolicy;
import java.lang.annotation.target;
import java.net.authenticator.requestortype;

@target(elementtype.field)
@retention(retentionpolicy.runtime)
public @interface autowired {

}

 

 这时候我们就可以把usercontroller和userservice:中的set方法直接去掉,然后在私有属性上定义注解:

package cn.com.wx.controller;

import cn.com.wx.annotation.autowired;
import cn.com.wx.annotation.controller;
import cn.com.wx.service.userservice;
import cn.com.wx.pojo.user.user;

@controller
//加入注解
public class usercontroller {
    @autowired
    private userservice userservice;

//    public void setuserservice(userservice userservice) {
//        this.userservice = userservice;
//    }

    public user get(integer id ,string name) {
        user user = userservice.get(id,name);
        return user;

    }
}
package cn.com.wx.service;

import cn.com.wx.annotation.autowired;
import cn.com.wx.annotation.service;
import cn.com.wx.dao.userdao;
import cn.com.wx.pojo.user.user;
@service
public class userservice {
    @autowired
    private userdao userdao;
       
//    public void setuserdao(userdao userdao) {
//        this.userdao = userdao;
//    }

    public user get(integer id,string name) {
        
        user user=userdao.selectone(id,name);
        return user;
        
    }
}

 

这时候我满看见runapp类的set的两个方法报错,我们直接去掉就可以,因为我们已经不需要set去注入了

在我们的接口中加入我们的inject方法:因为这里我们要用倒反射,所以后面呢肯定会抛异常,这里我直接提前抛出异常

package cn.com.wx.test;

import java.util.list;

public interface beanfactory {
    public list<string> fild(string dir, list<string> list) ;
    
    public void cover(string scandir, list<string> list) throws exception;
    
    //从容器中调用实例对象
    public<t> t getbean(string beanname);
    
    //自动注入
    public void inject() throws exception;
}

先分析放射的步骤:先获取到当前对象,通过当前对象getclass方法就获得class类,通过class类的getdeclaredfield()获取这个类上的所有的属性,declea。。这个方法可以获取类的成员变量,而且可以获取私有的。但是私有属性在操作时,

必须先设置其访问权限可用。然后利用反射属性set方法,设置它的

    在test类中加上我们的inject方法:里边的每一步我都有注释,解释当前这一步的目的

package cn.com.wx.test;

import java.lang.reflect.field;
import java.util.arraylist;
import java.util.hashmap;
import java.util.list;
import java.util.map;

import cn.com.wx.annotation.autowired;
import cn.com.wx.annotation.controller;
import cn.com.wx.annotation.repository;
import cn.com.wx.annotation.service;
import cn.com.wx.files.covers;
import cn.com.wx.files.junitfile;

public class test implements beanfactory {
    //创建容器
    private static final map<string, object> beans =new hashmap<string, object>();

    list<string> list = new arraylist<string>();// 创建集合用于接受junnitfile的返回值
    // 这个路径是我们项目的路径,一定要根据自己工作空间建的项目的目录
    // string dir = "c:\\users\\wangxiao\\eclipse-workspace\\bokespringioc\\bin\\";

    public list<string> fild(string dir, list<string> list) {
        list<string> dirlist = junitfile.getfilename(dir, list);
        return dirlist;
    }

    public void cover(string scandir, list<string> list) throws exception {
        list<string> coverlist = covers.getscanname(scandir, list);
        for (string classname : coverlist) {
            class<?> clazz = class.forname(classname);// 需要抛异常,记得把接口和实现的方法都抛异常

            // 通过反射拿到注解标识的类名
            controller controller = clazz.getannotation(controller.class);
            service service = clazz.getannotation(service.class);
            repository repository = clazz.getannotation(repository.class);
            // 判断如果拿到了注解类,就不会是空值,所以这里加上判断
            string beanname="";//这里我是方便看,把classname转成beanname
            if (controller != null || service != null || repository != null) {
                //一旦我们拿到注解类,就要创建实例对象
                object obj = clazz.newinstance();
                beanname=covers.getbeanname(classname);
                //把转换好的beanname放进容器
                beans.put(beanname, obj);
                system.out.println(beanname);
            }

        }
        for (string key : beans.keyset()) {
            system.out.println(beans.get(key));
        }

    }
    
    //<t>代表后面出现t就是泛型,t代表返回值
    //也称为泛型方法
    public<t> t getbean(string beanname) {
        return (t) beans.get(beanname);
    }
    
    public void inject() throws exception {
        for (string beanname : beans.keyset()) {
            //先从容器中获取对象
            object obj= getbean(beanname);
            //创建反射
            class<?> clazz =obj.getclass();
            //遍历所有属性:找到私有属性,然后判断他是否有autowired注解
            //这里边我只写了一个私有的,但是底层不可能就是只能去获取一个,
            //可能一个controller层有多个私有的属性,但是不一定每一个属性上都有autowired注解,这时候我们就需要用一个数组去接受
            //反射的基本apideclared获取私有的 fields获取属性,最后我们用一个数组作为返回值;
            field[] field =    clazz.getdeclaredfields();
            //但是不一定每一个属性上都有autowired注解,所以我们需要去判断一下
            for (field f : field) {
                //属性是私有的,访问需要开通权限
                f.setaccessible(true);
                
                autowired autowired =f.getannotation(autowired.class);
                //判断私有属性上是否有注解,有注解就不为空
                if(autowired!=null) {
                    //拿到注解,就证明我们这个私有属性需要去注入,下面就是注入
                    //拿到属性名,这个getname不是我写的方法,他是反射源码自己定义的一个方法,鼠标放上去,看到返回值是string类型,定义一个变量去接收。
                    string claname=f.getname();
                    //注入
                    f.set(obj, getbean(claname));
                }
                
            }
            
        }
    }
    
}

 

 然后在我们的runapp上调用我们的inject方法(一定不要忘记这一步,不写是不会调用自动注入,自然运行就会空指针异常)

 

package cn.com.wx;

import java.util.arraylist;
import java.util.list;

import cn.com.wx.controller.usercontroller;
import cn.com.wx.dao.userdao;
import cn.com.wx.service.userservice;
import cn.com.wx.pojo.user.user;
import cn.com.wx.test.beanfactory;
import cn.com.wx.test.test;

public class runapp {
    public static void main(string[] args) throws exception {
        // 获取主路径(bin之前)
        // string dir=runapp.class.getresource("/").getpath()
        // /c:/users/wangxiao/eclipse-workspace/bokespringioc/bin/
        // 上面是正常的获取主路径但是获取的路径前面会有一个斜杠,所以我们要加上一个substring的api去掉斜杠

        // 主路径
        string dir = runapp.class.getresource("/").getpath().substring(1);
        // system.out.println(dir);

        // 包路径
        string str = runapp.class.getpackage().getname();

        // system.out.println(str);//cn.com.wx 输出的结果,分割是用点分割的,所有要替换成斜杠

        // 最后在于我们的主路径拼接
        string scandir = dir + str.replace(".", "/");
        // system.out.println(scandir);
        list<string> list = new arraylist<string>();

        beanfactory context = new test();
        context.fild(scandir, list);
        context.cover(dir, list);
//       for (string string : list) {
//        system.out.println(string);
//      }

//       userservice us =context.getbean("userservice");
//       system.out.println("这是从容器中拿到的"+us);
        context.inject();
        usercontroller uc = context.getbean("usercontroller");
        userservice us = context.getbean("userservice");
        userdao ud = context.getbean("userdao");

//        uc.setuserservice(us);
//        us.setuserdao(ud);
        
        
        user user = uc.get(5,"马云");
        system.out.println(user);
    }
}

 

 运行结果:

cn.com.wx.controller.usercontroller@45ee12a7
cn.com.wx.dao.userdao@330bedb4
cn.com.wx.service.userservice@2503dbd3
user [id=5, name=马云]

 

 这样一来注入就成功,看到这个底层我们就能明白我上边所说的,di是在ioc的前提下完成的。

       最后说一下,我还是希望大家能耐下心来花几天的时间吧这些代码搞一下,收获还有蛮大的,前几次写可能某一步不能理解,但是多敲几遍,其义自见。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

package cn.com.wx;
import java.util.arraylist;import java.util.list;
import cn.com.wx.test.beanfactory;import cn.com.wx.test.test;
public class runapp {public static void main(string[] args) {//获取主路径(bin之前)// string dir=runapp.class.getresource("/").getpath()//      /c:/users/wangxiao/eclipse-workspace/bokespringioc/bin///上面是正常的获取主路径但是获取的路径前面会有一个斜杠,所以我们要加上一个substring的api去掉斜杠//主路径        string dir=runapp.class.getresource("/").getpath().substring(1);        //system.out.println(dir);                //包路径        string str=runapp.class.getpackage().getname();
       // system.out.println(str);//cn.com.wx  输出的结果,分割是用点分割的,所有要替换成斜杠                //最后在于我们的主路径拼接       string scandir=dir+str.replace(".", "/");       // system.out.println(scandir);       list<string> list =new arraylist<string>();              beanfactory context =new test();       context.fild(scandir, list);       context.cover(dir, list);       for (string string : list) {system.out.println(string);}                      }}