主頁 > 後端開發 > 【架構視角】一篇文章帶你徹底吃透Spring

【架構視角】一篇文章帶你徹底吃透Spring

2022-04-29 06:17:01 後端開發

spring-core-cover

框架的意義

對于程式員來說,我們通常知道很多概念,例如組件、模塊、系統、框架、架構等,而本文我們重點說 框架

02-spring-000

  • 框架,本質上是一些實用經驗集合,即是前輩們在實際開發程序中積攢下來的實戰經驗,累積成一套實用工具,避免你在開發程序中重復去造輪子,特別是幫你把日常中能遇到的場景或問題都給屏蔽掉,框架的意義在于屏蔽掉開發的基礎復雜度、屏蔽掉此類共性的東西,同時建立嚴格的編碼規范,讓框架使用者開箱即用,并且只需要關注差異面,即業務層面的實作,簡而言之,框架只干一件事,那就是 簡化開發,然后在此基礎上,可能會再考慮一些安全性、效率、性能、彈性、管理、拓展、解耦等等,

理解 Spring 核心

Spring 作為一個框架,目的也是:簡化開發 ,只不過在簡化開發的程序中 Spring 做了一個特別的設計,那就是 Bean管理,這也是 Spring 的設計核心,而 Bean 生命周期管理的設計巧妙的 解耦 了 Bean 之間的關系,

因此 Spring 核心特性就是 解耦簡化

02-spring-000

Spring 框架圖示展示得很清晰,基本描繪出 Spring 框架的核心:

  • 內核
  • 外延

簡單說,就是 Spring 設計了一個 核心容器 Core Container,這里頭主要就是管理 Bean 生命周期,然后為了服務這些業務 Bean ,引入了 Core , Context , SpEL 等工具到核心容器中,然后在核心容器基礎上,又為了把更多的能力集成進來,例如為了拓展 資料訪問 能力加入了 JDBC 、ORM 、OXM 、JMS 、Transactions 等,為了拓展 Web 能力加入了 WebSocket 、Servlet、Web、Portlet 等,其中為了把 RequestMapping 或 Servlet 等這些使用集成到業務 Bean 上,引入了 AOP ,包括還有引入(最終是提供) Aspects、Instrumentation、Messageing 等增強方式,

02-spring-000

所以仔細一看,Spring 就是把像資料庫訪問、Web支持、快取、訊息發送等等這些能力集成到業務 Bean 上,并提供一些測驗支持,總結來說理解 Spring 就兩點:

  1. Bean管理: 解耦Bean關系,理解為內核,從 Bean 的定義、創建、管理等,這是業務Bean,

  2. 功能增強: 解耦功能、宣告式簡化,理解為外延,在業務Bean基礎上,需要訪庫等能力,那就是功能增強,

基本體現的就是兩個核心特性,一個 解耦、一個 簡化

02-spring-000

Bean管理 本身就是在做 解耦,解除耦合,這個解耦指 Bean 和 Bean 之間的關聯關系,Bean 之間通過介面協議互相串聯起來的,至于每個介面有多少個實作類,那都不會有任何影響,Bean 之間只保留單點通道,通過介面相互隔離,關系都交給 Spring 管理,這樣就避免了實作類和實作類之間出現一些耦合,就算方法增減了、參考變更了也不至于互相污染,

功能增強 本身就是在做 簡化,例如宣告式簡化,像宣告式編程,使用者只需要告訴框架他要什么,不用管框架是如何實作的,另外簡化方面還有 約定優于配置 (當然這個確切的說是 SpringBoot 里的設計),約定優于配置其實就是約定好了無需去做復雜的配置,例如你引入一個什么組件或能力就像 redis 或 kafka,你不需要提前配置,因為 springboot 已經為你默認配置,開箱即用,

因此 Spring 框架特性怎么理解?就 解耦簡化

02-spring-000

而 SpringBoot,簡單理解就是在 Spring 框架基礎上添加了一個 SPI 可拓展機制版本管理,讓易用性更高,簡化升級,

springcloud.jpg

而 SpringCloud,簡單理解就是,由于 SpringBoot 的 依賴 可以被很好的管理,拓展 可以被可插拔的拓展,因此在 SpringBoot 基礎上集成了很多跟微服務架構相關的能力,例如集成了很多組件,便有了 SpringCloud 全生態,

基本了解了 Spring 特性之后,我們回到 Spring 的核心設計 IoC 與 AOP

IoC

我們說了 Spring 的其一特性是 解耦,那到底是使用什么來解耦?

02-spring-core-001

控制反轉(Inversion of Control,縮寫為 IoC),是面向物件編程中的一種設計原則,可以用來減低計算機代碼之間的耦合度,其中最常見的方式叫做依賴注入(Dependency Injection,簡稱 DI),還有一種方式叫“依賴查找”(Dependency Lookup,EJB 和 Apache Avalon 都使用這種方式),通過控制反轉,物件在被創建的時候,由一個調控系統內所有物件的外界物體將其所依賴的物件的參考傳遞給它,也可以說,依賴被注入到物件中,

簡單來說,就是原本 Bean 與 Bean 之間的這種互相呼叫,變成了由 IoC 容器去統一調配,如果沒使用 IoC 容器統一管理業務 Bean,你的應用在部署、修改、迭代的時候,業務 Bean 是會侵入代碼實作并互相呼叫的,

02-spring-000

那么問題來了,所有系統都需要引入 IOC 嗎?

IoC 容器是面向 迭代 起作用,如果你的應用就 不存在迭代 的情況,即系統是萬年不變的,那沒必要引入 IoC,因為你每引入一項技術,都勢必會增加復雜度,所以額外引入 IoC 也一樣會增加你整體應用的復雜度,所以假如 不存在迭代,大可直接寫死A類參考B類,B類又寫死參考C類,無需引入 IoC,一定要理解每一項技術背后是為了解決什么問題,同時在做架構設計的時候記住兩個原則:合適簡單,當然,實際上我們大部分應用是 持續迭代 的,在類實作上、互相參考上、甚至介面協議上都有可能變化,所以一般引入 IoC 是合適的(如果是介面協議變化,即引數或回傳值發生變化,那還是需要改動類間的代碼的),

具體的,IoC 相當于是把 Bean 實體的創建程序交給 Spring 管理,無論是通過 XML、JavaConfig,還是注解方式,最終都是把實體化的作業交給 Spring 負責,之后 Bean 之間通過介面相互呼叫,而實體化程序中就涉及到 注入,無論采用什么方式來實體化 Bean,注入 的類別就兩種:

  • Setter注入 : 通過 setter 來設定,發生在物件 實體化之后 設定,
  • 構造器注入 : 通過構造器注入,發生在物件 實體化之前 就得把引數/實體準備好,

setter注入:

  1. 與傳統的 JavaBean 的寫法更相似,程式開發人員更容易理解、接受,通過 setter 方法設定依賴關系顯得更加直觀、自然,
  2. 對于復雜的依賴關系,如果采用構造注入,會導致構造器過于臃腫,難以閱讀,Spring 在創建 Bean 實體時,需要同時實體化其依賴的全部實體,因而導致性能下降,而使用設值注入,則能避免這些問題,
  3. 尤其在某些成員變數可選的情況下,多引數的構造器更加笨重,

構造器注入:

  1. 構造器注入可以在構造器中決定依賴關系的注入順序,優先依賴的優先注入,
  2. 對于依賴關系無需變化的 Bean ,構造注入更有用處,因為沒有 setter 方法,所有的依賴關系全部在構造器內設定,無須擔心后續的代碼對依賴關系產生破壞,
  3. 依賴關系只能在構造器中設定,則只有組件的創建者才能改變組件的依賴關系,對組件的呼叫者而言,組件內部的依賴關系完全透明,更符合高內聚的原則,

而這兩種方式的注入方式都使用了 反射

02-spring-000

反射

了解反射相關類以及含義:

  • java.lang.Class: 代表整個位元組碼,代表一個型別,代表整個類,
  • java.lang.reflect.Method: 代表位元組碼中的方法位元組碼,代表類中的方法,
  • java.lang.reflect.Constructor: 代表位元組碼中的構造方法位元組碼,代表類中的構造方法,
  • java.lang.reflect.Field: 代表位元組碼中的屬性位元組碼,代表類中的成員變數(靜態變數+實體變數),

java.lang.reflect 包提供了許多反射類,用于獲取或設定實體物件,簡單來說,反射能夠:

  1. 在運行時 判斷任意一個物件所屬的類;
  2. 在運行時構造任意一個類的物件;
  3. 在運行時判斷任意一個類所具有的成員變數和方法;
  4. 在運行時呼叫任意一個物件的方法;
  5. 生成動態代理

IoC反射,只是把 Bean 的實體創建處理完,而后續還有 功能增強,功能增強靠的就是 AOP

AOP

AOP全名 Aspect-Oriented Programming ,中文直譯為面向切面編程,當前已經成為一種比較成熟的編程思想,可以用來很好的解決應用系統中分布于各個模塊的交叉關注點問題,在輕量級的J2EE中應用開發中,使用AOP來靈活處理一些具有 橫切性質 的系統級服務,如事務處理、安全檢查、快取、物件池管理等,已經成為一種非常適用的解決方案,

為什么需要AOP

當我們要進行一些日志記錄、權限控制、性能統計等時,在傳統應用程式當中我們可能在需要的物件或方法中進行編碼,而且比如權限控制、性能統計大部分是重復的,這樣代碼中就存在大量 重復代碼,即使有人說我把通用部分提取出來,那必然存在呼叫還是存在重復,像性能統計我們可能只是在必要時才進行,在診斷完畢后要洗掉這些代碼;還有日志記錄,比如記錄一些方法訪問日志、資料訪問日志等等,這些都會滲透到各個要訪問方法中;還有權限控制,必須在方法執行開始進行審核,想想這些是多么可怕而且是多么無聊的作業,如果采用 Spring,這些日志記錄、權限控制、性能統計從業務邏輯中分離出來,通過 Spring 支持的面向切面編程,在需要這些功能的地方動態添加這些功能,無需滲透到各個需要的方法或物件中;有人可能說了,我們可以使用“代理設計模式”或“包裝器設計模式”,你可以使用這些,但還是需要通過編程方式來創建代理物件,還是要 耦合 這些代理物件,而采用 Spring 面向 切面 編程能提供一種更好的方式來完成上述功能,一般通過 配置 方式,而且不需要在現有代碼中添加任何額外代碼,現有代碼專注業務邏輯,

02-spring-000

所以,AOP 以橫截面的方式插入到主流程中,Spring AOP 面向切面編程能幫助我們無耦合的實作:

  • 性能監控,在方法呼叫前后記錄呼叫時間,方法執行太長或超時報警,
  • 快取代理,快取某方法的回傳值,下次執行該方法時,直接從快取里獲取,
  • 軟體破解,使用 AOP 修改軟體的驗證類的判斷邏輯,
  • 記錄日志,在方法執行前后記錄系統操作日志,
  • 作業流系統,作業流系統需要將業務代碼和流程引擎代碼混合在一起執行,那么我們可以使用AOP將其分離,并動態掛接業務,
  • 權限驗證,方法執行前驗證是否有權限執行當前方法,沒有則拋出沒有權限執行例外,有業務代碼捕捉,
  • 等等

AOP 其實就是從應用中劃分出來了一個切面,然后在這個切面里面插入一些 “增強”,最后產生一個增加了新功能的 代理物件,注意,是代理物件,這是Spring AOP 實作的基礎,這個代理物件只不過比原始物件(Bean)多了一些功能而已,比如 Bean預處理Bean后處理例外處理 等, AOP 代理的目的就是 將切面織入到目標物件

AOP如何實作

前面我們說 IoC 的實作靠反射,然后解耦,那 AOP 靠啥實作?

AOP,簡單來說就是給物件增強一些功能,我們需要看 Java 給我們預留了哪些口或者在哪些階段,允許我們去織入某些增強功能,

我們可以從幾個層面來實作AOP,

02-spring-core-004

  • 編譯期

    • 原理:在編譯器編譯之前注入源代碼,源代碼被編譯之后的位元組碼自然會包含這部分注入的邏輯,
    • 代表作如:lombok, mapstruct(編譯期通過 pluggable annotation processing API 修改的),
  • 運行期,位元組碼加載前

    • 原理:位元組碼要經過 classloader(類加載器)加載,那我們可以通過 自定義類加載器 的方式,在位元組碼被自定義類加載器 加載前 給它修改掉,
    • 代表作如:javasist, java.lang.instrument ,ASM(操縱位元組碼),
    • 許多 agent 如 Skywaking, Arthas 都是這么搞,注意區分 靜態agent動態agent
    • JVMTI 是 JVM 提供操作 native 方法的工具,Instrument 就是提供給你操縱 JVMTI 的 java 介面,詳情見 java.lang.instrument.Instrumentation
  • 運行期,位元組碼加載后

    • 原理:位元組碼被類加載器加載后,動態構建位元組碼檔案生成目標類的 子類,將切面邏輯加入到子類中,
    • 代表作如:jdk proxy, cglib,

按照類別分類,基本可以理解為:

類別 原理 優點 缺點
靜態AOP 在編譯期,切面直接以位元組碼的形式編譯到目標位元組碼檔案中 對系統無性能影響 靈活度不夠
動態AOP 在運行期,目標類加載后,為介面動態生成代理類,將切面織入到代理類中 動態代理方式,相對于靜態AOP更加靈活 切入的關注點需要實作介面,對系統有一點性能影響
動態位元組碼生成 在運行期,目標類加載后,動態構建位元組碼檔案生成目標類的 子類,將切面邏輯加入到子類中 沒有介面也可以織入 擴展類的實體方法為final時,則無法進行織入,性能基本是最差的,因為需要生成子類嵌套一層,spring用的cglib就是這么搞的,所以性能比較差
自定義類加載器 在運行期,在位元組碼被自定義類加載器加載前,將切面邏輯加到目標位元組碼里,例如阿里的Pandora 可以對絕大部分類進行織入 代碼中如果使用了其他類加載器,則這些類將不會被織入
位元組碼轉換 在運行期,所有類加載器加載位元組碼前,進行攔截 可以對所有類進行織入 -

當然,理論上是越早織入,性能越好,像 lombok,mapstruct 這類靜態AOP,基本在編譯期之前都修改完,所以性能很好,但是靈活性方面當然會比較差,獲取不到運行時的一些資訊情況,所以需要權衡比較,

簡單說明5種類別:

02-spring-000

當然我整理了一份詳細的腦圖,可以直接在網頁上打開,

《腦圖:Java實作AOP思路》:

https://www.processon.com/embed/62333d1ce0b34d074452eec2

1、靜態AOP

發生在 編譯期,通過 Pluggable Annotation Processing API 修改原始碼,

02-spring-core-005

在 javac 進行編譯的時候,會根據源代碼生成抽象語法樹(AST),而 java 通過開放 Pluggable Annotation Processing API 允許你參與修改源代碼,最終生成位元組碼,典型的代表就是 lombok

2、動態AOP (動態代理)

發生在 運行期,于 位元組碼加載后,類、方法已經都被加載到方法區中了,

spring-aop-diy

典型的代表就是 JDK Proxy


    public static void main(String[] args) {

        // 需要代理的介面,被代理類實作的多個介面,都必須在這里定義
        Class[] proxyInterface = new Class[]{IBusiness.class,IBusiness2.class};
        
        // 構建AOP的Advice,這里需要傳入業務類的實體
        LogInvocationHandler handler = new LogInvocationHandler(new Business());
        
        // 生成代理類的位元組碼加載器
        ClassLoader classLoader = DynamicProxyDemo.class.getClassLoader();
        
        // 織入器,織入代碼并生成代理類
        IBusiness2 proxyBusiness = 
            (IBusiness2)Proxy.newProxyInstance(classLoader, proxyInterface, handler);
        
        // 使用代理類的實體來呼叫方法
        proxyBusiness.doSomeThing2();
        ((IBusiness)proxyBusiness).doSomeThing();
    }


其中代理實作 InvocationHandler 介面,最終實作邏輯在 invoke 方法中,生成代理類之后,只要目標物件的方法被呼叫了,都會優先進入代理類 invoke 方法,進行增強驗證等行為,


    public class LogInvocationHandler implements InvocationHandler{

        private Object target;  // 目標物件

        LogInvocationHandler(Object target){
            this.target = target;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

            // 執行原有邏輯
            Object rev = method.invoke(target,args);

            // 執行織入的日志,你可以控制那些方法執行切入邏輯
            if (method.getName().equals("doSomeThing2")){
                // 記錄日志
            }
            return rev;
        }
    }


當然動態代理相對也是性能差,畢竟也多走了一層代理,每多走一層就肯定是越難以優化,

雖然,動態代理在運行期通過介面動態生成代理類,這為其帶來了一定的靈活性,但這個靈活性卻帶來了兩個問題:

  1. 第一代理類必須實作一個介面,如果沒實作介面會拋出一個例外,
  2. 第二性能影響,因為動態代理使用反射的機制實作的,首先反射肯定比直接呼叫要慢,經過測驗大概每個代理類比靜態代理多出10幾毫秒的消耗,其次使用反射大量生成類檔案可能引起 Full GC 造成性能影響,因為位元組碼檔案加載后會存放在JVM運行時區的方法區(或者叫持久代,JDK1.8 之后已經在元空間)中,當方法區滿的時候,會引起 Full GC ,所以當你大量使用動態代理時,可以將持久代設定大一些,減少 Full GC 次數,

關于動態代理的詳細原理和流程,推薦閱讀《一文讀懂Java動態代理》,

3、動態位元組碼生成

發生在 運行期,于 位元組碼加載后 ,生成目標類的子類,將切面邏輯加入到子類中,所以使用Cglib實作AOP不需要基于介面,

此時類、方法同樣已經都被加載到方法區中了,

spring-aop-diy

典型的代表就是 Cglib(底層也是基于ASM操作位元組碼), Cglib 是一個強大的,高性能的 Code 生成類別庫,它可以在運行期間擴展Java類和實作Java介面,它封裝了 Asm,所以使用 Cglib 前需要引入 Asm 的jar,

    public static void main(String[] args) {   
        byteCodeGe();   
    }   
  
    /**  
     * 動態位元組碼生成  
     */  
    public static void byteCodeGe() {   
        //創建一個織入器   
        Enhancer enhancer = new Enhancer();   
        //設定父類   
        enhancer.setSuperclass(Business.class);   
        //設定需要織入的邏輯   
        enhancer.setCallback(new LogIntercept());   
        //使用織入器創建子類   
        IBusiness2 newBusiness = (IBusiness2) enhancer.create();   
        newBusiness.doSomeThing2();   
    }   
  
    /**  
     * 記錄日志  
     */   
    public static class LogIntercept implements MethodInterceptor {   
  
        @Override   
        public Object intercept(
			Object target, 
			Method method, 
			Object[] args, 
			MethodProxy proxy) throws Throwable {   
            
			//執行原有邏輯,注意這里是invokeSuper   
            Object rev = proxy.invokeSuper(target, args);   
            //執行織入的日志   
            if (method.getName().equals("doSomeThing")) {   
                System.out.println("recordLog");   
            }   
            return rev;   
        }   
    }  

Spring 默認采取 JDK 動態代理 機制實作 AOP,當動態代理不可用時(代理類無介面)會使用 CGlib 機制,缺點是:

  1. 只能對方法進行切入,不能對介面、欄位、static靜態代碼塊、private私有方法進行切入,

  2. 同類中的互相呼叫方法將不會使用代理類,因為要使用代理類必須從Spring容器中獲取Bean,同類中的互相呼叫方法是通過 this 關鍵字來呼叫,spring 基本無法去修改 jvm 里面的邏輯,

  3. 使用 CGlib 無法對 final 類進行代理,因為無法生成子類了,

4、自定義類加載器

發生在 運行期,于 位元組碼加載前,在類加載到JVM之前直接修改某些類的 方法,并將 切入邏輯 織入到這個方法里,然后將修改后的位元組碼檔案交給虛擬機運行,

02-spring-core-007

典型的代表就是 javasist,它可以獲得指定方法名的方法、執行前后插入代碼邏輯,

Javassist是一個編輯位元組碼的框架,可以讓你很簡單地操作位元組碼,它可以在運行期定義或修改Class,使用Javassist實作AOP的原理是在位元組碼加載前直接修改需要切入的方法,這比使用Cglib實作AOP更加高效,并且沒太多限制,實作原理如下圖:

02-spring-core-016

我們使用系統類加載器啟動我們自定義的類加載器,在這個類加載器里加一個類加載監聽器,監聽器發現目標類被加載時就織入切入邏輯,我們再看看使用Javassist 實作 AOP 的代碼:

/***啟動自定義的類加載器****/

//獲取存放CtClass的容器ClassPool   
ClassPool cp = ClassPool.getDefault();   
//創建一個類加載器   
Loader cl = new Loader();   
//增加一個轉換器   
cl.addTranslator(cp, new MyTranslator());   
//啟動MyTranslator的main函式   
cl.run("jsvassist.JavassistAopDemo$MyTranslator", args);  
// 類加載監聽器
public static class MyTranslator implements Translator {   
	public void start(ClassPool pool) throws 
				NotFoundException, CannotCompileException {   
	}     
  
    /**  
     * 類裝載到JVM前進行代碼織入  
     */  
	public void onl oad(ClassPool pool, String classname) {   
		if (!"model$Business".equals(classname)) {   
			return;   
		}   
		//通過獲取類檔案   
		try {   
			CtClass  cc = pool.get(classname);   
			//獲得指定方法名的方法   
			CtMethod m = cc.getDeclaredMethod("doSomeThing");   
			//在方法執行前插入代碼   
			m.insertBefore("{ System.out.println(\"recordLog\"); }");   
		} catch (NotFoundException e) {   
		} catch (CannotCompileException e) {   
		}   
	}   

	public static void main(String[] args) {   
		Business b = new Business();   
		b.doSomeThing2();   
		b.doSomeThing();   
	}   
} 

CtClass 是一個class檔案的抽象描述,也可以使用 insertAfter() 在方法的末尾插入代碼,或者使用 insertAt() 在指定行插入代碼,

使用自定義的類加載器實作AOP在性能上要優于動態代理和Cglib,因為它不會產生新類,但是它仍然存在一個問題,就是如果其他的類加載器來加載類的話,這些類將不會被攔截,

5、位元組碼轉換

自定義的類加載器實作AOP只能攔截自己加載的位元組碼,那么有沒有一種方式能夠監控所有類加載器加載位元組碼呢?有,使用Instrumentation,它是 Java 5 提供的新特性,使用 Instrumentation,開發者可以構建一個位元組碼轉換器,在位元組碼加載前進行轉換,

發生在 運行期 ,于 位元組碼加載前Java 1.5 開始提供的 Instrumentation APIInstrumentation API 就像是 JVM 預先放置的后門,它可以攔截在JVM上運行的程式,修改位元組碼,

這種方式是 Java API 天然提供的,在 java.lang.instrumentation ,就算 javasist 也是基于此實作,

一個代理實作 ClassFileTransformer 介面用于改變運行時的位元組碼(class File),這個改變發生在 jvm 加載這個類之前,對所有的類加載器有效,class File 這個術語定義于虛擬機規范3.1,指的是位元組碼的 byte 陣列,而不是檔案系統中的 class 檔案,介面中只有一個方法:

	/**  
     * 位元組碼加載到虛擬機前會進入這個方法  
     */   
    @Override   
    public byte[] transform(  
		        ClassLoader         loader,
                String              className,
                Class<?>            classBeingRedefined,
                ProtectionDomain    protectionDomain,
                byte[]              classfileBuffer)
        throws IllegalClassFormatException;

// 把 classBeingRedefined 重定義之后再交還回去

ClassFileTransformer 需要添加到 Instrumentation 實體中才能生效,

安全點注意

當對 JVM 中的位元組碼進行修改的時候,虛擬機也會通知所有執行緒通過安全點的方式停下來,因為修改會影響到類結構,

啟動流程

02-spring-core-014

Bean生命周期管理,基本從無到有(IoC),從有到增強(AOP),

任何Bean在Spring容器中只有三種形態,定義實體增強

從Bean定義資訊觀察,通過 xml 定義 bean關系propertiesyamljson定義 屬性,bean關系和屬性就構成Bean的定義,其中BeanDefinitionReader負責掃描定義資訊生成Bean定義物件 BeanDefinition,在此基礎上,允許對 BeanDefinition 定義進行增強(Mybatis與Spring存在很多使用場景),

Bean定義完成之后,開始通過反射實體化物件、填充屬性等,同時又再次預留了很多增強的口,最終生成一個完整的物件,

實體化流程與三級快取

從定義到擴展,然后反射實體化,到增強,每種狀態都會存在參考,

所以Spring設計 三級快取,說白了是對應存盤Bean生命周期的三種形態:

  • 定義
  • 實體
  • 增強

02-spring-core-015

總結

Spring 就是 反射 + 位元組碼增強

  • 反射,為了 IoC 和 解耦

  • 位元組碼增強,為了 簡化 和宣告式編程

深刻理解 Spring 這兩部分核心特性,關于 spring、springboot、springcloud 的所有語法糖設計與使用,就自然清楚,

參考

  • Understanding Java Agents
  • Java 1.5-java.lang.instrument
  • ASM 位元組碼插樁
  • arthas
  • ASM
  • cglib
  • javassist
  • Javassist/ASM Audit Log
  • bytebuddy tutorial
  • Performance Comparison of cglib, Javassist, JDK Proxy and Byte Buddy
  • 控制反轉
  • AOP 的實作機制
  • Spring AOP 總結
  • javaAgent、ASM、javassist、ByteBuddy 是什么?

首發訂閱

這里記錄技術內容,不定時發布,首發在

  • 潘深練個人網站
  • 微信公眾號:潘潘和他的朋友們

(本篇完)

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/466945.html

標籤:Java

上一篇:Spring5

下一篇:2022 Java生態系統報告:Java 11超Java 8、Oracle在縮水、Amazon在崛起!

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more