本篇博客我们来手写一个IOC,就是模拟出IOC里边的实现过程。这过程怎么做呢?
咱们主要基于java中的反射,再加注解,来实现spring框架中IOC的这个效果。
下面我们来具体看看这个过程。首先因为这里边要用到反射,咱们把反射中的相关内容我们先做一个复习。复习之后最终让我们来手写spring IOC的这个功能。
1、回顾Java反射
java中的反射机制是什么呢?
它指的是对于任何一个类,我们都能够知道这个类里面的属性方法。
对于任何一个对象都能调它的任意方法和属性。
而这种动态获取信息以及动态调用对象方法的功能,就称为java的反射机制。
说的简单点,你要做反射,首先要得到类的卡的对象,就是咱们通俗说的字节码文件。通过字节码文件能够操作类中所有内容,包括你的属性,包括你的方法等等。这个是对于反射一个简单的概述。
自定义类
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;}@Overridepublic String toString() {return "Car{" +"name='" + name + '\'' +", age=" + age +", color='" + color + '\'' +'}';}
}
这个是基本准备。下面咱们基于这个类来用一下反射中的相关内容。
1.1 获取Class对象的多种方式
第一个内容,获取Class对象的多种方式。
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对象多种方式@Testpublic 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对象的多种方式演示,最终进行实例化。下面我们演示第二个内容,通过反射来获取构造方法。
// 2 、 获取构造方法@Testpublic 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 获取属性
下面我们演示第三个获取属性
// 3、获取属性@Testpublic 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 获取方法
然后再看第四个,就是如何来操作方法。
// 4、获取方法@Testpublic 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());// 执行方法 toStringif(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) {//执行方法 runif(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接口
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实现
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 {@Overridepublic void print() {System.out.println("Dao层执行结束");}
}
创建UserService接口
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实现类
package com.jie.spring.service.impl;import com.jie.spring.service.UserService;public class UserServiceImpl implements UserService {@Overridepublic void out() {System.out.println("Service层执行结束");}
}
2.3 定义注解
我们通过注解的形式加载bean与实现依赖注入。
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 {
}
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容器接口
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。
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<>();@Overridepublic Object getBean(Class clazz) {return beanFactory.get(clazz);}/*** 创建有参构造器,传递包路径,设置包扫描规则* 扫描包路径下的所有类,判断类上是否有Bean注解,如果有,创建对象,放入Map集合** @param basePackage 包路径*/public ApplicationContextImpl(String basePackage) {}
}
2.6 编写扫描bean逻辑
我们通过构造方法传入包的base路径,扫描被@Bean注解的java对象,完整代码如下:
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;@Overridepublic 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对象作为keyif (clazz.getInterfaces().length > 0) {BEAN_FACTORY.put(clazz.getInterfaces()[0], instance);} else {// 4.7.2 如果没有实现接口,把当前类的class对象作为keyBEAN_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 的加载,现在来实现 依赖注入
/*** 属性注入** @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();}}}}
}