这个小demo是利用反射从最基础一步一步模拟实现了IOC的功能,所有的代码基本都给出了注释,方便大家阅读.
目录结构:
这里需要导入一下junit依赖
<!-- junit测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
两个工具类:
TestController类:
package com.heaboy.springioc.entity;
import com.heaboy.springioc.stereotype.Autowired;
import com.heaboy.springioc.stereotype.Component;
import sun.misc.Contended;
@Component
public class TestController {
@Autowired
private UserService userService;
public void test(){
//通过反射拿到userService对象调用addUser方法
userService.addUser("zhangsan",111);
}
}
UserService类:
package com.heaboy.springioc.entity;
import com.heaboy.springioc.stereotype.Component;
@Component
public class UserService {
//接受传参 并且输出
public void addUser(String name,int age){
System.out.println(name);
System.out.println(age);
}
}
两个注解类:
Autowired:
package com.heaboy.springioc.stereotype;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Autowired {
}
Component:
package com.heaboy.springioc.stereotype;
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 Component {
}
最重要的模拟IOC类:
package com.heaboy.springioc.ioc;
import com.heaboy.springioc.stereotype.Autowired;
import com.heaboy.springioc.stereotype.Component;
import java.io.File;
import java.io.FileNotFoundException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.*;
public class SpringIOC {
//声明存放bean名称的列表
private List<String> beanNames;
//声明存放文件路径的列表
private List<String> filePaths;
//声明基础路径字符串
private String basePath;
//声明基础包名字符串
private String basePackage;
//声明存放bean对象的映射
private Map<String,Object> beans = new HashMap<>();
/**
* 扫描所有的文件信息,存放到filePaths
*/
private void scan() throws FileNotFoundException{
//创建File 对象表示基础路径
File file = new File(basePath);
//初始化存放文件路径的列表
filePaths = new ArrayList<>();
//判断文件是否存在
if(file.exists()){
//定义一个列表 用于逐层深入的遍历每个文件(广度优先搜索)
Queue<File> queue = new LinkedList<>();
//将根文件添加到队列当中
queue.add(file);
//判断队列是否为空
while (!queue.isEmpty()){
//取出 根文件赋值给poll便于逐层深入
File poll = queue.poll();
//如果poll是空说明他下面已经不存在文件和文件路径了 直接跳过执行下一步
if(poll == null){
continue;
}
//如果是文件夹
if(poll.isDirectory()){
//创建一个File类型的数组 拿到poll里面的全部文件,文件夹
File[] files = poll.listFiles();
for (File file1:files){
//将poll里面的全部文件夹以及文件加入队列
queue.add(file1);
}
//如果此poll是文件
}else {
//直接将文件的路径添加到filePaths列表中
filePaths.add(poll.getPath());
}
}
}else {
//如果没找到文件
//抛出文件未找到异常
throw new FileNotFoundException(basePath+"not found");
}
}
/**
* 将所有的.java结尾的 全限定名放到 beanNames 列表里面
*/
public void initBeanNames(){
//遍历 filePaths里面的每一个文件的路径
for (String s :filePaths){
//将文件中的 基础路径部分替换成空串(因为所有文件路径的基础路径都是一样的,
// 我们需要获取的是以.java结尾的文件的全限定名 前缀需要去掉)
String replace = s.replace(basePath,"");
//判断文件的全限定名是不是以.java结尾
if (replace.endsWith(".java")){
//截取掉.java
replace = replace.substring(0,replace.length()-5);
}
//将字符串转换成字符数组 目的是为了方便把路径中的\\替换成.
char[] chars = replace.toCharArray();
for (int i=0;i<chars.length;i++){
if(chars[i]=='\\'){
chars[i]='.';
}
}
//拼接字符串 拼接出文件在该项目中的源根地址
beanNames.add(basePackage+"."+new String(chars));
}
}
/**
* 初始化所有的Bean对象 根据上个方法求出来的所有文件的源根地址
* 遍历每一个文件(其实就是java文件) 看四否含有Component注解(也就是被标记为
* 它表明这个类会被 Spring IoC 容器自动检测和注册为 Spring 的一个 bean。
* 也就是把类交给spring管理)
*/
public void initBeans(){
for (String beanName : beanNames){
try {
//根据路径 拿到具体的类对象
Class<?> aClass = Class.forName(beanName);
//拿到这个类声明的所有注解
Annotation[] declaredAnnotations = aClass.getDeclaredAnnotations();
//遍历所有注解 看是否存在@Component注解
for (Annotation declaredAnnotation : declaredAnnotations){
//如果是Component注解
if(declaredAnnotation instanceof Component){
//创建该类的实例化对象
Object o =aClass.newInstance();
//将对象放入beans映射中
//aClass.getName() 类名 o 实例化对象
beans.put(aClass.getName(),o);
}
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
//依次遍历 查看是否有Autowired注解 如果有,给对应字段附上相应的实例化对象
for (Map.Entry<String,Object> entry : beans.entrySet()){
//利用反射获取到对象对应类的所有声明字段
Field[] declaredFields = entry.getValue().getClass().getDeclaredFields();
//遍历所有字段 获取每个字段声明的注解看是否包含Autowired注解
for (Field field : declaredFields){
Annotation[] annotations = field.getDeclaredAnnotations();
for (Annotation annotation : annotations){
if(annotation instanceof Autowired){
//是Autowired注解我们需要为对应的变量 付给他对应的对象
//先获取该字段的类型对应的对象名称
String name = field.getType().getName();
//从beans里获取这个名字的对象
Object o = beans.get(name);
//设置字段可访问
field.setAccessible(true);
try {
//给字段赋值
//也就是说 原本没有赋值的时候entry对象里对应的字段的值是一个空的对象,
//然后通过set方法把这个字段的值替换成相应的对象
field.set(entry.getValue(),o);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
}
}
/**
* 根据bean名称获取对应的实例化对象
* beanName bean的名称
* 返回值:一个实例化对象
*/
public Object getInstance(String beanName){
return beans.get(beanName);
}
/**
* 初始化基础路径和包名
*/
private void initPath(){
//初始化文件的路径 你项目所在电脑中的位置
basePath="D:\\blog1\\ioc\\src\\main\\java\\com\\heaboy\\springioc\\";
//初始化基础包名
basePackage = "com.heaboy.springioc";
}
/**
* SpringIOC类的构造方法,初始化路径和扫描文件
*/
public SpringIOC(){
//初始化路径和包名
initPath();
try {
scan();//扫描文件
} catch (FileNotFoundException e) {
e.printStackTrace();
}
//初始化bean名称列表
beanNames = new ArrayList<>();
//初始化bean名称
initBeanNames();
}
}
测试类:
package com.heaboy.springioc.ioc;
import com.heaboy.springioc.entity.TestController;
import org.junit.Test;
import java.io.FileNotFoundException;
public class SpringIOCTest {
@Test
public void testScan() throws FileNotFoundException {
//创建SpringIOC类型的实例化对象
SpringIOC springIOC = new SpringIOC();
//调用SpringIOC的bean对象初始化方法
springIOC.initBeans();
//借用SpringIOC中的利用反射创建实例化对象方法 通过传入一个类名获取到实例化对象
TestController instance = (TestController)springIOC.getInstance(TestController.class.getName());
//对象调用test方法;
instance.test();
}
}