Java ”框架 = 注解 + 反射 + 設計模式“ 之 注解詳解

每博一文案
剎那間我真想令時光停住,好讓我回顧自己,回顧失去的年華,緬懷哪個穿一身短小的連衣裙
和瘦窄的短衫的小女孩,讓我追悔少年時代,我心靈的愚鈍無知,它輕易地錯過了我一生中本來
可以獲得歡樂和幸福,
—————— 《平凡的世界》
真的,如果痛苦不能改變生存,那還不如平靜地將自己毀滅,毀滅,一切都毀滅了,
只有生命還在茍延殘喘,這樣的生命還有有什么存在的價值,
—————— 《平凡的世界》
@
目錄- Java ”框架 = 注解 + 反射 + 設計模式“ 之 注解詳解
- 每博一文案
- 1. 注解的概念
- 2. 注解的作用
- 3. 檔案注釋中的注解
- 4. 自定義注解
- 4.1 注解中的屬性
- 4.2 注解中屬性為:陣列的賦值
- 5. JDK 內置的三個基本注解
- 5.1 @Override: 限定重寫父類方法, 該注解只能用于方法
- 5.2 @Deprecated: 用于表示所修飾的元素(類, 方法等)已過時,
- 5.3 @SuppressWarnings: 抑制編譯器警告
- 6. 元注解
- 6.1 @Target
- 6.2 @Retention
- 6.3 @Documented
- 6.4 @Inherited
- 7. 通過反射獲取到注解資訊
- 8. 總結:
- 9. 最后:
1. 注解的概念
注解,一種元資料形式提供了一個不屬于程式本身的程式的資料,注解對他們注解的代碼的操作沒有直接的影響,
注解有很多用途,其中:
- 編譯器的資訊 - 編譯器可以使用注解來檢測錯誤或抑制警告,
- 編譯和部署時處理 - 軟體工具可以處理注解資訊以生成代碼,XML 檔案等,
- 運行時處理 - 一些注解可以在運行時檢查
從 JDK5.0 開始,Java增加了對元資料(MetaData) 的支持,也就是 Annotation 注解,
- 注解: 其實就是代碼里的 特殊標記 ,這些標記可以在編譯,類加載,運行時被讀取,并執行相應的處理,通過使用 注解,
程式員可以在不改變原有的邏輯的情況下,在源檔案種嵌入一些補充的資訊,代碼分析工具,開發工具和部署工具可以通過這些補充資訊進行驗證或進行部署,
-
注解: 可以像修飾符一樣被使用,可以用于 修飾:包,類,構造器,方法,成員變數,引數,區域變數的宣告 ,這些資訊被保存在 注解 Annotaion 的“ name = value” 鍵值對中,
-
在JavaSE中,注解的使用目的比較簡單,例如標記過時的功能,忽略警告等,在JavaEE/Android中注解占據了更重要的角色,例如
用來配置應用程式的任何切面,代替JavaEE舊版中所遺留的繁冗 代碼和XML配置等,
-
未來的開發模式都是基于注解的,JPA 是基于注解的,Spring2.5 以上都是基于注解的,Hibernate3.x 以后也是基于注解的,
現在Struts2 有一部分也是基于注解的了,注解是一種趨勢,一定程度上可以說:框架 = 注解 + 反射 + 設計模式 ,
2. 注解的作用
從 JVM 的角度看,注解本身對代碼邏輯沒有任何影響,如何使用注解完全由工具決定,
Java的注解可以分為三類:
- 第一類:是由編譯器使用的注解:換句話說就是給編譯器看的,不是給 JVM 看的,例如:
- @Override : 讓編譯器檢查該方法是否正確的實作了 重寫操作,
- @Deprecated : 表示所修飾的元素(類,方法等)已過時了,不建議使用它了,
- @SuppressWarnings: 告訴編譯器忽略此處代碼產生的警告,
- 第二類: 是由工具處理
.class檔案使用的注解,比如有些工具會在加載 class 的時候,對 class 做動態修改,實作一些特殊的功能,這類注解會被編譯進入到.class檔案,但加載結束后并不會存在于記憶體中,這類注解只被一些底層使用,一般我們不必自己處理, - 第三類: 是在程式運行期間能夠讀取的注解,它們在加載后一直存在于 JVM 中,這也是最常用的注解,例如:一個配置了
@PostConstruct的方法會在呼叫構造方法后自動被呼叫,(這是Java代碼讀取該注解實作的功能,JVM 并不會識別該注解),
3. 檔案注釋中的注解


- @author 標明開發該類模塊的作者,多個作者之間使用,分割,
- @version 標明該類的模塊的版本,
- @see 參考轉向,也就是相關類的主題,
- @since 從哪個版本開始增加的,
- @param 對方法中某引數的說明,如果沒有引數就不能寫,
- @return 對方法回傳值的說明,如果方法的回傳值型別是 void 就不能寫
- @exception 對方法可能拋出的例外進行說明,如果方法沒有用 throws 顯拋出的例外就不能寫 其中:
- @param @ return 和 @ exeption 這三個標記都是只用于方法的,
- @param 的格式要求:@param 形參名 形參型別 形參說明
- @return 的格式要求:@return 回傳值型別 回傳值說明
- @exception 的格式要求:@exception 例外型別 例外說明
- @ param 和 @exception 可以并列多個,
4. 自定義注解
Annotaion注解 其實也是一種參考資料型別,編譯之后也是生成 xxx.class 位元組碼檔案的,
-
定義新的 Annotation 注解 型別使用 @interface 關鍵字
-
自定義注解自動實作了
java.lang.annotation.Annotation介面


自定義注解的格式如下:
public @interface MyAnnotation {
}
//
[修飾串列] @interface 注解名/(注解類名) {
}
使用 IDEA 創建一個 注解型別: 滑鼠右鍵 ——> new 一個選擇 :

如下查看該 我們該自定義的注解 MyAnnotation 的 UML 圖:可以清楚的看到該 注解型別是自動繼承了該 java.lang.annotation.Annotation 介面的

但是事實上卻是自動實作了 java.lang.annotation.Annotation 介面,

在Java 8之前,注解只能是在宣告的地方所使用,Java8 開始,注解可以應用 在任何地方 ,這里的任何地方包括:包,類,構造器,方法,成員變數,引數,區域變數的宣告 ,這些資訊被保存在 注解 Annotaion 的“ name = value” 鍵值對中,
舉例如下: 并沒有出現任何的報錯的情況,

4.1 注解中的屬性
在注解中可以定義屬性,
Java中的注解中的屬性:看著像方法,但實際在注解當中是屬性 name
格式如下:
String value();
// 資料型別 屬性名(); // 看似是方法,其實在注解中是屬性
注解中的屬性可以是任何型別:byte, short, int, long, float, double, boolean, char, String, long, Class, 列舉型別,再或者是自定義型別,
舉例:
public @interface MyAnnotation {
String value();
}
注意: 如果該注解中定義了屬性,則宣告使用該注解時 必須 為其注解中的屬性值賦上值,不然,是會報錯的,
如下,我們為該 @MyAnnotation 注解定義了屬性,使用時卻沒有賦值,報如下編譯錯誤,

為注解中的屬性賦值的格式如下:
@MyAnnotaion(value="https://www.cnblogs.com/TheMagicalRainbowSea/archive/2023/02/22/Tom") // 注意:在該注解中定義的是什么型別的值,就賦值對應的值,不然會報錯的
// @注解名(注解中的屬性名=對應賦值的屬性值)
舉例:

如果該注解中只有一個屬性值,并且該注解的屬性名為 value ,則在賦值時,可以省略其 為value的屬性名,直接寫值 ,
注意一定要滿足兩個條件:1. 該注解中只有一個屬性值,2.該屬性名為 value
舉例:

為注解中的多個屬性賦值格式如下: 多個屬性值,使用逗號分隔開來,就可以了,
@MyAnnotation(value = "https://www.cnblogs.com/TheMagicalRainbowSea/archive/2023/02/22/Tom",value2 = "123")
// @注解名(注解中的屬性名=值,注解中的屬性名=值) :多個屬性值使用逗號分隔開,
注意: 當注解中存在多個屬性值時,其中所有該注解中的屬性值都必須賦值,不然編譯報錯,如下:

必須將注解中的所有屬性值都賦值上值才行:如下:如果注解中存在兩個或兩個以上的屬性,就算其中存在一個屬性名為 value ,其賦值時,該value 屬性名是不可以省略的,必須寫明所有的屬性名的進行賦值,[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳

注解中的屬性可以設定默認值,使用關鍵字``格式如下:
String value() default "Tom";
// 資料型別 屬性名() default 默認值; 注意需要和定義的型別保證一致,
舉例:
public @interface MyAnnotation {
String value() default "Tom";
}
注解中屬性設定了,默認值的是可以選擇不進行賦值操作,使用定義的默認值,
舉例如下:

4.2 注解中屬性為:陣列的賦值
注解中的屬性值是可以定義為陣列屬性的格式如下:
String[] arr(); // 定義陣列為屬性值
資料型別[] 屬性名();
舉例:
public @interface MyAnnotation {
String value();
String[] arr();
}
注解中以陣列為屬性的賦值格式如下:
@MyAnnotation(value = "https://www.cnblogs.com/TheMagicalRainbowSea/archive/2023/02/22/Tom",arr = {"hello","world"})
@注解名(屬性名=值,屬性名={值,值})
舉例:

當陣列屬性所賦值的引數只有一個時,可以省略{} 花括號,如下

5. JDK 內置的三個基本注解
下面我們來認識一下,JDK 中三個常見的基本注解,
5.1 @Override: 限定重寫父類方法, 該注解只能用于方法

@Override : 的作用就是在編譯期間:讓編譯器檢查該方法是否正確的實作了 重寫 操作,其中的重寫的方法名是否存在錯誤,方法的回傳值型別是否是父類中/介面中的一致,不一致編譯報錯,提示我們改正,
@OVerride 注解的原始碼,可以看到該注解是沒有定義屬性的,
package java.lang;
import java.lang.annotation.*;
/**
* Indicates that a method declaration is intended to override a
* method declaration in a supertype. If a method is annotated with
* this annotation type compilers are required to generate an error
* message unless at least one of the following conditions hold:
*
* <ul><li>
* The method does override or implement a method declared in a
* supertype.
* </li><li>
* The method has a signature that is override-equivalent to that of
* any public method declared in {@linkplain Object}.
* </li></ul>
*
* @author Peter von der Ahé
* @author Joshua Bloch
* @jls 9.6.1.4 @Override
* @since 1.5
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
注意 : 該注解只能使用在方法上(因為該注解的原始碼中被@Target(ElementType.METHOD)元注解修飾了),在其他位置上是會編譯報錯的,如下:

@OVerride 注解的使用: 較少了我們編程程式時,上的簡單符號上以及一些基本的語法錯誤,

5.2 @Deprecated: 用于表示所修飾的元素(類, 方法等)已過時,

@Deprecated : 該注解表示:表示所修飾的元素(類,方法等)已過時了,不建議使用它了,建議替換成其他的方法,
@Depecated 原始碼: 可以看到該注解是沒有定義屬性的,
package java.lang;
import java.lang.annotation.*;
import static java.lang.annotation.ElementType.*;
/**
* A program element annotated @Deprecated is one that programmers
* are discouraged from using, typically because it is dangerous,
* or because a better alternative exists. Compilers warn when a
* deprecated program element is used or overridden in non-deprecated code.
*
* @author Neal Gafter
* @since 1.5
* @jls 9.6.3.6 @Deprecated
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value=https://www.cnblogs.com/TheMagicalRainbowSea/archive/2023/02/22/{CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public @interface Deprecated {
}
常見的,在我們的 java.util.lang 包下的 Date 中的 Date(String s) 構造器是被 @Deprecated 注解修飾了的,

在IDEA 中 如果我們呼叫了,被 @Deprecated 修飾的屬性,方法,構造器,會給一個直觀的將該屬性/方法以洗掉線的方式顯示處理并提示你建議使用別的方式替換 ,如下

我們也可以自己寫一個這樣的被@Deprecated 修飾的屬性/方法/類/構造器,舉例如下:

5.3 @SuppressWarnings: 抑制編譯器警告

@SuppressWarnings ** :指示應該在注解元素(以及包含在該注解元素中所有程式元素中的所有程式元素)中取消顯示指定的編譯器警告,換句話說:就是告訴編譯器忽略此處代碼產生的警告**, 注意是警告不是例外,
@SuppressWarnings的原始碼 ,可以看到該注解定義了一個名為 value 的屬性,
package java.lang;
import java.lang.annotation.*;
import static java.lang.annotation.ElementType.*;
/**
* Indicates that the named compiler warnings should be suppressed in the
* annotated element (and in all program elements contained in the annotated
* element). Note that the set of warnings suppressed in a given element is
* a superset of the warnings suppressed in all containing elements. For
* example, if you annotate a class to suppress one warning and annotate a
* method to suppress another, both warnings will be suppressed in the method.
*
* <p>As a matter of style, programmers should always use this annotation
* on the most deeply nested element where it is effective. If you want to
* suppress a warning in a particular method, you should annotate that
* method rather than its class.
*
* @author Josh Bloch
* @since 1.5
* @jls 4.8 Raw Types
* @jls 4.12.2 Variables of Reference Type
* @jls 5.1.9 Unchecked Conversion
* @jls 5.5.2 Checked Casts and Unchecked Casts
* @jls 9.6.3.5 @SuppressWarnings
*/
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
/**
* The set of warnings that are to be suppressed by the compiler in the
* annotated element. Duplicate names are permitted. The second and
* successive occurrences of a name are ignored. The presence of
* unrecognized warning names is <i>not</i> an error: Compilers must
* ignore any warning names they do not recognize. They are, however,
* free to emit a warning if an annotation contains an unrecognized
* warning name.
*
* <p> The string {@code "unchecked"} is used to suppress
* unchecked warnings. Compiler vendors should document the
* additional warning names they support in conjunction with this
* annotation type. They are encouraged to cooperate to ensure
* that the same names work across multiple compilers.
* @return the set of warnings to be suppressed
*/
String[] value();
}
舉例: 這里我們定義一個 int num = 1 / 0 陳述句,IDEA 該我們提示了警告了,

當我們加上了 @SuppressWarnings IDEA 就沒有這個警告了,如下:


具體的其他應用大家可以移步至:??????
https://blog.csdn.net/qq_43842093/article/details/122386115?ops_request_misc=&request_id=&biz_id=102&utm_term=%20@SuppressWarnings%E7%9A%84%E4%BD%BF%E7%94%A8&utm_medium=distribute.pc_search_result.none-task-blog-2allsobaiduweb~default-3-122386115.142
Eclipse Galileo版本支持的抑制警告的名稱:
| 關鍵字 | 用途 |
|---|---|
| all | to suppress all warnings (抑制所有警告) |
| boxing | to suppress warnings relative to boxing/unboxing operations (抑制裝箱、拆箱操作時候的警告) |
| cast | to suppress warnings relative to cast operations (抑制映射相關的警告) |
| dep-ann | to suppress warnings relative to deprecated annotation (抑制啟用注釋的警告) |
| deprecation | to suppress warnings relative to deprecation (抑制過期方法警告) |
| fallthrough | to suppress warnings relative to missing breaks in switch statements (抑制確在switch中缺失breaks的警告) |
| finally | to suppress warnings relative to finally block that don’t return (抑制finally模塊沒有回傳的警告) |
| hiding | to suppress warnings relative to locals that hide variable(抑制相對于隱藏變數的區域變數的警告) |
| incomplete-switch | to suppress warnings relative to missing entries in a switch statement (enum case)(忽略沒有完整的switch陳述句) |
| nls | to suppress warnings relative to non-nls string literals( 忽略非nls格式的字符) |
| null | to suppress warnings relative to null analysis( 忽略對null的操作) |
| rawtypes | to suppress warnings relative to un-specific types when using generics on class params( 使用generics時忽略沒有指定相應的型別) |
| restriction | to suppress warnings relative to usage of discouraged or forbidden references( 抑制禁止使用勸阻或禁止參考的警告) |
| serial | to suppress warnings relative to missing serialVersionUID field for a serializable class( 忽略在serializable類中沒有宣告serialVersionUID變數) |
| static-access | to suppress warnings relative to incorrect static access( 抑制不正確的靜態訪問方式警告) |
| synthetic-access | to suppress warnings relative to unoptimized access from inner classes( 抑制子類沒有按最優方法訪問內部類的警告) |
| unchecked | to suppress warnings relative to unchecked operations( 抑制沒有進行型別檢查操作的警告) |
| unqualified-field-access | to suppress warnings relative to field access unqualified( 抑制沒有權限訪問的域的警告) |
| unused | to suppress warnings relative to unused code( 抑制沒被使用過的代碼的警告) |
6. 元注解
有一些注解可以修飾其他注解,這些注解就稱為元注解(meta annotation),Java標準庫已經定義了一些元注解,我們只需要使用元注解,通常不需要自己去撰寫元注解,
6.1 @Target

@Target 原始碼:其中存在一個型別為 ElementType[] 列舉型別陣列屬性名為 value 的屬性,
package java.lang.annotation;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
/**
* Returns an array of the kinds of elements an annotation type
* can be applied to.
* @return an array of the kinds of elements an annotation type
* can be applied to
*/
ElementType[] value();
}
ElementType的列舉原始碼 :
/package java.lang.annotation;
public enum ElementType {
/** Class, interface (including annotation type), or enum declaration */
TYPE,
/** Field declaration (includes enum constants) */
FIELD,
/** Method declaration */
METHOD,
/** Formal parameter declaration */
PARAMETER,
/** Constructor declaration */
CONSTRUCTOR,
/** Local variable declaration */
LOCAL_VARIABLE,
/** Annotation type declaration */
ANNOTATION_TYPE,
/** Package declaration */
PACKAGE,
/**
* Type parameter declaration
*
* @since 1.8
*/
TYPE_PARAMETER,
/**
* Use of a type
*
* @since 1.8
*/
TYPE_USE
}
@Target : 最常用的元注解是@Target,使用@Target可以定義Annotation能夠被應用于原始碼的哪些位置:
- 類或介面:
ElementType.TYPE; - 欄位:
ElementType.FIELD; - 方法:
ElementType.METHOD; - 構造方法:
ElementType.CONSTRUCTOR; - 方法引數:
ElementType.PARAMETER,
舉例:


@Target元注解中的 value 屬性是列舉陣列型別的,可以賦值多個值:比如表示該注解可以宣告在方法,變數,類中 ,舉例:

6.2 @Retention

@Retention 原始碼 : 我們可以看到如下注解中只定義了一個RetentionPolicy 的列舉型別名為 value 的屬性名的屬性
package java.lang.annotation;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
/**
* Returns the retention policy.
* @return the retention policy
*/
RetentionPolicy value();
}
RetentionPolicy 列舉型別的原始碼
package java.lang.annotation;
public enum RetentionPolicy {
/**
* Annotations are to be discarded by the compiler.
*/
SOURCE,
/**
* Annotations are to be recorded in the class file by the compiler
* but need not be retained by the VM at run time. This is the default
* behavior.
*/
CLASS,
/**
* Annotations are to be recorded in the class file by the compiler and
* retained by the VM at run time, so they may be read reflectively.
*
* @see java.lang.reflect.AnnotatedElement
*/
RUNTIME
}
@Retention : 另一個重要的元注解@Retention定義了Annotation的生命周期:
@Rentention 時必須為該 value 成員變數指定值
- 僅編譯期:
RetentionPolicy.SOURCE表示該注解的生命周期只在編譯期間有效,在源檔案中有效(即源檔案保留),編譯器直接丟棄這種策略的注釋, - 僅class檔案:
RetentionPolicy.CLASS: 在class檔案中有效(即class保留) , 當運行 Java 程式時, JVM 不會保留注解, 這是默認值 - 運行期:
RetentionPolicy.RUNTIME,注意:只有定義該屬性的注解,才能被反射讀取到,上面兩種方式都無法被反射讀取到的,
如果@Retention不存在,則該Annotation默認為CLASS,因為通常我們自定義的Annotation都是RUNTIME,所以,務必要加上@Retention(RetentionPolicy.RUNTIME)這個元注解:
舉例:

6.3 @Documented

@Documented: 用于指定被該元 Annotation 修飾的 Annotation 類將被 javadoc 工具提取成檔案,默認情況下,javadoc是不包括注解的,
- 定義為Documented的注解必須設定Retention值為RUNTIME
@Documented 的原始碼 從原始碼看,該注解沒有任何屬性,
package java.lang.annotation;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Documented {
}
6.4 @Inherited

@Inherited: 被它修飾的 Annotation 將具有繼承性,如果某個類使用了被
@Inherited : 修飾的 Annotation, 則其子類將自動具有該注解,
-
比如:如果把標有@Inherited注解的自定義的注解標注在類級別上,子類則可以繼承父類類級別的注解
-
實際應用中,使用較少
@Inherited 原始碼 從原始碼看,該注解沒有任何屬性,
package java.lang.annotation;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Inherited {
}
7. 通過反射獲取到注解資訊
想要讓反射可以讀取到注解中的資訊,則該反射中的元注解必須是: @Retention(RetentionPolicy.RUNTIME) 才行,
舉例: 這里我們使用反射讀取到 fun() 方法中的 注解中的 value 屬性值:
注解
package blogs.blog10;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME) // 生命周期在:運行期,才可以被反射讀取到
public @interface MyAnnotation {
String value() default "Tom";
}
package blogs.blog10;
import java.lang.reflect.Method;
public class AnnotationTest {
public static void main(String[] args) {
Method method = null;
try {
// 獲取類加載器,類物件
Class clazz = Class.forName("blogs.blog10.AnnotationTest"); // 全類路徑名
// 獲取 fun()方法
method = clazz.getDeclaredMethod("fun");
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
// 判斷該方法是否存在該注解,存在才讀取該注解上的屬性值
if(method.isAnnotationPresent(MyAnnotation.class)) {
// 獲取該注解物件
MyAnnotation annotation = method.getAnnotation(MyAnnotation.class);
// // 獲取該注解的屬性值,就像物件.屬性一樣
String value = https://www.cnblogs.com/TheMagicalRainbowSea/archive/2023/02/22/annotation.value();
System.out.println(value);
}
}
@MyAnnotation("lihua")
public void fun() {
int num = 0;
}
}

8. 總結:
- 設計注解型別時,必須考慮該型別注解的基數,現在可以使用注解零次,一次,或者如果注解的型別被標記為
@Repeatable多次,也可以通過使用@Target元注解來限制注解型別的使用位置,例如,您可以創建只能在方法和欄位上使用的可重復注解型別,重要的是仔細設計注解型別,以確保使用注解的程式員發現它盡可能靈活和強大, - 注解的作用:減少程式中的錯誤,提高程式員的開發效率,以及框架上的運用,
- 注意:注解中的屬性必須賦值,不然編譯無法通過,除非該屬性設定了默認值資訊,建議注解中的屬性設定上默認值,
- 當注解中只有一個屬性,并且該屬性名為
value,則在賦值上可以省略屬性名, - 注解多個值上的賦值,以及陣列型別的屬性值的賦值,
- 元注解:修飾注解上的注解,特別掌握:@Target,@Retention 這兩個元注解,其中的屬性值上的賦值的意義,
9. 最后:
限于自身水平,其中存在的錯誤,希望大家給予指教,韓信點兵——多多益善 ,謝謝大家,江湖再見,后會有期 !!!

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/544703.html
標籤:其他
上一篇:Ansible 快速入門到放棄
