深入拆解Spring第二大核心思想:AOP

什么是AOP

Aspect Oriented Programming(面向切面編程)

  • 什么是面向切面編程呢? 切?就是指某?類特定問題, 所以AOP也可以理解為面向特定方法編程.

  • 什么是面向特定方法編程呢? 比如對于"登錄校驗", 就是?類特定問題. 登錄校驗攔截器, 就是對"登錄校驗"這類問題的統?處理. 所以, 攔截器也是AOP的?種應?. AOP是?種思想, 攔截器是AOP思想的?種實現. Spring框架實現了這種思想, 提供了攔截器技術的相關接?.

  • 同樣的, 統?數據返回格式和統?異常處理, 也是AOP思想的?種實現.

  • 簡單來說AOP是一種思想,是對某一類事情的集中處理,實現的方式有很多,有SpringAOP,AspectJ,CGLIB等

SpringAOP快速入門

我們通過一個經典的“方法耗時統計”案例來快速上手 Spring AOP。

引入AOP依賴:

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId> 
</dependency>

編寫切面代碼

一個切面包含了我們要執行的操作(通知) 和指定在何處執行(切點)

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;@Aspect      // 聲明這是一個切面類
@Component   // 將其作為Bean交由Spring容器管理
@Slf4j
public class TimeRecordAspect {// 1. 使用 @Pointcut 定義一個可重用的切點// 匹配 com.example.service 包及其子包下的所有類的所有方法@Pointcut("execution(* com.example.service..*.*(..))")public void serviceMethods() {}// 2. 定義通知(Advice),并引用上面的切點@Around("serviceMethods()")public Object recordTime(ProceedingJoinPoint joinPoint) throws Throwable {// joinPoint 代表被攔截的目標方法String methodName = joinPoint.getSignature().toShortString();long startTime = System.currentTimeMillis();log.info("==> 開始執行 [{}], 參數: {}", methodName, joinPoint.getArgs());// 3. 調用 proceed() 執行原始的目標方法Object result = joinPoint.proceed();long endTime = System.currentTimeMillis();log.info("<== 執行 [{}] 結束, 耗時: {} ms, 返回值: {}", methodName, (endTime - startTime), result);return result;}
}

代碼解析:

  • @Aspect: 標志著這個類是一個切面。
  • @Pointcut: 用于聲明一個可重用的切點表達式。這樣,當多個通知需要應用在相同的切點時,我們就不需要重復書寫冗長的 execution 表達式了。
  • @Around: 環繞通知。這是功能最強大的通知類型,它能完全控制目標方法的執行,你可以在方法執行前后添加自定義邏輯,甚至可以決定是否執行目標方法。
  • ProceedingJoinPoint: 連接點對象,只在 @Around 通知中使用。它代表了被攔截的目標方法,通過調用其 proceed() 方法來執行原始方法。

[!INFO] SpringAOP只是使用了Aspect的注解
注解分為兩個步驟:1. 聲明 2. 實現

Spring中AOP的通知類型有以下幾種:

  • @Around: 環繞通知。在目標方法執行前后都可執行,可以控制目標方法的執行。
  • @Before: 前置通知。在目標方法執行前執行。
  • @After: 后置通知。在目標方法執行后執行,無論方法是正常返回還是拋出異常,它都會執行(類似于 finally 塊)。
  • @AfterReturning: 返回后通知。在目標方法成功執行并返回結果后執行,如果方法拋出異常則不會執行。
  • @AfterThrowing: 異常后通知。在目標方法拋出異常后執行。

@PointCut

如果有大量的方法,那么就會存在大量的切點表達式,此時可以使用@PointCut把公共的切點表達式提取出來,需要時引入即可

@Aspect  
@Component  
@Slf4j  
public class TimeRecordAspect {  @Pointcut("execution(* com.doublez.springbook.controller.*.*(..))")  private void pt(){}  @Around("pt()")  public Object timeRecordAspect(ProceedingJoinPoint joinPoint) throws Throwable {  //... }@Around("pt()")public Object timeRecordAspect1(ProceedingJoinPoint joinPoint) throws Throwable {//...}@Around("pt()")public Object timeRecordAspect2(ProceedingJoinPoint joinPoint) throws Throwable {//...}@Around("pt()")public Object timeRecordAspect3(ProceedingJoinPoint joinPoint) throws Throwable {//...}
}

當切點定義使用private修飾時, 僅能在當前切?類中使用, 當其他切面類也要使用當前切點定義時, 就需要把private改為public. 引用方式為: 全限定類名.方法名()

切點表達式

常見的有兩種:@execution @annotation

execution

在這里插入圖片描述

  1. 通配符 * 的用法:

    • 匹配任意字符,但僅匹配一個元素,如返回類型、包名、類名、方法名或方法參數。
    • * 在包名中表示任意包(一層包)。
    • * 在類名中表示任意類。
    • * 在返回值中表示任意返回值類型。
    • * 在方法名中表示任意方法。
    • * 在參數中表示一個任意類型的參數。
  2. 通配符 .. 的用法:

    • 匹配多個連續的任意符號,可以通配任意層級的包,或任意類型、任意個數的參數。
    • .. 配置包名時,標識此包及其所有子包。
    • .. 配置參數時,表示任意個任意類型的參數。
  3. 切點表達式示例:

    • execution(public String com.example.demo.controller.TestController.t1()):匹配 TestController 下的 public 修飾,返回類型為 String,方法名為 t1,無參方法。
    • execution(String com.example.demo.controller.TestController.t1()):省略訪問修飾符的情況。
    • execution(* com.example.demo.controller.TestController.t1()):匹配所有返回類型。
    • execution(* com.example.demo.controller.TestController.*()):匹配 TestController 下的所有無參方法。
    • execution(* com.example.demo.controller.TestController.*(..)):匹配 TestController 下的所有方法。
    • execution(* com.example.demo.controller.*.*(..)):匹配 controller 包下所有類的所有方法。
    • execution(* com..TestController.*(..)):匹配所有包下面的 TestController。
    • execution(* com.example.demo...(..)):匹配 com.example.demo 包下,子孫包下的所有類的所有方法。

AOP可以識別類的私有方法嗎,可以的話推薦嗎? ->here

@annotation

用于自定義類的實現:[[@annotation自定義注解實現|here]]

切面優先級@Order

在這里插入圖片描述

  • 數字越大,優先級越低(可以有負數)
@Aspect  
@Component  
@Order(1) 
public class Demo {
}
@Aspect  
@Component  
@Order(3) 
public class Demo2 {
}

AOP原理

Spring AOP 并沒有在編譯期修改你的代碼,而是在運行時通過動態代理技術實現的。當你從 Spring 容器中獲取一個 Bean 時,如果這個 Bean 需要被 AOP 增強,那么你拿到的其實是一個代理對象,而不是原始對象。所有對方法的調用都會先經過這個代理對象,由它來決定何時執行切面邏輯、何時執行原始方法。

[[代理模式]]

  • 定義:為其他對象提供?種代理以控制對這個對象的訪問. 它的作用就是通過提供?個代理類, 讓我們在調用目標方法的時候, 不再是直接對目標方法進行調用, 而是通過代理類間接調用.

  • 在某些情況下, ?個對象不適合或者不能直接引用另?個對象,而代理對象可以在客戶端和目標對象之間起到中介的作用

代理模式的主要角色

  1. Subject: 業務接口類,可以是抽象類或者接口(不一定有)
  2. RealSubject:業務實現類,具體的業務執行,也就是被代理對象
  3. Proxy:代理類。RealSubject的代理
  • 根據代理的創建時期,代理模式可以分為靜態代理動態代理

靜態代理

靜態代理需要我們手動為每個被代理的類創建一個代理類,代理類和被代理類實現相同的接口。

  • 優點:簡單明了,容易理解。
  • 缺點:非常不靈活。如果接口增加一個方法,被代理類和代理類都需要修改。并且,每個業務類都需要一個對應的代理類,會導致類的數量急劇膨脹。

讓我們用一個發短信的例子來說明:

// 1. 業務接口
interface SmsService {void send(String message);
}// 2. 業務實現類(被代理對象)
class SmsServiceImpl implements SmsService {@Overridepublic void send(String message) {System.out.println("發送短信: " + message);}
}// 3. 靜態代理類
class SmsServiceStaticProxy implements SmsService {private final SmsService target;public SmsServiceStaticProxy(SmsService target) {this.target = target;}@Overridepublic void send(String message) {System.out.println("[靜態代理] 發送短信前,進行日志記錄...");// 調用原始對象的方法target.send(message);System.out.println("[靜態代理] 發送短信后,操作完成。");}
}// 使用
public static void main(String[] args) {SmsService smsService = new SmsServiceImpl();SmsServiceStaticProxy proxy = new SmsServiceStaticProxy(smsService);proxy.send("Hello, Static Proxy!");
}

動態代理

由于靜態代理的局限性,動態代理應運而生。它不需要我們手動創建代理類,而是在程序運行時動態地生成代理對象。Spring 主要使用兩種動態代理技術:

JDK 動態代理

這是 Java 官方提供的代理方式,它要求被代理的類必須實現至少一個接口。它的核心是 java.lang.reflect.Proxy 類和 InvocationHandler 接口。

定義 JDK 動態代理類 (JDKInvocationHandler):

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;// JDK代理工廠
class JdkProxyFactory {public static Object getProxy(Object target) {return Proxy.newProxyInstance(target.getClass().getClassLoader(), // 目標類的類加載器target.getClass().getInterfaces(),  // 目標類實現的接口new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("[JDK動態代理] 方法 " + method.getName() + " 執行前...");Object result = method.invoke(target, args); // 執行目標方法System.out.println("[JDK動態代理] 方法 " + method.getName() + " 執行后...");return result;}});}
}// 使用
public static void main(String[] args) {SmsService smsService = (SmsService) JdkProxyFactory.getProxy(new SmsServiceImpl());smsService.send("Hello, JDK Proxy!");
}

代碼簡單講解:

  1. InvocationHandler
    InvocationHandler 接口是 Java 動態代理的關鍵接口之一,它定義了一個單一方法 invoke(),用于處理被代理對象的方法調用。
    public interface InvocationHandler {// proxy: 代理對象// method: 代理對象調用的實際方法,即其中需要增強的方法// args: 方法的參數Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
    }
    
  2. Proxy
    Proxy 類中使用的最高頻率的方法是 newProxyInstance(),這個方法主要用來生成一個代理對象。
    public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException
    
    • loader: 類加載器,用于加載代理對象。
    • interfaces: 被代理類實現的一組接口(這個參數的定義,也決定了 JDK 動態代理只能代理實現了接口的類)。
    • h: 實現 InvocationHandler 接口的對象。

CGLIB 動態代理

JDK 動態代理有一個最致命的缺陷是其只能代理實現了接口的類。在有些場景下,業務代碼是直接實現的類,并沒有實現接口。為了解決這個問題,可以使用 CGLIB 動態代理機制來解決。
CGLIB 動態代理的特點:
CGLIB (Code Generation Library) 是一個基于 ASM 的字節碼生成庫,它允許在運行時對字節碼進行修改和動態生成。CGLIB 通過繼承方式實現代理,很多知名的開源框架都使用了 CGLIB,例如 Spring 中的 AOP 模塊中:如果目標對象實現了接口,默認采用 JDK 代理,否則采用 CGLIB 代理。

CGLIB 動態代理實現步驟:

  1. 定義一個類(被代理類)。
  2. 自定義 MethodInterceptor 并重寫 intercept 方法,intercept 用于增強目標方法。
  3. 通過 Enhancer 類的 create() 創建代理類(子類)。
  4. 因此,它不要求被代理類實現接口,但要求該類不能是 final 的,方法也不能是 final 的。

接下來看下實現:

JDK 動態代理不同,CGLIB (Code Generation Library) 實際是屬于一個開源項目,如果需要使用它的話,需要手動添加相關依賴。

<dependency><groupId>cglib</groupId><artifactId>cglib</artifactId><version>3.3.0</version>
</dependency>

自定義 MethodInterceptor (方法攔截器) (CGLIBInterceptor):

import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;// CGLIB代理工廠
class CglibProxyFactory {public static Object getProxy(Object target) {Enhancer enhancer = new Enhancer();enhancer.setSuperclass(target.getClass()); // 設置父類(被代理類)enhancer.setCallback(new MethodInterceptor() {@Overridepublic Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {System.out.println("[CGLIB動態代理] 方法 " + method.getName() + " 執行前...");Object result = method.invoke(target, args); // 執行目標方法System.out.println("[CGLIB動態代理] 方法 " + method.getName() + " 執行后...");return result;}});return enhancer.create(); // 創建代理對象}
}// 使用(假設 SmsServiceImpl 沒有實現接口)
public static void main(String[] args) {SmsServiceImpl smsService = (SmsServiceImpl) CglibProxyFactory.getProxy(new SmsServiceImpl());smsService.send("Hello, CGLIB Proxy!");
}

Spring 如何選擇代理方式?

  • 如果目標對象實現了接口,Spring AOP 默認會使用 JDK 動態代理
  • 如果目標對象沒有實現接口,Spring AOP 會使用 CGLIB 動態代理
  • 在 Spring Boot 2.x 之后,默認的代理方式改為了 CGLIB。你也可以通過配置文件 spring.aop.proxy-target-class=false 來強制使用 JDK 動態代理(前提是目標類實現了接口)。

代碼簡單講解:

  1. MethodInterceptor
    MethodInterceptor 和 JDK 代理中的 InvocationHandler 類似,它只定義了一個方法 intercept(),用于增強目標方法。
    public interface MethodInterceptor extends Callback {/*** 參數說明:* o: 被代理的對象* method: 目標方法(被攔截的方法,也就是需要增強的方法)* objects: 方法入參* methodProxy: 用于調用原始方法*/Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable;
    }
    
  2. Enhancer.create()
    Enhancer.create() 用來生成一個代理對象。
    public static Object create(Class type, Callback callback) {// ...省略
    }
    
    • type: 被代理類的類型(類或接口)。
    • callback: 自定義方法攔截器 MethodInterceptor

AOP 的一個重要“陷阱”:方法內部調用失效

當一個被 AOP 增強的 Bean,其內部的一個方法 methodB() 調用了另一個被增強的方法 methodA() 時,methodA() 的增強(如 @Transactional 或我們自定義的切面)會失效。

@Service
public class OrderService {@Transactional // 我們希望這個方法有事務public void createOrder() {// ... 業務邏輯 ...}public void processOrders() {// ... 其他邏輯 ...// 這種調用方式,createOrder() 的事務會失效!this.createOrder();}
}

原因:AOP 是通過代理對象實現的。外部調用 orderService.processOrders() 時,調用的是代理對象的方法。但是,當 processOrders() 內部執行 this.createOrder() 時,這里的 this 指向的是原始的 OrderService 對象,而不是代理對象。這次調用繞過了代理,直接訪問了原始對象的方法,因此所有的切面邏輯都不會被觸發。

如何解決?

  1. 注入自己:將自身的代理對象注入進來,通過代理對象來調用。

    @Service
    public class OrderService {@Autowiredprivate OrderService self; // 注入自身的代理對象@Transactionalpublic void createOrder() { ... }public void processOrders() {// 通過代理對象調用self.createOrder();}
    }
    

    注意:這可能會導致循環依賴問題,需要 Spring Boot 2.6+ 或額外配置來解決

  2. 使用 AopContext:更優雅的方式是使用 AopContext 來獲取當前的代理對象。

    @Service
    public class OrderService {@Transactionalpublic void createOrder() { ... }public void processOrders() {// 獲取當前代理對象并調用((OrderService) AopContext.currentProxy()).createOrder();}
    }
    

    為了使 AopContext.currentProxy() 生效,需要在啟動類上添加@EnableAspectJAutoProxy(exposeProxy = true)

總 結

  1. AOP是一種思想,是對某一類事情的集中處理。Spring框架實現了AOP,稱之為SpringAOP
  2. Spring AOP常見實現方式有兩種:1. 基于注解@Aspect來實現 2. 基于自定義注解來實現,還有一些更原始的方式,比如基于代理,基于xml配置的方式,但目標比較少見
  3. Spring AOP 是基于動態代理實現的,有兩種方式:1. 基本JDK動態代理實現 2. 基于CGLIB動態代理實現。運行時使用哪種方式與項目配置和代理的對象有關。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/89261.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/89261.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/89261.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

linux服務器stress-ng的使用

安裝方法 ? Ubuntu/Debian&#xff1a;sudo apt update && sudo apt install stress-ng -y? CentOS/RHEL&#xff08;需EPEL源&#xff09;&#xff1a;sudo yum install epel-release -ysudo yum install stress-ng -y? 源碼編譯&#xff08;適合定制化需求&#x…

探索阿里云DMS:解鎖高效數據管理新姿勢

一、阿里云 DMS 是什么 阿里云 DMS&#xff0c;全稱為 Data Management Service&#xff0c;即數據管理服務 &#xff0c;是一種集數據管理、結構管理、安全管理于一體的全面數據庫服務平臺。它能夠有效地支持各類數據庫產品&#xff0c;包括但不限于 MySQL、SQL Server、Post…

python爬取新浪財經網站上行業板塊股票信息的代碼

在這個多行業持續高速發展的時代&#xff0c;科技正在改變著我們的生活。 在世界科技領域中&#xff0c;中國正占據越來越重要的位置。當下&#xff0c;每個行業都提到了區塊鏈、人工智能、大數據、5G等科技力量&#xff0c;強調了科技在行業咨詢與數據分析領域的重要意義。 隨…

【JAVA】監聽windows中鼠標側面鍵的按鈕按下事件

監聽windows中鼠標側面鍵的按鈕按下事件用到的包核心類使用這個類用到的包 jna-5.11.0.jar jna-platform-5.11.0.jar核心類 package sample.tt.mouse;import com.sun.jna.Pointer; import com.sun.jna.platform.win32.*; import com.sun.jna.platform.win32.WinDef.HMODULE; …

Redis突發寫入阻斷?解析“MISCONF Redis is configured to save RDB…“故障處理

當你的Redis服務器突然拒絕寫入并拋出 MISCONF Redis is configured to save RDB snapshots... 錯誤時&#xff0c;別慌&#xff01;這是Redis的數據安全保護機制在發揮作用。本文帶你深度解析故障根因&#xff0c;并提供完整的解決方案。&#x1f525; 故障現象還原 客戶端&am…

產品更新丨谷云科技 iPaaS 集成平臺 V7.6 版本發布

六月&#xff0c;谷云科技iPaaS集成平臺更新了V7.6版本。這次更新中我們著重對API網關、API編排、組織管理權限、API監控等功能進行了增強以及優化&#xff0c;一起來看看有什么新變化吧&#xff01; 網關、監控、編排、組織權限全方位升級 1.API網關 錯誤碼預警&#xff0c;可…

圖像處理中的模板匹配:原理與實現

目錄 一、什么是模板匹配&#xff1f; 二、模板匹配的匹配方法 1. 平方差匹配&#xff08;cv2.TM_SQDIFF&#xff09; 2. 歸一化平方差匹配&#xff08;cv2.TM_SQDIFF_NORMED&#xff09; 3. 相關匹配&#xff08;cv2.TM_CCORR&#xff09; 4. 歸一化相關匹配&#xff08…

高性能架構模式——高性能NoSQL

目錄 一、關系數據庫的缺點二、常見的 NoSQL 方案分 類2.1、K-V 存儲2.2、文檔數據庫2.3、列式數據庫2.4、全文搜索引擎三、高性能 NoSQL 方案的典型特征和應用場景3.1、K-V 存儲典型特征和應用場景3.2、文檔數據庫典型特征和應用場景3.1.1、文檔數據庫的 no-schema 特性的優勢…

正確選擇光伏方案設計軟件:人力成本優化的關鍵一步

在競爭激烈的市場環境中&#xff0c;企業無不追求效率提升與成本控制。設計環節作為產品開發的核心流程&#xff0c;其效率高低直接影響整體項目進度與資源消耗。錯誤的設計軟件選擇如同在信息高速公路上設置路障——它不會阻止前行&#xff0c;卻會讓每一次溝通、每一次修改都…

Git問題排查與故障解決詳解

前言 在使用Git進行版本控制的過程中&#xff0c;開發者常常會遇到各種各樣的問題和錯誤。本文將詳細介紹常見的Git問題及其解決方法&#xff0c;幫助開發者快速定位和解決問題&#xff0c;避免在開發過程中浪費時間。 1. 基礎錯誤與解決 1.1 身份配置問題 問題&#xff1a…

使用Xinference部署語音模型實現文本轉語音:完整指南

文章目錄引言環境準備1. 安裝Xinference2. 啟動Xinference服務3. 部署語音模型Python實現文本轉語音關鍵參數說明應用場景性能優化建議常見問題解決結語引言 文本轉語音&#xff08;Text-to-Speech, TTS&#xff09;技術在智能助手、有聲讀物、語音導航等應用中扮演著重要角色…

【C#】實體類定義的是long和值識別到的是Int64,實體類反射容易出現Object does not match target type

&#x1f339;歡迎來到《小5講堂》&#x1f339; &#x1f339;這是《C#》系列文章&#xff0c;每篇文章將以博主理解的角度展開講解。&#x1f339; &#x1f339;溫馨提示&#xff1a;博主能力有限&#xff0c;理解水平有限&#xff0c;若有不對之處望指正&#xff01;&#…

C#獲取當前系統賬戶是否為管理員賬戶

傳統方式&#xff1a;WindowsPrincipal winPrincipal new WindowsPrincipal(WindowsIdentity.GetCurrent()); bool admin winPrincipal.IsInRole(WindowsBuiltInRole.Administrator);這種方式雖然是最常用的檢測管理員權限的方法&#xff0c;但是有個致命的缺陷&#xff0c;就…

【c++深入系列】:萬字詳解list(附模擬實現的list源碼)

&#x1f525; 本文專欄&#xff1a;c &#x1f338;作者主頁&#xff1a;努力努力再努力wz &#x1f4aa; 今日博客勵志語錄&#xff1a; 當你覺得累的時候&#xff0c;說明你在走上坡路 ★★★ 本文前置知識&#xff1a; 模版 那么在之前的學習中&#xff0c;我們已經學習了…

PandaWiki與GitBook深度對比:AI時代的知識管理工具,選誰好?

在當今信息爆炸的時代&#xff0c;知識管理工具已成為個人學習、團隊協作和企業文檔管理的必需品。PandaWik作為AI時代迅速崛起的廣受歡迎知識管理平臺&#xff0c;代表了新一代AI驅動的知識庫系統。本文將從功能特性、技術架構、適用場景等多個維度進行全面對比分析。產品定位…

清除 Android 手機 SIM 卡數據的4 種簡單方法

SIM 卡存儲了聯系人、短信和通話記錄等信息。在更換新 SIM 卡之前&#xff0c;徹底清除舊卡上的所有個人數據&#xff08;如 SIM 卡聯系人、短信、通話記錄和手機號碼&#xff09;非常重要。要在 Android 手機上清除 SIM 卡內存&#xff0c;您可以參考以下方法。但在開始之前&a…

算法學習筆記:20.分治法——從原理到實戰,涵蓋 LeetCode 與考研 408 例題

分治法&#xff08;Divide and Conquer&#xff09;是計算機科學中最經典的算法設計思想之一&#xff0c;其核心思想是將復雜問題分解為若干個規模較小的子問題&#xff0c;通過解決子問題并合并結果來求解原問題。這種思想不僅在排序、搜索等基礎算法中廣泛應用&#xff0c;也…

@classmethod

1. 基本概念 classmethod 是 Python 中用于定義類方法的一種裝飾器。類方法與常規的實例方法不同&#xff0c;它的第一個參數是 cls&#xff0c;表示類本身&#xff0c;而不是實例。 class MyClass:class_attr "Class Attribute"classmethoddef class_method(cls):p…

Qt 中使用 SQLite 數據庫

一、SQLite 數據庫介紹 SQLite 是一個輕量級的嵌入式關系型數據庫管理系統&#xff0c;它以庫的形式提供&#xff0c;不需要單獨的服務器進程&#xff0c;直接訪問存儲在普通磁盤文件中的數據庫。 主要特性 無服務器架構&#xff1a;SQLite 不需要單獨的服務器進程 零配置&a…

【Unity】IL2CPP相關理論知識學習

一種編譯技術。優點&#xff1a;性能優化&#xff1a;IL2CPP生成C代碼后由本地編譯器優化&#xff0c;一般在CPU性能和GC方面都優于Mono。特別在移動端或主機平臺&#xff0c;性能差距更加明顯。跨平臺支持&#xff1a;Unity作為跨平臺引擎&#xff0c;IL2CPP是支持iOS、Androi…