Spring - 原理手写IoC
大约 3 分钟
Spring - 原理手写IoC
Spring 框架的 IOC 是基于 Java 反射机制实现的,在学习手写 IoC 之前,你需要具备一定的 Java 反射相关的知识,参考本站内的 Java 教程。
Java教程
//TODO: 待更新
IoC(控制反转)和DI(依赖注入)是Spring里面核心的东西,本章节我们就来自己实现 Spring 框架中的 IoC 和 DI。
创建子模块
还是和前面的几个章节一致,为了避免冲突,这里我们创建一个新的子模块,名为 spring6-ioc-reflect。
- 引入依赖
引入相关的依赖,即将依赖项写入到子模块中的 pom.xml 配置文件中。如果之前在父模块中引入了该依赖,则不用重复添加。
<dependencies>
<!--junit5测试-->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.3.1</version>
</dependency>
</dependencies>
- 创建 UserDao
public interface UserDao {
public void print();
}
- 创建UserDaoImpl实现
public class UserDaoImpl implements UserDao {
@Override
public void print() {
System.out.println("Dao层执行结束");
}
}
- 创建UserService接口
public interface UserService {
public void out();
}
- 创建UserServiceImpl实现类
@Bean
public class UserServiceImpl implements UserService {
// private UserDao userDao;
@Override
public void out() {
//userDao.print();
System.out.println("Service层执行结束");
}
}
定义注解
- bean注解
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface Bean { }
- 依赖注入DI注解
@Target({ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) public @interface Di { }
说明:上面两个注解可以随意取名
- 定义容器接口
public interface ApplicationContext {
Object getBean(Class clazz);
}
- 定义容器接口实现类
AnnotationApplicationContext 实现类
public class AnnotationApplicationContext implements ApplicationContext {
//存储bean的容器
private HashMap<Class, Object> beanFactory = new HashMap<>();
private static String rootPath;
@Override
public Object getBean(Class clazz) {
return beanFactory.get(clazz);
}
/**
* 根据包扫描加载bean
*
* @param basePackage
*/
public AnnotationApplicationContext(String basePackage) {
// 这里要注意的是如果在 linux 或 mac 平台下,文件的绝对路径使用的是 / 斜杠
// 而 windows 平台下是 反斜杠 \
// 如果使用的错误的符号,那么就会导致 dirs 为空
// windows 使用这个
// String packageDirName = basePackage.replaceAll("\\.", "\\\\");
try {
String packageDirName = basePackage.replaceAll("\\.", "/");
Enumeration<URL> dirs = Thread.currentThread().getContextClassLoader().getResources(packageDirName);
while (dirs.hasMoreElements()) {
URL url = dirs.nextElement();
String filePath = URLDecoder.decode(url.getFile(), StandardCharsets.UTF_8);
rootPath = filePath.substring(0, filePath.length() - packageDirName.length());
loadBean(new File(filePath));
}
} catch (Exception e) {
throw new RuntimeException(e);
}
//依赖注入
loadDi();
}
private void loadBean(File fileParent) {
if (fileParent.isDirectory()) {
File[] childrenFiles = fileParent.listFiles();
if (childrenFiles == null || childrenFiles.length == 0) {
return;
}
for (File child : childrenFiles) {
if (child.isDirectory()) {
//如果是个文件夹就继续调用该方法,使用了递归
loadBean(child);
} else {
//通过文件路径转变成全类名,第一步把绝对路径部分去掉
String pathWithClass = child.getAbsolutePath().substring(rootPath.length() - 1);
//选中class文件
if (pathWithClass.contains(".class")) {
// com.codermast.dao.UserDao
//去掉.class后缀,并且把 \ 替换成 .
// 这里和上面同理,还是 windows 和 linux Mac 平台下的文件路径分隔符不一致的问题
// String fullName = pathWithClass.replaceAll("\\\\", ".").replace(".class", "");
String fullName = pathWithClass.replaceAll("/", ".").replace(".class", "").substring(1);
try {
Class<?> aClass = Class.forName(fullName);
//把非接口的类实例化放在map中
if (!aClass.isInterface()) {
Bean annotation = aClass.getAnnotation(Bean.class);
if (annotation != null) {
Object instance = aClass.newInstance();
//判断一下有没有接口
if (aClass.getInterfaces().length > 0) {
//如果有接口把接口的class当成key,实例对象当成value
System.out.println("正在加载【" + aClass.getInterfaces()[0] + "】,实例对象是:" + instance.getClass().getName());
beanFactory.put(aClass.getInterfaces()[0], instance);
} else {
//如果有接口把自己的class当成key,实例对象当成value
System.out.println("正在加载【" + aClass.getName() + "】,实例对象是:" + instance.getClass().getName());
beanFactory.put(aClass, instance);
}
}
}
} catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) {
e.printStackTrace();
}
}
}
}
}
}
private void loadDi() {
for (Map.Entry<Class, Object> entry : beanFactory.entrySet()) {
//就是咱们放在容器的对象
Object obj = entry.getValue();
Class<?> aClass = obj.getClass();
Field[] declaredFields = aClass.getDeclaredFields();
for (Field field : declaredFields) {
Di annotation = field.getAnnotation(Di.class);
if (annotation != null) {
field.setAccessible(true);
try {
System.out.println("正在给【" + obj.getClass().getName() + "】属性【" + field.getName() + "】注入值【" + beanFactory.get(field.getType()).getClass().getName() + "】");
field.set(obj, beanFactory.get(field.getType()));
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
}
}
警告
在 AnnotationApplicationContext
的代码中,有涉及到文件的路径转换。需要注意 Windows
和 Linux
、Mac
平台下的文件路径分隔符是不相同的。
Windows
下是\
,反斜杠Linux
、Mac
下是/
,斜杠
需要注意区分,否则容易造成无法反射和注入对应的类和对象,导致空指针异常。
- 对类进行@Bean注解标识
@Bean
public class UserServiceImpl implements UserService
@Bean
public class UserDaoImpl implements UserDao
- 执行测试
@Test
public void testIoc() {
ApplicationContext applicationContext = new AnnotationApplicationContext("com.codermast.reflect");
UserService userService = (UserService)applicationContext.getBean(UserService.class);
userService.out();
System.out.println("run success");
}