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
  • Timer是什麼 Timer 是一種用於創建定期粒度行為的機制。 與標準的 .NET System.Threading.Timer 類相似,Orleans 的 Timer 允許在一段時間後執行特定的操作,或者在特定的時間間隔內重覆執行操作。 它在分散式系統中具有重要作用,特別是在處理需要周期性執行的 ...
  • 前言 相信很多做WPF開發的小伙伴都遇到過表格類的需求,雖然現有的Grid控制項也能實現,但是使用起來的體驗感並不好,比如要實現一個Excel中的表格效果,估計你能想到的第一個方法就是套Border控制項,用這種方法你需要控制每個Border的邊框,並且在一堆Bordr中找到Grid.Row,Grid. ...
  • .NET C#程式啟動閃退,目錄導致的問題 這是第2次踩這個坑了,很小的編程細節,容易忽略,所以寫個博客,分享給大家。 1.第一次坑:是windows 系統把程式運行成服務,找不到配置文件,原因是以服務運行它的工作目錄是在C:\Windows\System32 2.本次坑:WPF桌面程式通過註冊表設 ...
  • 在分散式系統中,數據的持久化是至關重要的一環。 Orleans 7 引入了強大的持久化功能,使得在分散式環境下管理數據變得更加輕鬆和可靠。 本文將介紹什麼是 Orleans 7 的持久化,如何設置它以及相應的代碼示例。 什麼是 Orleans 7 的持久化? Orleans 7 的持久化是指將 Or ...
  • 前言 .NET Feature Management 是一個用於管理應用程式功能的庫,它可以幫助開發人員在應用程式中輕鬆地添加、移除和管理功能。使用 Feature Management,開發人員可以根據不同用戶、環境或其他條件來動態地控制應用程式中的功能。這使得開發人員可以更靈活地管理應用程式的功 ...
  • 在 WPF 應用程式中,拖放操作是實現用戶交互的重要組成部分。通過拖放操作,用戶可以輕鬆地將數據從一個位置移動到另一個位置,或者將控制項從一個容器移動到另一個容器。然而,WPF 中預設的拖放操作可能並不是那麼好用。為瞭解決這個問題,我們可以自定義一個 Panel 來實現更簡單的拖拽操作。 自定義 Pa ...
  • 在實際使用中,由於涉及到不同編程語言之間互相調用,導致C++ 中的OpenCV與C#中的OpenCvSharp 圖像數據在不同編程語言之間難以有效傳遞。在本文中我們將結合OpenCvSharp源碼實現原理,探究兩種數據之間的通信方式。 ...
  • 一、前言 這是一篇搭建許可權管理系統的系列文章。 隨著網路的發展,信息安全對應任何企業來說都越發的重要,而本系列文章將和大家一起一步一步搭建一個全新的許可權管理系統。 說明:由於搭建一個全新的項目過於繁瑣,所有作者將挑選核心代碼和核心思路進行分享。 二、技術選擇 三、開始設計 1、自主搭建vue前端和. ...
  • Csharper中的表達式樹 這節課來瞭解一下表示式樹是什麼? 在C#中,表達式樹是一種數據結構,它可以表示一些代碼塊,如Lambda表達式或查詢表達式。表達式樹使你能夠查看和操作數據,就像你可以查看和操作代碼一樣。它們通常用於創建動態查詢和解析表達式。 一、認識表達式樹 為什麼要這樣說?它和委托有 ...
  • 在使用Django等框架來操作MySQL時,實際上底層還是通過Python來操作的,首先需要安裝一個驅動程式,在Python3中,驅動程式有多種選擇,比如有pymysql以及mysqlclient等。使用pip命令安裝mysqlclient失敗應如何解決? 安裝的python版本說明 機器同時安裝了 ...