手写IOC

本篇博客我们来手写一个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);}
}

image-20231026072644461

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);}

image-20231026072711237

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"));}

image-20231026072819638

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);}}}

image-20231026073423284

2、实现Spring的IoC

我们知道,IoC(控制反转)和DI(依赖注入)是Spring里面核心的东西,那么,我们如何自己手写出这样的代码呢?下面我们就一步一步写出Spring框架最核心的部分。

2.1 搭建子模块

搭建模块:guigu-spring,搭建方式如其他spring子模块

image-20231027064621275

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 使用我们定义的注解。

image-20231027065545362

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注解

image-20231029184538323

image-20231029184600585

2.8 测试Bean加载

image-20231029184732111

2.9 依赖注入实现

我们实现了Bean 的加载,现在来实现 依赖注入

image-20231029190235776

/*** 属性注入** @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();}}}}
}

2.10 测试依赖注入

image-20231029190353388

image-20231029190339162

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/123038.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

实在没货,简历(软件测试)咋写?

简历咋写&#xff0c;这是很多没有【软件测试实际工作经验】的同学们非常头疼的事情。 简历咋写&#xff1f;首先你要知道简历的作用。 简历的作用是啥呢&#xff1f;一句话就是&#xff1a;让HR小姐姐约你。 如何让HR看你一眼&#xff0c;便相中你的简历&#xff0c;实现在众…

ubuntu扩大运行内存, 防止编译卡死

首先查看交换分区大小 grep SwapTotal /proc/meminfo 1、关闭交换空间 sudo swapoff -a 2、扩充交换空间大小&#xff0c;count64就是64G 1G x 64 sudo dd if/dev/zero of/swapfile bs1G count64 3、设置权限 sudo chmod 600 /swapfile 4、指定交换空间对应的设备文件 …

【不用开发板学习STM32】可设置电子时钟

• 实验环境 工程文件下载链接&#xff01;https://mp.weixin.qq.com/s?__bizMzU2OTc4ODA4OA&mid2247551559&idx1&sn721b9238bc58936ac41e6ad1b9988554&chksmfcfb1990cb8c9086490b11c05bc76c08da15c71caa38715a047c49d36f25a149920aee482f3e&token204641…

软件测试---等价类划分(功能测试)

能对穷举场景设计测试点-----等价类划分 等价类划分 说明&#xff1a;在所有测试数据中&#xff0c;具有某种共同特征的数据集合进行划分分类&#xff1a; 1&#xff09;有效等价类 2&#xff09;无效等价类步骤&#xff1a;1&#xff09;明确需求 2&#xff09;确定有效和无…

C语言实现输入一个字符串,递归将其逆序输出

完整代码&#xff1a; // 输入一个字符串&#xff0c;递归将其逆序输出。如输入 LIGHT&#xff0c;则输出 THGIL #include<stdio.h> #include<stdlib.h> //字符串的最大长度 #define N 20//逆序输出字符串 void func(char *str){if (*str\0){//结尾时直接退出递归…

[EFI]asus strix b760-i 13900F电脑 Hackintosh 黑苹果efi引导文件

硬件型号驱动情况主板 asus strix b760-i 处理器 I9 13900F 已驱动内存crucial ddr5-5200 64gb(32gb*2)(overclock 5600)已驱动硬盘 WD black sn850 500g*2 已驱动显卡rx570已驱动声卡Realtek ALCS1220A已驱动网卡Intel I225-V 2.5 Gigabit Ethernet已驱动无线网卡蓝牙Fevi T91…

Babylonjs学习笔记(六)——贴图的使用

书接上回&#xff0c;这里讨论贴图的运用&#xff01;&#xff01;&#xff01; // 创建球网格const ball MeshBuilder.CreateSphere(ball,{diameter:1},scene)ball.position new Vector3(0,1,0)// 创建PRB材质const ballMat new PBRMaterial(pbr,scene)// albedoTexture 反…

SHCTF 山河CTF Reverse方向[Week1]全WP 详解

文章目录 [WEEK1]ez_asm[WEEK1]easy_re[WEEK1]seed[WEEK1]signin[WEEK1]easy_math[WEEK1]ez_apk [WEEK1]ez_asm 从上往下读&#xff0c;第一处是xor 1Eh&#xff0c;第二处是sub 0Ah&#xff1b;逆向一下先加0A后异或1E 写个EXP data "nhuo[M7mc7uhc$7midgbTf7$7%#ubf7 …

保姆级教学安装Linux操作系统,以及Linux的语法入门

&#x1f3c5;我是默&#xff0c;一个在CSDN分享笔记的博主。&#x1f4da;&#x1f4da; &#x1f31f;在这里&#xff0c;我要推荐给大家我的专栏《Linux》。&#x1f3af;&#x1f3af; &#x1f680;无论你是编程小白&#xff0c;还是有一定基础的程序员&#xff0c;这个专…

附录B 其他第三方软件移植(FTP、OpenSSH、GDB)

目录 开发板 FTP 服务器移植与搭建vsftpd 源码下载vsftpd 移植vsftpd 服务器测试配置vsftpd添加新用户Filezilla 连接测试 开发板 OpenSSH 移植与使用OpenSSH 简介OpenSSH 移植OpenSSH 源码获取移植zlib 库移植openssl 库移植openssh 库 openssh 设置openssh 使用ssh 登录scp 命…

Java SE 学习笔记(十七)—— 单元测试、反射

目录 1 单元测试1.1 单元测试概述1.2 单元测试快速入门1.3 JUnit 常用注解 2 反射2.1 反射概述2.2 获取类对象2.3 获取构造器对象2.4 获取成员变量对象2.5 获取常用方法对象2.6 反射的作用2.6.1 绕过编译阶段为集合添加数据2.6.2 通用框架的底层原理 1 单元测试 1.1 单元测试概…

简单而高效:使用PHP爬虫从网易音乐获取音频的方法

概述 网易音乐是一个流行的在线音乐平台&#xff0c;提供了海量的音乐资源和服务。如果你想从网易音乐下载音频文件&#xff0c;你可能会遇到一些困难&#xff0c;因为网易音乐对其音频资源进行了加密和防盗链的处理。本文将介绍一种使用PHP爬虫从网易音乐获取音频的方法&…

【python笔记】小甲鱼

P3 查看内置函数 dir(__builtins__) P4 变量名命名规则&#xff1a; 1、变量名不能以数字打头&#xff1b; 2、变量名可以是中文 字符串可以是&#xff1a; 1、单引号&#xff1a;文本中存在双引号时使用单引号 2、双引号&#xff1a;文本中存在单引号时使用双引号 当…

Linux创建逻辑卷并扩容(超详细)

目录 ​编辑 一、概念解析 1、LV逻辑卷 2、PV物理卷 3、VG卷组 二、扩容前准备 三、创建逻辑卷并扩容 1、打开虚拟机 2、进入root用户 3、查看新加入的硬盘 4、创建主分区 5、创建物理卷 6、打包为一个卷组 7、创建逻辑卷 8、格式化逻辑卷 9、挂载逻辑卷--开机自…

Go学习第十四章——Gin请求与响应

Go web框架——Gin请求与响应 1 响应1.1 String1.2 JSON&#xff08;*&#xff09;1.3 HTML&#xff08;*&#xff09;1.4 XML1.5 文件&#xff08;*&#xff09; 2 请求2.1 请求参数查询参数 (Query)动态参数 (Param)表单参数 (PostForm)原始参数 (GetRawData) 2.2 请求头2.3 …

【送书福利-第二十一期】《ChatGPT进阶:提示工程入门》

&#x1f60e; 作者介绍&#xff1a;我是程序员洲洲&#xff0c;一个热爱写作的非著名程序员。CSDN全栈优质领域创作者、华为云博客社区云享专家、阿里云博客社区专家博主、前后端开发、人工智能研究生。公粽号&#xff1a;程序员洲洲。 &#x1f388; 本文专栏&#xff1a;本文…

elementUI 特定分辨率(如1920*1080)下el-row未超出一行却换行

在1920*1080分辨率下&#xff0c; el-col 内容未超出 el-col 宽度&#xff0c;el-col 不足以占据一行&#xff0c;el-row 却自动换行了&#xff08;其他分辨率没有这个问题&#xff09;。 截图&#xff1a; 排查&#xff1a; el-col 内容没有溢出&#xff1b;没有多余的 pad…

TELUS Ventures(泰勒斯)

TELUS Ventures&#xff08;泰勒斯&#xff09;高峰论坛于2023年10月28日在南京第5站正式开幕。该论坛是由泰勒斯风险投资公司主办的一项重要活动&#xff0c;旨在促进创新和创业精神的发展 。 这次高峰论坛将汇集来自全球各地的创业者、投资者和行业专家&#xff0c;共同探讨…

Mac 版 WPS 接入 WPS AI,支持内容创作、修改文章、提炼重点等功能

导读近日消息&#xff0c;“WPS 办公助手”公众号发文宣布&#xff0c;Mac 版 WPS 现已接入 WPS AI&#xff0c;将带来内容生成、内容修改、辅助阅读等功能。 汇总 Mac 版 WPS 接入 WPS AI 之后&#xff0c;在文字、PDF 方面的功能如下&#xff1a; 一键生成文章大纲、讲话稿、…

TS中类型别名和接口区别

在很多场景下&#xff0c;interface 和 type都能使用&#xff0c;因此两者在很多时候会被混淆&#xff1a; 接口可以通过之间的继承&#xff0c;实现多种接口的组合 使用类型别名也可以实现多种的&#xff0c;通过&连接,有差异&#xff1a; 子接口中不能重新覆盖父接口中…