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
  • Dapr Outbox 是1.12中的功能。 本文只介紹Dapr Outbox 執行流程,Dapr Outbox基本用法請閱讀官方文檔 。本文中appID=order-processor,topic=orders 本文前提知識:熟悉Dapr狀態管理、Dapr發佈訂閱和Outbox 模式。 Outbo ...
  • 引言 在前幾章我們深度講解了單元測試和集成測試的基礎知識,這一章我們來講解一下代碼覆蓋率,代碼覆蓋率是單元測試運行的度量值,覆蓋率通常以百分比表示,用於衡量代碼被測試覆蓋的程度,幫助開發人員評估測試用例的質量和代碼的健壯性。常見的覆蓋率包括語句覆蓋率(Line Coverage)、分支覆蓋率(Bra ...
  • 前言 本文介紹瞭如何使用S7.NET庫實現對西門子PLC DB塊數據的讀寫,記錄了使用電腦模擬,模擬PLC,自至完成測試的詳細流程,並重點介紹了在這個過程中的易錯點,供參考。 用到的軟體: 1.Windows環境下鏈路層網路訪問的行業標準工具(WinPcap_4_1_3.exe)下載鏈接:http ...
  • 從依賴倒置原則(Dependency Inversion Principle, DIP)到控制反轉(Inversion of Control, IoC)再到依賴註入(Dependency Injection, DI)的演進過程,我們可以理解為一種逐步抽象和解耦的設計思想。這種思想在C#等面向對象的編 ...
  • 關於Python中的私有屬性和私有方法 Python對於類的成員沒有嚴格的訪問控制限制,這與其他面相對對象語言有區別。關於私有屬性和私有方法,有如下要點: 1、通常我們約定,兩個下劃線開頭的屬性是私有的(private)。其他為公共的(public); 2、類內部可以訪問私有屬性(方法); 3、類外 ...
  • C++ 訪問說明符 訪問說明符是 C++ 中控制類成員(屬性和方法)可訪問性的關鍵字。它們用於封裝類數據並保護其免受意外修改或濫用。 三種訪問說明符: public:允許從類外部的任何地方訪問成員。 private:僅允許在類內部訪問成員。 protected:允許在類內部及其派生類中訪問成員。 示 ...
  • 寫這個隨筆說一下C++的static_cast和dynamic_cast用在子類與父類的指針轉換時的一些事宜。首先,【static_cast,dynamic_cast】【父類指針,子類指針】,兩兩一組,共有4種組合:用 static_cast 父類轉子類、用 static_cast 子類轉父類、使用 ...
  • /******************************************************************************************************** * * * 設計雙向鏈表的介面 * * * * Copyright (c) 2023-2 ...
  • 相信接觸過spring做開發的小伙伴們一定使用過@ComponentScan註解 @ComponentScan("com.wangm.lifecycle") public class AppConfig { } @ComponentScan指定basePackage,將包下的類按照一定規則註冊成Be ...
  • 操作系統 :CentOS 7.6_x64 opensips版本: 2.4.9 python版本:2.7.5 python作為腳本語言,使用起來很方便,查了下opensips的文檔,支持使用python腳本寫邏輯代碼。今天整理下CentOS7環境下opensips2.4.9的python模塊筆記及使用 ...