Spring框架的IOC是基于Java反射机制实现的。
实现Spring的IoC
我们知道,IoC(控制反转)和DI(依赖注入)是Spring里面核心的东西,那么,我们如何自己手写出这样的代码呢?下面我们就一步一步写出Spring框架最核心的部分。
1、定义注解
Bean注解
package com.cj.annotation.anno;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE) // 用于描述类、接口(包括注解类型) 或enum声明
@Retention(RetentionPolicy.RUNTIME) // 在运行时生效
public @interface Bean {
}
依赖注入注解
package com.cj.spring.core.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 {
}
2、定义bean容器接口
package com.cj.spring.core;
public interface ApplicationContext {
Object getBean(Class clazz);
}
3、编写注解bean容器接口实现
我们通过构造方法传入包的base路径,扫描被@Bean注解的java对象,完整代码如下
package com.cj.annotation.bean;
import com.cj.annotation.anno.Bean;
import com.cj.annotation.anno.Di;
import java.io.File;
import java.lang.reflect.Field;
import java.net.URL;
import java.net.URLDecoder;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class AnnotationApplicationContext implements ApplicationContext {
private static String rootPath;
// 存储bean的容器
private HashMap<Class, Object> beanFactory = new HashMap<>();
/**
* 根据包扫描加载bean
*
* @param basePackage
*/
public AnnotationApplicationContext(String basePackage) {
try {
// 1 把.替换成\
String packagePath = basePackage.replaceAll("\\.",
"\\\\");
// 2 获取包绝对路径
Enumeration<URL> urls
= Thread.currentThread().getContextClassLoader()
.getResources(packagePath);
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
String filePath = URLDecoder.decode(url.getFile(),
"utf-8");
// 获取包前面路径部分,字符串截取
rootPath = filePath.substring(0, filePath.length() - packagePath.length());
// 包扫描
loadBean(new File(filePath));
}
} catch (Exception e) {
throw new RuntimeException(e);
}
// 属性注入
loadDi();
}
/**
* 包扫描
*
* @param file 打包构建的路径
*/
public void loadBean(File file) throws Exception {
// 1、判断当前是否为文件夹
if (file.isDirectory()) {
// 2、获取文件夹中的所有内容
File[] childrenFiles = file.listFiles();
// 2.1 当前文件夹是否为空
if (childrenFiles == null || childrenFiles.length == 0) {
return;
}
// 3、当文件夹不为空的时候,获取文件夹中的全部内容
for (File child : childrenFiles) {
// 3.1 判断当前文件是否还是文件夹,如果是,就进行一个 递归 寻找
if (child.isDirectory()) {
loadBean(child);
} else {
// 3.2 在这里得到的 File 已经是文件,当得到包路径 + 类名称 - 字符串截取
String pathWithClass = child.getAbsolutePath().substring(rootPath.length() - 1);
// 3.3 在判断当前文件是否 .class
if (pathWithClass.contains(".class")) {
// 3.4 因为要用的是 .class ,要将路径中的 \ 替换成 . ,
// 最后将得到为 com.cj.service
String allClass = pathWithClass
.replaceAll("\\\\", ".")
.replace(".class", "");
// 3.5 获取类的 Class
Class<?> clazz = Class.forName(allClass);
// 3.6 判断是不是接口
if (!clazz.isInterface()) {
// 获取类上的注解
Bean annotation = clazz.getAnnotation(Bean.class);
// 3.7 判断类上面是否有注解
if (annotation != null) {
// 3.8 如果有就实例化
Object instance = clazz.getConstructor().newInstance();
// 3.9 把实例化后的对象放到 map 集合中
if (clazz.getInterfaces().length > 0) {
beanFactory.put(clazz.getInterfaces()[0], instance);
} else {
beanFactory.put(clazz, instance);
}
}
}
}
}
}
}
}
/**
* 属性的输入
*/
private void loadDi() {
// 实例化对象在 beanFactory 的 map 集合里边
// 1、遍历 beanFactory 的 map 集合
Set<Map.Entry<Class, Object>> entries = beanFactory.entrySet();
for (Map.Entry<Class, Object> entry : entries) {
// 获取每个 map 对象,每个对象属性取到
Object obj = entry.getValue();
// 获取对象 Class
Class<?> clazz = obj.getClass();
// 获取每个对象属性
Field[] declaredFields = clazz.getDeclaredFields();
// 遍历得到每个对象属性数组,得到每个属性
for (Field field : declaredFields) {
Di annotation = field.getAnnotation(Di.class);
if (annotation != null) {
// 为了防止拿不到 私有类型的属性
field.setAccessible(true);
try {
// 如果有 @Di 注解,就把对象进行注入
field.set(obj, beanFactory.get(field.getType()));
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}
}
}
@Override
public Object getBean(Class clazz) {
return beanFactory.get(clazz);
}
}
编写测试场景
package com.cj.annotation.service;
public interface UserService {
public void add();
}
package com.cj.annotation.service;
import com.cj.annotation.anno.Bean;
import com.cj.annotation.anno.Di;
import com.cj.annotation.dao.UserDao;
@Bean
public class UserServiceImpl implements UserService {
@Di
public UserDao userDao;
public void add() {
userDao.add();
System.out.println("Service->add()... ");
}
}
package com.cj.annotation;
import com.cj.annotation.bean.AnnotationApplicationContext;
import com.cj.annotation.bean.ApplicationContext;
import com.cj.annotation.service.UserService;
import org.junit.jupiter.api.Test;
public class UserControllerTest {
@Test
public void test1() {
ApplicationContext context = new AnnotationApplicationContext("com.cj");
UserService userService = (UserService) context.getBean(UserService.class);
System.out.println(userService);
userService.add();
}
}