day11-實現Spring底層機制-01

来源:https://www.cnblogs.com/liyuelian/archive/2023/01/27/17069508.html
-Advertisement-
Play Games

實現Spring底層機制-01 主要實現:初始化IOC容器+依賴註入+BeanPostProcessor機制+AOP 前面我們實際上已經使用代碼簡單實現了: Spring XML 註入 bean (Spring基本介紹02) Spring 註解方式註入 bean (Spring管理Bean-IOC- ...


實現Spring底層機制-01

主要實現:初始化IOC容器+依賴註入+BeanPostProcessor機制+AOP

前面我們實際上已經使用代碼簡單實現了:

  1. Spring XML 註入 bean (Spring基本介紹02)
  2. Spring 註解方式註入 bean (Spring管理Bean-IOC-04)
  3. Spring AOP 動態代理實現 (AOP-01)

1.引出問題

1.1原生Spring如何實現依賴註入、singleton和prototype

例子

1.創建新的Maven項目:

image-20230127171741303

2.在pom.xml文件中添加 spring 開發的基本包:

<dependencies>
    <!--加入 spring 開發的基本包-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.3.8</version>
    </dependency>
    <!--加入spring開發切麵編程需要的包-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aspects</artifactId>
        <version>5.3.8</version>
    </dependency>
</dependencies>

3.src/main/java/ 目錄下創建包 com/li/component,在該包下分別創建UserDao.java、UserService.java、UserAction.java

image-20230127180736676

UserDao:

package com.li.component;

import org.springframework.stereotype.Component;

/**
 * @author 李
 * @version 1.0
 */
//也可以使用 @Repository
@Component
public class UserDao {

    public void hi() {
        System.out.println("UserDao-hi()---");
    }
}

UserService:

package com.li.component;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 * @author 李
 * @version 1.0
 */
//也可以使用 @Service
@Component
public class UserService {
    //定義屬性
    //也可以使用 @Resource
    @Autowired
    private UserDao userDao;

    public void m1() {
        userDao.hi();
    }
}

UserAction:

package com.li.component;

import org.springframework.stereotype.Component;

/**
 * @author 李
 * @version 1.0
 * 一個 Controller
 */
//也可以使用 @Controller
@Component
public class UserAction {
}

4.在 src/main/resources 目錄下創建 spring 的容器文件 beans.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       https://www.springframework.org/schema/context/spring-context.xsd">

    <!--配置自動掃描的包,同時引入對應的名稱空間-->
    <!--說明:
        1.如果是普通的java項目,beans.xml 放在src 目錄下即可
        2.如果是maven項目,beans.xml文件就要放在 src/main/resources 目錄下-->
    <context:component-scan base-package="com.li.component"/>

</beans>

5.測試類中獲取配置的bean,並輸出對象的地址值

package com.li;

import com.li.component.UserAction;
import com.li.component.UserDao;
import com.li.component.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * @author 李
 * @version 1.0
 */
public class AppMain {
    public static void main(String[] args) {
        //測試是否可以得到spring容器中的bean
        ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");

        UserAction userAction = (UserAction) ioc.getBean("userAction");
        UserAction userAction2 = (UserAction) ioc.getBean("userAction");

        System.out.println("userAction=" + userAction);
        System.out.println("userAction2=" + userAction2);
    }
}

可以看到通過“userAction”名稱獲取的對象的地址值相同,這說明它們實際上是同一個對象

image-20230127182749076

在預設情況下,我們配置的@Component,@Controller,@Service,@Repository 是單例的,即spring的ioc容器只會創建一個bean實例

6.如果我們希望將一個類配置為多例的,怎麼辦呢?

只需要在對應的類頭部添加 @Scope(value = "prototype"),表示以多實例的形式返回該類的bean對象

package com.li.component;

import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

@Component
@Scope(value = "prototype")
public class UserAction {
}

現在我們重新運行測試類,可以看到通過“userAction”名稱獲取的對象的地址值不相同,這說明它們是不同的對象。

image-20230127182915108

7.我們在測試類中獲取userService對象,並調用m1方法

package com.li;

import com.li.component.UserAction;
import com.li.component.UserDao;
import com.li.component.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * @author 李
 * @version 1.0
 */
public class AppMain {
    public static void main(String[] args) {   
        ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");
        UserService userService = (UserService) ioc.getBean("userService");
        
        System.out.println("userService=" + userService);
        //測試依賴註入
        System.out.print("userService對象調用m1()=");
        userService.m1();

    }
}

輸出如下,成功獲取到userService對象,並且調用m1方法成功。這說明UserService類中的userDao屬性成功通過@AutoWired 註解裝配。

image-20230127183147828

問題一:spring底層是如何通過註解來完成多例或者單例對象的創建的?

問題二:Spring容器如何實現依賴註入?

1.2原生Spring如何實現BeanPostProcessor

BeanPosecessor詳見Spring管理Bean-IOC-03-2.16後置處理器

  1. 後置處理器會在 bean 初始化方法調用前 和 初始化方法調用後 被調用
  2. 後置處理器對象會作用在容器配置文件的所有bean對象中(即使bean對象沒有初始化方法)

例子

1.創建一個後置處理器:

package com.li.process;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;

/**
 * @author 李
 * @version 1.0
 * 一個後置處理器
 */
@Component
public class MyBeanPostProcessor implements BeanPostProcessor {

    //在 Bean的 init初始化方法前被調用
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("postProcessBeforeInitialization 被調用 " + beanName + " bean= " + bean.getClass());
        return bean;
    }

    //在 Bean的 init初始化方法後被調用
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("postProcessAfterInitialization 被調用 " + beanName + " bean= " + bean.getClass());
        return bean;
    }
}

要使用後置處理器,需要進行配置,配置的方式有兩種:(1)在xml容器文件中進行配置(2)添加註解

使用註解時,還要保證掃描的範圍要覆蓋到該類

2.在UserService類中添加初始化方法:

package com.li.component;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;

/**
 * @author 李
 * @version 1.0
 */
//也可以使用 @Service
@Component
public class UserService {
    //定義屬性
    //也可以使用 @Resource
    @Autowired
    private UserDao userDao;

    public void m1() {
        userDao.hi();
    }

    //初始化方法-名稱隨意,需要@PostConstruct指定init為初始化方法
    @PostConstruct
    public void init(){
        System.out.println("UserService-init()");
    }
}

3.在測試類中進行測試:

package com.li;

import com.li.component.UserAction;
import com.li.component.UserDao;
import com.li.component.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * @author 李
 * @version 1.0
 */
public class AppMain {
    public static void main(String[] args) {

        ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");

        UserAction userAction = (UserAction) ioc.getBean("userAction");
        UserAction userAction2 = (UserAction) ioc.getBean("userAction");

        System.out.println("userAction=" + userAction);
        System.out.println("userAction2=" + userAction2);

        UserDao userDao = (UserDao) ioc.getBean("userDao");
        System.out.println("userDao=" + userDao);

        UserService userService = (UserService) ioc.getBean("userService");
        System.out.println("userService=" + userService);

        System.out.print("userService對象調用m1()=");
        userService.m1();

    }
}

如下,後置處理器對象會作用在容器配置文件的所有bean對象中(即使bean對象沒有初始化方法),根據之前的配置,容器中一共有四個對象(UserAction為多例),因此一共調用了八次。

這裡userAction對象因為是多例的,強製為懶載入,因此在被獲取時(getBean())才創建,因此排在最後。

image-20230127191759269

問題三:原生Spring如何實現BeanPostProcessor?

1.3原生spring如何實現AOP

例子-在上述代碼的基礎上添加如下內容

1.SmartAnimal 介面:

package com.li.aop;

/**
 * @author 李
 * @version 1.0
 */
public interface SmartAnimal {
    public float getSum(float i, float j);

    public float getSub(float i, float j);
}

2.SmartDog 實現類:

package com.li.aop;

import org.springframework.stereotype.Component;

/**
 * @author 李
 * @version 1.0
 */
@Component
public class SmartDog implements SmartAnimal {
    @Override
    public float getSum(float i, float j) {
        float res = i + j;
        System.out.println("SmartDog-getSum()-res=" + res);
        return res;
    }

    @Override
    public float getSub(float i, float j) {
        float res = i - j;
        System.out.println("SmartDog-getSub()-res=" + res);
        return res;
    }
}

3.SmartAnimalAspect 切麵類:

package com.li.aop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

import java.util.Arrays;

/**
 * @author 李
 * @version 1.0
 * 切麵類
 */
@Component
@Aspect
public class SmartAnimalAspect {
    @Pointcut(value = "execution(public float com.li.aop.SmartAnimal.*(float,float))")
    public void myPointCut() {
    }

    //前置通知
    @Before(value = "myPointCut()")
    public void before(JoinPoint joinpoint) {
        Signature signature = joinpoint.getSignature();
        System.out.println("SmartAnimalAspect切麵類-before()-" + signature.getName()
                + "-參數-" + Arrays.toString(joinpoint.getArgs()));
    }

    //返回通知
    @AfterReturning(value = "myPointCut()", returning = "res")
    public void afterReturning(JoinPoint joinpoint, Object res) {
        Signature signature = joinpoint.getSignature();
        System.out.println("SmartAnimalAspect切麵類-afterReturning()-" + signature.getName() + "-res-" + res);
    }

    //異常通知
    @AfterThrowing(value = "myPointCut()", throwing = "res")
    public void afterThrowing(JoinPoint joinpoint, Throwable res) {
        Signature signature = joinpoint.getSignature();
        System.out.println("SmartAnimalAspect切麵類-afterThrowing()-" + signature.getName() + "-res-" + res);
    }

    //最終通知
    @After(value = "myPointCut()")
    public void after(JoinPoint joinpoint) {
        Signature signature = joinpoint.getSignature();
        System.out.println("SmartAnimalAspect切麵類-after()-" + signature.getName());
    }
}

4.在容器文件中開啟基於註解的aop功能:

<!--配置自動掃描的包,同時引入對應的名稱空間-->
<context:component-scan base-package="com.li.aop"/>

<!--開啟基於註解的 aop 功能-->
<aop:aspectj-autoproxy/>

5.進行測試:

package com.li;

import com.li.aop.SmartAnimal;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * @author 李
 * @version 1.0
 */
public class AppMain {
    public static void main(String[] args) {
        ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");
        SmartAnimal smartDogProxy = ioc.getBean(SmartAnimal.class);
        smartDogProxy.getSum(101, 99);
    }
}

測試結果:

image-20230127205915935

前面的輸出是因為之前配置了後置處理器,它在創建ioc容器時會被調用,對所有bean對象生效。

紅框處,後置處理器的 postProcessBeforeInitialization() 方法調用時,bean對象的還是原生的類型,但是到了 postProcessAfterInitialization() 方法調用時,已經變成了代理對象 $Proxy。這說明後置處理器和aop切麵編程有著密切的關係。


簡單分析AOP和BeanPostProcessor的關係:

1.AOP實現Spring可以通過一個類加入註解@EnableAspectJAutoProxy來執行

2.我們來追一下@EnableAspectJAutoProxy

image-20230127210755653 image-20230127211052203 image-20230127211109821 image-20230127211125182 image-20230127211221107 image-20230127211229934

3.看一下 AnnotationAwareAspectJAutoProxyCreator 的類圖

image-20230127211701341

4.解讀:

(1)AOP底層是基於BeanPostProcessor機制的

(2)即在Bean對象創建好後,根據是否需要AOP處理,決定返回代理對象還是原生的Bean對象

(3)在返回代理對象時,就可以根據要代理的類和方法來返回

(4)這個機制並不難,本質就是BeanPostProcessor 機制+動態代理技術

2.Spring整體架構分析


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 2023-01-25 一、redis中的數據類型 1、redis列表(List) redis列表底層是一個雙向鏈表。 (1)從左邊/右邊插入一個或多個值 lpush/rpush <key><value1><value2><value3> 例如: (2)從左邊/右邊吐出一個值。值在鍵在,值光鍵亡 lp ...
  • MySQL 作為最流行的關係型資料庫管理系統之一,非常多系統的後端存儲都有著MySQL 的身影,可謂是廣泛應用於各行各業。與此同時,資料庫作為應用服務的核心組件,直接影響著應用服務運行。資料庫的瓶頸往往也是整個系統的瓶頸,其重要性不言而喻,所以對於 MySQL 的監控必不可少,及時發現 MySQL ... ...
  • 2023-01-27 一、安卓(Android Studio)的下載路徑 https://developer.android.google.cn/studio/ 二、創建一個空的安卓project 1、打開安卓後,點擊“New Project” 2、點擊選擇一個“空的安卓項目” 3、選擇文件存放路徑 ...
  • AngularJS的重要概念 MVC模式 AngularJS最早按照MVC模式設計,在這種設計模式下,AngularJS組件可以分為: M: Model,即模型,是應用程式中用於處理應用程式數據邏輯的部分,在AngularJS中: 即作用域對象(當前為$rootScope), 它可以包含一些屬性或方 ...
  • 預設情況下:Photoshop 導出切片為【GIF】格式 當你很嗨皮的把【GIF】調整為【PNG】或【JPG】格式,並保存時: 你會發現,自己的圖片格式莫名其妙還是【GIF】: 但,我們的期望是: 原因是“因為我們沒有選中全部切片,並將其格式設置為【PNG】”,解決方案(選中全部切片設置為png或其 ...
  • 閉包和作用域 變數聲明 var 聲明特點 在使用var聲明變數時,變數會被自動添加到最接近的上下文 var存在聲明提升。var聲明會被拿到函數或全局作用域的頂部,位於作用域中所有代碼之前。 可多次重覆聲明。而重覆的var聲明則會被忽略 let 聲明特點 let聲明存在塊級作用域 let聲明(創建過程 ...
  • 如果你在項目中使用了 vuex模塊化,並且在項目中使用actions中函數調用頻率高,推薦瞭解一下這種方式。 比如下麵兩種方式調用 , 第一個是直接傳參設置, 第二個是添加了非同步ajax返回內容 在回調到等下我們要封裝的js中的成功回調里,然後這個成功回調就會反饋給組件 1.創建文件utils/vu ...
  • 一、Lua應用場景 游戲開發 獨立應用腳本 Web 應用腳本 擴展和資料庫插件如:MySQL Proxy 和 MySQL WorkBench 安全系統,如入侵檢測系統 教程採用Aide Lua Pro或AndLua+開發安卓應用。在學習開發安卓應用前,先學習lua的基礎課程。 二、配置手機開發環境 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...