Spring(四) 關于AOP的源碼解析與思考

Spring(四) 關于AOP的源碼解析與思考

每種語言都有其獨特的機制和特點,那么說到Java你可能會首先想到反射,反射是Java語言提供的一種能夠在程序運行時動態操作類或對象的能力,比如獲取某個對象的類定義、獲取類聲明的屬性和方法、調用方法或構造對象、動態修改屬性值等,這是因為JVM內存方法區中保存了加載的類元數據信息,并且每個對象實例的對象頭中都包含指向類元數據的指針,這為Java提供了強大的靈活性和創造性,幾乎所有Java技術和框架中都有反射的影子,通過反射獲取Class類對象的方式包括 具體類.class對象實例.getClass()Class.forName()ClassLoader.loadClass()

除此之外,Java是面向對象的語言,即支持OOPObject Oriented Programming)面向對象編程,這是一種技術或思想,OOP的核心是抽象模型、反應客觀事物的普遍規律和行為特征、模擬人類在現實世界中的思維認知,比如類與對象(整體與個體)、屬性與方法(特征與行為)、對象實例間的通信(實體間的聯系)等,其核心原則我們也耳熟能詳即封裝、繼承、多態,可以看出OOP強調的是多樣性、靈活性。與之相對的是AOPAspect Oriented Programming)面向切面編程,切面(Aspect)可以理解為關注點、在程序中要切入的方面,AOP的核心是將跨越多個業務或模型的橫切關注點分離出來并進行模塊化、模板化、流程化,這些關注點通常與核心業務邏輯無關,它要解決的是業務交叉、代碼糾纏問題,比如日志、事務、安全、監控、審計等,AOP強調的是統一性、復用性。需要注意的是,OOPAOP代表了兩種不同的代碼組織和模塊化方式,但OOPAOP不是對立關系而是互補關系,OOP通過對象封裝和層次關系在縱向構建了應用程序的核心結構和業務邏輯(主要范式),而AOP則通過分離橫切關注點在橫向解決了業務交叉或跨越模型上的復雜性和代碼糾纏問題。

上述內容可能比較抽象,但該部分已經把OOPAOP的核心觀念與設計思想說得非常清楚,接下來我們將主要通過幾個實例和實踐來講解下AOP思想/技術在Spring中的應用與實現。本篇純干貨!

1. 動態代理

首當其沖的就是動態代理,我們知道靜態代理和動態代理都是代理模式的實現方式,該設計模式可以簡單看作是對目標方法(關注點)做增強,它本質上也是AOP思想的實現。相比靜態代理來說動態代理能在運行時動態生成代理類,這依賴于字節碼生成技術,Spring常用的兩種動態代理技術分別是JDK ProxyCGLIB,其中JDK Proxy是基于接口實現的,即其只能代理實現了接口的類或直接代理接口;而CGLIB則是基于ASM直接修改.class文件生成對應子類,因此其無需接口即可代理任意普通類,Spring AOP默認優先使用JDK代理,無接口時使用CGLIB代理。當然,在動態代理中也有反射的結合,代理方法中通常需要通過反射來調用目標方法,但動態代理和反射兩種機制不可混淆。

2. AspectJ

AspectJJava生態系統中完全獨立、完整且強大、功能豐富的專業AOP框架,AspectJ有著對AOP中切點、通知、切面等概念的完整實現與拓展,并支持編譯時織入(在編譯時將切面邏輯織入到目標類的字節碼中)、編譯后織入(在編譯后織入到現有class類文件或jar文件中)、加載時織入(在Java類被類加載器加載到JVM時進行織入)三種織入方式,AspectJ可以攔截任何Java對象的執行(不受Spring容器限制),其可以攔截方法執行、構造器調用、字段讀寫、靜態初始化、異常處理等幾乎所有連接點。這里我們再看下AOP中的幾個核心概念:

  • 連接點:程序執行過程中可以插入切面代碼、實現增強的位置,在SpringAOP中通常為可調用方法的執行;
  • 切點:決定在哪些連接點應用通知(切點可以看作是連接點的子集),通常表現為能夠匹配連接點的表達式(AspectJ 切點表達式);
  • 通知:在切點處執行插入增強的具體邏輯
    • 前置通知:目標方法執行前;
    • 后置通知:目標方法執行后(無論成功/失敗都會執行);
    • 環繞通知:包圍目標方法執行;
    • 返回通知:目標方法成功返回后;
    • 異常通知:目標方法拋出異常后;
  • 切面:切點+通知的封裝組成切面,表示一個完整模塊化、解耦化的橫切關注點(比如日志、事務等)
  • 織入:將切面應用到目標對象的實現過程,比如Spring通過運行時動態代理實現切面;

3. AOP 實現

Spring AOP本身是一個簡化版的、基于代理實現的AOP框架(更加輕量),其核心實現方式與完整的AspectJ框架不同,它是在運行時通過動態代理機制(JDK ProxyCGLIB)實現切面功能,因此Spring AOP僅支持作用于Spring IoC 容器管理的Bean,而無法攔截非Spring管理的對象或AOP Bean對自身方法的調用。雖然Spring AOPAspectJ在底層實現的本質上是不同的,但Spring AOP直接使用了AspectJ項目的注解風格(AspectJ定義的@Aspect@Pointcut@Before@After@Around等核心注解)和切點表達式語法,可以認為Spring AOP利用了AspectJ的方言和概念,提供了與AspectJ兼容的編程模型。

3.1 引入AOP依賴

首先需要在項目中添加Spring Boot Starter AOP依賴,該依賴包含了AspectJ中的核心注解以及切點表達式語法。

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

3.2 定義AOP切面

3.2.1 業務實現
package com.example.aop.service;@Service
public class EverythingService {// 我們的業務邏輯public void processOrders(int type){if(type == 1){// 模擬拋出異常System.out.println("Processing orders error...");throw new RuntimeException("Error processing orders");}else{System.out.println("Processing orders success...");}}public void dealPayments(){System.out.println("Deal payments success...");}
}
3.2.2 切面實現
  • 定義切面:@Aspect+@Component
  • 定義切點:@Pointcut+切點表達式
  • 定義通知:@Before、@Around、@After、@AfterReturning、@AfterThrowing+通知增強邏輯
/*** 0.定義切面 Spring AOP*   - @Aspect: 聲明切面以實現匹配和織入*   - @Component: 將切面對象加入到Spring管理*/
@Aspect
@Component
public class LogAop {/*** 1.定義切點: @Pointcut(切點表達式)* 1.1 匹配方法 execution(<訪問修飾符-可選> <返回類型> <全限定包名.類名.?法(?法參數列表)> <異常-可選>)*   1.1.1 通配符*     - *: 匹配任意字符元素(返回類型、類名、方法名)*     - ..: 匹配任意子包或多級目錄、任意數量參數*   1.1.2 例子*     - execution(* com.example.aop.service.*.*(..)) 匹配com.example.aop.service包下所有類的所有方法*     - execution(* com.example.aop.service..*.find*(..)) 匹配com.example.aop.service包及其所有子包下所有類中以find開頭的所有方法* 1.2 匹配注解 @annotation(注解全限定名稱)*   - 例子: @annotation(com.example.aop.annotation.Loggable) 匹配帶有自定義@Loggable注解的方法*/@Pointcut("execution(* com.example.aop.service..*.process*(..))")public void logPointCut() {}/*** 2.定義通知-前置通知: @Before(切點/切點表達式)* @param joinPoint 切點對象(可選)*/@Before("logPointCut()")public void beforePointCut(JoinPoint joinPoint) {String methodName = joinPoint.getSignature().getName();System.out.println("【前置通知】" + methodName);}/*** 2.定義通知-后置通知: @After(切點/切點表達式)* @param joinPoint 切點對象(可選)*/@After("logPointCut()")public void afterPointCut(JoinPoint joinPoint) {String methodName = joinPoint.getSignature().getName();System.out.println("【后置通知】" + methodName);}/*** 2.定義通知-環繞通知: @Around(切點/切點表達式)* @param joinPoint 切點調用器(必須)* @return* @throws Throwable 異常*/@Around("logPointCut()")public Object aroundPointCut(ProceedingJoinPoint joinPoint) throws Throwable {String methodName = joinPoint.getSignature().getName();System.out.println("【環繞通知-前環繞】" + methodName);Object result = joinPoint.proceed(); //執行攔截器鏈上的后續目標(通知或目標方法)System.out.println("【環繞通知-后環繞】" + methodName);return result;}/*** 2.定義通知-返回通知: @AfterReturning(切點/切點表達式)* @param joinPoint 切點對象(可選)*/@AfterReturning("logPointCut()")public void afterReturningPointCut(JoinPoint joinPoint) {String methodName = joinPoint.getSignature().getName();System.out.println("【返回通知】" + methodName);}/*** 2.定義通知-異常通知: @AfterThrowing(切點/切點表達式)* @param joinPoint 切點對象(可選)*/@AfterThrowing("logPointCut()")public void afterThrowingPointCut(JoinPoint joinPoint) {String methodName = joinPoint.getSignature().getName();System.out.println("【異常通知】" + methodName);}}
3.2.3 切面織入
@SpringBootTest
class AopDemoApplicationTests {@ResourceEverythingService everythingService;@Testvoid contextLoads() {// 1.processOrders匹配切點表達式無異常everythingService.processOrders(0);System.out.println("==============================");try{// 2.processOrders匹配切點表達式有異常everythingService.processOrders(1);}catch (Exception e){}System.out.println("==============================");// 3.dealPayments不匹配切點表達式everythingService.dealPayments();}}

由下面的執行結果可以看出,聲明的切面僅匹配到了processOrders()方法,并且不同類型通知之間具有特定執行順序;需要注意的是在目標方法拋出異常時會由異常通知代替返回通知,但后置通知無論是否出現異常都會執行。

【環繞通知-前環繞】processOrders
【前置通知】processOrders
Processing orders success...
【返回通知】processOrders
【后置通知】processOrders
【環繞通知-后環繞】processOrders
==============================
【環繞通知-前環繞】processOrders
【前置通知】processOrders
Processing orders error...
【異常通知】processOrders
【后置通知】processOrders
==============================
Deal payments success...

4. 原理淺析

4.1 通知器執行順序

Spring AOP是基于Spring動態代理(JDK ProxyCGLIB)動態生成目標代理對象,該代理對象的作用是攔截目標方法調用,并根據切面匹配的通知Advisor進行邏輯增強。在代理類中,首先會將匹配切點Pointcut的所有通知器Advisor轉化為攔截器類型MethodInterceptor并構建攔截器鏈(包含所有通知的列表),在實際執行代理方法時會按照攔截器鏈中的順序依次調用攔截執行。切點對應攔截器鏈的構造順序如下:

  • 不同切面@Aspect按優先級排序:多個切面之間可以通過@Order注解來指定切面優先級(越小優先級越高),優先級越高則切面下的通知在攔截器鏈中就越靠前;
  • 相同切面下按通知類型排序:相同切面下的多種通知按照 Around->Before->After->AfterReturning->AfterThrowing 的類型順序在攔截器鏈排序
  • 相同類型通知再按字符串升序排序:相同類型的按照通知的全限定名稱(包名+類名+方法名)即字符串升序排序;
public class ReflectiveAspectJAdvisorFactory extends AbstractAspectJAdvisorFactory implements Serializable {private static final Comparator<Method> adviceMethodComparator;static {// 類型排序器 Around.class, Before.class, After.class, AfterReturning.class, AfterThrowing.classComparator<Method> adviceKindComparator = new ConvertingComparator<>(new InstanceComparator<>(Around.class, Before.class, After.class, AfterReturning.class, AfterThrowing.class),(Converter<Method, Annotation>) method -> {AspectJAnnotation<?> ann = AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(method);return (ann != null ? ann.getAnnotation() : null);});// 字符串排序器 Method::getName 升序Comparator<Method> methodNameComparator = new ConvertingComparator<>(Method::getName);// 先按類型排序,然后通知器類型相同再按方法名稱字符串升序排序adviceMethodComparator = adviceKindComparator.thenComparing(methodNameComparator);}// ...
}

需要注意的是: 盡管后置通知@After在返回通知@AfterReturning和異常通知@AfterThrowing之前排序,但@After方法實際上會在@AfterReturning@AfterThrowing方法之后被調用(如上面切面織入時的輸出效果),這是因為@After實際上只在相應的finally塊中起作用,這里我們后面具體再看它的源碼分析。

4.2 通知器執行原理

我們首先來看下基于JDK動態代理的Spring AOP切面織入源碼JdkDynamicAopProxy.invoke()如下,可以看到在攔截器鏈不為空時會創建方法調用器ReflectiveMethodInvocation對象,來啟動攔截器鏈上通知的調用執行:

// 基于JDK動態代理實現的Spring AOP,用于創建動態代理增強
final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializable {// 目標方法增強的織入過程public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// ...try {// ...target = targetSource.getTarget();Class<?> targetClass = (target != null ? target.getClass() : null);// 篩選出匹配當前方法的切面通知,并將Advisor轉換成MethodInterceptor類型的攔截器鏈(已經按照上述規則排好序)List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);// 如果攔截器鏈為空直接反射執行目標方法if (chain.isEmpty()) {Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);}else {// 創建方法調用器,并傳入需要執行的攔截器鏈 chainMethodInvocation invocation =new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);// 執行攔截器鏈,類似洋蔥模型的鏈上遞歸調用retVal = invocation.proceed();}// ...}}// ...
}

我們這里再看下ReflectiveMethodInvocation的源碼,重點是其proceed()方法是如何處理傳入的攔截器鏈List<Object> interceptorsAndDynamicMethodMatchers的:

public class ReflectiveMethodInvocation implements ProxyMethodInvocation, Cloneable {// ...protected ReflectiveMethodInvocation(Object proxy, @Nullable Object target, Method method, @Nullable Object[] arguments,@Nullable Class<?> targetClass, List<Object> interceptorsAndDynamicMethodMatchers) {this.proxy = proxy;this.target = target;this.targetClass = targetClass;this.method = BridgeMethodResolver.findBridgedMethod(method);this.arguments = AopProxyUtils.adaptArgumentsIfNecessary(method, arguments);// 傳入的攔截器鏈this.interceptorsAndDynamicMethodMatchers = interceptorsAndDynamicMethodMatchers;}public Object proceed() throws Throwable {// 1.攔截器鏈都執行完畢,執行最終目標方法if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {// 調用切點return invokeJoinpoint();}// 2.否則繼續執行攔截器通知:獲取列表中當前要執行的攔截器 ++this.currentInterceptorIndexObject interceptorOrInterceptionAdvice =this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {// 適用于動態切點匹配: 在方法執行時根據參數動態判斷目標方法是否可以應用當前攔截器并執行InterceptorAndDynamicMethodMatcher dm =(InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;Class<?> targetClass = (this.targetClass != null ? this.targetClass : this.method.getDeclaringClass());// 匹配成功: 執行當前攔截器if (dm.methodMatcher.matches(this.method, targetClass, this.arguments)) {return dm.interceptor.invoke(this);}else {// 匹配失敗: 跳過當前攔截器繼續proceed()判斷下個return proceed();}}else {// 適用于靜態切點匹配: 根據先前靜態創建的攔截器鏈應用當前攔截器(默認已經匹配)return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);}}// ...
}

其總體邏輯就是從攔截器鏈列表的首個通知器開始執行(已經排好序),直到最后的通知器然后執行最終目標方法。需要注意的是,在執行每個攔截器具體通知方法.invoke(this)時,都會將方法調用器本身this作為參數傳遞過去,這點非常重要! 攔截器鏈正是通過方法調用器MethodInvocationproceed()方法作為觸發錨點,使得攔截器鏈能夠不斷遞歸調用下去。接下來我們分別看下這五類通知器的代理攔截邏輯實現(類似于棧壓入、彈出的方式)。

4.2.1 @Before前置通知 MethodBeforeAdviceInterceptor
public class MethodBeforeAdviceInterceptor implements MethodInterceptor, BeforeAdvice, Serializable {private final MethodBeforeAdvice advice;// MethodInvocation 正是傳遞的方法調用器public Object invoke(MethodInvocation mi) throws Throwable {// 執行前置通知方法this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis());// 調用錨點-方法調用器的proceed()方法執行下個攔截器通知return mi.proceed();}
}
4.2.2 @After后置通知 AspectJAfterAdvice

@After后置通知中,是優先執行下個攔截器通知,最后在finally塊中才執行后置通知邏輯。因此這也是為什么后置通知@After在返回通知@AfterReturning和異常通知@AfterThrowing之前排序,但@After方法實際上會在@AfterReturning@AfterThrowing方法之后被調用的原因。

public class AspectJAfterAdvice extends AbstractAspectJAdviceimplements MethodInterceptor, AfterAdvice, Serializable {public Object invoke(MethodInvocation mi) throws Throwable {try {// 調用錨點-方法調用器的proceed()方法執行下個攔截器通知return mi.proceed();}finally {// 執行后置通知方法invokeAdviceMethod(getJoinPointMatch(), null, null);}}
}
4.2.3 @Around環繞通知 AspectJAroundAdvice

@Around環繞通知的攔截器邏輯比較特殊,其在invoke方法中是沒有主動調用MethodInvocationproceed()方法來觸發下個攔截器的,因此需要我們在環繞通知的實現中手動決定方法調用器的觸發,才能使得攔截器鏈繼續執行下去,這也是環繞的概念或者說洋蔥模型的由來。

public class AspectJAroundAdvice extends AbstractAspectJAdvice implements MethodInterceptor, Serializable {public Object invoke(MethodInvocation mi) throws Throwable {if (!(mi instanceof ProxyMethodInvocation)) {throw new IllegalStateException("MethodInvocation is not a Spring ProxyMethodInvocation: " + mi);}ProxyMethodInvocation pmi = (ProxyMethodInvocation) mi;ProceedingJoinPoint pjp = lazyGetProceedingJoinPoint(pmi);JoinPointMatch jpm = getJoinPointMatch(pmi);// 執行環繞通知方法return invokeAdviceMethod(pjp, jpm, null, null);}}
4.2.4 @AfterReturning返回通知 AfterReturningAdviceInterceptor
public class AfterReturningAdviceInterceptor implements MethodInterceptor, AfterAdvice, Serializable {private final AfterReturningAdvice advice;public Object invoke(MethodInvocation mi) throws Throwable {// 調用proceed()方法執行下個攔截器通知,并暫存結果Object retVal = mi.proceed();// 執行返回通知方法: 前面沒有異常時this.advice.afterReturning(retVal, mi.getMethod(), mi.getArguments(), mi.getThis());// 返回結果給上層return retVal;}
}
4.2.5 @AfterThrowing異常通知 AspectJAfterThrowingAdvice
public class AspectJAfterThrowingAdvice extends AbstractAspectJAdviceimplements MethodInterceptor, AfterAdvice, Serializable {public Object invoke(MethodInvocation mi) throws Throwable {try {// 調用proceed()方法執行下個攔截器通知return mi.proceed();}catch (Throwable ex) {// 執行異常通知方法: 前面出現異常時if (shouldInvokeOnThrowing(ex)) {invokeAdviceMethod(getJoinPointMatch(), null, ex);}// 繼續拋出異常throw ex;}}}

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

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

相關文章

Android 15 Settings 搜索框:引入關鍵字過濾功能

在日常使用 Android 手機時,我們經常會用到“設置”應用中的搜索功能來快速定位所需選項。然而,有時搜索結果可能會包含一些我們不希望看到或者過于寬泛的條目。 本文將深入探討這一變化,通過分析 SearchResultsAdapter.java 文件中的代碼修改,揭示 Android 如何實現對特定…

Python-魔術方法-創建、初始化與銷毀-hash-bool-可視化-運算符重載-容器和大小-可調用對象-上下文管理-反射-描述器-二分-學習筆記

序 欠4前年的一份筆記 &#xff0c;獻給今后的自己。 魔術方法 特殊屬性查看屬性如果dir&#xff08;lobji&#xff09;參數obj包含方法 __dir__()&#xff0c;該方法將被調用。如果參數obj不包含__dir__()&#xff0c; 該方法將最大限度地收集參數信息。 dir()對于不同類型的對…

redis的一些疑問

spring集成redisCacheEvict(value "commonCache", key "#uniqueid_userInfo")什么時候會執行緩存移除呢&#xff1f;如果方法執行異常是否移除&#xff1f;如果緩存不存在還會移除么&#xff1f;這個移除會在redis的執行歷史命令中監控到么&#xff1f;.…

3.檢查函數 if (!CheckStart()) return 的妙用 C#例子

在桌面/WPF 開發中&#xff0c;我們經常需要在按鈕事件里先判斷“能不能做”&#xff0c;再決定“怎么做”。如果校驗不過&#xff0c;就直接返回&#xff1b;校驗通過&#xff0c;才繼續執行業務邏輯。 今天分享一個極簡寫法&#xff1a;if (!CheckStart()) return;&#xff0…

炎熱工廠救援:算法打造安全壁壘

高溫天氣下智慧工廠&#xff1a;算法賦能&#xff0c;安全救援無憂背景&#xff1a;極端高溫下工廠的嚴峻挑戰近年來&#xff0c;極端高溫天氣頻發&#xff0c;部分地區氣溫接近甚至超過50℃。在這樣酷熱的環境中&#xff0c;工廠面臨著諸多嚴峻問題。一方面&#xff0c;高溫容…

pgsql模板是什么?

查找所有的數據庫 select datname from pg_database運行該命令后&#xff0c;我們會發現其中出現了一些其它的數據庫接下來&#xff0c;我們分析 template0 和 template1 的作用。template1 template1 是 PostgreSQL 默認用于創建新數據庫的模板。當執行 CREATE DATABASE new_d…

LLM 不知道答案,但是知道去調用工具獲取答案?

思考&#xff1a; LLM 自己“不知道”某個事實性問題的答案&#xff0c;但仍然能“知道”去調用工具獲取正確答案&#xff0c;這聽起來確實有點像個悖論該內容觸及了大型語言模型&#xff08;LLM&#xff09;的核心局限性以及&#xff08;Agents&#xff09;的智能所在。實際上…

2025年7月11日學習筆記一周歸納——模式識別與機器學習

2025年7月11日學習筆記&一周歸納——模式識別與機器學習一.一周工作二.我的一些筆記匯總三.發現的一些新的學習資料和愛用好物1.百度網盤AI筆記&#xff1a;2.b站資料&#xff1a;3.聽說的一些好書&#xff1a;一.一周工作 本周學習了清華大學張學工汪小我老師的模式識別與…

LeetCode 138題解 | 隨機鏈表的復制

隨機鏈表的復制一、題目鏈接二、題目三、分析四、代碼一、題目鏈接 138.隨機鏈表的復制 二、題目 三、分析 數據結構初階階段&#xff0c;為了控制隨機指針&#xff0c;我們將拷貝結點鏈接在原節點的后面解決&#xff0c;后面拷貝節點還得解下來鏈接&#xff0c;非常麻煩。這…

【計算機存儲架構】分布式存儲架構

引言&#xff1a;數據洪流時代的存儲革命“數據是新時代的石油” —— 但傳統存儲正成為制約數據價值釋放的瓶頸核心矛盾&#xff1a;全球數據量爆炸增長&#xff1a;IDC預測2025年全球數據量將達175ZB&#xff08;1ZB10億TB&#xff09;傳統存儲瓶頸&#xff1a;單機IOPS上限僅…

【Linux-云原生-筆記】數據庫操作基礎

一、什么是數據庫&#xff1f;數據庫就是一個有組織、可高效訪問、管理和更新的電子化信息&#xff08;數據&#xff09;集合庫。簡單來說&#xff0c;數據庫就是一個高級的Excel二、安裝數據庫并初始化1、安裝數據庫&#xff08;MySQL&#xff09;dnf search一下mysql數據庫的…

HarmonyOS中各種動畫的使用介紹

鴻蒙&#xff08;HarmonyOS&#xff09;提供了豐富的動畫能力&#xff0c;涵蓋屬性動畫、顯式動畫、轉場動畫、幀動畫等多種類型&#xff0c;適用于不同場景的交互需求。以下是鴻蒙中各類動畫的詳細解析及使用示例&#xff1a;1. 屬性動畫&#xff08;Property Animation&#…

CSP-S 模擬賽 10

T1 洛谷 U490727 返鄉 思路 首先要意識到一個問題&#xff0c;就是如果所有人總分一定&#xff0c;那么是不會出現偏序的。 可以感性理解一下&#xff0c;就是對于 i,ji, ji,j&#xff0c; 若 ai≤aj,bi≤bja_i \leq a_j, b_i \leq b_jai?≤aj?,bi?≤bj?&#xff0c;那么…

CMD,PowerShell、Linux/MAC設置環境變量

以下是 CMD&#xff08;Windows&#xff09;、PowerShell&#xff08;Windows&#xff09;、Linux/Mac 在 臨時/永久 環境變量操作上的對比表格&#xff1a;環境變量操作對照表&#xff08;CMD vs PowerShell vs Linux/Mac&#xff09;操作CMD&#xff08;Windows&#xff09;P…

MySQL(131)如何解決MySQL CPU使用率過高問題?

解決MySQL CPU使用率過高的問題需要從多個方面進行排查和優化&#xff0c;包括查詢優化、索引優化、配置優化和硬件資源的合理使用等。以下是詳細的解決方案和相應的代碼示例。 一、查詢優化 1. 檢查慢查詢 使用MySQL的慢查詢日志來找到執行時間長的查詢。 SET GLOBAL slow_que…

docker基礎與常用命令

目錄 一.docker概述 1.docker與虛擬機區別 2.Linux 六大命名空間 3.Docker 的核心技術及概念 二.docker部署安裝 三.docker常用命令 1.搜索鏡像 2.獲取鏡像 3.查看鏡像信息 4.添加鏡像標簽 5.刪除鏡像 6.存出與載入鏡像 7.上傳鏡像 8.創建容器 9.查看容器狀態 1…

Cypress與多語言后端集成指南

Cypress 簡介 基于 JavaScript 的前端測試工具,可以對瀏覽器中運行的任何內容進行快速、簡單、可靠的測試Cypress 是自集成的,提供了一套完整的端到端測試,無須借助其他外部工具,安裝后即可快速地創建、編寫、運行測試用例,且對每一步操作都支持回看不同于其他只能測試 UI…

計算機畢業設計ssm基于JavaScript的餐廳點餐系統 SSM+Vue智慧餐廳在線點餐管理平臺 JavaWeb前后端分離式餐飲點餐與桌臺調度系統

計算機畢業設計ssm基于JavaScript的餐廳點餐系統0xig8788&#xff08;配套有源碼 程序 mysql數據庫 論文&#xff09; 本套源碼可以在文本聯xi,先看具體系統功能演示視頻領取&#xff0c;可分享源碼參考。掃碼點單、手機支付、后廚實時出票已經成為食客對餐廳的基本預期。傳統的…

wedo稻草人-----第32節(免費分享圖紙)

夸克網盤&#xff1a;https://pan.quark.cn/s/ce4943156861 高清圖紙源文件&#xff0c;需要的請自取

Jmeter函數的使用

函數名作用用法${__Random(,,)}${__RandomString(,,)}隨機生成一些東西${__Random(000,999,)} ${__Random(${test1},${test2},)}${__RandomString(${__Random(3,9,)},asdfghjkl,)}${__time(,)}獲取當前的時間戳&#xff0c;也可以定義格式${__CSVRead(,)}讀取CSV文件的格式&…