利用递归,反射,注解等,手写Spring Ioc和Di 底层(分分钟喷倒面试官)了解一下
再我们现在项目中Spring框架是目前各大公司必不可少的技术,而大家都知道去怎么使用Spring ,但是有很多人都不知道SpringIoc底层是如何工作的,而一个开发人员知道他的源码,底层工作原理,对于我们对项目的理解是有非常大的帮助的,有可能工作了两三年的中级工程师,乃至四五年的,只知其然,却不知其所以然。我的一个盆友,今年年初以实习生的身份去北京面试 ,面试官让我的朋友说Spring源码,作为一个实习生,就要去知道Spring的源码。虽然我们可以不用知道,也可以做项目,但他会成为我们面试结果的绊脚石,
而各个公司面试喜欢提问都是Spring的原理,底层,源码。而看了今天的文章,再去面试我们就不用怕了。
当面试官问我们,了解Spring吗,我们回答,了解而且对Ioc还很深入,这时候,面试官的兴趣一下子就被你勾引了,心想,一个小菜鸟居然敢说听深入,他必会让你讲,殊不知他已经上当了,这时候我们给他来一手手写SpringIoc和Di的工作原理,而把这些都写完,解释完,怎么的也得半个多小时过去了,而面试官不会去花太多时间去面试一个人,甚至有可能你把这个问题说完,随便问几句,直接就录用你了,(因为面试官都有可能都知道他底层的工作原理)这样即使面试官百分之八十就会高看我们一眼,起始的薪资呢,也不会低。
进入正题:一张图就可以看出我们的整体结构。是用MVC模式,去引出我们的底层代码。
Spring Ioc                                                       
首先创建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,有value,key就是类的全路径,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 = ; 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(, 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(".") + ;
        classname = classname.substring(pos);
        //把第一个字母变成小写,并且拼接字符串
        classname =classname.toLowerCase().charAt()+classname.substring();
        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();
// 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(,"马云");
System.out.println(user);
}
}
运行结果:
cn.com.wx.Controller.UserController@45ee12a7
cn.com.wx.Dao.UserDao@330bedb4
cn.com.wx.Service.UserService@2503dbd3
User [id=, name=马云]
这样一来注入就成功,看到这个底层我们就能明白我上边所说的,Di是在ioc的前提下完成的。
最后说一下,我还是希望大家能耐下心来花几天的时间吧这些代码搞一下,收获还有蛮大的,前几次写可能某一步不能理解,但是多敲几遍,其义自见。
利用递归,反射,注解等,手写Spring Ioc和Di 底层(分分钟喷倒面试官)了解一下的更多相关文章
- 从零开始手写 spring ioc 框架,深入学习 spring 源码
		
IoC Ioc 是一款 spring ioc 核心功能简化实现版本,便于学习和理解原理. 创作目的 使用 spring 很长时间,对于 spring 使用非常频繁,实际上对于源码一直没有静下心来学习过 ...
 - 我自横刀向天笑,手写Spring IOC容器,快来Look Look!
		
目录 IOC分析 IOC是什么 IOC能够带来什么好处 IOC容器是做什么工作的 IOC容器是否是工厂模式的实例 IOC设计实现 设计IOC需要什么 定义接口 一:Bean工厂接口 二:Bean定义的 ...
 - 手写Spring Config,最终一战,来瞅瞅撒!
		
上一篇说到了手写Spring AOP,来进行功能的增强,下面本篇内容主要是手写Spring Config.通过配置的方式来使用Spring 前面内容链接: 我自横刀向天笑,手写Spring IOC容器 ...
 - 手写Spring DI依赖注入,嘿,你的益达!
		
目录 提前实例化单例Bean DI分析 DI的实现 构造参数依赖 一:定义分析 二:定义一个类BeanReference 三:BeanDefinition接口及其实现类 四:DefaultBeanFa ...
 - 手写Spring AOP,快来瞧一瞧看一看撒!
		
目录 AOP分析 Advice实现 定义Advice接口 定义前置.后置.环绕和异常增强接口 Pointcut实现 定义PointCut接口 定义正则表达式的实现类:RegExpressionPoin ...
 - -手写Spring注解版本&事务传播行为
		
视频参考C:\Users\Administrator\Desktop\蚂蚁3期\[www.zxit8.com] 0018-(每特教育&每特学院&蚂蚁课堂)-3期-源码分析-手写Spri ...
 - 《四 spring源码》利用TransactionManager手写spring的aop
		
事务控制分类 编程式事务控制 自己手动控制事务,就叫做编程式事务控制. Jdbc代码: Conn.setAutoCommite(false); // 设置手动控制事务 Hibern ...
 - 手写 Spring
		
手写 Spring 不多说,简历装 X 必备.不过练好还是需要求一定的思维能力. 一.整体思路 思路要熟练背下来 1)配置阶段 配置 web.xml: XDispatchServlet 设定 init ...
 - 手写Spring+demo+思路
		
我在学习Spring的时候,感觉Spring是很难的,通过学习后,发现Spring没有那么难,只有你去学习了,你才会发现,你才会进步 1.手写Spring思路: 分为配置.初始化.运行三个阶段如下图 ...
 
随机推荐
- Sqoop学习及使用
			
Sqoop 简介 Sql + Hadoop = Sqoop Apache Sqoop™是一种旨在有效地在 Apache Hadoop 和诸如关系数据库等结构化数据存 储之间传输大量数据的工具 原理 将 ...
 - python课堂整理19----迭代器和生成器
			
一.概念 • 迭代器协议: 对象必须提供一个next方法,执行该方法要么返回迭代中的下一项,要么引起一个stopIteration异常,以终止迭代(只能往后走,不能往前退) • 协议是一种约定,pyt ...
 - linux基础命令期末考试总结
			
1.关闭防火墙:service iptables stop 2.启动防火墙:service iptables start 3.mount命令:挂载某一设备使之成为某个目录名称 4.NFS服务:linu ...
 - python 读取文件1
			
1.脚本 from sys import argv script,filename = argv txt = open(filename) print ("the filename is % ...
 - “$Bitmap 有标记已使用的未用簇”
			
前几天在电脑上用 DiskGenius 给移动硬盘分区的时候出现了这个错误,如下图所示: 解决方法: 在 cmd 命令行窗口中输入如下代码: chkdsk /f /x c: PS: 其中 " ...
 - 【iOS】获取项目名和版本号
			
iOS 开发中,有时候需要获取项目名和版本号,示例代码如下: -(void)getProjectNameAndVersion{ appName = [[[NSBundle mainBundle] in ...
 - 对比度拉伸(一些基本的灰度变换函数)基本原理及Python实现
			
1. 基本原理 对比度拉伸是扩展图像灰度级动态范围的处理.通过在灰度级中确定两个点来控制变换函数的形状.下面是对比度拉伸函数中阈值处理的代码示例,阈值为平均值. 2. 测试结果 图源自skimage ...
 - Spring Cloud 之 Stream.
			
一.简介 Spring Cloud Stream 是一个用来为微服务应用构建消息驱动能力的框架. Spring Cloud Stream 为一些供应商的消息中间件产品(目前集成了 RabbitMQ 和 ...
 - Python中input()的使用方法
			
input()以字符串的方式获取用户输入: >>> x = input() 4.5 >>> type(x) <class 'str'> >> ...
 - Python 之父的解析器系列之三:生成一个 PEG 解析器
			
原题 | Generating a PEG Parser 作者 | Guido van Rossum(Python之父) 译者 | 豌豆花下猫("Python猫"公众号作者) 声明 ...