手写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、指定交换空间对应的设备文件 …

Golang WebSocket 创建单独会话

引言 在互联网应用程序中&#xff0c;实时通信是一种非常重要的功能。WebSocket 是一种基于 TCP 的协议&#xff0c;它允许客户端和服务器之间进行双向通信。Golang 是一种高性能的编程语言&#xff0c;它提供了对 WebSocket 的原生支持&#xff0c;使得在 Golang 中创建 WebS…

ROS自学笔记十九:URDF集成Gazebo

URDF (Unified Robot Description Format) 是一个用于描述机器人模型的 XML 文件格式&#xff0c;通常用于机器人仿真和控制应用中。Gazebo 是一个开源的机器人仿真工具&#xff0c;常用于测试和开发机器人控制算法。你可以将 URDF 模型集成到 Gazebo 中&#xff0c;以在仿真环…

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

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

C++配平化学方程式,附源码。配平化学方程式的C++代码实现

化学方程式是化学反应简明的表达形式&#xff0c;它从“质”和“量”两个方面表达了化学反应的意义。故化学方程式的书写是我们学习化学的过程中不可或缺的一个重要环节。当我们遇到简单的化学方程式例如&#xff1a;2H2 O2 2H2O 时&#xff0c;配平则是毫无压力&#xff0c;…

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

能对穷举场景设计测试点-----等价类划分 等价类划分 说明&#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){//结尾时直接退出递归…

基于MATLAB的电流、电压互感器特性的仿真分析

摘 要 电压互感器和电流互感器是电力系统中进行电能计量和继电保护的基本测量和继电保护等设备之一&#xff0c;其准确度及可靠性与电力系统的安全、可靠、经济运行密切相关。随着电力系统在监测、控制及保护等方面自动化和智能化要求的不断提高&#xff0c;传统的电磁式电压互…

[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 …

多线程---单例模式

文章目录 什么是单例模式&#xff1f;饿汉模式懒汉模式版本一&#xff1a;最简单的懒汉模式版本二&#xff1a;考虑懒汉模式存在的线程安全问题版本三&#xff1a;更完善的解决线程安全问题版本四&#xff1a;解决指令重排序问题 什么是单例模式&#xff1f; 单例模式&#xf…

Nginx快速部署SSL证书_2023年10月29日可用

一、目的 Nginx快速部署SSL证书--2023年10月29日可用二、环境 Centos/Ubuntu服务器 前端代码文件夹&#xff1a; 假设为&#xff1a;/root/demo文件夹 域名 假设为&#xff1a;demo.com SSL证书&#xff08;通过上一篇博客获取&#xff0c;获取其他方式获取到的SSL证书&…

modelsim仿真报错:vlog-2388 ‘scl‘ already declared in this scope

问题背景&#xff1a; 1、使用vivado直接仿真的时候没有报错。 2、在vivado中调用modelsim的时候报错。 报错的代码&#xff1a; module iic_write(input clk,input rst,output scl,input en,inout sda);reg scl&#xff1b;……报错的意思是scl已经声明过了&#xff0c;mode…

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

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

芯片设计中RTL级别总结

目录 一、简介二、详细介绍2.1 RTL级的基本要素和设计步骤2.2 书中推荐的设计步骤2.3 常用RTL级建模 三、其他相关链接芯片行业常用英文术语详细总结 一、简介 芯片设计级别简介 行为级&#xff08;Behavior Level&#xff09;&#xff1a;通过行为级算法描述数字系统。 寄存器…

附录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爬虫从网易音乐获取音频的方法&…