Spring之手写IoC

本文涉及的产品
服务治理 MSE Sentinel/OpenSergo,Agent数量 不受限
可观测可视化 Grafana 版,10个用户账号 1个月
简介: 【1月更文挑战第15天】我们都知道,Spring框架的IOC是基于Java反射机制实现的,下面我们先回顾一下java反射。

文章目录

前言

一、回顾Java反射

二、实现Spring的IoC

①搭建子模块

②准备测试需要的bean

③定义注解

④定义bean容器接口

⑤编写注解bean容器接口实现

⑥编写扫描bean逻辑

⑦java类标识Bean注解

⑧测试Bean加载

⑨依赖注入

⑩依赖注入实现

总结


前言

我们都知道,Spring框架的IOC是基于Java反射机制实现的,下面我们先回顾一下java反射。


一、回顾Java反射

Java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为Java语言的反射机制。简单来说,反射机制指的是程序在运行时能够获取自身的信息。

要想解剖一个类,必须先要获取到该类的Class对象。而剖析一个类或用反射解决具体的问题就是使用相关API**(1)java.lang.Class(2)java.lang.reflect**,所以,Class对象是反射的根源

自定义类

packagecom.atguigu.reflect;
publicclassCar {
//属性privateStringname;
privateintage;
privateStringcolor;
//无参数构造publicCar() {
    }
//有参数构造publicCar(Stringname, intage, Stringcolor) {
this.name=name;
this.age=age;
this.color=color;
    }
//普通方法privatevoidrun() {
System.out.println("私有方法-run.....");
    }
//get和set方法publicStringgetName() {
returnname;
    }
publicvoidsetName(Stringname) {
this.name=name;
    }
publicintgetAge() {
returnage;
    }
publicvoidsetAge(intage) {
this.age=age;
    }
publicStringgetColor() {
returncolor;
    }
publicvoidsetColor(Stringcolor) {
this.color=color;
    }
@OverridepublicStringtoString() {
return"Car{"+"name='"+name+'\''+", age="+age+", color='"+color+'\''+'}';
    }
}

编写测试类

packagecom.atguigu.reflect;
importorg.junit.jupiter.api.Test;
importjava.lang.reflect.Constructor;
importjava.lang.reflect.Field;
importjava.lang.reflect.Method;
publicclassTestCar {
//1、获取Class对象多种方式@Testpublicvoidtest01() throwsException {
//1 类名.classClassclazz1=Car.class;
//2 对象.getClass()Classclazz2=newCar().getClass();
//3 Class.forName("全路径")Classclazz3=Class.forName("com.atguigu.reflect.Car");
//实例化Carcar= (Car)clazz3.getConstructor().newInstance();
System.out.println(car);
    }
//2、获取构造方法@Testpublicvoidtest02() throwsException {
Classclazz=Car.class;
//获取所有构造// getConstructors()获取所有public的构造方法//        Constructor[] constructors = clazz.getConstructors();// getDeclaredConstructors()获取所有的构造方法public  privateConstructor[] constructors=clazz.getDeclaredConstructors();
for (Constructorc: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 构造privateConstructorc2=clazz.getDeclaredConstructor(String.class, int.class, String.class);
c2.setAccessible(true);
Carcar2= (Car)c2.newInstance("捷达", 15, "白色");
System.out.println(car2);
    }
//3、获取属性@Testpublicvoidtest03() throwsException {
Classclazz=Car.class;
Carcar= (Car)clazz.getDeclaredConstructor().newInstance();
//获取所有public属性//Field[] fields = clazz.getFields();//获取所有属性(包含私有属性)Field[] fields=clazz.getDeclaredFields();
for (Fieldfield:fields) {
if(field.getName().equals("name")) {
//设置允许访问field.setAccessible(true);
field.set(car,"五菱宏光");
System.out.println(car);
            }
System.out.println(field.getName());
        }
    }
//4、获取方法@Testpublicvoidtest04() throwsException {
Carcar=newCar("奔驰",10,"黑色");
Classclazz=car.getClass();
//1 public方法Method[] methods=clazz.getMethods();
for (Methodm1:methods) {
//System.out.println(m1.getName());//执行方法 toStringif(m1.getName().equals("toString")) {
Stringinvoke= (String)m1.invoke(car);
//System.out.println("toString执行了:"+invoke);            }
        }
//2 private方法Method[] methodsAll=clazz.getDeclaredMethods();
for (Methodm:methodsAll) {
//执行方法 runif(m.getName().equals("run")) {
m.setAccessible(true);
m.invoke(car);
            }
        }
    }
}

二、实现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接口

packagecom.atguigu.spring6.test.dao;
publicinterfaceUserDao {
publicvoidprint();
}

创建UserDaoImpl实现

packagecom.atguigu.spring6.test.dao.impl;
importcom.atguigu.spring.dao.UserDao;
publicclassUserDaoImplimplementsUserDao {
@Overridepublicvoidprint() {
System.out.println("Dao层执行结束");
    }
}

创建UserService接口

packagecom.atguigu.spring6.test.service;
publicinterfaceUserService {
publicvoidout();
}

创建UserServiceImpl实现类

packagecom.atguigu.spring.test.service.impl;
importcom.atguigu.spring.core.annotation.Bean;
importcom.atguigu.spring.service.UserService;
@BeanpublicclassUserServiceImplimplementsUserService {
//    private UserDao userDao;@Overridepublicvoidout() {
//userDao.print();System.out.println("Service层执行结束");
    }
}

③定义注解

我们通过注解的形式加载bean与实现依赖注入

bean注解

packagecom.atguigu.spring.core.annotation;
importjava.lang.annotation.ElementType;
importjava.lang.annotation.Retention;
importjava.lang.annotation.RetentionPolicy;
importjava.lang.annotation.Target;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public@interfaceBean {
}

依赖注入注解

packagecom.atguigu.spring.core.annotation;
importjava.lang.annotation.ElementType;
importjava.lang.annotation.Retention;
importjava.lang.annotation.RetentionPolicy;
importjava.lang.annotation.Target;
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public@interfaceDi {
}

说明:上面两个注解可以随意取名

④定义bean容器接口

packagecom.atguigu.spring.core;
publicinterfaceApplicationContext {
ObjectgetBean(Classclazz);
}

⑤编写注解bean容器接口实现

AnnotationApplicationContext基于注解扫描bean

packagecom.atguigu.spring.core;
importjava.util.HashMap;
publicclassAnnotationApplicationContextimplementsApplicationContext {
//存储bean的容器privateHashMap<Class, Object>beanFactory=newHashMap<>();
@OverridepublicObjectgetBean(Classclazz) {
returnbeanFactory.get(clazz);
    }
/*** 根据包扫描加载bean* @param basePackage*/publicAnnotationApplicationContext(StringbasePackage) {
    }
}

⑥编写扫描bean逻辑

我们通过构造方法传入包的base路径,扫描被@Bean注解的java对象,完整代码如下:

packagecom.atguigu.spring.core;
importcom.atguigu.spring.core.annotation.Bean;
importjava.io.File;
importjava.util.HashMap;
publicclassAnnotationApplicationContextimplementsApplicationContext {
//存储bean的容器privateHashMap<Class, Object>beanFactory=newHashMap<>();
privatestaticStringrootPath;
@OverridepublicObjectgetBean(Classclazz) {
returnbeanFactory.get(clazz);
    }
/*** 根据包扫描加载bean* @param basePackage*/publicAnnotationApplicationContext(StringbasePackage) {
try {
StringpackageDirName=basePackage.replaceAll("\\.", "\\\\");
Enumeration<URL>dirs=Thread.currentThread().getContextClassLoader().getResources(packageDirName);
while (dirs.hasMoreElements()) {
URLurl=dirs.nextElement();
StringfilePath=URLDecoder.decode(url.getFile(),"utf-8");
rootPath=filePath.substring(0, filePath.length()-packageDirName.length());
loadBean(newFile(filePath));
            }
        } catch (Exceptione) {
thrownewRuntimeException(e);
        }
    }
privatevoidloadBean(FilefileParent) {
if (fileParent.isDirectory()) {
File[] childrenFiles=fileParent.listFiles();
if(childrenFiles==null||childrenFiles.length==0){
return;
            }
for (Filechild : childrenFiles) {
if (child.isDirectory()) {
//如果是个文件夹就继续调用该方法,使用了递归loadBean(child);
                } else {
//通过文件路径转变成全类名,第一步把绝对路径部分去掉StringpathWithClass=child.getAbsolutePath().substring(rootPath.length() -1);
//选中class文件if (pathWithClass.contains(".class")) {
//    com.xinzhi.dao.UserDao//去掉.class后缀,并且把 \ 替换成 .StringfullName=pathWithClass.replaceAll("\\\\", ".").replace(".class", "");
try {
Class<?>aClass=Class.forName(fullName);
//把非接口的类实例化放在map中if(!aClass.isInterface()){
Beanannotation=aClass.getAnnotation(Bean.class);
if(annotation!=null){
Objectinstance=aClass.newInstance();
//判断一下有没有接口if(aClass.getInterfaces().length>0) {
//如果有接口把接口的class当成key,实例对象当成valueSystem.out.println("正在加载【"+aClass.getInterfaces()[0] +"】,实例对象是:"+instance.getClass().getName());
beanFactory.put(aClass.getInterfaces()[0], instance);
                                    }else{
//如果有接口把自己的class当成key,实例对象当成valueSystem.out.println("正在加载【"+aClass.getName() +"】,实例对象是:"+instance.getClass().getName());
beanFactory.put(aClass, instance);
                                    }
                                }
                            }
                        } catch (ClassNotFoundException|IllegalAccessException|InstantiationExceptione) {
e.printStackTrace();
                        }
                    }
                }
            }
        }
    }
}

⑦java类标识Bean注解

@BeanpublicclassUserServiceImplimplementsUserService
@BeanpublicclassUserDaoImplimplementsUserDao

⑧测试Bean加载

packagecom.atguigu.spring;
importcom.atguigu.spring.core.AnnotationApplicationContext;
importcom.atguigu.spring.core.ApplicationContext;
importcom.atguigu.spring.test.service.UserService;
importorg.junit.jupiter.api.Test;
publicclassSpringIocTest {
@TestpublicvoidtestIoc() {
ApplicationContextapplicationContext=newAnnotationApplicationContext("com.atguigu.spring.test");
UserServiceuserService= (UserService)applicationContext.getBean(UserService.class);
userService.out();
System.out.println("run success");
    }
}

控制台打印测试

⑨依赖注入

只要userDao.print();调用成功,说明就注入成功

packagecom.atguigu.spring.test.service.impl;
importcom.atguigu.spring.core.annotation.Bean;
importcom.atguigu.spring.core.annotation.Di;
importcom.atguigu.spring.dao.UserDao;
importcom.atguigu.spring.service.UserService;
@BeanpublicclassUserServiceImplimplementsUserService {
@DiprivateUserDaouserDao;
@Overridepublicvoidout() {
userDao.print();
System.out.println("Service层执行结束");
    }
}

执行第八步:报错了,说明当前userDao是个空对象

⑩依赖注入实现

packagecom.atguigu.spring.core;
importcom.atguigu.spring.core.annotation.Bean;
importcom.atguigu.spring.core.annotation.Di;
importjava.io.File;
importjava.lang.reflect.Field;
importjava.util.HashMap;
importjava.util.Map;
publicclassAnnotationApplicationContextimplementsApplicationContext {
//存储bean的容器privateHashMap<Class, Object>beanFactory=newHashMap<>();
privatestaticStringrootPath;
@OverridepublicObjectgetBean(Classclazz) {
returnbeanFactory.get(clazz);
    }
/*** 根据包扫描加载bean* @param basePackage*/publicAnnotationApplicationContext(StringbasePackage) {
try {
StringpackageDirName=basePackage.replaceAll("\\.", "\\\\");
Enumeration<URL>dirs=Thread.currentThread().getContextClassLoader().getResources(packageDirName);
while (dirs.hasMoreElements()) {
URLurl=dirs.nextElement();
StringfilePath=URLDecoder.decode(url.getFile(),"utf-8");
rootPath=filePath.substring(0, filePath.length()-packageDirName.length());
loadBean(newFile(filePath));
            }
        } catch (Exceptione) {
thrownewRuntimeException(e);
        }
//依赖注入loadDi();
    }
privatevoidloadBean(FilefileParent) {
if (fileParent.isDirectory()) {
File[] childrenFiles=fileParent.listFiles();
if(childrenFiles==null||childrenFiles.length==0){
return;
            }
for (Filechild : childrenFiles) {
if (child.isDirectory()) {
//如果是个文件夹就继续调用该方法,使用了递归loadBean(child);
                } else {
//通过文件路径转变成全类名,第一步把绝对路径部分去掉StringpathWithClass=child.getAbsolutePath().substring(rootPath.length() -1);
//选中class文件if (pathWithClass.contains(".class")) {
//    com.xinzhi.dao.UserDao//去掉.class后缀,并且把 \ 替换成 .StringfullName=pathWithClass.replaceAll("\\\\", ".").replace(".class", "");
try {
Class<?>aClass=Class.forName(fullName);
//把非接口的类实例化放在map中if(!aClass.isInterface()){
Beanannotation=aClass.getAnnotation(Bean.class);
if(annotation!=null){
Objectinstance=aClass.newInstance();
//判断一下有没有接口if(aClass.getInterfaces().length>0) {
//如果有接口把接口的class当成key,实例对象当成valueSystem.out.println("正在加载【"+aClass.getInterfaces()[0] +"】,实例对象是:"+instance.getClass().getName());
beanFactory.put(aClass.getInterfaces()[0], instance);
                                    }else{
//如果有接口把自己的class当成key,实例对象当成valueSystem.out.println("正在加载【"+aClass.getName() +"】,实例对象是:"+instance.getClass().getName());
beanFactory.put(aClass, instance);
                                    }
                                }
                            }
                        } catch (ClassNotFoundException|IllegalAccessException|InstantiationExceptione) {
e.printStackTrace();
                        }
                    }
                }
            }
        }
    }
privatevoidloadDi() {
for(Map.Entry<Class,Object>entry : beanFactory.entrySet()){
//就是咱们放在容器的对象Objectobj=entry.getValue();
Class<?>aClass=obj.getClass();
Field[] declaredFields=aClass.getDeclaredFields();
for (Fieldfield : declaredFields){
Diannotation=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 (IllegalAccessExceptione) {
e.printStackTrace();
                    }
                }
            }
        }
    }
}

执行第八步:执行成功,依赖注入成功


总结

以上就是Spring之容器:IOC(3)的相关知识点,希望对你有所帮助。

积跬步以至千里,积怠惰以至深渊。时代在这跟着你一起努力哦!

相关文章
|
2月前
|
XML Java 数据格式
Spring IoC容器初始化过程(xml形式)
Spring IoC容器初始化过程(xml形式)
46 0
|
2月前
|
Java Spring
Spring5深入浅出篇:Spring中ioc(控制反转)与DI(依赖注入)
Spring5深入浅出篇:Spring中ioc(控制反转)与DI(依赖注入)
|
1月前
|
Java 数据库连接 API
【Spring】1、Spring 框架的基本使用【读取配置文件、IoC、依赖注入的几种方式、FactoryBean】
【Spring】1、Spring 框架的基本使用【读取配置文件、IoC、依赖注入的几种方式、FactoryBean】
49 0
|
21小时前
|
Java Spring 容器
【Spring系列笔记】IOC与DI
IoC 和 DI 是面向对象编程中的两个相关概念,它们主要用于解决程序中的依赖管理和解耦问题。 控制反转是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。其中最常见的方式叫做依赖注入和依赖查找。
8 2
|
1天前
|
Java 测试技术 数据库连接
Spring中ioc的优点
总之,Spring中的IoC提供了一种更加灵活、可维护、可测试和可扩展的方式来管理组件之间的依赖关系,从而提高了应用程序的质量和可维护性。这使得开发人员能够更专注于业务逻辑而不是底层的技术细节。
9 1
|
25天前
|
XML Java 数据格式
Spring(一)IOC小案例
Spring(一)IOC小案例
|
1月前
|
XML Java 数据格式
Spring 的奇幻起源:从 IoC 容器到 Bean 的魔法世界 (下)
Spring 的奇幻起源:从 IoC 容器到 Bean 的魔法世界
|
1月前
|
XML Java 数据格式
Spring 的奇幻起源:从 IoC 容器到 Bean 的魔法世界 (上)
Spring 的奇幻起源:从 IoC 容器到 Bean 的魔法世界 (上)
|
2月前
|
XML 缓存 Java
Spring IoC原理解读
Spring IoC原理解读
27 0
|
2月前
|
Java 测试技术 开发者
探究 Spring Boot 的核心:IOC 和 AOP
Spring Boot 作为一种简化 Spring 应用开发的工具,继承了 Spring 框架的核心概念,其中最重要的是控制反转(IOC)和面向切面编程(AOP)。它们是 Spring 框架的基础,同时也深深植根于 Spring Boot 中。本文将讨论 IOC 和 AOP 的概念以及它们在 Spring Boot 中的应用。
60 4

热门文章

最新文章