兩種動態代理(可以看到代理類的樣子,方便理解)

這里寫目錄標題

  • jdk動態代理例子
  • CGlib動態代理例子
  • 手寫spring中的事務
  • 部分自定義注解版aop實現方式

Spring的兩大重點,IOC和AOP,今天我們就來學AOP,眾所周知AOP的底層是動態代理,讓我們看一下這兩種動態代理的區別。

例子:
我們常常使用aop切面編程打印日志,但是他的底層是什么呢?我們常常看到的是封裝好的注解,并不知道他的底層是如何實現的。
那我們下邊先看手動調用動態代理實現執行方法前后打印日志。

jdk動態代理例子

在這里插入圖片描述
客戶端首先調用代理對象的方法
在這里插入圖片描述

CalculatorProxy類

package AOP.Proxy;import AOP.service.Calculator;
import AOP.util.Util;import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;/*** @BelongsProject: JAVAtest* @BelongsPackage: AOP.Proxy* @Author: GuoYuan.Zhao* @Description: 描述什么人干什么事兒* @CreateTime: 2024-01-24 14:47* @Version: 1.0*/public class CalculatorProxy {//必須要有接口,如果沒有接口,不能使用,這種方式是用jdk提供的reflect 包下邊的類,但是//生產環境中我不能保證每個類都有具體的接口,所有有第二中方式cglib//兩種動態代理的方式,一種是JDK   一種是cglibpublic  static Calculator  getCalculator(  final Calculator  calculator){//獲取被代理對象的類加載器ClassLoader loader = calculator.getClass().getClassLoader();Class<?>[] interfaces  = calculator.getClass().getInterfaces();InvocationHandler handler = new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {Object result = null;try{System.out.println(method.getName()+"方法開始執行,參數列表是:"+ Arrays.asList(args));result = method.invoke(calculator,args);System.out.println(method.getName()+"方法結束執行,參數列表是:"+ result);}catch (Exception e){System.out.println(method.getName()+"方法拋出異常"+e.getMessage());}finally {System.out.println(method.getName()+"方法執行結束over");}
//return result;}};Object instance = Proxy.newProxyInstance(loader, interfaces, handler);return (Calculator) instance;}Calculator接口//        //獲取被代理對象的類加載器
//        ClassLoader loader = calculator.getClass().getClassLoader();
//
//        Class<?>[] interfaces  = calculator.getClass().getInterfaces();
//        InvocationHandler handler = new InvocationHandler() {
//            @Override
//            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//                Object result = null;
//                try{System.out.println(method.getName()+"方法開始執行,參數列表是:"+ Arrays.asList(args));
//                    Util.start(method,args);
//                     result = method.invoke(calculator,args);System.out.println(method.getName()+"方法開始執行,參數列表是:"+ result);
//                    Util.stop(method,result);
//                }catch (Exception e){System.out.println(method.getName()+"方法拋出異常"+e.getMessage());
//                    Util.logExpection(method,e);
//                }finally {System.out.println(method.getName()+"方法執行結束over");
//
//                    Util.logFinally(method);
//                }//                return result;
//            }
//        };
//        Object instance = Proxy.newProxyInstance(loader, interfaces, handler);
//        return (Calculator) instance;
//    }MyCalculator類//        //獲取被代理對象的類加載器
//        ClassLoader loader = calculator.getClass().getClassLoader();
//
//        Class<?>[] interfaces  = calculator.getClass().getInterfaces();
//        InvocationHandler handler = new InvocationHandler() {
//            @Override
//            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//                Object result = null;
//                try{System.out.println(method.getName()+"方法開始執行,參數列表是:"+ Arrays.asList(args));
//                    Util.start(method,args);
//                    result = method.invoke(calculator,args);System.out.println(method.getName()+"方法開始執行,參數列表是:"+ result);
//                    Util.stop(method,result);
//                }catch (Exception e){System.out.println(method.getName()+"方法拋出異常"+e.getMessage());
//                    Util.logExpection(method,e);
//                }finally {System.out.println(method.getName()+"方法執行結束over");
//
//                    Util.logFinally(method);
//                }//                return result;
//            }
//        };
//        Object instance = Proxy.newProxyInstance(loader, interfaces, handler);
//        return (Calculator) instance;}
public interface Calculator {public Integer  add(Integer i,Integer j) throws NoSuchMethodException;public Integer  div(Integer i,Integer j);public Integer  mul(Integer i,Integer j);public Integer  sub(Integer i,Integer j);}
package AOP.service;import AOP.util.Util;
import org.springframework.stereotype.Service;import java.lang.reflect.Method;/*** @BelongsProject: JAVAtest* @BelongsPackage: AOP.service* @Author: GuoYuan.Zhao* @Description: 描述什么人干什么事兒* @CreateTime: 2024-01-24 14:15* @Version: 1.0*/@Service
public class MyCalculator implements  Calculator{@Overridepublic Integer add(Integer i, Integer j) throws NoSuchMethodException {//11111
//        System.out.println("add方法開始執行:參數是"+i+"___"+j);
//        int result = i+j;
//        System.out.println("add方法結束,執行結果是"+result);
//        return result;//22222//        Method add = MyCalculator.class.getMethod("add", Integer.class, Integer.class);
//        Util.start("add",i,j);
//        Integer result = i+j;
//        Util.stop(add,result);
//        return result;//333333Integer result = i+j;return result;}@Overridepublic Integer div(Integer i, Integer j) {System.out.println("div方法開始執行:參數是"+i+"___"+j);Integer result = i/j;System.out.println("div方法結束,執行結果是"+result);return result;}@Overridepublic Integer mul(Integer i, Integer j) {System.out.println("mul方法開始執行:參數是"+i+"___"+j);int result = i*j;System.out.println("mul方法結束,執行結果是"+result);return result;}@Overridepublic Integer sub(Integer i, Integer j) {System.out.println("sub方法開始執行:參數是"+i+"___"+j);Integer result = i-j;System.out.println("sub方法結束,執行結果是"+result);return result;}
}

客戶端


主函數{Calculator calculator = CalculatorProxy.getCalculator(new MyCalculator());calculator.add(1,1);saveProxyClass("E:\\zy\\TGB-zgy-2022\\MiMust\\MiDesignDemo\\JAVAtest\\NormalTest1\\demo\\src");}public static void saveProxyClass(String path) throws IOException {byte[] $proxy1s = ProxyGenerator.generateProxyClass("$Proxy1", MyCalculator.class.getInterfaces());FileOutputStream out = new FileOutputStream(new File(path + "$Proxy1.class"));out.write($proxy1s);}

最后可以看到代理類
在這里插入圖片描述
這個類里邊有 我的目標方法,其實客戶端調用的就是這個方法
在這里插入圖片描述
然后h就是我們上邊那個匿名內部類的對象

CGlib動態代理例子

CglibFactory類

public class CglibFactory  implements MethodInterceptor {public CglibFactory(MyCalculatorCGlib target) {}@Overridepublic Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {System.out.println("執行方法"+ method.getName());Object result = methodProxy.invokeSuper(o, objects);//這里實現將返回值字符串變為大寫的邏輯
//        if(result != null) {
//            result = ((String) result).toUpperCase();
//        }System.out.println("執行方法完畢結果是"+result);return result;}public MyCalculatorCGlib myCglibCreator() {System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY,"E:\\zy\\TGB-zgy-2022\\MiMust\\MiDesignDemo\\JAVAtest\\NormalTest1\\demo\\src");Enhancer enhancer = new Enhancer();//將目標類設置為父類,cglib動態代理增強的原理就是子類增強父類,cglib不能增強目標類為final的類//因為final類不能有子類enhancer.setSuperclass(MyCalculatorCGlib.class);//設置回調接口,這里的MethodInterceptor實現類回調接口,而我們又實現了MethodInterceptor,其實//這里的回調接口就是本類對象,調用的方法其實就是intercept()方法enhancer.setCallback(this);//create()方法用于創建cglib動態代理對象return (MyCalculatorCGlib) enhancer.create();}
}

MyCalculatorCGlib類

public class MyCalculatorCGlib {public Integer add(int i, int j) {Integer result = i+j;return result;}public void doSecond() {
//        System.out.println("doSecond()方法");}}

客戶端

     MyCalculatorCGlib target = new MyCalculatorCGlib();
//        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY,"E:\\zy\\TGB-zgy-2022\\MiMust\\MiDesignDemo\\JAVAtest\\NormalTest1\\demo\\src");MyCalculatorCGlib proxy = new CglibFactory(target).myCglibCreator();Integer result = proxy.add(1,1);

按照上邊的思路,說完上邊兩種代理方式,我們下邊來說如何使用這種方式手寫事務呢

手寫spring中的事務

首先從原理分析,事務的原理就是當sql出現問題的時候,數據回滾為原來的樣子,具體他們是通過spring中的DataSourceTransactionManager實現的,在sql執行前,開啟事務事務,然后執行sql,如果正常就提交事務,如果不正常就回滾事務。
這就是大概得邏輯,是不是就像上邊打印日志一樣,在執行方法前進行一些操作,在執行方法后進行一些操作。

在這里插入圖片描述

public class Main {public static void main(String[] args)  {
//        TransactionManager transactionManager = new SimpleTransactionManager();
//        TransactionProxyFactory factory = new TransactionProxyFactory(transactionManager);
//        MyService service = factory.createProxy(new MyService());
//        service.doSomething(); // 這個調用將被代理攔截,并處理事務MyService myService = new MyServiceImpl();TransactionManager transactionManager = new SimpleTransactionManager();DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();HikariDataSource dataSource = new HikariDataSource();dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");dataSource.setJdbcUrl("jdbc:mysql://127.0.0.1:3306/zipkin?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai");dataSource.setUsername("root");dataSource.setPassword("123456");dataSourceTransactionManager.setDataSource(dataSource);transactionManager.setDataSourceTransactionManager(dataSourceTransactionManager);// 創建代理對象MyService proxy = TransactionalInvocationHandler.createProxy(myService, transactionManager, MyService.class);// 調用代理對象的方法,這將觸發事務管理proxy.doSomething();}
}
@Service
public interface MyService {//    @Transactionalpublic void doSomething()  ;
}
@Service
public class MyServiceImpl implements MyService {@Overridepublic void doSomething()  {try {System.out.println("我真的要執行sql語句");int i = 1/0;} catch (ArithmeticException e) {// 處理算術異常,例如記錄日志或返回錯誤消息throw new RuntimeException("An arithmetic operation has failed", e);}}
}
@Component
public interface TransactionManager {TransactionStatus beginTransaction();void commitTransaction(TransactionStatus transactionStatus);void rollbackTransaction(TransactionStatus transactionStatus);public void setDataSourceTransactionManager(DataSourceTransactionManager dataSourceTransactionManager) ;}
@Component
public class SimpleTransactionManager implements TransactionManager {private boolean isActive = false;@Overridepublic void setDataSourceTransactionManager(DataSourceTransactionManager dataSourceTransactionManager) {this.dataSourceTransactionManager = dataSourceTransactionManager;}private  DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();@Overridepublic TransactionStatus beginTransaction()  {if (isActive) {throw new IllegalStateException("Transaction already active");}// 模擬事務開始邏輯(在實際應用中,這里會涉及到數據庫連接的獲取、設置隔離級別等操作)TransactionStatus transaction = dataSourceTransactionManager.getTransaction(new DefaultTransactionAttribute());isActive = true;System.out.println("開啟事務了");return  transaction;}@Overridepublic void commitTransaction(TransactionStatus transactionStatus) {if (!isActive) {throw new IllegalStateException("No active transaction");}// 模擬事務提交邏輯dataSourceTransactionManager.commit(transactionStatus);isActive = false;System.out.println("提交事務了");}@Overridepublic void rollbackTransaction(TransactionStatus transactionStatus)  {if (!isActive) {throw new IllegalStateException("No active transaction");}// 模擬事務回滾邏輯dataSourceTransactionManager.rollback(transactionStatus);isActive = false;System.out.println("回滾事務了");}
}
public class TransactionalInvocationHandler implements InvocationHandler {@Autowiredprivate final Object target;@Autowiredprivate final TransactionManager transactionManager;public TransactionalInvocationHandler(Object target, TransactionManager transactionManager) {this.target = target;this.transactionManager = transactionManager;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Exception {TransactionStatus transactionStatus = null;try {transactionStatus = transactionManager.beginTransaction();Object result = method.invoke(target, args);transactionManager.commitTransaction(transactionStatus);return result;} catch (Exception e) {transactionManager.rollbackTransaction(transactionStatus);throw e;}}public static <T> T createProxy(T target, TransactionManager transactionManager, Class<T> interfaceType) {return (T) Proxy.newProxyInstance(interfaceType.getClassLoader(),new Class<?>[]{interfaceType},new TransactionalInvocationHandler(target, transactionManager));}
}

運行結果:
在這里插入圖片描述

部分自定義注解版aop實現方式

在這里插入圖片描述

自定義注解 MyTransactionAnnotation

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyTransactionAnnotation {
}

MyTransaction類

@Component
@Slf4j
public class MyTransaction {@Autowiredprivate DataSourceTransactionManager dataSourceTransactionManager;/*** 開啟事務,并配置默認的事務傳播機制* @return*/public TransactionStatus begin(){TransactionStatus transaction = dataSourceTransactionManager.getTransaction(new DefaultTransactionAttribute());log.info("事務開啟成功");return transaction;}/*** 事務提交* @param transaction*/public void commit(TransactionStatus transaction){log.info("事務提交成功");dataSourceTransactionManager.commit(transaction);}/*** 事務回滾* @param transaction*/public void rollback(TransactionStatus transaction){log.info("事務回滾成功");dataSourceTransactionManager.rollback(transaction);}
}

MyTransactionAop類

@Slf4j
@Aspect
@Component
public class MyTransactionAop {@Autowiredprivate MyTransaction myTransaction;@Around(value = "@annotation(com.transaction.annotation.MyTransactionAnnotation)")public Object myTransactionAop(ProceedingJoinPoint joinPoint){log.info("進入到事務aop, 準備開啟事務");TransactionStatus transactionStatus = myTransaction.begin();try {log.info("準備執行目標方法");// 執行目標方法Object object = joinPoint.proceed();log.info("目標方法執行完畢,準備提交");// 執行方法完畢,提交事務,并返回myTransaction.commit(transactionStatus);return object;}catch (Throwable throwable) {log.error("目標函數異常,手動回滾");// 目標函數異常,手動回滾myTransaction.rollback(transactionStatus);return "目標函數異常";}}
}

TestController類

@RestController
@Slf4j
public class TestController {@GetMapping("test_transaction")@MyTransactionAnnotationpublic String testTransaction(@RequestParam(value = "name") String name){//        int i = 1/0;return name;}
}
@SpringBootApplication
@EnableTransactionManagement
public class HbzTransactionApplication {public static void main(String[] args) {SpringApplication.run(HbzTransactionApplication.class, args);}
}

application.yml文件

server:port: 9001servlet:context-path: /test
spring:datasource:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://127.0.0.1:3306/zipkin?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghaiusername: rootpassword: 123456

在這里插入圖片描述

總結:當我們大概了解這個AOP的思想以后,再去看源碼,發現都離不開動態代理,那就是離不開回調反射

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

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

相關文章

tvm交叉編譯android可執行參考資料整理

主要參考這個&#xff1a; TVM部署神經網絡模型到android端_tvm android-CSDN博客 其他相關鏈接&#xff1a; TVM部署神經網絡模型到android端 - 代碼先鋒網 Ubuntu交叉編譯 arm板子上的TVM_tvm arm-CSDN博客 TVM部署神經網絡模型到android端 - 代碼先鋒網 tvm部署c神經網…

【Spring連載】使用Spring Data訪問 MongoDB(五)----生命周期事件

【Spring連載】使用Spring Data訪問 MongoDB&#xff08;五&#xff09;----生命周期事件Lifecycle Events 一、實體回調Entity Callbacks1.1 實現實體回調1.2 注冊實體回調 二、特定存儲的實體回調 一、實體回調Entity Callbacks 1.1 實現實體回調 1.2 注冊實體回調 二、特…

【結合OpenAI官方文檔】解決Chatgpt的API接口請求速率限制

OpenAI API接口請求速率限制 速率限制以五種方式衡量&#xff1a;RPM&#xff08;每分鐘請求數&#xff09;、RPD&#xff08;每天請求數&#xff09;、TPM&#xff08;每分鐘令牌數&#xff09;、TPD&#xff08;每天令牌數&#xff09;和IPM&#xff08;每分鐘圖像數&#x…

BUUCTF第二十四、二十五題解題思路

目錄 第二十四題CrackRTF 第二十五題[2019紅帽杯]easyRE1 第二十四題CrackRTF 查殼 無殼&#xff0c;32位&#xff0c;用32位IDA打開&#xff0c;打開后的main函數很短&#xff0c;可以找到一句“jmz _main_0”——跳轉到 _main_0&#xff0c;說明真正的主函數是_main_0&am…

React 模態框的設計(二)

自定義組件是每個前端開發者必備的技能。我們在使用現有框架時難免有一些超乎框架以處的特別的需求&#xff0c;比如關于彈窗&#xff0c;每個應用都會用到&#xff0c;但是有時我們使用的框架中提供的彈窗功能也是功能有限&#xff0c;無法滿足我們的應用需求&#xff0c;今天…

【linux】使用 acme.sh 實現了 acme 協議生成免費的SSL 證書

acme.sh 實現了 acme 協議, 可以從 letsencrypt 生成免費的證書. 主要步驟: 安裝 acme.sh生成證書copy 證書到 nginx/apache 或者其他服務更新證書更新 acme.sh出錯怎么辦, 如何調試 下面詳細介紹. 1. 安裝 acme.sh 安裝很簡單, 一個命令: curl https://get.acme.sh | sh…

隱藏餓了么el-select組件的el-select-dropdown部分,只使用el-select的顯示框

隱藏餓了么el-select組件的el-select-dropdown部分,只使用el-select的顯示框 問題: 由于el-select組件的el-select-dropdown部分是自動插入在最外層Body上的&#xff0c;所以在當前組件的scoped中讓el-select-dropdown組件display:none不會生效所以需要&#xff1a; :popper-…

Java架構師之路六、高并發與性能優化:高并發編程、性能調優、線程池、NIO、Netty、高性能數據庫等。

目錄 高并發編程&#xff1a; 性能調優&#xff1a; 線程池&#xff1a; NIO&#xff1a; Netty&#xff1a; 高性能數據庫&#xff1a; 上篇&#xff1a;Java架構師之路五、微服務&#xff1a;微服務架構、服務注冊與發現、服務治理、服務監控、容器化等。-CSDN博客 下篇…

TiDB 7.5.0 LTS 高性能數據批處理方案

過去&#xff0c;TiDB 由于不支持存儲過程、大事務的使用也存在一些限制&#xff0c;使得在 TiDB 上進行一些復雜的數據批量處理變得比較復雜。 TiDB 在面向這種超大規模數據的批處理場景&#xff0c;其能力也一直在演進&#xff0c;其復雜度也變得越來越低&#xff1a; ○ 從…

11.CSS3的媒介(media)查詢

CSS3 的媒介(media)查詢 經典真題 如何使用媒體查詢實現視口寬度大于 320px 小于 640px 時 div 元素寬度變成 30% 媒體查詢 媒體查詢英文全稱 Media Query&#xff0c;顧名思義就是會查詢用戶所使用的媒體或者媒介。 在現在&#xff0c;網頁的瀏覽終端是越來越多了。用戶可…

C++:string類

標準庫中的string類 string類 1. 字符串是表示字符序列的類 2. 標準的字符串類提供了對此類對象的支持&#xff0c;其接口類似于標準字符容器的接口&#xff0c;但添加了專門用于操作單字節字符字符串的設計特性。 3. string類是使用char(即作為它的字符類型&#xff0c;使用…

ChatGPT 是什么

文章目錄 一、ChatGPT 是什么二、ChatGPT的發明者三、ChatGPT的運作方式四、ChatGPT的技術五、ChatGPT的優勢六、ChatGPT的局限性七、ChatGPT的應用八、ChatGPT的未來九、總結 一、ChatGPT 是什么 OpenAI的ChatGPT&#xff0c;即Chat Generative Pre-Trained Transformer&…

3個精美的wordpress企業網站模板

WordPress企業網站模板 https://www.zhanyes.com/qiye/6305.html WordPress企業官網模板 https://www.zhanyes.com/qiye/6309.html WordPress律師模板 https://www.zhanyes.com/qiye/23.html

SQL注入漏洞解析--less-2

首先我們進入第二關 思路&#xff1a; 1.先判斷是什么類型的注入 2.根據類型我們在找注入點 步驟&#xff1a; 1.提示我們輸入id數字&#xff0c;那我們先輸入1猜一下 2.這里正常回顯&#xff0c;當我們后邊加上時可以看到報錯&#xff0c;且報錯信息看不到數字&#xff0…

輕松掌握opencv的8種圖像變換

文章目錄 opencv的8種圖像變換1. 圖像放大、縮小2. 圖像平移3. 圖像旋轉4. 圖像仿射變換5. 圖像裁剪6. 圖像的位運算&#xff08;AND, OR, XOR&#xff09;7. 圖像的分離和融合8. 圖像的顏色空間 opencv的8種圖像變換 1. 圖像放大、縮小 我們先看下原圖 import cv2 import ma…

C++面試:程序的編譯與運行

程序的編譯和運行是軟件開發中的基本環節&#xff0c;尤其是在使用編譯型語言&#xff08;如C/C、Java等&#xff09;進行開發時。這個過程涉及將人類可讀的源代碼轉換成機器能夠執行的指令&#xff0c;然后運行這些指令來完成既定的任務。下面是這一過程的詳細介紹&#xff0c…

基于java+springboot+vue實現的美食信息推薦系統(文末源碼+Lw)23-170

1 摘 要 使用舊方法對美食信息推薦系統的信息進行系統化管理已經不再讓人們信賴了&#xff0c;把現在的網絡信息技術運用在美食信息推薦系統的管理上面可以解決許多信息管理上面的難題&#xff0c;比如處理數據時間很長&#xff0c;數據存在錯誤不能及時糾正等問題。這次開發…

Shell好用的工具: cut

目標 使用cut可以切割提取指定列\字符\字節的數據 介紹 cut 譯為“剪切, 切割” , 是一個強大文本處理工具&#xff0c;它可以將文本按列進行劃分的文本處理。cut命令逐行讀入文本&#xff0c;然后按列劃分字段并進行提取、輸出等操作。 語法 cut [options] filename opti…

樹中枝繁葉茂:探索 B+ 樹、B 樹、二叉樹、紅黑樹和跳表的世界

歡迎來到我的博客&#xff0c;代碼的世界里&#xff0c;每一行都是一個故事 樹中枝繁葉茂&#xff1a;探索 B 樹、B 樹、二叉樹、紅黑樹和跳表的世界 前言B樹和B樹B樹&#xff08;Binary Tree&#xff09;&#xff1a;B樹&#xff08;B Plus Tree&#xff09;&#xff1a;應用場…

Cobra在ubuntu中設置自動補全

Cobra在ubuntu中設置自動補全 yourprogram指的是你程序&#xff0c;并且必須是使用了Cobra cli bash設置 $ source <(yourprogram completion bash)$ yourprogram completion bash > /etc/bash_completion.d/yourprogramzsh設置 $ echo "autoload -U compinit; …