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.pswp.cn/web/62426.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/62426.shtml
英文地址,請注明出處:http://en.pswp.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…