spring6:4、原理-手写IoC

目录

    • 4、原理-手写IoC
      • 4.1、回顾Java反射
      • 4.2、实现Spring的IoC

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;}@Overridepublic 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对象多种方式@Testpublic void test01() throws Exception {//1 类名.classClass 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、获取构造方法@Testpublic void test02() throws Exception {Class clazz = Car.class;//获取所有构造// getConstructors()获取所有public的构造方法
//        Constructor[] constructors = clazz.getConstructors();// getDeclaredConstructors()获取所有的构造方法public  privateConstructor[] 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 构造privateConstructor c2 = clazz.getDeclaredConstructor(String.class, int.class, String.class);c2.setAccessible(true);Car car2 = (Car)c2.newInstance("捷达", 15, "白色");System.out.println(car2);}//3、获取属性@Testpublic 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、获取方法@Testpublic 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());//执行方法 toStringif(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) {//执行方法 runif(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 {@Overridepublic 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;@Overridepublic 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<>();@Overridepublic 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;@Overridepublic 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,实例对象当成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 | 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 {@Testpublic 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 {@Diprivate UserDao userDao;@Overridepublic 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;@Overridepublic 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,实例对象当成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 | 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();}}}}}}

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

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

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

相关文章

不是“我应该做什么”,而是“我想做什么”

1. 识别内心的渴望 首先&#xff0c;我们需要识别自己真正的愿望和激情所在。这可能需要一些时间和自我反思。问自己&#xff1a;在没有任何外界压力的情况下&#xff0c;我真正想做的是什么&#xff1f;是赚钱、生活、旅行、追星&#xff0c;还是其他什么&#xff1f;识别这些…

30天学会Go--第7天 GO语言 Redis 学习与实践

30天学会Go–第7天 GO语言 Redis 学习与实践 文章目录 30天学会Go--第7天 GO语言 Redis 学习与实践前言一、Redis 基础知识1.1 Redis 的核心特性1.2 Redis 常见使用场景 二、安装 Redis2.1 在 Linux 上安装2.2 在 Windows 上安装2.3 使用 Docker 安装 Redis 三、Redis 常用命令…

Vue项目开发 如何实现父组件与子组件数据间的双向绑定?

在 Vue.js 中&#xff0c;实现父组件与子组件数据之间的双向绑定&#xff0c;可以通过以下几种方式。下面我将介绍几种常见的方法&#xff0c;并解释它们的实现原理和适用场景。 1. 使用 v-model 实现双向绑定 v-model 是 Vue.js 中最常见的双向绑定方式&#xff0c;它可以使…

React第十七章(useRef)

useRef 当你在React中需要处理DOM元素或需要在组件渲染之间保持持久性数据时&#xff0c;便可以使用useRef。 import { useRef } from react; const refValue useRef(initialValue) refValue.current // 访问ref的值 类似于vue的ref,Vue的ref是.value&#xff0c;其次就是vu…

【C++】内存分布、new、delete、 operator new、operator delete

内存分布 在C语言和C中&#xff0c;程序内存被划分成六个部分&#xff1a; 内核空间、栈、内存映射段、堆、数据段、代码段 栈&#xff1a;又称堆栈&#xff0c;主要为非静态局部变量、函数参数、返回值等&#xff0c;栈的生长方向是向下生长的 内存映射段&#xff1a;高效的…

代码随想录算法训练营day37|动态规划part5

今天的几道题目都比较简单&#xff0c;思路也比较相似&#xff0c;都是利用完全背包。完全背包和01背包的不同点在于完全背包每个元素可以取多次&#xff0c;而01背包只能取1次&#xff0c;所以在dp一维数组遍历时&#xff0c;完全背包仍然要从前往后遍历&#xff0c;并且无论是…

混合云策略在安全领域受到青睐

Genetec 发布了《2025 年物理安全状况报告》&#xff0c;该报告根据超过 5,600 名该领域领导者&#xff08;其中包括 100 多名来自澳大利亚和新西兰的领导者&#xff09;的回应&#xff0c;揭示了物理安全运营的趋势。 报告发现&#xff0c;澳大利亚和新西兰的组织采用混合云策…

FastAPI解决跨域报错net::ERR_FAILED 200 (OK)

目录 一、跨域问题的本质 二、FastAPI中的CORS处理 1. 安装FastAPI和CORS中间件 2. 配置CORS中间件 3. 运行FastAPI应用 三、解决跨域报错的步骤 四、案例:解决Vue.js与FastAPI的跨域问题 1. Vue.js前端应用 2. FastAPI后端API 3. 配置CORS中间件 4. 运行和测试 五…

为什么 JavaScript 中的 `new` 运算符报错?

在 JavaScript 中&#xff0c;new 运算符通常用于创建一个新对象并调用构造函数来初始化对象。然而&#xff0c;new 运算符可能会引发一些错误&#xff0c;通常是由于以下原因导致的&#xff1a; 构造函数没有正确的定义&#xff1a; 如果使用 new 运算符调用的函数没有正确地定…

霍尔效应电流传感器

适用于大电流&#xff0c;低功耗的电流检测&#xff1a; TVS选型: RS232的隔离电路: 单片机采集200伏高压 如何做隔离电路&#xff1a; 采用线性光电耦合器HCNR200实现高压直流母线电压的精确采样。还是用电阻分压&#xff0c;只是在ADC检测阶段加上隔离芯片&#xff1a;

如何设置Java爬虫的异常处理?

在Java爬虫中设置异常处理是非常重要的&#xff0c;因为网络请求可能会遇到各种问题&#xff0c;如连接超时、服务器错误、网络中断等。通过合理的异常处理&#xff0c;可以确保爬虫的稳定性和健壮性。以下是如何在Java爬虫中设置异常处理的步骤和最佳实践&#xff1a; 1. 使用…

ceph /etc/ceph-csi-config/config.json: no such file or directory

环境 rook-ceph 部署的 ceph。 问题 kubectl describe pod dragonfly-redis-master-0Warning FailedMount 7m59s (x20 over 46m) kubelet MountVolume.MountDevice failed for volume "pvc-c63e159a-c940-4001-bf0d-e6141634cc55" : rpc error: cod…

【计网笔记】习题

物理层 不属于物理层接口规范定义范畴的是&#xff08;C&#xff09; A. 接口形状 B. 引脚功能 C. 物理地址 D. 信号电平 【2023-912】光网络只能通过导向型介质传播。&#xff08;&#xff09; 【2017-408】若信道在无噪声情况下的极限数据传输速率不小于信噪比为30dB条件下的…

最新 AI 编程工具全面对比:v0、Bolt.new、Cursor、Windsurf

随着人工智能的快速发展&#xff0c;越来越多的 AI 驱动的开发工具应运而生&#xff0c;旨在提升开发效率、优化开发流程&#xff0c;并减轻开发者的工作负担。在这个背景下&#xff0c;四款新兴的 AI 编程工具&#xff1a;v0、Bolt.new、Cursor 和 Windsurf&#xff0c;各具特…

【C++算法】35.位运算_两整数之和

文章目录 题目链接&#xff1a;题目描述&#xff1a;解法C 算法代码&#xff1a; 题目链接&#xff1a; 371. 两整数之和 题目描述&#xff1a; 解法 笔试的话直接 return ab&#xff1b; 接下来讲一下这题的解法&#xff1a; 位运算&#xff08;异或运算-无进位相加&#xff…

PyCharm+Selenium+Pytest配置小记

1、下载ChromeDriver&#xff1a; Chrome130以后的Driver下载&#xff1a; Chrome for Testing availabilityhttps://googlechromelabs.github.io/chrome-for-testing/ &#xff08;1&#xff09;查看自己Crome浏览器的版本&#xff1a;设置-->关于 Chrome&#xff1b; &…

【C++】虚函数

类中声明函数成员的时候&#xff0c;在函数的前面加上virtual关键字&#xff0c;则该成员为虚函数 虚函数的特点 如果在类中定义的虚函数&#xff0c;那么系统会为这个类维护一个虚函数表类中会多出4个字节的指针去指向这个虚函数表&#xff0c;在虚函数表中保存了虚函数的首…

如何在UI自动化测试中创建稳定的定位器?

如何在UI自动化测试中创建稳定的定位器&#xff1f; 前言1. 避免使用绝对路径2. 避免在定位器中使用索引3. 避免多个类名的定位器4. 避免动态和自动生成的ID5. 确保定位器唯一6. 处理隐藏元素的策略7. 谨慎使用基于文本的定位器8. 使用AI创建稳定的定位器 总结 前言 在自动化测…

明日宣讲 香港科技大学广州|数据科学与分析学域硕博招生宣讲会【湖南大学专场】

&#x1f4e3;&#x1f4e3;&#x1f4e3;明日宣讲 香港科技大学广州&#xff5c;数据科学与分析学域硕博招生宣讲会【湖南大学专场】 时间&#xff1a;2024年12月10日&#xff08;星期二&#xff09;15:00 地点&#xff1a;湖南大学信息科学与工程学院624会议室 报名链接&…

BurpSuite-8(FakeIP与爬虫审计)

声明&#xff1a;学习视频来自b站up主 泷羽sec&#xff0c;如涉及侵权马上删除文章 感谢泷羽sec 团队的教学 视频地址&#xff1a;IP伪造和爬虫审计_哔哩哔哩_bilibili 一、FakeIP 1.配置环境 BurpSuite是java环境下编写的&#xff0c;而今天的插件是python编写的&#xff0c…