为自己模拟的IOC容器添加上DI注入
一、介绍
上一篇中,模拟Spring
实现了一个简易的IOC
容器,完成了初始化bean
的操作,统一交给了一个Map
集合进行管理。
模拟Spring实现一个简易的IOC容器
现在,将为这个IOC
容器添加DI
注入功能
二、实现
在编写之前,我们先加一个工具类,用来获取接口所实现的子类Class
对象,也是通过子类
<dependency>
<groupId>org.reflections</groupId>
<artifactId>reflections</artifactId>
<version>0.10.2</version>
</dependency>
代码如下,对比上一次,稍稍做了一点封装,使得步骤更加清晰
代码语言:javascript复制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.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Di {
}
代码语言:javascript复制package com.banmoon.test.mockioc.core;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import com.banmoon.test.mockioc.annotation.Bean;
import com.banmoon.test.mockioc.annotation.Di;
import lombok.extern.slf4j.Slf4j;
import org.reflections.Reflections;
import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
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;
import java.util.Set;
@Slf4j
@SuppressWarnings("all")
public class MyApplicationContext {
/**
* 真正的容器存储集合
*/
private static final Map<String, Object> singleObjects = new HashMap<>();
/**
* 当前运行环境的路径
*/
private static String currentAbsPath = null;
/**
* 扫描的包路径
*/
private String packagePath;
/**
* 扫描的包路径反射工具类
*/
private Reflections packageReflections;
public MyApplicationContext(String packagePath) throws Exception {
this.packagePath = packagePath;
this.packageReflections = new Reflections(packagePath);
// 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);
initializeBean(clazz, false);
}
}
}
}
}
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;
}
/**
* 处理DI注解,实现注入属性
*
* @param currentObj
*/
private void diField(Object currentObj, Class<?> clazz) {
// 1、获取所有属性
Field[] fields = clazz.getDeclaredFields();
// 2、遍历属性
for (Field field : fields) {
// 3、找到有DI注解的属性
Di annotation = field.getAnnotation(Di.class);
if (Objects.nonNull(annotation)) {
// 4、找到对应属性在容器中的实例对象
Class<?> fieldClazz = field.getType();
// 5、在容器中查找bean,没有则进行初始化bean
Object o = initializeBean(fieldClazz, true);
// 6、通过反射设置到属性中
try {
field.set(currentObj, o);
} catch (IllegalAccessException e) {
log.error("DI注入异常", e);
}
}
}
}
/**
* 初始化bean
*
* @param clazz beanClass对象
* @param find 是否在容器中进行寻找
* @return 返回bean
*/
public Object initializeBean(Class<?> clazz, boolean find) {
// 1、判断是否是接口,且判断是不是要查找子类
if (clazz.isInterface() && find) {
Set<Class<?>> set = packageReflections.getSubTypesOf((Class<Object>) clazz);
clazz = CollUtil.get(set, 0);
} else if (clazz.isInterface() && !find) {
return null;
}
// 2、查找上面是否有@bean注解
Bean beanAnnotation = clazz.getAnnotation(Bean.class);
if (Objects.isNull(beanAnnotation)) {
return null;
}
try {
// 3、找到bean名字,获取在容器中的实例对象
String beanName = generateBeanName(beanAnnotation, clazz);
// 4、判断是不是需要查找bean
Object o = null;
if (find) {
o = singleObjects.get(beanName);
}
// 5、如果在容器中没有找到,则进行初始化
if (Objects.isNull(o)) {
Constructor<?> constructor = clazz.getConstructor();
Object obj = constructor.newInstance();
// 6、将实例化后的对象,放入map容器中
singleObjects.put(beanName, obj);
// 7、处理属性,DI注入属性
diField(obj, clazz);
}
return o;
} catch (Exception exception) {
log.error("初始化bean异常", exception);
return null;
}
}
}
三、测试
同样,service
及其实现类,dao
及其实现类
package com.banmoon.test.mockioc.service;
public interface TestService {
void hello();
}
代码语言:javascript复制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 {
@Di
public TestDao testDao;
@Override
public void hello() {
System.out.println("service hello...");
testDao.hello();
}
}
代码语言:javascript复制package com.banmoon.test.mockioc.dao;
public interface TestDao {
void hello();
}
代码语言:javascript复制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 {
@Override
public void hello() {
System.out.println("dao hello...");
}
}
在service
实现类上面有dao
类型的属性,并加上了@Di
注解,试试能不能成功注入
package com.banmoon.test.mockioc;
import com.banmoon.test.mockioc.core.MyApplicationContext;
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);
testService.hello();
}
}
运行查看结果,成功
四、最后
其实,这也是最为简单的注入,我就问问
都说
Spring
使用了三级缓存,那么这三级缓存是怎么使用的,它有什么作用呢?
我是半月,你我一同共勉!!!