再我们现在项目中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 底层(分分钟喷倒面试官)了解一下的更多相关文章

  1. 从零开始手写 spring ioc 框架,深入学习 spring 源码

    IoC Ioc 是一款 spring ioc 核心功能简化实现版本,便于学习和理解原理. 创作目的 使用 spring 很长时间,对于 spring 使用非常频繁,实际上对于源码一直没有静下心来学习过 ...

  2. 我自横刀向天笑,手写Spring IOC容器,快来Look Look!

    目录 IOC分析 IOC是什么 IOC能够带来什么好处 IOC容器是做什么工作的 IOC容器是否是工厂模式的实例 IOC设计实现 设计IOC需要什么 定义接口 一:Bean工厂接口 二:Bean定义的 ...

  3. 手写Spring Config,最终一战,来瞅瞅撒!

    上一篇说到了手写Spring AOP,来进行功能的增强,下面本篇内容主要是手写Spring Config.通过配置的方式来使用Spring 前面内容链接: 我自横刀向天笑,手写Spring IOC容器 ...

  4. 手写Spring DI依赖注入,嘿,你的益达!

    目录 提前实例化单例Bean DI分析 DI的实现 构造参数依赖 一:定义分析 二:定义一个类BeanReference 三:BeanDefinition接口及其实现类 四:DefaultBeanFa ...

  5. 手写Spring AOP,快来瞧一瞧看一看撒!

    目录 AOP分析 Advice实现 定义Advice接口 定义前置.后置.环绕和异常增强接口 Pointcut实现 定义PointCut接口 定义正则表达式的实现类:RegExpressionPoin ...

  6. -手写Spring注解版本&事务传播行为

    视频参考C:\Users\Administrator\Desktop\蚂蚁3期\[www.zxit8.com] 0018-(每特教育&每特学院&蚂蚁课堂)-3期-源码分析-手写Spri ...

  7. 《四 spring源码》利用TransactionManager手写spring的aop

    事务控制分类 编程式事务控制          自己手动控制事务,就叫做编程式事务控制. Jdbc代码: Conn.setAutoCommite(false);  // 设置手动控制事务 Hibern ...

  8. 手写 Spring

    手写 Spring 不多说,简历装 X 必备.不过练好还是需要求一定的思维能力. 一.整体思路 思路要熟练背下来 1)配置阶段 配置 web.xml: XDispatchServlet 设定 init ...

  9. 手写Spring+demo+思路

    我在学习Spring的时候,感觉Spring是很难的,通过学习后,发现Spring没有那么难,只有你去学习了,你才会发现,你才会进步 1.手写Spring思路: 分为配置.初始化.运行三个阶段如下图 ...

随机推荐

  1. python函数知识二 动态参数、函数的注释、名称空间、函数的嵌套、global,nonlocal

    6.函数的动态参数 *args,**kwargs:能接受动态的位置参数和动态的关键字参数 *args -- tuple *kwargs -- dict 动态参数优先级:位置参数 > 动态位置参数 ...

  2. 初识nginx!

    What--什么是nginx nginx是一款高性能的http服务器/反向代理服务器及电子邮件(IMAP/POP3)代理服务器.官方测试nginx能够支撑5w并发连接.并且cup.内存等资源消耗却非常 ...

  3. [leetcode] 62 Unique Paths (Medium)

    原题链接 字母题 : unique paths Ⅱ 思路: dp[i][j]保存走到第i,j格共有几种走法. 因为只能走→或者↓,所以边界条件dp[0][j]+=dp[0][j-1] 同时容易得出递推 ...

  4. c实现哲学家进餐问题。WINDOWS下。

    // 解决哲学家就餐问题// 每个哲学家可用一个线程来模拟.// 设有5个哲学家,5只筷子,每个哲学家吃饭时间为一个随机值,哲学家吃饭后的思考时间也是一个随机值.#include <Window ...

  5. Java EE.JavaBean

    JavaBean是一组可移植.可重用.并可以组装到应用程序中的Java类.一个Model类(属性+构造函数).

  6. HTML --- <a href=”#”>与 <a href=”javascript:void(0)” 的区别

    <a href=”#”>中的“#”其实是锚点的意思,默认为#top,所以当页面比较长的时候,使用这种方式会让页面刷新到页首(页面的最上部) javascript:void(0)其实是一个死 ...

  7. 配置 IDEA 远程连接应用服务器

    当调试 Web 应用时,经常需要使用 ide 远程连接,来进行 debug 调试.使用 Springboot 内置服务器和使用 Tomcat 服务器是常见的应用部署方式,可以用不同的配置方式来启动远程 ...

  8. Tips 14:思维导图读书笔记法

    Tips 14:思维导图读书笔记法作读书笔记不仅能提高阅读书.文的效率,而且能提高科学研究和写作能力.读书笔记一般分为摘录.提纲.批注.心得几种,这里特别推荐思维导图式的读书笔记. 通过思维导图先大概 ...

  9. Apache和Spring提供的StopWatch执行时间监视器

    相关阅读 [小家java]java5新特性(简述十大新特性) 重要一跃 [小家java]java6新特性(简述十大新特性) 鸡肋升级 [小家java]java7新特性(简述八大新特性) 不温不火 [小 ...

  10. python 读取文件1

    1.脚本 from sys import argv script,filename = argv txt = open(filename) print ("the filename is % ...