手写ioc容器-爱代码爱编程
1. 实现Spring的IOC
我们知道,IOC(控制反转)和DI(依赖注入)是Spring里面核心的东西,那么,我们如何自己手写IOC
- 搭建子模块
搭建模块:guigu-spring,搭建方式如其他spring子模块 - 准备测试需要的bean
添加依赖
<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();
}
创建UserDaoIm实现
public class UserDaoImpl implements UserDao {
@Override
public void print() {
System.out.println("Dao层执行结束");
}
}
创建UserService接口
public interface UserService {
public void out();
}
创建UserServiceImp实现类
@Bean
public class UserServiceImpl implements UserService {
// private UserDao userDao;
@Override
public void out() {
//userDao.print();
System.out.println("Service层执行结束");
}
}
- 定义注解
我们通过注解的形式加载bean与实现依赖注入
bean注解
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
//注解的作用范围
/*@Target(ElementType.TYPE)——接口、类、枚举、注解
@Target(ElementType.FIELD)——字段、枚举的常量
@Target(ElementType.METHOD)——方法
@Target(ElementType.PARAMETER)——方法参数
@Target(ElementType.CONSTRUCTOR) ——构造函数
@Target(ElementType.LOCAL_VARIABLE)——局部变量
@Target(ElementType.ANNOTATION_TYPE)——注解
@Target(ElementType.PACKAGE)——包,用于记录java文件的package信息
*/
@Target(ElementType.TYPE)
/*@Retention修饰注解,用来表示注解的生命周期
//RetentionPolicy.SOURCE---只是做一些检查性的操作,,比如 @Override 和 @SuppressWarnings
RetentionPolicy.CLASS0---要在编译时进行一些预处理操作,比如生成一些辅助代码(如 ButterKnife)
要在编译时进行一些预处理操作,比如生成一些辅助代码(如 ButterKnife)
RetentionPolicy.RUNTIME---需要在运行时去动态获取注解信息
*/
@Retention(RetentionPolicy.RUNTIME)
public @interface Bean {
}
依赖注入注解
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 {
}
- 定义bean接口
public interface ApplicationContext {
Object getBean(Class clazz);
}
- 编写扫描bean逻辑
public class AnnotationApplicationContext implements ApplicationContext{
//创建map集合,放bean对象
private Map<Class,Object> beanFactory = new HashMap<>();
private static String rootPath;
//返回对象
@Override
public Object getBean(Class clazz) {
return beanFactory.get(clazz);
}
//创建有参数构造,传递包路径,设置包扫描规则
//当前包及其子包,哪个类有@Bean注解,把这个类通过反射实例化
public AnnotationApplicationContext(String basePackage) {
// com.atguigu
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();
}
//包扫描过程,实例化
private void loadBean(File file) throws Exception {
//1 判断当前是否文件夹
if(file.isDirectory()) {
//2 获取文件夹里面所有内容
File[] childrenFiles = file.listFiles();
//3 判断文件夹里面为空,直接返回
if(childrenFiles == null || childrenFiles.length == 0) {
return;
}
//4 如果文件夹里面不为空,遍历文件夹所有内容
for(File child : childrenFiles) {
//4.1 遍历得到每个File对象,继续判断,如果还是文件夹,递归
if(child.isDirectory()) {
//递归
loadBean(child);
} else {
//4.2 遍历得到File对象不是文件夹,是文件,
//4.3 得到包路径+类名称部分-字符串截取
String pathWithClass =
child.getAbsolutePath().substring(rootPath.length() - 1);
//4.4 判断当前文件类型是否.class
if(pathWithClass.contains(".class")) {
//4.5 如果是.class类型,把路径\替换成. 把.class去掉
// com.atguigu.service.UserServiceImpl
String allName = pathWithClass.replaceAll("\\\\", ".")
.replace(".class", "");
//4.6 判断类上面是否有注解 @Bean,如果有实例化过程
//4.6.1 获取类的Class
Class<?> clazz = Class.forName(allName);
//4.6.2 判断不是接口
if(!clazz.isInterface()) {
//4.6.3 判断类上面是否有注解 @Bean
Bean annotation = clazz.getAnnotation(Bean.class);
if(annotation != null) {
//4.6.4 实例化
Object instance = clazz.getConstructor().newInstance();
//4.7 把对象实例化之后,放到map集合beanFactory
//4.7.1 判断当前类如果有接口,让接口class作为map的key
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) {
//2 获取map集合每个对象(value),每个对象属性获取到
Object obj = entry.getValue();
//获取对象Class
Class<?> clazz = obj.getClass();
//获取每个对象属性获取到
Field[] declaredFields = clazz.getDeclaredFields();
//3 遍历得到每个对象属性数组,得到每个属性
for(Field field:declaredFields) {
//4 判断属性上面是否有@Di注解
Di annotation = field.getAnnotation(Di.class);
if(annotation != null) {
//如果私有属性,设置可以设置值
field.setAccessible(true);
//5 如果有@Di注解,把对象进行设置(注入)
try {
field.set(obj,beanFactory.get(field.getType()));
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}
}
}
}