本篇博客我们来手写一个IOC,就是模拟出IOC里边的实现过程。这过程怎么做呢?
咱们主要基于java中的反射,再加注解,来实现spring框架中IOC的这个效果。
下面我们来具体看看这个过程。首先因为这里边要用到反射,咱们把反射中的相关内容我们先做一个复习。复习之后最终让我们来手写spring IOC的这个功能。
1、回顾Java反射
java中的反射机制是什么呢?
它指的是对于任何一个类,我们都能够知道这个类里面的属性方法。
对于任何一个对象都能调它的任意方法和属性。
而这种动态获取信息以及动态调用对象方法的功能,就称为java的反射机制。
说的简单点,你要做反射,首先要得到类的卡的对象,就是咱们通俗说的字节码文件。通过字节码文件能够操作类中所有内容,包括你的属性,包括你的方法等等。这个是对于反射一个简单的概述。
自定义类
代码语言:javascript复制package com.jie.reflect.model;
/**
* Car类
* 用于反射测试
*
* @author 阿杰 2416338031@qq.com
* @version 1.0
* @date 2023/10/26 6:46
*/
public class Car {
/**
* 属性
*/
private String name;
private int age;
private String color;
/**
* 无参数构造
*/
public Car() {
}
/**
* 有参数构造
*/
public Car(String name, int age, String color) {
this.name = name;
this.age = age;
this.color = color;
}
/**
* 私有方法
*/
private void run() {
System.out.println("私有方法-run.....");
}
/**
* get和set方法
*/
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
@Override
public String toString() {
return "Car{"
"name='" name '''
", age=" age
", color='" color '''
'}';
}
}
这个是基本准备。下面咱们基于这个类来用一下反射中的相关内容。
1.1 获取Class对象的多种方式
第一个内容,获取Class对象的多种方式。
代码语言:javascript复制import com.jie.reflect.model.Car;
import org.junit.jupiter.api.Test;
/**
* 测试反射类
*
* @author 阿杰 2416338031@qq.com
* @version 1.0
* @date 2023/10/26 6:51
*/
public class TestCar {
//1、获取Class对象多种方式
@Test
public void test01() throws Exception {
// 1.1、通过类名.class获取
Class clazz1 = Car.class;
// 1.2、通过对象.getClass()获取
Class clazz2 = new Car().getClass();
// 1.3、通过Class.forName("全类名")获取
Class clazz3 = Class.forName("com.jie.reflect.model.Car");
// 1.4、通过类加载器获取
ClassLoader classLoader = TestCar.class.getClassLoader();
Class clazz4 = classLoader.loadClass("com.jie.reflect.model.Car");
//实例化
Car car = (Car)clazz4.getConstructor().newInstance();
System.out.println(car);
}
}
1.2 获取构造方法
刚才咱们完成了第一个操作,获取class对象的多种方式演示,最终进行实例化。下面我们演示第二个内容,通过反射来获取构造方法。
代码语言:javascript复制 // 2 、 获取构造方法
@Test
public void test02() throws Exception {
// 2.1、获取所有的构造方法
Class clazz = Class.forName("com.jie.reflect.model.Car");
// getConstructors()获取所有的公有构造方法
Constructor[] constructors = clazz.getConstructors();
for (Constructor constructor : constructors) {
System.out.println("方法名称:" constructor.getName() " 参数个数:" constructor.getParameterCount());
}
System.out.println("====================================");
// getDeclaredConstructors()获取所有的构造方法 包括私有的
Constructor[] constructors2 = clazz.getDeclaredConstructors();
for (Constructor constructor : constructors2) {
System.out.println("方法名称:" constructor.getName() " 参数个数:" constructor.getParameterCount());
}
// 2.2、获取指定有参数的构造方法构造对象
// getConstructor 获取公有的构造方法
Constructor constructor = clazz.getConstructor(String.class, int.class, String.class);
Car car = (Car)constructor.newInstance("奔驰", 20, "黑色");
System.out.println(car);
// 2.3、获取私有的构造方法
// getDeclaredConstructor 获取私有的构造方法
Constructor c2 = clazz.getDeclaredConstructor(String.class, int.class, String.class);
c2.setAccessible(true);
Car car2 = (Car)c2.newInstance("捷达", 15, "白色");
System.out.println(car2);
}
1.3 获取属性
下面我们演示第三个获取属性
代码语言:javascript复制 // 3、获取属性
@Test
public void test03() throws Exception {
// 3.1、获取所有的属性
Class clazz = Class.forName("com.jie.reflect.model.Car");
Car car = (Car) clazz.getDeclaredConstructor().newInstance();
// getFields()获取所有的公有属性
System.out.println("获取所有的公有属性");
System.out.println(clazz.getFields().length);
// 因为Car类没有公有属性,所以获取不到
// System.out.println(clazz.getFields()[0].getName());
System.out.println("====================================");
// getDeclaredFields()获取所有的属性 包括私有的
System.out.println("获取所有的属性");
System.out.println(clazz.getDeclaredFields().length);
Field[] declaredFields = clazz.getDeclaredFields();
for (Field declaredField : declaredFields) {
if (declaredField.getName().equals("name")) {
// 私有属性需要设置访问权限
declaredField.setAccessible(true);
declaredField.set(car, "奔驰");
}
System.out.println(declaredField.getName());
System.out.println(car);
}
// 3.2、获取指定的属性
// getField 获取公有的属性
// 因为Car类没有公有属性,所以获取不到
// System.out.println("获取指定的公有属性");
// System.out.println(clazz.getField("name"));
System.out.println("====================================");
// getDeclaredField 获取私有的属性
System.out.println("获取指定的属性");
System.out.println(clazz.getDeclaredField("color"));
}
1.4 获取方法
然后再看第四个,就是如何来操作方法。
代码语言:javascript复制 // 4、获取方法
@Test
public void test04() throws Exception {
// 4.1、获取所有的方法
Class clazz = Class.forName("com.jie.reflect.model.Car");
Car car = (Car) clazz.getDeclaredConstructor().newInstance();
car.setName("奔驰");
car.setAge(20);
car.setColor("黑色");
// getMethods()获取所有的公有方法
System.out.println("获取所有的公有方法");
System.out.println(clazz.getMethods().length);
// 因为Car类没有公有方法,所以获取不到
Method[] methods = clazz.getMethods();
for (Method method : methods) {
// 获取方法名称
// System.out.println(method.getName());
// 执行方法 toString
if(method.getName().equals("toString")) {
System.out.println(method.invoke(car));
}
}
// 4.2 getDeclaredMethods()获取所有的方法 包括私有的
System.out.println("获取所有的方法");
System.out.println(clazz.getDeclaredMethods().length);
Method[] methodsAll = clazz.getDeclaredMethods();
for (Method m:methodsAll) {
//执行方法 run
if(m.getName().equals("run")) {
m.setAccessible(true);
m.invoke(car);
}
}
}
2、实现Spring的IoC
我们知道,IoC(控制反转)和DI(依赖注入)是Spring里面核心的东西,那么,我们如何自己手写出这样的代码呢?下面我们就一步一步写出Spring框架最核心的部分。
2.1 搭建子模块
搭建模块:guigu-spring,搭建方式如其他spring子模块
2.2 准备测试需要的bean
创建UserDao接口
代码语言:javascript复制package com.jie.spring.dao;
/**
* UserDao
*
* @author 阿杰 2416338031@qq.com
* @date 2023/10/27 6:39
* @version 1.0
*/
public interface UserDao {
public void print();
}
创建UserDaoImpl实现
代码语言:javascript复制package com.jie.spring.dao.impl;
import com.jie.spring.dao.UserDao;
/**
* UserDaoImpl
*
* @author 阿杰 2416338031@qq.com
* @version 1.0
* @date 2023/10/27 6:44
*/
public class UserDaoImpl implements UserDao {
@Override
public void print() {
System.out.println("Dao层执行结束");
}
}
创建UserService接口
代码语言:javascript复制package com.jie.spring.service;
/**
* UserService
*
* @author 阿杰 2416338031@qq.com
* @date 2023/10/27 6:45
* @version 1.0
*/
public interface UserService {
public void out();
}
创建UserServiceImpl实现类
代码语言:javascript复制package com.jie.spring.service.impl;
import com.jie.spring.service.UserService;
public class UserServiceImpl implements UserService {
@Override
public void out() {
System.out.println("Service层执行结束");
}
}
2.3 定义注解
我们通过注解的形式加载bean与实现依赖注入。
代码语言:javascript复制package com.jie.spring.anno;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Bean 用于创建对象
* 作用于类上
* 运行时生效
*
* @author 阿杰 2416338031@qq.com
* @version 1.0
* @date 2023/10/27 6:51
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Bean {
}
代码语言:javascript复制package com.jie.spring.anno;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Di 用于依赖注入
* 作用于属性上
* 运行时生效
*
* @author 阿杰 2416338031@qq.com
* @version 1.0
* @date 2023/10/27 6:52
*/
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Di {
}
说明:上面两个注解可以随意取名
然后我们就可以在我们的UserDaoImpl 使用我们定义的注解。
2.4定义bean容器接口
代码语言:javascript复制package com.jie.spring.bean;
/**
* ApplicationContext 用于获取bean
*
* @author 阿杰 2416338031@qq.com
* @date 2023/10/27 7:03
* @version 1.0
*/
public interface ApplicationContext {
Object getBean(Class clazz);
}
2.5 编写注解bean容器接口实现
AnnotationApplicationContext基于注解扫描bean。
代码语言:javascript复制package com.jie.spring.bean;
import java.net.URL;
import java.net.URLDecoder;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
/**
* ApplicationContext 用于获取bean
*
* @author 阿杰 2416338031@qq.com
* @version 1.0
* @date 2023/10/27 7:06
*/
public class ApplicationContextImpl implements ApplicationContext {
/**
* 创建Map集合,放Bean对象
*/
private Map<Class, Object> beanFactory = new HashMap<>();
@Override
public Object getBean(Class clazz) {
return beanFactory.get(clazz);
}
/**
* 创建有参构造器,传递包路径,设置包扫描规则
* 扫描包路径下的所有类,判断类上是否有Bean注解,如果有,创建对象,放入Map集合
*
* @param basePackage 包路径
*/
public ApplicationContextImpl(String basePackage) {
}
}
2.6 编写扫描bean逻辑
我们通过构造方法传入包的base路径,扫描被@Bean注解的java对象,完整代码如下:
代码语言:javascript复制package com.jie.spring.bean;
import com.jie.spring.anno.Bean;
import java.io.File;
import java.net.URL;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
/**
* ApplicationContext 用于获取bean
*
* @author 阿杰 2416338031@qq.com
* @version 1.0
* @date 2023/10/27 7:06
*/
public class ApplicationContextImpl implements ApplicationContext {
/**
* 创建Map集合,放Bean对象
*/
private static final Map<Class, Object> BEAN_FACTORY = new HashMap<>();
private static String rootPath;
@Override
public Object getBean(Class clazz) {
return BEAN_FACTORY.get(clazz);
}
/**
* 创建有参构造器,传递包路径,设置包扫描规则
* 扫描包路径下的所有类,判断类上是否有Bean注解,如果有,创建对象,放入Map集合
*
* @param basePackage 包路径
*/
public ApplicationContextImpl(String basePackage) {
try {
// 包路径都是 com.jie.spring
// 1 我们需要把 . 替换成
String packagePath = basePackage.replaceAll("\.", "\\");
// 2 获取包的绝对路径
Enumeration<URL> urls = Thread.currentThread().getContextClassLoader().getResources(packagePath);
// 3 遍历包下的所有类
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
// 4 获取类的绝对路径
String filePath = URLDecoder.decode(url.getPath(), StandardCharsets.UTF_8);
// 5 获取包前面的路径部分,字符串截取
rootPath = filePath.substring(0, filePath.length() - packagePath.length());
// 6 调用方法,获取包下的所有类
loadBean(new File(filePath));
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 包扫描过程 递归
*
* @param file 文件
* @return: void
* @author 阿杰 2416338031@qq.com
* @date: 2023/10/29 17:48
*/
private static 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 childrenFile : childrenFiles) {
// 4.1 遍历得到每个File对象,继续判断,如果还是文件夹,递归调用
if (childrenFile.isDirectory()) {
// 递归调用
loadBean(childrenFile);
} else {
// 4.2 遍历得到每个File对象不是文件夹,是文件
// 4.3 得到包路径 类名称部分(字符串截取过程)
String patgWithClass = childrenFile
.getAbsolutePath()
.substring(rootPath.length() - 1);
// 4.4 判断是否是class文件
if (patgWithClass.contains(".class")) {
// 4.5 如果是class文件,把路径替换成.,把.class去掉,得到类的全限定名
String allName = patgWithClass
.replaceAll("\\", ".")
.replaceAll(".class", "");
// 4.6 判断类上是否有Bean注解,如果有,就进行实例化
// 4.6.1 获取类的字节码对象
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集合
// 4.7.1 判断当前类如果实现了接口,把接口的class对象作为key
if (clazz.getInterfaces().length > 0) {
BEAN_FACTORY.put(clazz.getInterfaces()[0], instance);
} else {
// 4.7.2 如果没有实现接口,把当前类的class对象作为key
BEAN_FACTORY.put(clazz, instance);
}
}
}
}
}
}
}
}
public static void main(String[] args) {
ApplicationContextImpl applicationContext = new ApplicationContextImpl("com.jie.spring");
}
}
2.7 java类标识Bean注解
2.8 测试Bean加载
2.9 依赖注入实现
我们实现了Bean 的加载,现在来实现 依赖注入
代码语言:javascript复制/**
* 属性注入
*
* @return: void
* @author 阿杰 2416338031@qq.com
* @date: 2023/10/29 18:50
*/
private void loadDi() {
// 1 遍历BEAN_FACTORY Map集合
Set<Map.Entry<Class, Object>> entries = BEAN_FACTORY.entrySet();
for (Map.Entry<Class, Object> entry : entries) {
// 2 获取map 集合每个对象(value),获取每个对象属性
Object value = entry.getValue();
// 获取class对象
Class<?> clazz = value.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(value, BEAN_FACTORY.get(field.getType()));
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
}