文章目录
1. 回顾Java反射
Spring框架的IOC是基于Java反射机制实现的。
Java
反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为Java
语言的反射机制。简单来说,反射机制指的是程序在运行时能够获取自身的信息。
要想解剖一个类,必须先要获取到该类的Class对象。而剖析一个类或用反射解决具体的问题就是使用相关API**(1)java.lang.Class(2)java.lang.reflect**,所以,Class对象是反射的根源。
2. 实现Spring的IoC
2.1 添加依赖以及准备测试需要的bean
<dependencies>
<!--junit5测试-->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.3.1</version>
</dependency>
</dependencies>
由于测试所需要的bean是controller、service、dao所以不过多赘述,只展示一个
package com.atguigu.spring.test.service.impl;
import com.atguigu.spring.core.annotation.Bean;
import com.atguigu.spring.service.UserService;
@Bean
public class UserServiceImpl implements UserService {
// private UserDao userDao;
@Override
public void out() {
//userDao.print();
System.out.println("Service层执行结束");
}
}
2.2 定义注解
我们通过注解的形式加载bean与实现依赖注入
Bean注解:
package com.atguigu.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.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Bean {
}
依赖注入注解:
package com.atguigu.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.3 定义Bean容器接口
package com.atguigu.spring.core;
public interface ApplicationContext {
Object getBean(Class clazz);
}
2.4 编写注解bean容器接口实现
AnnotationApplicationContext基于注解扫描bean
package com.atguigu.spring.core;
import java.util.HashMap;
public class AnnotationApplicationContext implements ApplicationContext {
//存储bean的容器(分析底层源码,bean其实是存在一个HashMap当中)
private HashMap<Class, Object> beanFactory = new HashMap<>();
@Override
public Object getBean(Class clazz) {
return beanFactory.get(clazz);
}
/**
* 根据包扫描加载bean
* @param basePackage
*/
public AnnotationApplicationContext(String basePackage) {
}
}
2.5 编写扫描bean逻辑
2.5.1 扫描bean的逻辑实现
首先分析逻辑如何实现:
- 创建一个存储bean的容器
- 实现自定义接口ApplicationContext的getBean方法
- 传入的basePackage样式为
com.atguigu.spring6
,我们需要先将·
转化为\
,得到com\atguigu\spring6
- 然后通过
Thread.currentThread().getContextClassLoader().getResources(packageDirName)
获取包的绝对路径 - 依次扫描包的绝对路径,将每个文件(或者文件夹)传入包的扫描方法loadBean(File file)
- 内部方法的实现:
- 1.判断当前是否为文件夹
- 2 获取文件夹中的所有内容
- 3 判断文件夹中是否为空,如果为空,直接返回
- 4 如果文件夹不为空,遍历文件夹中所有内容
- 4.1 遍历得到每个File对象,继续判断,如果还是文件夹,递归
- 4.2 遍历得到的File不是文件夹,是文件,
- 4.3 得到包路径 + 类名称部分-字符串截取
- 4.4 判断当前文件类型是否为.class
- 4.5 如果是.class类型,把路径\替换成. 把.class去掉
- 4.6 判断类上面是否有注解 @Bean,如果有实例化过程
- 4.7 判断是不是接口
- 判断类上面是否有注解
- 4.7 把对象实例化之后,放到map集合中(如果有接口则将接口放入map集合中)
2.5.2 依赖注入DI的逻辑实现
由于实例化得对象在beanFactory得map集合中
- 遍历beanFactory中的map集合
- 获取map集合每个对象(value),每个对象属性获取到
- 遍历得到每个对象属性数组,得到每个属性
- 获取对象class
- 获取对象属性
- 遍历每个对象属性,得到每个属性
- 判断属性上面是否有@Di注解
- 如果是私有属性,那么需要设置setAccessible(true)
- 如果有@Di注解,把对象进行设置(注入)
我们通过构造方法传入包的base路径,扫描被@Bean注解的java对象,完整代码如下:
package com.atguigu.spring.core;
import com.atguigu.spring.core.annotation.Bean;
import java.io.File;
import java.util.HashMap;
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) {
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(),"utf-8");
rootPath = filePath.substring(0, filePath.length()-packageDirName.length());
loadBean(new File(filePath));
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
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.xinzhi.dao.UserDao
//去掉.class后缀,并且把 \ 替换成 .
String fullName = pathWithClass.replaceAll("\\\\", ".").replace(".class", "");
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();
}
}
}
}
}
}
}