代理模式深度解析

目錄

一 靜態代理

1.1 優點

1.2 缺點

1.3 適用場景

?二 JDK動態代理

1 JDK動態代理的工作原理

1.1 創建代理類

1.2 加載代理類

1.3 實現方法調用

2. Proxy.newProxyInstance() 的核心工作流程

方法簽名

工作步驟

3. 代理類的生成與加載

3.1 代理類生成的關鍵方法

Proxy.getProxyClass()

ProxyGenerator.generateProxyClass()

3.2 代理類的加載

代理類是如何加載的?

代理類加載的時機

三?CGLIB動態代理

1. 代理方式

2. 核心組件

二、字節碼生成

1. 動態生成原理

2. 性能優化

3. 示例:生成的字節碼文件(偽代碼)

三、類加載與代理對象創建

1. 類加載流程

2. 突破限制

四、與 JDK 動態代理的關鍵區別

五、典型限制與解決方案

1. 無法代理 final 方法/類

2. 構造函數調用問題

六、應用場景


一 靜態代理

靜態代理就是手動編寫代理類,在編譯期就確定代理關系,并讓代理類和目標類實現相同的接口。代理類通過調用目標類的方法來完成任務,同時可以在調用前后添加一些額外的操作。

1.1 優點

  1. 簡單直觀:代碼結構清晰,易于理解和實現。
  2. 無侵入性:無需修改目標類代碼,通過代理類實現功能增強。
  3. 性能好:代理邏輯在編譯期確定,沒有動態生成的額外開銷。

1.2 缺點

  1. 冗余代碼:每個目標類都需要手動編寫對應的代理類,代碼量大。
  2. 靈活性差:代理關系在編譯期固定,無法動態切換代理邏輯。
  3. 接口依賴:若目標類沒有實現接口,則無法使用靜態代理(需通過繼承實現,類似CGLIB的思路,但需要手動編寫)。

1.3 適用場景

  • 需要代理的類數量較少。
  • 代理邏輯簡單且無需頻繁變更。
  • 對性能要求高,避免動態代理的開銷。

public interface BaseSimpleService {public void save(Object obj);}
@Slf4j
public class BaseSimpleServiceImpl implements BaseSimpleService {@Overridepublic void save(Object obj) {System.out.println("對象保存成功");}}
/*** 對 BaseSimpleService類做一個增強,在不侵入原業務代碼的基礎上,實現日志記錄*/
@Slf4j
public class SimpleServiceProxy {private BaseSimpleService baseSimpleService;// 注入原對象public SimpleServiceProxy(BaseSimpleService baseSimpleService){this.baseSimpleService=baseSimpleService;}public void save(Object obj) {log.info("save obj:{}",obj);baseSimpleService.save(obj);log.info("保存: {}對象成功",obj);}
}
public class TestProxy {public static void main(String[] args) {// 基礎實現BaseSimpleService baseService=new BaseSimpleServiceImpl();Object obj = new Object();baseService.save(obj);// 靜態代理實現SimpleServiceProxy simpleServiceProxy=new SimpleServiceProxy(baseService);simpleServiceProxy.save(obj);// 原對象不受代理影響baseService.save(obj);}
}

?二 JDK動態代理

JDK動態代理的實現基于反射和類加載器, 通過 Proxy.newProxyInstance() 方法在運行時動態生成代理類,并將其加載到JVM中。

1 JDK動態代理的工作原理

Proxy.newProxyInstance()是JDK動態代理的核心方法,它會動態生成一個代理類,并返回該代理類的實例(即代理對象)。代理類的生成和加載涉及以下核心步驟

1.1 創建代理類

  • 當調用 Proxy.newProxyInstance() 時,JVM 會動態生成一個代理類,該代理類繼承自 java.lang.reflect.Proxy 并實現目標對象的接口。
  • 代理類的名稱是動態生成的,形如 com.sun.proxy.$Proxy0$Proxy0 是 JDK 動態代理生成的代理類的默認命名規則。

1.2 加載代理類

  • 生成的代理類會通過指定的類加載器(ClassLoader)加載到 JVM 中。
  • 代理類的字節碼在內存中生成,并不會保存為 .class 文件。

1.3 實現方法調用

  • 代理類會實現目標接口的所有方法,但這些方法的邏輯會被重定向到 InvocationHandlerinvoke() 方法。
  • 代理類中,方法的實際調用行為是通過反射來完成的。

2. Proxy.newProxyInstance() 的核心工作流程

方法簽名

public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)

工作步驟

1 校驗參數

    • 檢查 ClassLoader 是否為 null
    • 檢查 interfaces 是否為空,并確保所有接口都是有效的(如不能是 final 接口)。
    • 檢查 InvocationHandler 是否為 null

2?生成代理類

    • 調用 Proxy 類的私有方法 getProxyClass0(),動態生成代理類的字節碼。
    • 代理類的字節碼是通過 sun.misc.ProxyGenerator 工具生成的。

3?加載代理類

    • 使用指定的 ClassLoader 將代理類的字節碼加載到 JVM 中。

4?實例化代理類對象

    • 調用代理類的構造方法,傳入 InvocationHandler 對象。
    • 返回動態生成的代理類的實例(即代理對象),它繼承自 java.lang.reflect.Proxy 并實現了 Service 接口。

3. 代理類的生成與加載

3.1 代理類生成的關鍵方法

Proxy.getProxyClass()
  • 該方法通過目標接口數組生成代理類。
  • 代理類的字節碼由 sun.misc.ProxyGenerator 工具生成。
  • 代理類繼承自 java.lang.reflect.Proxy,并實現了目標接口。
Class<?> proxyClass = Proxy.getProxyClass(loader, interfaces);
ProxyGenerator.generateProxyClass()
  • 生成代理類的字節碼。
  • 代理類的每個方法都會調用 InvocationHandler.invoke()
  • 字節碼存儲在內存中,并不會生成 .class 文件。

3.2 代理類的加載

代理類是如何加載的?
  • 代理類的字節碼通過 ClassLoader 加載到 JVM 中。
  • 加載時會為代理類分配內存,并將其方法表注冊到 JVM 的方法區中。
代理類加載的時機
  • 代理類是在 Proxy.newProxyInstance() 調用時動態生成并加載的。
  • 每次調用 newProxyInstance() 都會檢查是否已存在對應接口的代理類。如果已存在,則直接加載;否則,重新生成代理類。

import lombok.extern.slf4j.Slf4j;import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;// 目標接口
interface BaseSimpleService {public void save(Object obj);}// 接口實現類
class BaseSimpleServiceImpl implements BaseSimpleService {@Overridepublic void save(Object obj) {System.out.println("對象保存成功");}}// 調用處理器 不改變目標對象方法的基礎上 記錄日志
@Slf4j
class JdkLoggingInvocationHandler implements InvocationHandler {private Object target;public JdkLoggingInvocationHandler(Object target) {this.target = target;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {log.info("save obj:{}",args);Object result = method.invoke(target, args);log.info("保存: {}對象成功",args);return result;}}
import java.lang.reflect.Proxy;public class TestProxy {public static void main(String[] args) {BaseSimpleService baseSimpleService=new BaseSimpleServiceImpl();JdkLoggingInvocationHandler handler=new JdkLoggingInvocationHandler(baseSimpleService);// 該實例具有代理類的指定調用處理程序,該代理類由指定的類加載器定義并實現指定的接口// simpleServiceProxy代理類由指定的類加載器加載 并實現了BaseSimpleService的接口BaseSimpleService simpleServiceProxy = (BaseSimpleService) Proxy.newProxyInstance(baseSimpleService.getClass().getClassLoader(), baseSimpleService.getClass().getInterfaces(),handler);simpleServiceProxy.save(new Object());}
}

三?CGLIB動態代理

CGLIB(Code Generation Library)是一個基于字節碼生成的第三方庫,用于在運行時動態生成Java類的子類,從而實現對目標類的代理。它主要用于代理沒有實現接口的類,解決了JDK動態代理只能基于接口的局限性。其核心原理如下:

1. 代理方式
  • 繼承式代理:生成目標類的子類(如 UserService$$EnhancerByCGLIB$$123456),通過重寫父類非 final 方法實現代理。
  • 無需接口:直接代理普通類,彌補 JDK 動態代理的局限性。
  • 代理類會重寫目標方法,并在方法中插入攔截邏輯
2. 核心組件
  • Enhancer
    負責生成代理類,配置代理策略(如回調方法、類加載器)。
        // 創建 CGLIB 代理的 Enhancer對象Enhancer enhancer=new Enhancer();// 設置代理的目標類// CGLIB通過繼承這個目標類來生成代理類enhancer.setSuperclass(BaseService.class);// 設置方法攔截器,代理會調用這個攔截器enhancer.setCallback(new CglibLoggingMethodInterceptor(baseService));// 創建代理對象 當前的代理對象不是原始對象了 而是通過繼承目標類BaseService得到的新類BaseService baseServiceProxy = (BaseService) enhancer.create();baseServiceProxy.save(new Object());
  • MethodInterceptor
    代理類的方法調用會被轉發到MethodInterceptor接口的intercept方法中,實現增強邏輯。
/**
* obj: 代理對象本身
* method: 被攔截的方法(目標方法的反射對象)
* args: 方法參數
* proxy: 方法代理對象,用于調用父類(目標類)的原始方法
*/
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {// 前置增強(如日志)Object result = proxy.invokeSuper(obj, args); // 調用父類(目標類)原始方法// 后置增強(如事務提交)return result;
}

二、字節碼生成

1. 動態生成原理
  • 基于 ASM:直接操作字節碼生成 .class 文件的二進制內容,避免源碼編譯。
  • 方法重寫
    生成子類時,為每個非 final 方法生成重寫版本,插入攔截邏輯(調用 MethodInterceptor)。
2. 性能優化
  • FastClass 機制
    為代理類和目標類的方法建立索引,直接通過索引調用方法,繞過了反射的Method.invoke(),提升了性能。
// FastClass 通過索引調用方法(偽代碼)
public Object invoke(int methodIndex, Object obj, Object[] args) {switch (methodIndex) {case 0: return ((TargetClass)obj).method1();case 1: return ((TargetClass)obj).method2();}
}
3. 示例:生成的字節碼文件(偽代碼)

生成的代理類可能如下(簡化后的偽代碼):

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
public class Cat$$EnhancerByCGLIB$$8ca2de8b extends Cat implements Factory {private boolean CGLIB$BOUND;public static Object CGLIB$FACTORY_DATA;private static final ThreadLocal CGLIB$THREAD_CALLBACKS;private static final Callback[] CGLIB$STATIC_CALLBACKS;private MethodInterceptor CGLIB$CALLBACK_0;private static Object CGLIB$CALLBACK_FILTER;private static final Method CGLIB$sleep$0$Method;private static final MethodProxy CGLIB$sleep$0$Proxy;private static final Object[] CGLIB$emptyArgs;private static final Method CGLIB$wakeup$1$Method;private static final MethodProxy CGLIB$wakeup$1$Proxy;private static final Method CGLIB$equals$2$Method;private static final MethodProxy CGLIB$equals$2$Proxy;private static final Method CGLIB$toString$3$Method;private static final MethodProxy CGLIB$toString$3$Proxy;private static final Method CGLIB$hashCode$4$Method;private static final MethodProxy CGLIB$hashCode$4$Proxy;private static final Method CGLIB$clone$5$Method;private static final MethodProxy CGLIB$clone$5$Proxy;static void CGLIB$STATICHOOK4() {CGLIB$THREAD_CALLBACKS = new ThreadLocal();CGLIB$emptyArgs = new Object[0];Class var0 = Class.forName("com.example.demo.cglibproxy.vo.Cat$$EnhancerByCGLIB$$8ca2de8b");Class var1;Method[] var10000 = ReflectUtils.findMethods(new String[]{"equals", "(Ljava/lang/Object;)Z", "toString", "()Ljava/lang/String;", "hashCode", "()I", "clone", "()Ljava/lang/Object;"}, (var1 = Class.forName("java.lang.Object")).getDeclaredMethods());CGLIB$equals$2$Method = var10000[0];CGLIB$equals$2$Proxy = MethodProxy.create(var1, var0, "(Ljava/lang/Object;)Z", "equals", "CGLIB$equals$2");CGLIB$toString$3$Method = var10000[1];CGLIB$toString$3$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/String;", "toString", "CGLIB$toString$3");CGLIB$hashCode$4$Method = var10000[2];CGLIB$hashCode$4$Proxy = MethodProxy.create(var1, var0, "()I", "hashCode", "CGLIB$hashCode$4");CGLIB$clone$5$Method = var10000[3];CGLIB$clone$5$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/Object;", "clone", "CGLIB$clone$5");var10000 = ReflectUtils.findMethods(new String[]{"sleep", "()V", "wakeup", "()V"}, (var1 = Class.forName("com.example.demo.cglibproxy.vo.Cat")).getDeclaredMethods());CGLIB$sleep$0$Method = var10000[0];CGLIB$sleep$0$Proxy = MethodProxy.create(var1, var0, "()V", "sleep", "CGLIB$sleep$0");CGLIB$wakeup$1$Method = var10000[1];CGLIB$wakeup$1$Proxy = MethodProxy.create(var1, var0, "()V", "wakeup", "CGLIB$wakeup$1");}final void CGLIB$sleep$0() {super.sleep();}public final void sleep() {MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;if (this.CGLIB$CALLBACK_0 == null) {CGLIB$BIND_CALLBACKS(this);var10000 = this.CGLIB$CALLBACK_0;}if (var10000 != null) {var10000.intercept(this, CGLIB$sleep$0$Method, CGLIB$emptyArgs, CGLIB$sleep$0$Proxy);} else {super.sleep();}}final void CGLIB$wakeup$1() {super.wakeup();}public final void wakeup() {MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;if (this.CGLIB$CALLBACK_0 == null) {CGLIB$BIND_CALLBACKS(this);var10000 = this.CGLIB$CALLBACK_0;}if (var10000 != null) {var10000.intercept(this, CGLIB$wakeup$1$Method, CGLIB$emptyArgs, CGLIB$wakeup$1$Proxy);} else {super.wakeup();}}public static MethodProxy CGLIB$findMethodProxy(Signature var0) {String var10000 = var0.toString();switch(var10000.hashCode()) {case -1385928386:if (var10000.equals("sleep()V")) {return CGLIB$sleep$0$Proxy;}break;case -508378822:if (var10000.equals("clone()Ljava/lang/Object;")) {return CGLIB$clone$5$Proxy;}break;case 391780310:if (var10000.equals("wakeup()V")) {return CGLIB$wakeup$1$Proxy;}break;case 1826985398:if (var10000.equals("equals(Ljava/lang/Object;)Z")) {return CGLIB$equals$2$Proxy;}break;case 1913648695:if (var10000.equals("toString()Ljava/lang/String;")) {return CGLIB$toString$3$Proxy;}break;case 1984935277:if (var10000.equals("hashCode()I")) {return CGLIB$hashCode$4$Proxy;}}return null;}public Cat$$EnhancerByCGLIB$$8ca2de8b() {CGLIB$BIND_CALLBACKS(this);}public Cat$$EnhancerByCGLIB$$8ca2de8b(String var1) {super(var1);CGLIB$BIND_CALLBACKS(this);}public static void CGLIB$SET_THREAD_CALLBACKS(Callback[] var0) {CGLIB$THREAD_CALLBACKS.set(var0);}public static void CGLIB$SET_STATIC_CALLBACKS(Callback[] var0) {CGLIB$STATIC_CALLBACKS = var0;}private static final void CGLIB$BIND_CALLBACKS(Object var0) {Cat$$EnhancerByCGLIB$$8ca2de8b var1 = (Cat$$EnhancerByCGLIB$$8ca2de8b)var0;if (!var1.CGLIB$BOUND) {var1.CGLIB$BOUND = true;Object var10000 = CGLIB$THREAD_CALLBACKS.get();if (var10000 == null) {var10000 = CGLIB$STATIC_CALLBACKS;if (CGLIB$STATIC_CALLBACKS == null) {return;}}var1.CGLIB$CALLBACK_0 = (MethodInterceptor)((Callback[])var10000)[0];}}public Object newInstance(Callback[] var1) {CGLIB$SET_THREAD_CALLBACKS(var1);Cat$$EnhancerByCGLIB$$8ca2de8b var10000 = new Cat$$EnhancerByCGLIB$$8ca2de8b();CGLIB$SET_THREAD_CALLBACKS((Callback[])null);return var10000;}public Object newInstance(Callback var1) {CGLIB$SET_THREAD_CALLBACKS(new Callback[]{var1});Cat$$EnhancerByCGLIB$$8ca2de8b var10000 = new Cat$$EnhancerByCGLIB$$8ca2de8b();CGLIB$SET_THREAD_CALLBACKS((Callback[])null);return var10000;}public Object newInstance(Class[] var1, Object[] var2, Callback[] var3) {CGLIB$SET_THREAD_CALLBACKS(var3);Cat$$EnhancerByCGLIB$$8ca2de8b var10000 = new Cat$$EnhancerByCGLIB$$8ca2de8b;switch(var1.length) {case 0:var10000.<init>();break;case 1:if (var1[0].getName().equals("java.lang.String")) {var10000.<init>((String)var2[0]);break;}throw new IllegalArgumentException("Constructor not found");default:throw new IllegalArgumentException("Constructor not found");}CGLIB$SET_THREAD_CALLBACKS((Callback[])null);return var10000;}public Callback getCallback(int var1) {CGLIB$BIND_CALLBACKS(this);MethodInterceptor var10000;switch(var1) {case 0:var10000 = this.CGLIB$CALLBACK_0;break;default:var10000 = null;}return var10000;}public void setCallback(int var1, Callback var2) {switch(var1) {case 0:this.CGLIB$CALLBACK_0 = (MethodInterceptor)var2;default:}}public Callback[] getCallbacks() {CGLIB$BIND_CALLBACKS(this);return new Callback[]{this.CGLIB$CALLBACK_0};}public void setCallbacks(Callback[] var1) {this.CGLIB$CALLBACK_0 = (MethodInterceptor)var1[0];}static {CGLIB$STATICHOOK4();}
}

三、類加載與代理對象創建

1. 類加載流程
  • 動態加載:生成的字節碼通過類加載器(默認目標類的 ClassLoader)加載到 JVM。
  • 緩存優化:已生成的代理類會被緩存,避免重復生成。
2. 突破限制
  • 繞過構造函數
    使用 Objenesis 庫直接實例化代理對象,無需調用父類構造函數(即使父類有無參構造)。
Objenesis objenesis = new ObjenesisStd();
TargetClass proxy = objenesis.newInstance(proxyClass); // 直接實例化

四、與 JDK 動態代理的關鍵區別

特性

CGLIB

JDK 動態代理

代理方式

繼承目標類生成子類

實現目標接口

目標要求

不能是 final 類/方法

必須實現接口

方法調用速度

快(FastClass 直接調用)

慢(反射調用)

內存消耗

較高(需生成子類)

較低

五、典型限制與解決方案

1. 無法代理 final 方法/類
  • 表現:若目標類或方法是 final,CGLIB 無法生成子類。
  • 解決:重構代碼移除 final 修飾,或改用 JDK 動態代理。
2. 構造函數調用問題
  • 表現:代理類會調用父類構造函數,若父類沒有無參構造函數會報錯。
  • 解決:使用 Objenesis 繞過構造函數(需添加依賴)。

六、應用場景

  • Spring AOP:默認對未實現接口的類使用 CGLIB 代理。
  • 性能敏感場景:如高頻調用的工具類增強。
  • 歷史代碼擴展:無法修改原有類/接口時,直接代理實現功能增強。

import org.springframework.cglib.proxy.Enhancer;public class TestProxy {public static void main(String[] args) {BaseService baseService=new BaseService();// 創建 CGLIB 代理的 Enhancer對象Enhancer enhancer=new Enhancer();// 設置代理的目標類// CGLIB通過繼承這個目標類來生成代理類enhancer.setSuperclass(BaseService.class);// 設置方法攔截器,代理會調用這個攔截器enhancer.setCallback(new CglibLoggingMethodInterceptor(baseService));// 創建代理對象 當前的代理對象不是原始對象了 而是通過繼承目標類BaseService得到的新類BaseService baseServiceProxy = (BaseService) enhancer.create();baseServiceProxy.save(new Object());}
}
import lombok.extern.slf4j.Slf4j;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;/*** 目標類*/
class BaseService {public void save(Object obj) {System.out.println("對象保存成功");}}@Slf4j
public class CglibLoggingMethodInterceptor  implements MethodInterceptor {private Object target;public CglibLoggingMethodInterceptor(Object target) {this.target = target;}@Overridepublic Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {log.info("save obj:{}",args);// 調用父類方法Object result = proxy.invokeSuper(obj, args);log.info("保存: {}對象成功",args);return result;}
}

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

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

相關文章

Spring Cache與Redis集成原理

一、核心架構圖解 #mermaid-svg-aiWGQLhmWx7kOfLz {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-aiWGQLhmWx7kOfLz .error-icon{fill:#552222;}#mermaid-svg-aiWGQLhmWx7kOfLz .error-text{fill:#552222;stroke:#5…

編程技能:調試02,設置斷點與刪除斷點

專欄導航 本節文章分別屬于《Win32 學習筆記》和《MFC 學習筆記》兩個專欄&#xff0c;故劃分為兩個專欄導航。讀者可以自行選擇前往哪個專欄。 &#xff08;一&#xff09;WIn32 專欄導航 上一篇&#xff1a;編程技能&#xff1a;調試01&#xff0c;調試介紹 回到目錄 下…

flink寫doris時的優化

1.概念 doris并不擅長高頻、小量數據的導入&#xff1b; 因為doris每一次數據導入都會在be節點上生成數據文件&#xff1b;如果高頻導入小量數據&#xff0c;就會在存儲層產生大量的小文件&#xff08;必然會影響到后續的查詢效率&#xff0c;也會對系統產生更多的compaction…

ElementNotInteractableException原因及解決辦法

在自動化測試中,ElementNotInteractableException是一個常見的異常,它通常發生在嘗試與網頁上的某個元素進行交互(例如點擊、輸入等操作)時,但由于該元素當前不可交互。這可能由多種原因引起,以下是一些常見的原因及其解決方法: 元素未完全加載 如果嘗試與頁面上的元素交…

如何從 GitHub 鏡像倉庫到極狐GitLab?

最近 GitHub 封禁中國用戶的事情鬧得沸沸揚揚,雖然官方發布的報道說中國用戶被限制登錄是因為配置錯誤導致,已經撤回了更新,中國用戶已經可以正常使用。但是這就像橫在國內開發者和企業頭上的“達摩克利斯之劍”。為了避免 GitHub 不可用而帶來的影響,國內開發者和企業可以…

服務器安裝nacos

1.下載依賴 docker pull nacos/nacos-server:v2.4.3安裝 docker run -d --name nacos-server -p 8848:8848 -e MODEstandalone nacos/nacos-server:v2.4.3把nacos中的data 文件和conf 文件copy到自己服務的文件夾 docker cp nacos-server:/home/nacos/data /home/admin1/…

Matter協議暗戰:蘋果、谷歌、亞馬遜的智能家居霸權爭奪

原文地址&#xff1a;Matter協議暗戰&#xff1a;蘋果、谷歌、亞馬遜的智能家居霸權爭奪 一、Matter 協議&#xff1a;巨頭聯手打造的 “智能家居聯合國” 1.1 從 CHIP 到 Matter&#xff1a;標準統一的十年長跑 智能家居發展多年&#xff0c;卻始終被 “孤島效應” 困擾。各…

軟件設計師2009-2022歷年真題與答案解析(附pdf下載)

軟考在即&#xff0c;現在給大家分享一下軟件設計師2009-2022真題與答案解析 pdf全套&#xff0c;文末提供大家免費下載&#xff0c;大家都知道在軟考備考過程中&#xff0c;擁有一套全面且實用的考試資料對于考生來說至關重要。目錄如下&#xff1a; 歷年真題及詳解2004-2019 …

基于EasyX庫開發的球球大作戰游戲

目錄 球球大作戰 一、開發環境 二、流程圖預覽 三、代碼邏輯 1、初始化時間 2、設置開始界面大小 3、設置開始界面 4、讓玩家選擇速度 5、設置玩家小球、人機小球、食物的屬性 6、一次性把圖繪制到界面里 7、進入死循環 8、移動玩家小球 9、移動人機 10、食物刷新…

aslist和list的區別

?Arrays.asList和List的主要區別在于它們的固定長度和不可變性、與原始數組的關系、性能以及使用場景。 一、固定長度和不可變性 ?Arrays.asList?&#xff1a;通過Arrays.asList方法創建的List是一個固定長度的List&#xff0c;其長度與原始數組相同。這意味著你不能通過添…

大模型預標注和自動化標注在OCR標注場景的應用

OCR&#xff0c;即光學字符識別&#xff0c;簡單來說就是利用光學設備去捕獲圖像并識別文字&#xff0c;最終將圖片中的文字轉換為可編輯和可搜索的文本。在數字化時代&#xff0c;OCR&#xff08;光學字符識別&#xff09;技術作為處理圖像中文字信息的關鍵手段&#xff0c;其…

stm32工程,拷貝到另一臺電腦編譯,錯誤提示頭文件找不到cannot open source input file “core_cm4.h”

提示 cannot open source input file “core_cm4.h” ,找不到 [ core_cm4.h ] 這個頭文件 . 于是我在原電腦工程文件里找也沒有找到這個頭文件 接下來查看原電腦keil的頭文件引入配置,發現只引入了工程文件下的頭文件, 那么core_cm4.h到底哪里來的? (到現在我也不清楚怎…

STM32 模塊化開發指南 · 第 2 篇 如何編寫高復用的外設驅動模塊(以 UART 為例)

本文是《STM32 模塊化開發實戰指南》的第 2 篇,聚焦于“串口驅動模塊的設計與封裝”。我們將從一個最基礎的裸機 UART 初始化開始,逐步實現:中斷支持、環形緩沖收發、模塊接口抽象與測試策略,構建一個可移植、可擴展、可復用的 UART 驅動模塊。 一、模塊化 UART 的設計目標…

【NLP 59、大模型應用 —— 字節對編碼 bpe 算法】

目錄 一、詞表的構造問題 二、bpe(byte pair encoding) 壓縮算法 算法步驟 示例&#xff1a; 步驟 1&#xff1a;初始化符號表和頻率統計 步驟 2&#xff1a;統計相鄰符號對的頻率 步驟 3&#xff1a;合并最高頻的符號對 步驟 4&#xff1a;重復合并直至終止條件 三、bpe在NLP中…

TMS320F28P550SJ9學習筆記15:Lin通信SCI模式結構體寄存器

今日初步認識與配置使用Lin通信SCI模式&#xff0c;用結構體寄存器的方式編程 文章提供完整工程下載、測試效果圖 我的單片機平臺是這個&#xff1a; LIN通信引腳&#xff1a; LIN通信PIE中斷&#xff1a; 這個 PIE Vector Table 表在手冊111頁&#xff1a; 這是提到LINa的PI…

linux-設置每次ssh登錄服務器的時候提醒多久需要修改密碼

在 Linux 系統中,你可以通過設置 motd(Message of the Day)或 sshd 配置來在用戶通過 SSH 登錄時提醒他們密碼即將過期。以下是具體步驟: 方法 1: 使用 motd 文件 motd 文件在用戶登錄時顯示,你可以通過腳本動態生成內容,提醒用戶密碼過期時間。 編輯 /etc/motd 文件:…

matlab求和∑函數方程編程?

matlab求和∑函數方程編程&#xff1f; 一 題目&#xff1a;求下列函數方程式的和 二&#xff1a;代碼如下&#xff1a; >> sum_result 0; % 初始化求和變量 for x 1:10 % 設…

electron桌面端開發-打開指定軟件和文件

electron桌面端開發 現在越來越多的軟件開發已經趨向于簡單化&#xff0c;桌面端開發已經不在依賴之前的java、c等主流技術&#xff0c;目前基于node的開發越來越廣泛。功能點也越來越多元化。 文章目錄 electron桌面端開發前言一、打開文件的方式&#xff1f;二、exec使用步驟…

ShenNiusModularity項目源碼學習(17:ShenNius.Admin.Mvc項目分析-2)

ShenNiusModularity項目的后臺管理主頁面如下圖所示&#xff0c;該頁面為ShenNius.Admin.Mvc項目的Views\Home\Index.cshtml&#xff0c;使用的是layuimini后臺模板&#xff08;參考文獻2&#xff09;&#xff0c;在layuimini的GitHub主頁中提供有不同樣式的頁面模版鏈接&#…

SpringBoot 與 Vue3 實現前后端互聯全解析

在當前的互聯網時代&#xff0c;前后端分離架構已經成為構建高效、可維護且易于擴展應用系統的主流方式。本文將詳細介紹如何利用 SpringBoot 與 Vue3 構建一個前后端分離的項目&#xff0c;展示兩者如何通過 RESTful API 實現無縫通信&#xff0c;讓讀者了解從環境搭建、代碼實…