在喵星人的眼里我们是行动缓慢的巨人
本次手写了一个Mini版本(只考虑了比较简单的场景)的IoC代码,主要意义在于帮助大家理解SpringIoC的原理。
这里我们使用注解方式完成对象的创建
既然我们使用注解方式那么我们就需要定义两种注解,一种在Class头上的表示这个Class需要加入到IoC容器中,第二种就是在成员变量上的表示我这个对象需要依赖注入。那么我定义两个注解如下,废话不多说直接上源码:
package com.yi.jfire;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* @author MoYi
* Created time 2019/7/3 16:54
* 就像Spring中的@Service一样
*/
@Target({TYPE})
@Retention(RUNTIME)
public @interface JF {
String value() default "";
}
package com.yi.jfire;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* @author MoYi
* Created time 2019/7/4 14:34
* 就像Spring中的@Resource一样
*/
@Target({FIELD})
@Retention(RUNTIME)
public @interface JFResource {
}
注解定义好了,我们来看完成Bean的加载和依赖的注入。此代码不能覆盖所有情况,仅是表达IoC的基本原理。
package com.yi.jfire;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.Enumeration;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author MoYi
* Created time 2019/7/3 14:44
* Mini IoC
*/
public class JFireIoc{
private static final String FILE_TYPE = "file";
// 缓存所有主要实例的Bean
public static ConcurrentHashMap<String, Object> beansMap = new ConcurrentHashMap<>();
// 缓存实例的类
private static ConcurrentHashMap<String, Class<?>> classMap = new ConcurrentHashMap<>();
// 扫描的包名
private String baseScanPackage;
public JFireIoc(String baseScanPackage) {
this.baseScanPackage = baseScanPackage;
}
// 入口
public void execute() throws IOException {
// 将包名替换成路径
String path = baseScanPackage.replace('.', '/');
// 通过当前线程获取到类加载器来获取目录下的节点列表
Enumeration<URL> dirs = Thread.currentThread().getContextClassLoader().getResources(path);
// 遍历扫描到的节点
while (dirs.hasMoreElements()) {
URL url = dirs.nextElement();
String protocol = url.getProtocol();
// 这里只考虑的文件类型,可能是jar包这里没有做处理了
if (FILE_TYPE.equals(protocol)) {
addClass(new File(url.getFile()), baseScanPackage);
}
addResource();
}
}
private void addClass(File file, String packageName) {
if (file.isDirectory()) {
// 获取目录下的文件列表,过滤出文件夹和.class文件
File[] files = file.listFiles(pathname -> pathname.isDirectory()
|| pathname.getName().endsWith(".class"));
if (files == null) {
return;
}
// 遍历文件列表
for (File f : files) {
// 如果是.class文件
if (f.isFile()) {
// 获取到类名
String className = f.getName().substring(0, f.getName().length() - 6);
try {
// 反射获取类型对象
Class<?> clz = Class.forName(packageName + "." + className);
// 寻找@JF注解的类
JF jf = clz.getAnnotation(JF.class);
if (jf != null) {
// 给实例的对象映射一个名字
String beanName = jf.value();
if ("".equals(beanName)) {
beanName = clz.getSimpleName();
}
// 缓存Bean和类型
beansMap.put(beanName, clz.newInstance());
classMap.put(beanName, clz);
}
} catch (Exception e) {
throw new RuntimeException("反射创建对象失败", e);
}
// 如果是目录则进入递归
} else {
addClass(new File(f.getPath()), packageName + "." + f.getName());
}
}
}
}
// 依赖注入
private void addResource() {
// 遍历所有类型
classMap.forEach((s, aClass) -> {
Object obj = beansMap.get(s);
if (obj == null){
throw new RuntimeException("没有找到对应对象");
}
// 列出类中的成员变量
Field[] fields = obj.getClass().getDeclaredFields();
for (Field f : fields) {
// 找出@JFResource注解的变量
JFResource jfResource = f.getAnnotation(JFResource.class);
if (jfResource != null){
try {
// 注入依赖对象
f.setAccessible(true);
f.set(obj, beansMap.get(f.getName()));
} catch (IllegalAccessException e) {
throw new RuntimeException(f.getName()+"注入失败");
}
}
}
});
}
}
OK,实现一个简单版本的IoC就结束了,是不是很简单。接下来看看怎么使用呢。
package com.yi.jfire.test;
import com.yi.jfire.JF;
/**
* @author MoYi
* Created time 2019/7/4 14:38
* 定义是简单服务类
*/
@JF("jFOnlineService")
public class JFOnlineServiceImpl implements JFOnlineService{
@Override
public void online(boolean defaultOnline) {
System.out.println(".>>>>>>>>>>"+defaultOnline);
}
}
=====================================没羞没臊的分割线=============================================
package com.yi.jfire.test;
import com.yi.jfire.JF;
import com.yi.jfire.JFResource;
/**
* @author MoYi
* Created time 2019/7/3 17:02
* 这个服务类依赖上一个
*/
@JF("jFTestService")
public class JFTestServiceImpl implements JFTestService {
@JFResource
private JFOnlineService jFOnlineService;
@Override
public void run(){
System.out.println("running...");
jFOnlineService.online(true);
}
}
OK~写一个程序入口类,开始试试效果吧
package com.yi.jfire.test;
import com.yi.jfire.JFireIoc;
import java.io.IOException;
/**
* @author MoYi
* Created time 2019/7/4 17:07
* 昨夜星辰昨夜风
*/
public class MainTest {
public static void main(String[] args) throws IOException {
JFireIoc ioc = new JFireIoc("com.yi.jfire");
ioc.execute();
JFireIoc.beansMap.forEach((str, obj) -> {
System.out.println(str + "\t" + obj);
});
JFTestService jfTestService = (JFTestService)JFireIoc.beansMap.get("jFTestService");
jfTestService.run();
}
}
输出结果
jFTestService com.yi.jfire.test.JFTestServiceImpl@6bf256fa
jFOnlineService com.yi.jfire.test.JFOnlineServiceImpl@6cd8737
running...
.>>>>>>>>>>true
到此手写Mini版IoC就结束了。代码量已经非常精简了。希望能给大家提供一个思路吧。IoC是面向对象编程的基本思想,可以通过代码看到,IoC是一个Bean的容器也是解决DI(依赖注入)的核心。关于理论大家自行百度已经有很多大神对其进行了非常详细的解析。