我是南城余!阿里云开发者平台专家博士证书获得者!
欢迎关注我的博客!一同成长!
一名从事运维开发的worker,记录分享学习。
专注于AI,运维开发,windows Linux 系统领域的分享!
本章节对应知识库
https://www.yuque.com/nanchengcyu/java
本内容来自尚硅谷课程,此处在知识库做了个人理解
4、原理-手写IoC
我们都知道,Spring框架的IOC是基于Java反射机制实现的,下面我们先回顾一下java反射。
4.1、回顾Java反射
Java
反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为Java
语言的反射机制。简单来说,反射机制指的是程序在运行时能够获取自身的信息。
要想解剖一个类,必须先要获取到该类的Class对象。而剖析一个类或用反射解决具体的问题就是使用相关API**(1)java.lang.Class(2)java.lang.reflect**,所以,Class对象是反射的根源。
自定义类
package com.atguigu.reflect; 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 + '\'' + '}'; } }
编写测试类
package com.atguigu.reflect; import org.junit.jupiter.api.Test; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; public class TestCar { //1、获取Class对象多种方式 @Test public void test01() throws Exception { //1 类名.class Class clazz1 = Car.class; //2 对象.getClass() Class clazz2 = new Car().getClass(); //3 Class.forName("全路径") Class clazz3 = Class.forName("com.atguigu.reflect.Car"); //实例化 Car car = (Car)clazz3.getConstructor().newInstance(); System.out.println(car); } //2、获取构造方法 @Test public void test02() throws Exception { Class clazz = Car.class; //获取所有构造 // getConstructors()获取所有public的构造方法 // Constructor[] constructors = clazz.getConstructors(); // getDeclaredConstructors()获取所有的构造方法public private Constructor[] constructors = clazz.getDeclaredConstructors(); for (Constructor c:constructors) { System.out.println("方法名称:"+c.getName()+" 参数个数:"+c.getParameterCount()); } //指定有参数构造创建对象 //1 构造public // Constructor c1 = clazz.getConstructor(String.class, int.class, String.class); // Car car1 = (Car)c1.newInstance("夏利", 10, "红色"); // System.out.println(car1); //2 构造private Constructor c2 = clazz.getDeclaredConstructor(String.class, int.class, String.class); c2.setAccessible(true); Car car2 = (Car)c2.newInstance("捷达", 15, "白色"); System.out.println(car2); } //3、获取属性 @Test public void test03() throws Exception { Class clazz = Car.class; Car car = (Car)clazz.getDeclaredConstructor().newInstance(); //获取所有public属性 //Field[] fields = clazz.getFields(); //获取所有属性(包含私有属性) Field[] fields = clazz.getDeclaredFields(); for (Field field:fields) { if(field.getName().equals("name")) { //设置允许访问 field.setAccessible(true); field.set(car,"五菱宏光"); System.out.println(car); } System.out.println(field.getName()); } } //4、获取方法 @Test public void test04() throws Exception { Car car = new Car("奔驰",10,"黑色"); Class clazz = car.getClass(); //1 public方法 Method[] methods = clazz.getMethods(); for (Method m1:methods) { //System.out.println(m1.getName()); //执行方法 toString if(m1.getName().equals("toString")) { String invoke = (String)m1.invoke(car); //System.out.println("toString执行了:"+invoke); } } //2 private方法 Method[] methodsAll = clazz.getDeclaredMethods(); for (Method m:methodsAll) { //执行方法 run if(m.getName().equals("run")) { m.setAccessible(true); m.invoke(car); } } } }
4.2、实现Spring的IoC
我们知道,IoC(控制反转)和DI(依赖注入)是Spring里面核心的东西,那么,我们如何自己手写出这样的代码呢?下面我们就一步一步写出Spring框架最核心的部分。
①搭建子模块
搭建模块: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接口
package com.atguigu.spring6.test.dao; public interface UserDao { public void print(); }
创建UserDaoImpl实现
package com.atguigu.spring6.test.dao.impl; import com.atguigu.spring.dao.UserDao; public class UserDaoImpl implements UserDao { @Override public void print() { System.out.println("Dao层执行结束"); } }
创建UserService接口
package com.atguigu.spring6.test.service; public interface UserService { public void out(); }
创建UserServiceImpl实现类
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层执行结束"); } }
③定义注解
我们通过注解的形式加载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 { }
说明:上面两个注解可以随意取名
④定义bean容器接口
package com.atguigu.spring.core; public interface ApplicationContext { Object getBean(Class clazz); }
⑤编写注解bean容器接口实现
AnnotationApplicationContext基于注解扫描bean
package com.atguigu.spring.core; import java.util.HashMap; public class AnnotationApplicationContext implements ApplicationContext { //存储bean的容器 private HashMap<Class, Object> beanFactory = new HashMap<>(); @Override public Object getBean(Class clazz) { return beanFactory.get(clazz); } /** * 根据包扫描加载bean * @param basePackage */ public AnnotationApplicationContext(String basePackage) { } }
⑥编写扫描bean逻辑
我们通过构造方法传入包的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(); } } } } } } }
⑦java类标识Bean注解
@Bean public class UserServiceImpl implements UserService
@Bean public class UserDaoImpl implements UserDao
⑧测试Bean加载
package com.atguigu.spring; import com.atguigu.spring.core.AnnotationApplicationContext; import com.atguigu.spring.core.ApplicationContext; import com.atguigu.spring.test.service.UserService; import org.junit.jupiter.api.Test; public class SpringIocTest { @Test public void testIoc() { ApplicationContext applicationContext = new AnnotationApplicationContext("com.atguigu.spring.test"); UserService userService = (UserService)applicationContext.getBean(UserService.class); userService.out(); System.out.println("run success"); } }
控制台打印测试
⑨依赖注入
只要userDao.print();调用成功,说明就注入成功
package com.atguigu.spring.test.service.impl; import com.atguigu.spring.core.annotation.Bean; import com.atguigu.spring.core.annotation.Di; import com.atguigu.spring.dao.UserDao; import com.atguigu.spring.service.UserService; @Bean public class UserServiceImpl implements UserService { @Di private UserDao userDao; @Override public void out() { userDao.print(); System.out.println("Service层执行结束"); } }
执行第八步:报错了,说明当前userDao是个空对象
⑩依赖注入实现
package com.atguigu.spring.core; import com.atguigu.spring.core.annotation.Bean; import com.atguigu.spring.core.annotation.Di; import java.io.File; import java.lang.reflect.Field; import java.util.HashMap; import java.util.Map; 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); } //依赖注入 loadDi(); } 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(); } } } } } } private void loadDi() { for(Map.Entry<Class,Object> entry : beanFactory.entrySet()){ //就是咱们放在容器的对象 Object obj = entry.getValue(); Class<?> aClass = obj.getClass(); Field[] declaredFields = aClass.getDeclaredFields(); for (Field field : declaredFields){ Di annotation = field.getAnnotation(Di.class); if( annotation != null ){ field.setAccessible(true); try { System.out.println("正在给【"+obj.getClass().getName()+"】属性【" + field.getName() + "】注入值【"+ beanFactory.get(field.getType()).getClass().getName() +"】"); field.set(obj,beanFactory.get(field.getType())); } catch (IllegalAccessException e) { e.printStackTrace(); } } } } } }
执行第八步:执行成功,依赖注入成功