1. IoC容器概述
IoC 是 Inversion of Control 的简写,译为“控制反转”,它不是一门技术,而是一种设计思想,是一个重要的面向对象编程法则,能够指导我们如何设计出松耦合、更优良的程序。
Spring 通过 IoC 容器来管理所有 Java 对象的实例化和初始化,控制对象与对象之间的依赖关系。我们将由 IoC 容器管理的 Java 对象称为 Spring Bean,它与使用关键字 new 创建的 Java 对象没有任何区别。
IoC 容器是 Spring 框架中最重要的核心组件之一,它贯穿了 Spring 从诞生到成长的整个过程。
2. 控制反转(IoC)
控制反转是一种思想,可以降低程序耦合度,提高程序扩展力。
控制反转, 就是将对象的创建以及对象和对象之间关系的维护(也就是对象中属性的赋值)交给第三方容器负责。
也就是说,我们可以不需要手动创建Bean对象以及手动对Bean对象中的属性赋值,这些操作都可以由IoC容器完成。
控制反转这种思想是根据依赖注入(Dependency Injection,DI)实现的。
3. 依赖注入
指Spring创建对象的过程中,将对象依赖属性通过配置进行注入。
依赖注入常见的实现方式包括两种:
- 第一种:set注入
- 第二种:构造注入
IOC 就是一种控制反转的思想, 而 DI 是对IoC的一种具体实现。
4. 代码实现
该代码只是粗略实现,目的是为了更好的展示IoC及依赖注入的实现放视。其中,Bean注解表示将该类交由IoC容器管理(spring中的@component),Di注解表示对该属性进行依赖注入(spring中的@Autowired)。
实现思路:
- 根据用户输入的包名,首先获取到这个包的所在路径。
- 之后,对这个路径下的所有文件及文件夹进行扫描,寻找添加了Bean注解的的类。
- 找到满足条件的类后创建该类的实例对象,并将这个类的实例对象以及其类型放入到map当中。其中,key是类型,value是实例对象。这样。我们就可以根据类型获取到相对应的实例对象。
- 将所有加了Bean注解的类都放入map之后,对map中的所有对象进行遍历。根据反射获取其所有属性,判断是否有属性添加了注解Di。
- 如果存在添加了注解Di的属性,我们就要根据其属性的类型在map中得到相对应类型的实例对象,并调用set函数将该对象赋值给该属性,完成依赖注入操作。
实现代码如下:
public class AnnotationApplicationContext implements ApplicationContext{
//存放bean对象
private HashMap<Class, Object> beanFactory = new HashMap<>();
private static String rootPath;
//当前包及其子包,哪个类有@Bean注解,把这个类通过反射实例化
public AnnotationApplicationContext(String basePackage) throws Exception {
// 把.替换成\\
String packageDir = basePackage.replaceAll("\\.", "\\\\");
//得到绝对路径
Enumeration<URL> absolutePath = Thread.currentThread().getContextClassLoader().getResources(packageDir);
while (absolutePath.hasMoreElements()) {
URL url = absolutePath.nextElement();
//斜杠'/'可能被编码,因此需要解码
String filePath = URLDecoder.decode(url.getFile(),"utf-8");
//获得包前面的路劲,以便后续操作
rootPath = filePath.substring(0, filePath.length()-packageDir.length());
//包扫描
scanBean(new File(filePath));
//属性注入
loadDi();
}
}
private void loadDi() throws Exception {
Set<Map.Entry<Class, Object>> entries = beanFactory.entrySet();
//遍历map集合中的每个对象
for(Map.Entry<Class, Object> entry : entries) {
Object obj = entry.getValue();
//得到每个对象的class
Class<?> clazz = obj.getClass();
//获得每个对象的所有属性
Field[] declaredFields = clazz.getDeclaredFields();
for(Field field : declaredFields) {
//如果存在属性含有注解Di
if(field.getAnnotation(Di.class) != null) {
//如果时私有属性,设置可访问
field.setAccessible(true);
//为属性赋值,按类型获取对应的对象
field.set(obj, beanFactory.get(field.getType()));
}
}
}
}
private void scanBean(File file) throws Exception {
// 如果当前是文件夹
if(file.isDirectory()) {
//获得所有子文件(夹)
File[] childFiles = file.listFiles();
//如果当前文件夹空
if(childFiles == null || childFiles.length == 0) {
return;
}
for(File childFile : childFiles) {
//如果当前子文件时文件夹,递归
if(childFile.isDirectory()) {
scanBean(childFile);
}else {
//得到包路径+类名称
String classPath = childFile.getAbsolutePath().substring(rootPath.length() - 1);
//如果当前文件类型时class
if(classPath.endsWith(".class")) {
//把路径\替换成. 把.class去掉
String allName = classPath.replaceAll("\\\\", ".").replace(".class", "");
//获取类的class
Class clazz = Class.forName(allName);
//如果不是接口而且上面有注解Bean
if(!clazz.isInterface() && clazz.getAnnotation(Bean.class) != null) {
// 实例化对象
Object instance = clazz.getConstructor().newInstance();
// 如果当前类实现了接口
if(clazz.getInterfaces().length > 0) {
//把接口的类型作为该实例的类别
beanFactory.put(clazz.getInterfaces()[0], instance);
}else {
//没有接口,就把该类的类型作为key
beanFactory.put(clazz, instance);
}
}
}
}
}
}
}
@Override
public Object getBean(Class clazz) {
return beanFactory.get(clazz);
}
}
使用代码如下:
@Test
public void testIoc() throws Exception {
ApplicationContext context = new AnnotationApplicationContext("org.kkk.spring6");
UserService userService = (UserService) context.getBean(UserService.class);
userService.print();
}