模拟Spring实现一个简易的IOC容器
一、介绍
在模拟实现IOC
容器之前,我们必须要掌握反射相关的知识,毕竟IOC
容器采用的是反射进行的查找创建。
反射可以看我以前的这篇文章
Java注解的介绍和反射使用 | 半月无霜 (banmoon.top)
二、步骤
在spring
的包中,有这么一个接口ApplicationContext.java
,他是一个容器接口,一切的开始都是由它开始,spring
中定义了各种场景下使用的实现类,其中常见有以下几种实现类
AnnotationConfigApplicationContext
:通过注解配置初始化容器ClassPathXmlApplicationContext
:通过xml
配置文件初始化容器AnnotationConfigServletWebApplicationContext
:注解配置加载web
环境的容器
既然我们要自己写一个容器,我们也需要一个这样的容器类,接口不接口的无所谓,我们可以不考虑结构,只考虑效果。故我们只需要一个容器类就好了。
除了容器外,我们还需要一个注解,分别作用于类上,作为标识此类要进行初始化被容器管理
@bean
:标注为需要实例化的类,被容器管理
那么步骤如下
- 先创建一个容器类,这个容器类中有初始化
bean
,获取bean
的方法 - 初始化
bean
时,需要传入一个包路径,自动扫描这个包路径下的类 - 如果类上面要是有
@bean
注解,我们就进行实例化类,并加入到容器中
三、实现
我们先把注解写了
代码语言:java复制package com.banmoon.test.mockioc.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 Bean {
String value() default "";
}
然后,我们可以开始写容器了,代码一步到位,看构造器初始化方法,查看是如何加载到类,并将其实例化的。
以及容器中有个Map<String, Object> singleObjects = new HashMap<>();
,这才是用来存储实例化后的对象的
package com.banmoon.test.mockioc.core;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import com.banmoon.test.mockioc.annotation.Bean;
import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.net.URLDecoder;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
public class MyApplicationContext {
/**
* 真正的容器存储集合
*/
private static final Map<String, Object> singleObjects = new HashMap<>();
/**
* 当前运行环境的路径
*/
private static String currentAbsPath = null;
public MyApplicationContext(String packagePath) throws Exception {
// 1、将包路径中的.变成
String basePackage = StrUtil.replace(packagePath, ".", "\");
// 2、获取包的绝对路径,我们要获取class包的绝对路径,也就是target里面的那些
URL url = Thread.currentThread().getContextClassLoader().getResource(basePackage);
// 3、得到url后还需要进行转码
if (Objects.nonNull(url)) {
String filePath = URLDecoder.decode(url.getFile(), "utf-8");
// 4、为了方便,此处记录target包的绝对路径
currentAbsPath = filePath.substring(0, filePath.length() - basePackage.length());
// 5、扫描包里面所有的类
scanBean(new File(filePath));
}
}
private void scanBean(File file) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
// 1、判断当前是否文件夹
if (file.isDirectory()) {
// 2、获取文件夹中所有的内容,如果为空直接返回
File[] children = file.listFiles();
// 3、遍历内容
if (ArrayUtil.isEmpty(children)) {
return;
}
for (File child : children) {
// 4.1、如果是文件夹,则进行递归
if (child.isDirectory()) {
scanBean(child);
} else {
// 5.1、如果是文件,则进行判断是否为class文件
String pathWithClass = child.getAbsolutePath().substring(currentAbsPath.length() - 1);
if (pathWithClass.endsWith(".class")) {
// 5.2、反射得到当前文件的Class对象
String classPath = StrUtil.replace(pathWithClass, "\", ".")
.replace(".class", "");
Class<?> clazz = Class.forName(classPath);
// 5.3、判断上面是否有@Bean注解,有的话进行实例化
Bean annotation = clazz.getAnnotation(Bean.class);
if (!clazz.isInterface() && Objects.nonNull(annotation)) {
Constructor<?> constructor = clazz.getConstructor();
Object obj = constructor.newInstance();
// 5.4、将实例化后的对象,放入map容器中
String beanName = generateBeanName(annotation, clazz);
singleObjects.put(beanName, obj);
}
}
}
}
}
}
@SuppressWarnings("all")
public <T> T getBean(String beanName, Class<T> clazz) {
Object o = singleObjects.get(beanName);
if (!clazz.isInstance(o)) {
throw new UnsupportedOperationException("获取的类型错误");
}
return (T) o;
}
/**
* 获取bean的名字
* @param annotation bean注解
* @param clazz class对象
* @return bean的名字
*/
private String generateBeanName(Bean annotation, Class<?> clazz) {
String value = annotation.value();
if (StrUtil.isBlank(value)) {
Class<?>[] interfaces = clazz.getInterfaces();
if (ArrayUtil.isNotEmpty(interfaces)) {
value = StrUtil.lowerFirst(interfaces[0].getSimpleName());
} else {
value = StrUtil.lowerFirst(clazz.getSimpleName());
}
}
return value;
}
}
四、测试
写一个service
、一个dao
,以及对应的实现类
package com.banmoon.test.mockioc.service;
public interface TestService {
}
代码语言:java复制package com.banmoon.test.mockioc.service.impl;
import com.banmoon.test.mockioc.annotation.Bean;
import com.banmoon.test.mockioc.annotation.Di;
import com.banmoon.test.mockioc.dao.TestDao;
import com.banmoon.test.mockioc.service.TestService;
@Bean
public class TestServiceImpl implements TestService {
}
代码语言:java复制package com.banmoon.test.mockioc.dao;
public interface TestDao {
}
代码语言:java复制package com.banmoon.test.mockioc.dao.impl;
import com.banmoon.test.mockioc.annotation.Bean;
import com.banmoon.test.mockioc.dao.TestDao;
@Bean
public class TestDaoImpl implements TestDao {
}
再编写我们的主程序,来初始化这个容器
代码语言:java复制package com.banmoon.test.mockioc;
import com.banmoon.test.mockioc.core.MyApplicationContext;
import com.banmoon.test.mockioc.dao.TestDao;
import com.banmoon.test.mockioc.service.TestService;
public class Test {
public static void main(String[] args) throws Exception {
MyApplicationContext context = new MyApplicationContext("com.banmoon.test.mockioc");
TestService testService = context.getBean("testService", TestService.class);
System.out.println(testService);
TestDao testDao = context.getBean("testDao", TestDao.class);
System.out.println(testDao);
}
}
运行结果如下
如此,一个简易的IOC
容器就已经搭建完成了
五、最后
想象很美好,实际问题会很多,在此只展示基本的原理。
如果真的这么简单的话,spring
也就不会这么庞大了,555!!!
上面这段代码存在不少的问题,后续总结一下spring
中是如何解决这些个问题的吧。
- 实例化的
bean
存在多个接口,该如何存储 - 上述代码没有完成依赖注入,也就是
DI
spring
是如何存储,才完成了既可以通过名称获取,又可以通过类型获取bean
我是半月,你我一同共勉!!!